Skip to content

Commit

Permalink
feat: validators ensure transactions live in their p2p pool before at…
Browse files Browse the repository at this point in the history
…testing (#8410)
  • Loading branch information
Maddiaa0 authored Sep 11, 2024
1 parent 79995c8 commit bce1eea
Show file tree
Hide file tree
Showing 35 changed files with 537 additions and 120 deletions.
6 changes: 5 additions & 1 deletion l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -191,18 +191,21 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
bytes32[] memory _txHashes,
SignatureLib.Signature[] memory _signatures,
bytes calldata _body
) external override(IRollup) {
bytes32 txsEffectsHash = TxsDecoder.decode(_body);

// Decode and validate header
HeaderLib.Header memory header = HeaderLib.decode(_header);

bytes32 digest = keccak256(abi.encode(_archive, _txHashes));
setupEpoch();
_validateHeader({
_header: header,
_signatures: _signatures,
_digest: _archive,
_digest: digest,
_currentTime: block.timestamp,
_txEffectsHash: txsEffectsHash,
_flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false})
Expand Down Expand Up @@ -419,6 +422,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
revert Errors.Rollup__SlotAlreadyInChain(lastSlot, slot);
}

// Make sure that the proposer is up to date
bytes32 tipArchive = archive();
if (tipArchive != _archive) {
revert Errors.Rollup__InvalidArchive(tipArchive, _archive);
Expand Down
1 change: 1 addition & 0 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ interface IRollup {
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
bytes32[] memory _txHashes,
SignatureLib.Signature[] memory _signatures,
bytes calldata _body
) external;
Expand Down
3 changes: 0 additions & 3 deletions l1-contracts/src/core/sequencer_selection/Leonidas.sol
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,6 @@ contract Leonidas is Ownable, ILeonidas {

/**
* @notice Get the validator set for the current epoch
*
* @dev Makes a call to setupEpoch under the hood, this should ONLY be called as a view function, and not from within
* this contract.
* @return The validator set for the current epoch
*/
function getCurrentEpochCommittee() external view override(ILeonidas) returns (address[] memory) {
Expand Down
25 changes: 16 additions & 9 deletions l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,12 @@ contract RollupTest is DecoderBase {
bytes memory header = data.header;
bytes32 archive = data.archive;
bytes memory body = data.body;
bytes32[] memory txHashes = new bytes32[](0);

// We jump to the time of the block. (unless it is in the past)
vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp));

rollup.propose(header, archive, bytes32(0), signatures, body);
rollup.propose(header, archive, bytes32(0), txHashes, signatures, body);

rollup.submitBlockRootProof(header, archive, bytes32(0), "", "");

Expand Down Expand Up @@ -186,6 +187,7 @@ contract RollupTest is DecoderBase {
bytes memory header = data.header;
bytes32 archive = data.archive;
bytes memory body = data.body;
bytes32[] memory txHashes = new bytes32[](0);

// Progress time as necessary
vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp));
Expand All @@ -206,7 +208,7 @@ contract RollupTest is DecoderBase {
assertEq(coinbaseBalance, 0, "invalid initial coinbase balance");

// Assert that balance have NOT been increased by proposing the block
rollup.propose(header, archive, bytes32(0), signatures, body);
rollup.propose(header, archive, bytes32(0), txHashes, signatures, body);
assertEq(portalERC20.balanceOf(coinbase), 0, "invalid coinbase balance");

vm.expectRevert(
Expand Down Expand Up @@ -251,9 +253,10 @@ contract RollupTest is DecoderBase {
bytes memory header = data.header;
bytes32 archive = data.archive;
bytes memory body = data.body;
bytes32[] memory txHashes = new bytes32[](0);

vm.warp(max(block.timestamp, data.decodedHeader.globalVariables.timestamp));
rollup.propose(header, archive, bytes32(0), signatures, body);
rollup.propose(header, archive, bytes32(0), txHashes, signatures, body);

vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NonSequentialProving.selector));
rollup.submitBlockRootProof(header, archive, bytes32(0), "", "");
Expand Down Expand Up @@ -282,50 +285,53 @@ contract RollupTest is DecoderBase {
bytes memory header = data.header;
bytes32 archive = data.archive;
bytes memory body = data.body;
bytes32[] memory txHashes = new bytes32[](0);

assembly {
// TODO: Hardcoding offsets in the middle of tests is annoying to say the least.
mstore(add(header, add(0x20, 0x0174)), 0x420)
}

vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidBlockNumber.selector, 1, 0x420));
rollup.propose(header, archive, bytes32(0), signatures, body);
rollup.propose(header, archive, bytes32(0), txHashes, signatures, body);
}

