Skip to content

Commit

Permalink
chore: create initial attestation for 1-node committee
Browse files Browse the repository at this point in the history
  • Loading branch information
LHerskind committed Aug 5, 2024
1 parent 0d72e0d commit eb702a5
Show file tree
Hide file tree
Showing 9 changed files with 144 additions and 59 deletions.
6 changes: 5 additions & 1 deletion l1-contracts/src/core/sequencer_selection/Leonidas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {Ownable} from "@oz/access/Ownable.sol";
import {SignatureLib} from "./SignatureLib.sol";
import {SampleLib} from "./SampleLib.sol";
import {Constants} from "../libraries/ConstantsGen.sol";
import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol";

import {ILeonidas} from "./ILeonidas.sol";

Expand All @@ -28,6 +29,7 @@ import {ILeonidas} from "./ILeonidas.sol";
contract Leonidas is Ownable, ILeonidas {
using EnumerableSet for EnumerableSet.AddressSet;
using SignatureLib for SignatureLib.Signature;
using MessageHashUtils for bytes32;

/**
* @notice The data structure for an epoch
Expand Down Expand Up @@ -333,14 +335,16 @@ contract Leonidas is Ownable, ILeonidas {
// Validate the attestations
uint256 validAttestations = 0;

bytes32 ethSignedDigest = _digest.toEthSignedMessageHash();

for (uint256 i = 0; i < _signatures.length; i++) {
SignatureLib.Signature memory signature = _signatures[i];
if (signature.isEmpty) {
continue;
}

// The verification will throw if invalid
signature.verify(epoch.committee[i], _digest);
signature.verify(epoch.committee[i], ethSignedDigest);
validAttestations++;
}

Expand Down
11 changes: 0 additions & 11 deletions l1-contracts/test/sparta/DevNet.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -230,15 +230,4 @@ contract DevNetTest is DecoderBase {
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}

function createSignature(address _signer, bytes32 _digest)
internal
view
returns (SignatureLib.Signature memory)
{
uint256 privateKey = privateKeys[_signer];
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, _digest);

return SignatureLib.Signature({isEmpty: false, v: v, r: r, s: s});
}
}
6 changes: 5 additions & 1 deletion l1-contracts/test/sparta/Sparta.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {DecoderBase} from "../decoders/Base.sol";
import {DataStructures} from "../../src/core/libraries/DataStructures.sol";
import {Constants} from "../../src/core/libraries/ConstantsGen.sol";
import {SignatureLib} from "../../src/core/sequencer_selection/SignatureLib.sol";
import {MessageHashUtils} from "@oz/utils/cryptography/MessageHashUtils.sol";

import {Registry} from "../../src/core/messagebridge/Registry.sol";
import {Inbox} from "../../src/core/messagebridge/Inbox.sol";
Expand All @@ -29,6 +30,8 @@ import {TxsDecoderHelper} from "../decoders/helpers/TxsDecoderHelper.sol";
* We will skip these test if we are running with IS_DEV_NET = true
*/
contract SpartaTest is DecoderBase {
using MessageHashUtils for bytes32;

Registry internal registry;
Inbox internal inbox;
Outbox internal outbox;
Expand Down Expand Up @@ -307,7 +310,8 @@ contract SpartaTest is DecoderBase {
returns (SignatureLib.Signature memory)
{
uint256 privateKey = privateKeys[_signer];
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, _digest);
bytes32 digestForSig = _digest.toEthSignedMessageHash();
(uint8 v, bytes32 r, bytes32 s) = vm.sign(privateKey, digestForSig);

return SignatureLib.Signature({isEmpty: false, v: v, r: r, s: s});
}
Expand Down
5 changes: 4 additions & 1 deletion yarn-project/archiver/src/archiver/eth_log_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,12 +134,15 @@ async function getBlockBodiesFromAvailabilityOracleTx(
): Promise<Body> {
const { input: data } = await publicClient.getTransaction({ hash: txHash });
const DATA_INDEX = [3, 2, 0];

// @note Use `forge inspect Rollup methodIdentifiers to get this,
// If using `forge sig` you will get an INVALID value for the case with a struct.
// [
// "publishAndProcess(bytes calldata _header,bytes32 _archive,SignatureLib.Signature[] memory _signatures,bytes calldata _body)",
// "publishAndProcess(bytes calldata _header,bytes32 _archive,bytes calldata _body)",
// "publish(bytes calldata _body)"
// ]
const SUPPORTED_SIGS = ['0x29a09106', '0xe86e3595', '0x7fd28346'];
const SUPPORTED_SIGS = ['0xe4e90c26', '0xe86e3595', '0x7fd28346'];

const signature = slice(data, 0, 4);

Expand Down
8 changes: 6 additions & 2 deletions yarn-project/end-to-end/src/e2e_p2p_network.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,12 @@ describe('e2e_p2p_network', () => {
await rollup.write.addValidator([account.address]);
logger.info(`Adding sequencer ${account.address}`);
} else {
// We add all 4 to the set and then jump ahead to the next epoch.
for (let i = 0; i < NUM_NODES; i++) {
// @todo Should be updated when we have attestations to add all the sequencers
// Since it is currently a mess because sequencer selection needs attestations for
// validity, but we currently have no way to collect them.
// When attestations works, add all 4, and lets ROLL!

for (let i = 0; i < 1; i++) {
const hdAccount = mnemonicToAccount(MNEMONIC, { addressIndex: i + 1 });
const publisherPrivKey = Buffer.from(hdAccount.getHdKey().privateKey!);
const account = privateKeyToAccount(`0x${publisherPrivKey!.toString('hex')}`);
Expand Down
31 changes: 24 additions & 7 deletions yarn-project/sequencer-client/src/publisher/l1-publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,20 @@ export type MinimalTransactionReceipt = {
logs: any[];
};

/**
* @notice An attestation for the sequencing model.
* @todo This is not where it belongs. But I think we should do a bigger rewrite of some of
* this spaghetti.
*/
export type Attestation = { isEmpty: boolean; v: number; r: `0x${string}`; s: `0x${string}` };

/**
* Pushes txs to the L1 chain and waits for their completion.
*/
export interface L1PublisherTxSender {
/** Attests to the given archive root. */
attest(archive: `0x${string}`): Promise<Attestation>;

/** Returns the EOA used for sending txs to L1. */
getSenderAddress(): Promise<EthAddress>;

Expand Down Expand Up @@ -112,6 +122,8 @@ export type L1ProcessArgs = {
archive: Buffer;
/** L2 block body. */
body: Buffer;
/** Attestations */
attestations?: Attestation[];
};

/** Arguments to the submitProof method of the rollup contract */
Expand Down Expand Up @@ -146,6 +158,10 @@ export class L1Publisher implements L2BlockReceiver {
this.sleepTimeMs = config?.l1PublishRetryIntervalMS ?? 60_000;
}

public async attest(archive: `0x${string}`): Promise<Attestation> {
return await this.txSender.attest(archive);
}

public async senderAddress(): Promise<EthAddress> {
return await this.txSender.getSenderAddress();
}
Expand All @@ -161,7 +177,7 @@ export class L1Publisher implements L2BlockReceiver {
* @param block - L2 block to publish.
* @returns True once the tx has been confirmed and is successful, false on revert or interrupt, blocks otherwise.
*/
public async processL2Block(block: L2Block): Promise<boolean> {
public async processL2Block(block: L2Block, attestations: Attestation[] | undefined = undefined): Promise<boolean> {
const ctx = { blockNumber: block.number, blockHash: block.hash().toString() };
// TODO(#4148) Remove this block number check, it's here because we don't currently have proper genesis state on the contract
const lastArchive = block.header.lastArchive.root.toBuffer();
Expand All @@ -171,14 +187,15 @@ export class L1Publisher implements L2BlockReceiver {
}
const encodedBody = block.body.toBuffer();

const processTxArgs = {
header: block.header.toBuffer(),
archive: block.archive.root.toBuffer(),
body: encodedBody,
attestations,
};

// Process block and publish the body if needed (if not already published)
while (!this.interrupted) {
const processTxArgs = {
header: block.header.toBuffer(),
archive: block.archive.root.toBuffer(),
body: encodedBody,
};

let txHash;

if (await this.txSender.checkIfTxsAreAvailable(block)) {
Expand Down
93 changes: 70 additions & 23 deletions yarn-project/sequencer-client/src/publisher/viem-tx-sender.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@ import {
getContract,
hexToBytes,
http,
parseSignature,
} from 'viem';
import { type PrivateKeyAccount, privateKeyToAccount } from 'viem/accounts';
import * as chains from 'viem/chains';

import { type TxSenderConfig } from './config.js';
import {
type Attestation,
type L1PublisherTxSender,
type L1SubmitProofArgs,
type MinimalTransactionReceipt,
Expand Down Expand Up @@ -73,6 +75,20 @@ export class ViemTxSender implements L1PublisherTxSender {
});
}

async attest(archive: `0x{string}`): Promise<Attestation> {
// @note Something seems slightly off in viem, think it should be Hex instead of Hash
// but as they both are just `0x${string}` it should be fine anyways.
const signature = await this.account.signMessage({ message: { raw: archive } });
const { r, s, v } = parseSignature(signature as `0x${string}`);

return {
isEmpty: false,
v: v ? Number(v) : 0,
r: r,
s: s,
};
}

getSenderAddress(): Promise<EthAddress> {
return Promise.resolve(EthAddress.fromString(this.account.address));
}
Expand Down Expand Up @@ -162,39 +178,70 @@ export class ViemTxSender implements L1PublisherTxSender {
* @returns The hash of the mined tx.
*/
async sendProcessTx(encodedData: ProcessTxArgs): Promise<string | undefined> {
const args = [`0x${encodedData.header.toString('hex')}`, `0x${encodedData.archive.toString('hex')}`] as const;
if (encodedData.attestations) {
const args = [
`0x${encodedData.header.toString('hex')}`,
`0x${encodedData.archive.toString('hex')}`,
encodedData.attestations,
] as const;

const gas = await this.rollupContract.estimateGas.process(args, {
account: this.account,
});
const hash = await this.rollupContract.write.process(args, {
gas,
account: this.account,
});
return hash;
const gas = await this.rollupContract.estimateGas.process(args, {
account: this.account,
});
return await this.rollupContract.write.process(args, {
gas,
account: this.account,
});
} else {
const args = [`0x${encodedData.header.toString('hex')}`, `0x${encodedData.archive.toString('hex')}`] as const;

const gas = await this.rollupContract.estimateGas.process(args, {
account: this.account,
});
return await this.rollupContract.write.process(args, {
gas,
account: this.account,
});
}
}

/**
* @notice Publishes the body AND process the block in one transaction
* @param encodedData - Serialized data for processing the new L2 block.
* @param encodedBody - Encoded block body.
* @returns The hash of the transaction
*/
async sendPublishAndProcessTx(encodedData: ProcessTxArgs): Promise<string | undefined> {
const args = [
`0x${encodedData.header.toString('hex')}`,
`0x${encodedData.archive.toString('hex')}`,
`0x${encodedData.body.toString('hex')}`,
] as const;
// @note This is quite a sin, but I'm committing war crimes in this code already.
if (encodedData.attestations) {
const args = [
`0x${encodedData.header.toString('hex')}`,
`0x${encodedData.archive.toString('hex')}`,
encodedData.attestations,
`0x${encodedData.body.toString('hex')}`,
] as const;

const gas = await this.rollupContract.estimateGas.publishAndProcess(args, {
account: this.account,
});
const hash = await this.rollupContract.write.publishAndProcess(args, {
gas,
account: this.account,
});
return hash;
const gas = await this.rollupContract.estimateGas.publishAndProcess(args, {
account: this.account,
});
return await this.rollupContract.write.publishAndProcess(args, {
gas,
account: this.account,
});
} else {
const args = [
`0x${encodedData.header.toString('hex')}`,
`0x${encodedData.archive.toString('hex')}`,
`0x${encodedData.body.toString('hex')}`,
] as const;

const gas = await this.rollupContract.estimateGas.publishAndProcess(args, {
account: this.account,
});
return await this.rollupContract.write.publishAndProcess(args, {
gas,
account: this.account,
});
}
}

/**
Expand Down
11 changes: 3 additions & 8 deletions yarn-project/sequencer-client/src/receiver.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { type L2Block } from '@aztec/circuit-types';
import type { Fr, Proof } from '@aztec/circuits.js';

import { type Attestation } from './publisher/l1-publisher.js';

/**
* Given the necessary rollup data, verifies it, and updates the underlying state accordingly to advance the state of the system.
* See https://hackmd.io/ouVCnacHQRq2o1oRc5ksNA#RollupReceiver.
*/
export interface L2BlockReceiver {
/**
* Receive and L2 block and process it, returns true if successful.
* @param l2BlockData - L2 block to process.
* @param aggregationObject - The aggregation object for the block's proof.
* @param proof - The proof for the block.
*/
processL2Block(l2BlockData: L2Block, aggregationObject: Fr[], proof: Proof): Promise<boolean>;
processL2Block(block: L2Block, attestations?: Attestation[]): Promise<boolean>;
}
32 changes: 27 additions & 5 deletions yarn-project/sequencer-client/src/sequencer/sequencer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
PROVING_STATUS,
} from '@aztec/circuit-types/interfaces';
import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
import { AztecAddress, EthAddress, type GlobalVariables, type Header } from '@aztec/circuits.js';
import { AztecAddress, EthAddress, type GlobalVariables, type Header, IS_DEV_NET } from '@aztec/circuits.js';
import { Fr } from '@aztec/foundation/fields';
import { createDebugLogger } from '@aztec/foundation/log';
import { RunningPromise } from '@aztec/foundation/running-promise';
Expand All @@ -24,7 +24,7 @@ import { Attributes, type TelemetryClient, type Tracer, trackSpan } from '@aztec
import { type WorldStateStatus, type WorldStateSynchronizer } from '@aztec/world-state';

import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
import { type L1Publisher } from '../publisher/l1-publisher.js';
import { type Attestation, type L1Publisher } from '../publisher/l1-publisher.js';
import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
import { type SequencerConfig } from './config.js';

Expand Down Expand Up @@ -362,7 +362,11 @@ export class Sequencer {
...block.getStats(),
} satisfies L2BlockBuiltStats);

await this.publishL2Block(block);
// @todo Should collect attestations here
// When in devnet, just have it empty.
const attestations = IS_DEV_NET ? undefined : await this.collectAttestations(block);

await this.publishL2Block(block, attestations);
this.log.info(
`Submitted rollup block ${block.number} with ${
processedTxs.length
Expand All @@ -384,17 +388,35 @@ export class Sequencer {
}
}

protected async collectAttestations(block: L2Block): Promise<Attestation[]> {
// @todo This should collect attestations properly and fix the ordering of them to make sense
// the current implementation is a PLACEHOLDER and should be nuked from orbit.
// It is assuming that there will only be ONE (1) validator, so only one attestation
// is needed.
// @note This is quite a sin, but I'm committing war crimes in this code already.
// _ ._ _ , _ ._
// (_ ' ( ` )_ .__)
// ( ( ( ) `) ) _)
// (__ (_ (_ . _) _) ,__)
// `~~`\ ' . /`~~`
// ; ;
// / \
// _____________/_ __ \_____________
const myAttestation = await this.publisher.attest(block.archive.root.toString());
return [myAttestation];
}

/**
* Publishes the L2Block to the rollup contract.
* @param block - The L2Block to be published.
*/
@trackSpan('Sequencer.publishL2Block', block => ({
[Attributes.BLOCK_NUMBER]: block.number,
}))
protected async publishL2Block(block: L2Block) {
protected async publishL2Block(block: L2Block, attestations?: Attestation[]) {
// Publishes new block to the network and awaits the tx to be mined
this.state = SequencerState.PUBLISHING_BLOCK;
const publishedL2Block = await this.publisher.processL2Block(block);
const publishedL2Block = await this.publisher.processL2Block(block, attestations);
if (publishedL2Block) {
this.lastPublishedBlock = block.number;
} else {
Expand Down

0 comments on commit eb702a5

Please sign in to comment.