Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Proposers claim proving rights #8832

Merged
merged 51 commits into from
Sep 27, 2024
Merged
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3f8f5d1
define escrow contract interface
just-mitch Sep 17, 2024
a567dc3
prune if needed before propose and prove.
just-mitch Sep 17, 2024
4cdeb3b
update getSlotAt check
just-mitch Sep 18, 2024
9d09b1d
Update test to hit code path
just-mitch Sep 18, 2024
6b1a7ba
feat: proofClaim in Rollup (#8636)
just-mitch Sep 20, 2024
09d3064
clean up escrow interface
just-mitch Sep 20, 2024
1fa4fba
feat: basic epoch proof quote pool and interface on aztec node
just-mitch Sep 23, 2024
ff68ae8
rename TxProvider to ProverCoordination
just-mitch Sep 23, 2024
6337b84
Merge remote-tracking branch 'origin/mt/8572-prune-if-needed' into pw…
PhilWindle Sep 24, 2024
4273d2a
Initial work
PhilWindle Sep 26, 2024
c280ad9
Passing test
PhilWindle Sep 26, 2024
c94febe
Merge remote-tracking branch 'origin/master' into pw/proposer-claims-…
PhilWindle Sep 26, 2024
39a3e39
Merge remote-tracking branch 'origin/master' into pw/proposer-claims-…
PhilWindle Sep 26, 2024
8f0ab89
Merge fixes
PhilWindle Sep 26, 2024
735fa3b
More merge fixes
PhilWindle Sep 26, 2024
9de33b0
More merge fixes
PhilWindle Sep 26, 2024
ce6a728
Refactoring
PhilWindle Sep 26, 2024
431a1c6
Formatting
PhilWindle Sep 26, 2024
cfb609d
Comments
PhilWindle Sep 26, 2024
7a20502
Merge branch 'master' into pw/proposer-claims-proof-right
PhilWindle Sep 26, 2024
6144011
Update yarn-project/sequencer-client/src/sequencer/sequencer.ts
PhilWindle Sep 27, 2024
2380ac7
Update yarn-project/sequencer-client/src/sequencer/sequencer.ts
PhilWindle Sep 27, 2024
901c47d
Review changes
PhilWindle Sep 27, 2024
a2d793d
feat: basic epoch proof quote pool and interface on aztec node
just-mitch Sep 23, 2024
4c283da
rename TxProvider to ProverCoordination
just-mitch Sep 23, 2024
c6ccab9
define escrow contract interface
just-mitch Sep 17, 2024
ed522c8
prune if needed before propose and prove.
just-mitch Sep 17, 2024
427b4e9
Update test to hit code path
just-mitch Sep 18, 2024
caad4ff
feat: proofClaim in Rollup (#8636)
just-mitch Sep 20, 2024
cfceaff
clean up escrow interface
just-mitch Sep 20, 2024
7376ca3
Initial work
PhilWindle Sep 26, 2024
c24355e
Passing test
PhilWindle Sep 26, 2024
466e65b
Merge fixes
PhilWindle Sep 26, 2024
0693ee6
More merge fixes
PhilWindle Sep 26, 2024
0a036e5
More merge fixes
PhilWindle Sep 26, 2024
2e698c7
Refactoring
PhilWindle Sep 26, 2024
4578384
Comments
PhilWindle Sep 26, 2024
3eb5f9b
Update yarn-project/sequencer-client/src/sequencer/sequencer.ts
PhilWindle Sep 27, 2024
6bcc3a0
Update yarn-project/sequencer-client/src/sequencer/sequencer.ts
PhilWindle Sep 27, 2024
8274b4a
Review changes
PhilWindle Sep 27, 2024
900c8e2
update conflicts
just-mitch Sep 27, 2024
28ba9cb
Delete expired epoch proofs
PhilWindle Sep 27, 2024
d7294e4
Merge branch 'pw/proposer-claims-proof-right' of github.com:AztecProt…
PhilWindle Sep 27, 2024
34d91cf
Delete expired epoch quotes from pool
PhilWindle Sep 27, 2024
7f96f02
Delete expired epoch quotes
PhilWindle Sep 27, 2024
e3d0cc7
Formatting
PhilWindle Sep 27, 2024
cf63fea
Merge branch 'master' into pw/proposer-claims-proof-right
PhilWindle Sep 27, 2024
f7fbbb4
Handle no proven epochs
PhilWindle Sep 27, 2024
1de3dad
Merge branch 'pw/proposer-claims-proof-right' of github.com:AztecProt…
PhilWindle Sep 27, 2024
fcbe7ef
Fixed test
PhilWindle Sep 27, 2024
602d2e2
Merge branch 'master' into pw/proposer-claims-proof-right
spalladino Sep 27, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Initial work
PhilWindle authored and just-mitch committed Sep 27, 2024
commit 7376ca387bdd60ec8138b63dbe9b7a59db7a93a3
11 changes: 11 additions & 0 deletions l1-contracts/src/core/Leonidas.sol
Original file line number Diff line number Diff line change
@@ -320,6 +320,17 @@ contract Leonidas is Ownable, ILeonidas {
return _ts < GENESIS_TIME ? Slot.wrap(0) : SlotLib.fromTimestamp(_ts - GENESIS_TIME);
}

/**
* @notice Computes the epoch at a specific slot
*
* @param _slotNumber - The slot number to compute the epoch for
*
* @return The computed epoch
*/
function getEpochAtSlot(uint256 _slotNumber) public pure override(ILeonidas) returns (uint256) {
return _slotNumber / EPOCH_DURATION;
}

/**
* @notice Adds a validator to the set WITHOUT setting up the epoch
* @param _validator - The validator to add
266 changes: 153 additions & 113 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
@@ -159,62 +159,6 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
vkTreeRoot = _vkTreeRoot;
}

function claimEpochProofRight(DataStructures.SignedEpochProofQuote calldata _quote)
external
override(IRollup)
{
Slot currentSlot = getCurrentSlot();
address currentProposer = getCurrentProposer();
Epoch epochToProve = getEpochToProve();

if (currentProposer != address(0) && currentProposer != msg.sender) {
revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender);
}

if (_quote.quote.epochToProve != epochToProve) {
revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.quote.epochToProve);
}

if (currentSlot.positionInEpoch() >= CLAIM_DURATION_IN_L2_SLOTS) {
revert Errors.Rollup__NotInClaimPhase(
currentSlot.positionInEpoch(), CLAIM_DURATION_IN_L2_SLOTS
);
}

// if the epoch to prove is not the one that has been claimed,
// then whatever is in the proofClaim is stale
if (proofClaim.epochToProve == epochToProve && proofClaim.proposerClaimant != address(0)) {
revert Errors.Rollup__ProofRightAlreadyClaimed();
}

if (_quote.quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) {
revert Errors.Rollup__InsufficientBondAmount(
PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.quote.bondAmount
);
}

if (_quote.quote.validUntilSlot < currentSlot) {
revert Errors.Rollup__QuoteExpired(currentSlot, _quote.quote.validUntilSlot);
}

// We don't currently unstake,
// but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652.
// Blocked on submitting epoch proofs to this contract.
PROOF_COMMITMENT_ESCROW.stakeBond(_quote.quote.bondAmount, _quote.quote.prover);

proofClaim = DataStructures.EpochProofClaim({
epochToProve: epochToProve,
basisPointFee: _quote.quote.basisPointFee,
bondAmount: _quote.quote.bondAmount,
bondProvider: _quote.quote.prover,
proposerClaimant: msg.sender
});

emit ProofRightClaimed(
epochToProve, _quote.quote.prover, msg.sender, _quote.quote.bondAmount, currentSlot
);
}

/**
* @notice Publishes the body and propose the block
* @dev `eth_log_handlers` rely on this function
@@ -225,68 +169,17 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
* @param _signatures - Signatures from the validators
* @param _body - The body of the L2 block
*/
function propose(
function proposeAndClaim(
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
bytes32[] memory _txHashes,
SignatureLib.Signature[] memory _signatures,
bytes calldata _body
bytes calldata _body,
DataStructures.EpochProofQuote calldata _quote
) external override(IRollup) {
if (_canPrune()) {
_prune();
}
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: digest,
_currentTime: Timestamp.wrap(block.timestamp),
_txEffectsHash: txsEffectsHash,
_flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false})
});