function testRevertInvalidChainId() public setUpFor("empty_block_1") {
DecoderBase.Data memory data = load("empty_block_1").block;
bytes memory header = data.header;
bytes32 archive = data.archive;
bytes memory body = data.body;
bytes32[] memory txHashes = new bytes32[](0);

assembly {
// TODO: Hardcoding offsets in the middle of tests is annoying to say the least.
mstore(add(header, add(0x20, 0x0134)), 0x420)
}

vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidChainId.selector, 31337, 0x420));
rollup.propose(header, archive, bytes32(0), signatures, body);
rollup.propose(header, archive, bytes32(0), txHashes, signatures, body);
}

function testRevertInvalidVersion() public setUpFor("empty_block_1") {
DecoderBase.Data memory data = load("empty_block_1").block;
bytes memory header = data.header;
bytes32 archive = data.archive;
bytes memory body = data.body;
bytes32[] memory txHashes = new bytes32[](0);

assembly {
mstore(add(header, add(0x20, 0x0154)), 0x420)
}

vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidVersion.selector, 1, 0x420));
rollup.propose(header, archive, bytes32(0), signatures, body);
rollup.propose(header, archive, bytes32(0), txHashes, signatures, body);
}

function testRevertInvalidTimestamp() public setUpFor("empty_block_1") {
DecoderBase.Data memory data = load("empty_block_1").block;
bytes memory header = data.header;
bytes32 archive = data.archive;
bytes memory body = data.body;
bytes32[] memory txHashes = new bytes32[](0);

uint256 realTs = data.decodedHeader.globalVariables.timestamp;
uint256 badTs = realTs + 1;
Expand All @@ -337,7 +343,7 @@ contract RollupTest is DecoderBase {
}

vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__InvalidTimestamp.selector, realTs, badTs));
rollup.propose(header, archive, bytes32(0), signatures, body);
rollup.propose(header, archive, bytes32(0), txHashes, signatures, body);
}