uint256 blockNumber = ++tips.pendingBlockNumber;

blocks[blockNumber] = BlockLog({
archive: _archive,
blockHash: _blockHash,
slotNumber: Slot.wrap(header.globalVariables.slotNumber)
});

// @note The block number here will always be >=1 as the genesis block is at 0
bytes32 inHash = INBOX.consume(blockNumber);
if (header.contentCommitment.inHash != inHash) {
revert Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash);
}

// TODO(#7218): Revert to fixed height tree for outbox, currently just providing min as interim
// Min size = smallest path of the rollup tree + 1
(uint256 min,) = MerkleLib.computeMinMaxPathLength(header.contentCommitment.numTxs);
uint256 l2ToL1TreeMinHeight = min + 1;
OUTBOX.insert(blockNumber, header.contentCommitment.outHash, l2ToL1TreeMinHeight);

emit L2BlockProposed(blockNumber, _archive);

// Automatically flag the block as proven if we have cheated and set assumeProvenThroughBlockNumber.
if (blockNumber <= assumeProvenThroughBlockNumber) {
tips.provenBlockNumber = blockNumber;

if (header.globalVariables.coinbase != address(0) && header.totalFees > 0) {
// @note This will currently fail if there are insufficient funds in the bridge
// which WILL happen for the old version after an upgrade where the bridge follow.
// Consider allowing a failure. See #7938.
FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees);
}

emit L2ProofVerified(blockNumber, "CHEAT");
}
propose(_header, _archive, _blockHash, _txHashes, _signatures, _body);
claimEpochProofRight(_quote);
}

/**
@@ -694,6 +587,14 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
_validateHeader(header, _signatures, _digest, _currentTime, _txsEffectsHash, _flags);
}

function nextEpochToClaim() external view override(IRollup) returns (uint256) {
uint256 epochClaimed = proofClaim.epochToProve;
if (proofClaim.proposerClaimant == address(0) && epochClaimed == 0) {
return 0;
}
return 1 + epochClaimed;
}

function computeTxsEffectsHash(bytes calldata _body)
external
pure
@@ -703,6 +604,145 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
return TxsDecoder.decode(_body);
}

function claimEpochProofRight(DataStructures.EpochProofQuote calldata _quote)
public
override(IRollup)
{
validateEpochProofRightClaim(_quote);

uint256 currentSlot = getCurrentSlot();
uint256 epochToProve = getEpochToProve();

// We don't currently unstake,
// but we will as part of https://github.com/AztecProtocol/aztec-packages/issues/8652.
// Blocked on submitting epoch proofs to this contract.
address bondProvider = PROOF_COMMITMENT_ESCROW.stakeBond(_quote.signature, _quote.bondAmount);

proofClaim = DataStructures.EpochProofClaim({
epochToProve: epochToProve,
basisPointFee: _quote.basisPointFee,
bondAmount: _quote.bondAmount,
bondProvider: bondProvider,
proposerClaimant: msg.sender
});

emit ProofRightClaimed(epochToProve, bondProvider, msg.sender, _quote.bondAmount, currentSlot);
}

/**
* @notice Publishes the body and propose the block
* @dev `eth_log_handlers` rely on this function
*
* @param _header - The L2 block header
* @param _archive - A root of the archive tree after the L2 block is applied
* @param _blockHash - The poseidon2 hash of the header added to the archive tree in the rollup circuit
* @param _signatures - Signatures from the validators
* @param _body - The body of the L2 block
*/
function propose(
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
bytes32[] memory _txHashes,
SignatureLib.Signature[] memory _signatures,
bytes calldata _body
) public override(IRollup) {
if (_canPrune()) {
_prune();
}
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: digest,
_currentTime: block.timestamp,
_txEffectsHash: txsEffectsHash,
_flags: DataStructures.ExecutionFlags({ignoreDA: false, ignoreSignatures: false})
});

uint256 blockNumber = ++tips.pendingBlockNumber;

blocks[blockNumber] = BlockLog({
archive: _archive,
blockHash: _blockHash,
slotNumber: header.globalVariables.slotNumber.toUint128()
});

// @note The block number here will always be >=1 as the genesis block is at 0
bytes32 inHash = INBOX.consume(blockNumber);
if (header.contentCommitment.inHash != inHash) {
revert Errors.Rollup__InvalidInHash(inHash, header.contentCommitment.inHash);
}