function testBlocksWithAssumeProven() public setUpFor("mixed_block_1") {
Expand Down Expand Up @@ -418,6 +424,7 @@ contract RollupTest is DecoderBase {
bytes32 archive = full.block.archive;
bytes memory body = full.block.body;
uint32 numTxs = full.block.numTxs;
bytes32[] memory txHashes = new bytes32[](0);

// Overwrite some timestamps if needed
if (_slotNumber != 0) {
Expand All @@ -436,7 +443,7 @@ contract RollupTest is DecoderBase {

_populateInbox(full.populate.sender, full.populate.recipient, full.populate.l1ToL2Content);

rollup.propose(header, archive, bytes32(0), signatures, body);
rollup.propose(header, archive, bytes32(0), txHashes, signatures, body);

if (_submitProof) {
uint256 pre = rollup.provenBlockCount();
Expand Down
9 changes: 6 additions & 3 deletions l1-contracts/test/sparta/Sparta.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -171,14 +171,17 @@ contract SpartaTest is DecoderBase {

rollup.setupEpoch();

bytes32[] memory txHashes = new bytes32[](0);

if (_signatureCount > 0 && ree.proposer != address(0)) {
address[] memory validators = rollup.getEpochCommittee(rollup.getCurrentEpoch());
ree.needed = validators.length * 2 / 3 + 1;

SignatureLib.Signature[] memory signatures = new SignatureLib.Signature[](_signatureCount);

bytes32 digest = keccak256(abi.encode(archive, txHashes));
for (uint256 i = 0; i < _signatureCount; i++) {
signatures[i] = createSignature(validators[i], archive);
signatures[i] = createSignature(validators[i], digest);
}

if (_expectRevert) {
Expand Down Expand Up @@ -208,15 +211,15 @@ contract SpartaTest is DecoderBase {
}

vm.prank(ree.proposer);
rollup.propose(header, archive, bytes32(0), signatures, body);
rollup.propose(header, archive, bytes32(0), txHashes, signatures, body);

if (ree.shouldRevert) {
return;
}
} else {
SignatureLib.Signature[] memory signatures = new SignatureLib.Signature[](0);

rollup.propose(header, archive, bytes32(0), signatures, body);
rollup.propose(header, archive, bytes32(0), txHashes, signatures, body);
}

assertEq(_expectRevert, ree.shouldRevert, "Does not match revert expectation");
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/archiver/src/archiver/archiver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ function makeRollupTx(l2Block: L2Block) {
const input = encodeFunctionData({
abi: RollupAbi,
functionName: 'propose',
args: [header, archive, blockHash, [], body],
args: [header, archive, blockHash, [], [], body],
});
return { input } as Transaction<bigint, number>;
}
2 changes: 1 addition & 1 deletion yarn-project/archiver/src/archiver/eth_log_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ async function getBlockFromRollupTx(
if (!(functionName === 'propose')) {
throw new Error(`Unexpected method called ${functionName}`);
}
const [headerHex, archiveRootHex, , , bodyHex] = args! as readonly [Hex, Hex, Hex, ViemSignature[], Hex];
const [headerHex, archiveRootHex, , , , bodyHex] = args! as readonly [Hex, Hex, Hex, Hex[], ViemSignature[], Hex];

const header = Header.fromBuffer(Buffer.from(hexToBytes(headerHex)));
const blockBody = Body.fromBuffer(Buffer.from(hexToBytes(bodyHex)));
Expand Down
21 changes: 17 additions & 4 deletions yarn-project/circuit-types/src/p2p/block_attestation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';

import { recoverMessageAddress } from 'viem';

import { TxHash } from '../tx/tx_hash.js';
import { get0xStringHashedSignaturePayload, getSignaturePayload } from './block_utils.js';
import { Gossipable } from './gossipable.js';
import { Signature } from './signature.js';
import { TopicType, createTopicString } from './topic_type.js';
Expand All @@ -31,6 +33,7 @@ export class BlockAttestation extends Gossipable {
public readonly header: Header,
// TODO(https://github.com/AztecProtocol/aztec-packages/pull/7727#discussion_r1713670830): temporary
public readonly archive: Fr,
public readonly txHashes: TxHash[],
/** The signature of the block attester */
public readonly signature: Signature,
) {
Expand All @@ -53,8 +56,9 @@ export class BlockAttestation extends Gossipable {
async getSender() {
if (!this.sender) {
// Recover the sender from the attestation
const hashed = get0xStringHashedSignaturePayload(this.archive, this.txHashes);
const address = await recoverMessageAddress({
message: { raw: this.p2pMessageIdentifier().to0xString() },
message: { raw: hashed },
signature: this.signature.to0xString(),
});
// Cache the sender for later use
Expand All @@ -64,16 +68,25 @@ export class BlockAttestation extends Gossipable {
return this.sender;
}

getPayload(): Buffer {
return getSignaturePayload(this.archive, this.txHashes);
}

toBuffer(): Buffer {
return serializeToBuffer([this.header, this.archive, this.signature]);
return serializeToBuffer([this.header, this.archive, this.txHashes.length, this.txHashes, this.signature]);
}

static fromBuffer(buf: Buffer | BufferReader): BlockAttestation {
const reader = BufferReader.asReader(buf);
return new BlockAttestation(reader.readObject(Header), reader.readObject(Fr), reader.readObject(Signature));
return new BlockAttestation(
reader.readObject(Header),
reader.readObject(Fr),
reader.readArray(reader.readNumber(), TxHash),
reader.readObject(Signature),
);
}

static empty(): BlockAttestation {
return new BlockAttestation(Header.empty(), Fr.ZERO, Signature.empty());
return new BlockAttestation(Header.empty(), Fr.ZERO, [], Signature.empty());
}
}
20 changes: 19 additions & 1 deletion yarn-project/circuit-types/src/p2p/block_proposal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { BufferReader, serializeToBuffer } from '@aztec/foundation/serialize';
import { recoverMessageAddress } from 'viem';

import { TxHash } from '../tx/tx_hash.js';
import { get0xStringHashedSignaturePayload, getHashedSignaturePayload, getSignaturePayload } from './block_utils.js';
import { Gossipable } from './gossipable.js';
import { Signature } from './signature.js';
import { TopicType, createTopicString } from './topic_type.js';
Expand Down Expand Up @@ -49,14 +50,27 @@ export class BlockProposal extends Gossipable {
return BlockProposalHash.fromField(this.archive);
}

static async createProposalFromSigner(
header: Header,
archive: Fr,
txs: TxHash[],
payloadSigner: (payload: Buffer) => Promise<Signature>,
) {
const hashed = getHashedSignaturePayload(archive, txs);
const sig = await payloadSigner(hashed);

return new BlockProposal(header, archive, txs, sig);
}

/**Get Sender
* Lazily evaluate the sender of the proposal; result is cached
*/
async getSender() {
if (!this.sender) {
// performance note(): this signature method requires another hash behind the scenes
const hashed = get0xStringHashedSignaturePayload(this.archive, this.txs);
const address = await recoverMessageAddress({
message: { raw: this.p2pMessageIdentifier().to0xString() },
message: { raw: hashed },
signature: this.signature.to0xString(),
});
// Cache the sender for later use
Expand All @@ -66,6 +80,10 @@ export class BlockProposal extends Gossipable {
return this.sender;
}

getPayload() {
return getSignaturePayload(this.archive, this.txs);
}

toBuffer(): Buffer {
return serializeToBuffer([this.header, this.archive, this.txs.length, this.txs, this.signature]);
}
Expand Down
34 changes: 34 additions & 0 deletions yarn-project/circuit-types/src/p2p/block_utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { keccak256 as keccak256Buffer } from '@aztec/foundation/crypto';
import { type Fr } from '@aztec/foundation/fields';

import { encodeAbiParameters, keccak256 as keccak2560xString, parseAbiParameters } from 'viem';

import { type TxHash } from '../tx/tx_hash.js';

/**
* Get the payload for the signature of the block proposal
* @param archive - The archive of the block
* @param txs - The transactions in the block
* @returns The payload for the signature of the block proposal
*/
export function getSignaturePayload(archive: Fr, txs: TxHash[]) {
const abi = parseAbiParameters('bytes32, bytes32[]');
const txArray = txs.map(tx => tx.to0xString());
const encodedData = encodeAbiParameters(abi, [archive.toString(), txArray] as const);

return Buffer.from(encodedData.slice(2), 'hex');
}

/**
* Get the hashed payload for the signature of the block proposal
* @param archive - The archive of the block
* @param txs - The transactions in the block
* @returns The hashed payload for the signature of the block proposal
*/
export function getHashedSignaturePayload(archive: Fr, txs: TxHash[]): Buffer {
return keccak256Buffer(getSignaturePayload(archive, txs));
}

export function get0xStringHashedSignaturePayload(archive: Fr, txs: TxHash[]): `0x${string}` {
return keccak2560xString(getSignaturePayload(archive, txs));
}
1 change: 1 addition & 0 deletions yarn-project/circuit-types/src/p2p/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './interface.js';
export * from './gossipable.js';
export * from './topic_type.js';
export * from './signature.js';
export * from './block_utils.js';
Loading

0 comments on commit bce1eea

Please sign in to comment.