// TODO(#7218): Revert to fixed height tree for outbox, currently just providing min as interim
// Min size = smallest path of the rollup tree + 1
(uint256 min,) = MerkleLib.computeMinMaxPathLength(header.contentCommitment.numTxs);
uint256 l2ToL1TreeMinHeight = min + 1;
OUTBOX.insert(blockNumber, header.contentCommitment.outHash, l2ToL1TreeMinHeight);

emit L2BlockProposed(blockNumber, _archive);

// Automatically flag the block as proven if we have cheated and set assumeProvenThroughBlockNumber.
if (blockNumber <= assumeProvenThroughBlockNumber) {
tips.provenBlockNumber = blockNumber;

if (header.globalVariables.coinbase != address(0) && header.totalFees > 0) {
// @note This will currently fail if there are insufficient funds in the bridge
// which WILL happen for the old version after an upgrade where the bridge follow.
// Consider allowing a failure. See #7938.
FEE_JUICE_PORTAL.distributeFees(header.globalVariables.coinbase, header.totalFees);
}

emit L2ProofVerified(blockNumber, "CHEAT");
}
}

function validateEpochProofRightClaim(DataStructures.EpochProofQuote calldata _quote)
public
view
override(IRollup)
{
uint256 currentSlot = getCurrentSlot();
address currentProposer = getCurrentProposer();
uint256 epochToProve = getEpochToProve();

if (currentProposer != address(0) && currentProposer != msg.sender) {
revert Errors.Leonidas__InvalidProposer(currentProposer, msg.sender);
}

if (_quote.epochToProve != epochToProve) {
revert Errors.Rollup__NotClaimingCorrectEpoch(epochToProve, _quote.epochToProve);
}

if (currentSlot % Constants.AZTEC_EPOCH_DURATION >= CLAIM_DURATION_IN_L2_SLOTS) {
revert Errors.Rollup__NotInClaimPhase(
currentSlot % Constants.AZTEC_EPOCH_DURATION, CLAIM_DURATION_IN_L2_SLOTS
);
}

// if the epoch to prove is not the one that has been claimed,
// then whatever is in the proofClaim is stale
if (proofClaim.epochToProve == epochToProve && proofClaim.proposerClaimant != address(0)) {
revert Errors.Rollup__ProofRightAlreadyClaimed();
}

if (_quote.bondAmount < PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST) {
revert Errors.Rollup__InsufficientBondAmount(
PROOF_COMMITMENT_MIN_BOND_AMOUNT_IN_TST, _quote.bondAmount
);
}

if (_quote.validUntilSlot < currentSlot) {
revert Errors.Rollup__QuoteExpired(currentSlot, _quote.validUntilSlot);
}
}

/**
* @notice Get the current archive root
*
@@ -733,7 +773,7 @@ contract Rollup is Leonidas, IRollup, ITestRollup {
if (tips.provenBlockNumber == tips.pendingBlockNumber) {
revert Errors.Rollup__NoEpochToProve();
} else {
return getEpochAt(blocks[getProvenBlockNumber() + 1].slotNumber);
return getEpochAt(getTimestampForSlot(blocks[getProvenBlockNumber() + 1].slotNumber));
}
}

1 change: 1 addition & 0 deletions l1-contracts/src/core/interfaces/ILeonidas.sol
Original file line number Diff line number Diff line change
@@ -32,4 +32,5 @@ interface ILeonidas {

function getEpochAt(Timestamp _ts) external view returns (Epoch);
function getSlotAt(Timestamp _ts) external view returns (Slot);
function getEpochAtSlot(Slot _slotNumber) external view returns (Epoch);
}
14 changes: 14 additions & 0 deletions l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
@@ -41,6 +41,16 @@ interface IRollup {
bytes calldata _body
) external;

function proposeAndClaim(
bytes calldata _header,
bytes32 _archive,
bytes32 _blockHash,
bytes32[] memory _txHashes,
SignatureLib.Signature[] memory _signatures,
bytes calldata _body,
DataStructures.EpochProofQuote calldata _quote
) external;

function submitBlockRootProof(
bytes calldata _header,
bytes32 _archive,
@@ -105,5 +115,9 @@ interface IRollup {
function getProvenBlockNumber() external view returns (uint256);
function getPendingBlockNumber() external view returns (uint256);
function getEpochToProve() external view returns (Epoch);
function nextEpochToClaim() external view returns (Epoch);
function validateEpochProofRightClaim(DataStructures.EpochProofQuote calldata _quote)
external
view;
function computeTxsEffectsHash(bytes calldata _body) external pure returns (bytes32);
}
4 changes: 3 additions & 1 deletion yarn-project/archiver/src/archiver/data_retrieval.ts
Original file line number Diff line number Diff line change
@@ -134,7 +134,9 @@ async function getBlockFromRollupTx(
data,
});

if (!(functionName === 'propose')) {
const allowedMethods = ['propose', 'proposeAndClaim'];

if (!allowedMethods.includes(functionName)) {
throw new Error(`Unexpected method called ${functionName}`);
}
const [headerHex, archiveRootHex, , , , bodyHex] = args! as readonly [Hex, Hex, Hex, Hex[], ViemSignature[], Hex];
22 changes: 22 additions & 0 deletions yarn-project/circuit-types/src/mocks.ts
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@ import {
AztecAddress,
CallContext,
ClientIvcProof,
type EthAddress,
GasSettings,
LogHash,
MAX_ENCRYPTED_LOGS_PER_TX,
@@ -28,11 +29,14 @@ import { type ContractArtifact, NoteSelector } from '@aztec/foundation/abi';
import { makeTuple } from '@aztec/foundation/array';
import { padArrayEnd, times } from '@aztec/foundation/collection';
import { randomBytes } from '@aztec/foundation/crypto';
import { Signature } from '@aztec/foundation/eth-signature';
import { Fr } from '@aztec/foundation/fields';
import { type ContractInstanceWithAddress, SerializableContractInstance } from '@aztec/types/contracts';

import { EncryptedNoteTxL2Logs, EncryptedTxL2Logs, Note, UnencryptedTxL2Logs } from './logs/index.js';
import { ExtendedNote, UniqueNote } from './notes/index.js';
import { EpochProofQuote } from './prover_coordination/epoch_proof_quote.js';
import { EpochProofQuotePayload } from './prover_coordination/epoch_proof_quote_payload.js';
import { PublicExecutionRequest } from './public_execution_request.js';
import { NestedProcessReturnValues, PublicSimulationOutput, SimulatedTx, Tx, TxHash } from './tx/index.js';

@@ -223,6 +227,24 @@ export const mockSimulatedTx = (seed = 1, hasLogs = true) => {
return new SimulatedTx(tx, dec, output);
};

export const mockEpochProofQuote = (
epochToProve: bigint,
validUntilSlot: bigint,
bondAmount: bigint,
rollupAddress: EthAddress,
basisPointFee: number,
) => {
const quotePayload: EpochProofQuotePayload = new EpochProofQuotePayload(
epochToProve,
validUntilSlot,
bondAmount,
rollupAddress,
basisPointFee,
);
const sig: Signature = Signature.empty();
return new EpochProofQuote(quotePayload, sig);
};

export const randomContractArtifact = (): ContractArtifact => ({
name: randomBytes(4).toString('hex'),
functions: [],
Original file line number Diff line number Diff line change
@@ -52,4 +52,15 @@ export class EpochProofQuote extends Gossipable {

return this.sender;
}

toViemArgs() {
return {
epochToProve: this.payload.epochToProve,
validUntilSlot: this.payload.validUntilSlot,
bondAmount: this.payload.bondAmount,
rollupAddress: this.payload.rollupAddress,
basisPointFee: this.payload.basisPointFee,
signature: this.signature.toViemSignature(),
};
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { type DebugLogger, createDebugLogger } from '@aztec/aztec.js';
import { makeRandomEpochProofQuote } from '@aztec/p2p';
import { getSchnorrAccount } from '@aztec/accounts/schnorr';
import { type AccountWalletWithSecretKey, type DebugLogger, EthCheatCodes, createDebugLogger } from '@aztec/aztec.js';
import { type EpochProofQuote, mockEpochProofQuote } from '@aztec/circuit-types';
import { AZTEC_EPOCH_DURATION, AZTEC_SLOT_DURATION, type AztecAddress, EthAddress } from '@aztec/circuits.js';
import { times } from '@aztec/foundation/collection';
import { RollupAbi } from '@aztec/l1-artifacts';
import { StatefulTestContract } from '@aztec/noir-contracts.js';

import { beforeAll } from '@jest/globals';
import { type PublicClient, getAddress, getContract } from 'viem';

import {
type ISnapshotManager,
@@ -16,6 +22,12 @@ import {
// the coordination through L1 between the sequencer and the prover node.
describe('e2e_prover_node', () => {
let ctx: SubsystemsContext;
let wallet: AccountWalletWithSecretKey;
let recipient: AztecAddress;
let contract: StatefulTestContract;
let rollupContract: any;
let publicClient: PublicClient;
let cc: EthCheatCodes;

let logger: DebugLogger;
let snapshotManager: ISnapshotManager;
@@ -24,17 +36,202 @@ describe('e2e_prover_node', () => {
logger = createDebugLogger('aztec:prover_coordination:e2e_json_coordination');
snapshotManager = createSnapshotManager(`prover_coordination/e2e_json_coordination`, process.env.E2E_DATA_PATH);

await snapshotManager.snapshot('setup', addAccounts(2, logger));
logger.info(`1`);

await snapshotManager.snapshot('setup', addAccounts(2, logger), async ({ accountKeys }, ctx) => {
const accountManagers = accountKeys.map(ak => getSchnorrAccount(ctx.pxe, ak[0], ak[1], 1));
await Promise.all(accountManagers.map(a => a.register()));
const wallets = await Promise.all(accountManagers.map(a => a.getWallet()));
wallets.forEach((w, i) => logger.verbose(`Wallet ${i} address: ${w.getAddress()}`));
wallet = wallets[0];
recipient = wallets[1].getAddress();
});

await snapshotManager.snapshot(
'deploy-test-contract',
async () => {
const owner = wallet.getAddress();
const contract = await StatefulTestContract.deploy(wallet, owner, owner, 42).send().deployed();
return { contractAddress: contract.address };
},
async ({ contractAddress }) => {
contract = await StatefulTestContract.at(contractAddress, wallet);
},
);

ctx = await snapshotManager.setup();

await ctx.proverNode.stop();

cc = new EthCheatCodes(ctx.aztecNodeConfig.l1RpcUrl);

publicClient = ctx.deployL1ContractsValues.publicClient;
rollupContract = getContract({
address: getAddress(ctx.deployL1ContractsValues.l1ContractAddresses.rollupAddress.toString()),
abi: RollupAbi,
client: ctx.deployL1ContractsValues.walletClient,
});
});

it('Prover can submit an EpochProofQuote to the node via jsonrpc', async () => {
const { quote } = makeRandomEpochProofQuote();
// it('Prover can submit an EpochProofQuote to the node via jsonrpc', async () => {
// const { quote } = makeRandomEpochProofQuote();

// await ctx.proverNode.sendEpochProofQuote(quote);
// const receivedQuotes = await ctx.aztecNode.getEpochProofQuotes(quote.payload.epochToProve);
// expect(receivedQuotes.length).toBe(1);
// expect(receivedQuotes[0]).toEqual(quote);
// });

const expectProofClaimOnL1 = async (quote: EpochProofQuote, _: EthAddress) => {
const claimFromContract = await rollupContract.read.proofClaim();
expect(claimFromContract[0]).toEqual(quote.payload.epochToProve);
expect(claimFromContract[1]).toEqual(BigInt(quote.payload.basisPointFee));
expect(claimFromContract[2]).toEqual(quote.payload.bondAmount);
//expect(claimFromContract[4]).toEqual(proposer.toString());
};

const getL1Timestamp = async () => {
return BigInt((await publicClient.getBlock()).timestamp);
};

const getSlot = async () => {
const ts = await getL1Timestamp();
return await rollupContract.read.getSlotAt([ts]);
};

const getEpoch = async () => {
const slotNumber = await getSlot();
return await rollupContract.read.getEpochAtSlot([slotNumber]);
};

const getPendingBlockNumber = async () => {
return await rollupContract.read.getPendingBlockNumber();
};

const getProvenBlockNumber = async () => {
return await rollupContract.read.getProvenBlockNumber();
};

const getEpochToProve = async () => {
return await rollupContract.read.getEpochToProve();
};

const getTimestampForSlot = async (slotNumber: bigint) => {
return await rollupContract.read.getTimestampForSlot([slotNumber]);
};

const verifyQuote = async (quote: EpochProofQuote) => {
try {
const args = [quote.toViemArgs()] as const;
await rollupContract.read.validateEpochProofRightClaim(args);
logger.info('QUOTE VERIFIED');
} catch (error) {
console.log(error);
}
};

const logState = async () => {
logger.info(`Pending block: ${await getPendingBlockNumber()}`);
logger.info(`Proven block: ${await getProvenBlockNumber()}`);
logger.info(`Slot number: ${await getSlot()}`);
logger.info(`Epoch number: ${await getEpoch()}`);
logger.info(`Epoch to prove ${await getEpochToProve()}`);
};

const advanceToNextEpoch = async () => {
const slot = await getSlot();
const slotsUntilNextEpoch = BigInt(AZTEC_EPOCH_DURATION) - (slot % BigInt(AZTEC_EPOCH_DURATION)) + 1n;
const timeToNextEpoch = slotsUntilNextEpoch * BigInt(AZTEC_SLOT_DURATION);
logger.info(`SLOTS TO NEXT EPOCH ${slotsUntilNextEpoch}`);
const l1Timestamp = await getL1Timestamp();
await cc.warp(Number(l1Timestamp + timeToNextEpoch));
await logState();
};

it('Sequencer selects best valid proving quote for each block', async () => {
// We want to create a set of proving quotes, some valid and some invalid
// The sequencer should select the cheapest valid quote when it proposes the block
logger.info(`Start`);

// Here we are creating a proof quote for epoch 0, this will NOT get used yet
const quoteForEpoch0 = mockEpochProofQuote(
0n, // epoch 0
BigInt(AZTEC_EPOCH_DURATION + 10), // valid until slot 10 into epoch 1
10000n,
EthAddress.random(),
1,
);

// Send in the quote
await ctx.proverNode.sendEpochProofQuote(quoteForEpoch0);

// Build a block, this should NOT use the above quote as it is for the current epoch (0)
await contract.methods.create_note(recipient, recipient, 10).send().wait();

await logState();

const epoch0BlockNumber = await getPendingBlockNumber();

// Verify that the claim state on L1 is unitialised
const uninitialisedProofClaim = mockEpochProofQuote(
0n, // epoch 0
BigInt(0),
0n,
EthAddress.random(),
0,
);

// The rollup contract should have an uninitialised proof claim struct
await expectProofClaimOnL1(uninitialisedProofClaim, EthAddress.random());

// Now go to epoch 1
await advanceToNextEpoch();

const blockSlot = await getSlot();

logger.info(`TIMESTAMP FOR SLOT: ${await getTimestampForSlot(blockSlot)}`);

await logState();

// Build a block in epoch 1, we should see the quote for epoch 0 submitted earlier published to L1
await contract.methods.create_note(recipient, recipient, 10).send().wait();

// Check it was published
await expectProofClaimOnL1(quoteForEpoch0, EthAddress.random());

// now 'prove' epoch 0
await rollupContract.write.setAssumeProvenThroughBlockNumber([BigInt(epoch0BlockNumber)]);

logger.info(`SET PROVEN BLOCK NUMBER`);

await logState();

// Now go to epoch 2
await advanceToNextEpoch();

const currentSlot = await getSlot();

// Now create a number of quotes, some valid some invalid for epoch 1, the lowest priced valid quote should be chosen
const validQuotes = times(3, (i: number) =>
mockEpochProofQuote(1n, currentSlot + 2n, 10000n, EthAddress.random(), 10 + i),
);

// Check the L1 verification of this quote
await verifyQuote(validQuotes[0]);

const proofQuoteInvalidSlot = mockEpochProofQuote(1n, 3n, 10000n, EthAddress.random(), 1);

const proofQuoteInvalidEpoch = mockEpochProofQuote(2n, currentSlot + 4n, 10000n, EthAddress.random(), 2);

const allQuotes = [proofQuoteInvalidSlot, proofQuoteInvalidEpoch, ...validQuotes];

//await Promise.all(allQuotes.map(x => ctx.proverNode.sendEpochProofQuote(x)));

// now build another block and we should see the best valid quote being published
await contract.methods.create_note(recipient, recipient, 10).send().wait();

const expectedQuote = validQuotes[0];

await ctx.proverNode.sendEpochProofQuote(quote);
const receivedQuotes = await ctx.aztecNode.getEpochProofQuotes(quote.payload.epochToProve);
expect(receivedQuotes.length).toBe(1);
expect(receivedQuotes[0]).toEqual(quote);
await expectProofClaimOnL1(expectedQuote, EthAddress.random());
});
});