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

fix: support empty epochs #9341

Merged
merged 3 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 12 additions & 6 deletions l1-contracts/src/core/Rollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -358,12 +358,18 @@ contract Rollup is EIP712("Aztec Rollup", "1"), Leonidas, IRollup, ITestRollup {
_validateHeader(header, _signatures, _digest, _currentTime, _txsEffectsHash, _flags);
}

function nextEpochToClaim() external view override(IRollup) returns (Epoch) {
Epoch epochClaimed = proofClaim.epochToProve;
if (proofClaim.proposerClaimant == address(0) && epochClaimed == Epoch.wrap(0)) {
return Epoch.wrap(0);
}
return Epoch.wrap(1) + epochClaimed;
/**
* @notice Get the next epoch that can be claimed
* @dev Will revert if the epoch has already been claimed or if there is no epoch to prove
*/
function getClaimableEpoch() external view override(IRollup) returns (Epoch) {
Epoch epochToProve = getEpochToProve();
require(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bit confusing that both of these are epochToProve, mind explaining this?

proofClaim.epochToProve != epochToProve
|| (proofClaim.proposerClaimant == address(0) && proofClaim.epochToProve == Epoch.wrap(0)),
Errors.Rollup__ProofRightAlreadyClaimed()
);
return epochToProve;
}

function computeTxsEffectsHash(bytes calldata _body)
Expand Down
2 changes: 1 addition & 1 deletion l1-contracts/src/core/interfaces/IRollup.sol
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ 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 getClaimableEpoch() external view returns (Epoch);
function getEpochForBlock(uint256 blockNumber) external view returns (Epoch);
function validateEpochProofRightClaim(EpochProofQuoteLib.SignedEpochProofQuote calldata _quote)
external
Expand Down
17 changes: 17 additions & 0 deletions l1-contracts/test/Rollup.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,23 @@ contract RollupTest is DecoderBase {
vm.warp(Timestamp.unwrap(rollup.getTimestampForSlot(Slot.wrap(_slot))));
}

function testClaimableEpoch(uint256 epochForMixedBlock) public setUpFor("mixed_block_1") {
epochForMixedBlock = bound(epochForMixedBlock, 1, 10);
vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__NoEpochToProve.selector));
assertEq(rollup.getClaimableEpoch(), 0, "Invalid claimable epoch");

quote.epochToProve = Epoch.wrap(epochForMixedBlock);
quote.validUntilSlot = Slot.wrap(epochForMixedBlock * Constants.AZTEC_EPOCH_DURATION + 1);
signedQuote = _quoteToSignedQuote(quote);

_testBlock("mixed_block_1", false, epochForMixedBlock * Constants.AZTEC_EPOCH_DURATION);
assertEq(rollup.getClaimableEpoch(), Epoch.wrap(epochForMixedBlock), "Invalid claimable epoch");

rollup.claimEpochProofRight(signedQuote);
vm.expectRevert(abi.encodeWithSelector(Errors.Rollup__ProofRightAlreadyClaimed.selector));
rollup.getClaimableEpoch();
}

function testClaimWithNothingToProve() public setUpFor("mixed_block_1") {
assertEq(rollup.getCurrentSlot(), 0, "genesis slot should be zero");

Expand Down
17 changes: 15 additions & 2 deletions yarn-project/aztec.js/src/utils/cheat_codes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,20 @@ export class RollupCheatCodes {
return await this.rollup.read.getEpochAtSlot([slotNumber]);
}

/**
* Returns the pending and proven chain tips
* @returns The pending and proven chain tips
*/
public async getTips(): Promise<{
/** The pending chain tip */
pending: bigint;
/** The proven chain tip */
proven: bigint;
}> {
const [pending, proven] = await this.rollup.read.tips();
return { pending, proven };
}

/** Warps time in L1 until the next epoch */
public async advanceToNextEpoch() {
const slot = await this.getSlot();
Expand Down Expand Up @@ -373,8 +387,7 @@ export class RollupCheatCodes {
: await this.rollup.read.tips().then(([pending]) => pending);

await this.asOwner(async account => {
// TODO: Figure out proper typing for viem
await this.rollup.write.setAssumeProvenThroughBlockNumber([blockNumber], { account } as any);
await this.rollup.write.setAssumeProvenThroughBlockNumber([blockNumber], { account, chain: this.client.chain });
this.logger.verbose(`Marked ${blockNumber} as proven`);
});
}
Expand Down
9 changes: 6 additions & 3 deletions yarn-project/end-to-end/Earthfile
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,9 @@ kind-network-smoke:
kind-network-transfer:
ARG values_file
LOCALLY
# TODO(https://github.com/AztecProtocol/aztec-packages/issues/9166):
# This has || true so it does NOT report failure in github actions job. To be reenabled once stable
RUN NAMESPACE=transfer FRESH_INSTALL=true VALUES_FILE=${values_file:-default.yaml} ./scripts/network_test.sh ./src/spartan/transfer.test.ts || true
RUN NAMESPACE=transfer FRESH_INSTALL=true VALUES_FILE=${values_file:-default.yaml} ./scripts/network_test.sh ./src/spartan/transfer.test.ts

kind-network-4epochs:
ARG values_file
LOCALLY
RUN NAMESPACE=transfer FRESH_INSTALL=true VALUES_FILE=${values_file:-default.yaml} ./scripts/network_test.sh ./src/spartan/4epochs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ exec > >(tee -a "$(dirname $0)/logs/${SCRIPT_NAME}.log") 2> >(tee -a "$(dirname

# Set environment variables
export PORT=${PORT:-"8080"}
export DEBUG=${DEBUG:-"aztec:*,-aztec:avm_simulator:*"}
export DEBUG=${DEBUG:-"aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*,-aztec:l2_block_stream,-aztec:world-state:*"}
export LOG_LEVEL=${LOG_LEVEL:-"debug"}
export ETHEREUM_HOST="http://127.0.0.1:8545"
export P2P_ENABLED="true"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export BOOTSTRAP_NODES=$(echo "$output" | grep -oP 'Node ENR: \K.*')

# Set environment variables
export LOG_LEVEL=${LOG_LEVEL:-"debug"}
export DEBUG=${DEBUG:-"aztec:*"}
export DEBUG=${DEBUG:-"aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*,-aztec:l2_block_stream,-aztec:world-state:*"}
export ETHEREUM_HOST="http://127.0.0.1:8545"
export PROVER_AGENT_ENABLED="true"
export PROVER_PUBLISHER_PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export ETHEREUM_HOST="http://127.0.0.1:8545"
export AZTEC_NODE_URL="http://127.0.0.1:8080"
export LOG_JSON="1"
export LOG_LEVEL=${LOG_LEVEL:-"debug"}
export DEBUG="aztec:*"
export DEBUG="aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*,-aztec:l2_block_stream,-aztec:world-state:*"
export BOT_PRIVATE_KEY="0xcafe"
export BOT_TX_INTERVAL_SECONDS="5"
export BOT_PRIVATE_TRANSFERS_PER_TX="1"
Expand Down
9 changes: 7 additions & 2 deletions yarn-project/end-to-end/scripts/native-network/validator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export LOG_LEVEL=${LOG_LEVEL:-"debug"}
export VALIDATOR_PRIVATE_KEY=$(echo $json_account | jq -r '.privateKey')
export L1_PRIVATE_KEY=$VALIDATOR_PRIVATE_KEY
export SEQ_PUBLISHER_PRIVATE_KEY=$VALIDATOR_PRIVATE_KEY
export DEBUG=${DEBUG:-"aztec:*,-aztec:avm_simulator:*"}
export DEBUG=${DEBUG:-"aztec:*,-aztec:avm_simulator*,-aztec:libp2p_service*,-aztec:circuits:artifact_hash,-json-rpc*,-aztec:l2_block_stream,-aztec:world-state:*"}
export ETHEREUM_HOST="http://127.0.0.1:8545"
export P2P_ENABLED="true"
export VALIDATOR_DISABLED="false"
Expand All @@ -56,7 +56,12 @@ export P2P_TCP_LISTEN_ADDR="0.0.0.0:$P2P_PORT"
export P2P_UDP_LISTEN_ADDR="0.0.0.0:$P2P_PORT"

# Add L1 validator
node --no-warnings "$REPO"/yarn-project/aztec/dest/bin/index.js add-l1-validator --validator $ADDRESS --rollup $ROLLUP_CONTRACT_ADDRESS
# this may fail, so try 3 times
for i in {1..3}; do
node --no-warnings "$REPO"/yarn-project/aztec/dest/bin/index.js add-l1-validator --validator $ADDRESS --rollup $ROLLUP_CONTRACT_ADDRESS && break
sleep 1
done

# Fast forward epochs
node --no-warnings "$REPO"/yarn-project/aztec/dest/bin/index.js fast-forward-epochs --rollup $ROLLUP_CONTRACT_ADDRESS --count 1
# Start the Validator Node with the sequencer and archiver
Expand Down
4 changes: 3 additions & 1 deletion yarn-project/end-to-end/scripts/network_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,12 @@ kubectl wait pod -l app==pxe --for=condition=Ready -n "$NAMESPACE" --timeout=10m

# tunnel in to get access directly to our PXE service in k8s
(kubectl port-forward --namespace $NAMESPACE svc/spartan-aztec-network-pxe 9082:8080 2>/dev/null >/dev/null || true) &
(kubectl port-forward --namespace $NAMESPACE svc/spartan-aztec-network-anvil 9545:8545 2>/dev/null >/dev/null || true) &

docker run --rm --network=host \
-e PXE_URL=http://localhost:9082 \
-e PXE_URL=http://127.0.0.1:9082 \
-e DEBUG="aztec:*" \
-e LOG_LEVEL=debug \
-e ETHEREUM_HOST=http://127.0.0.1:9545 \
-e LOG_JSON=1 \
aztecprotocol/end-to-end:$AZTEC_DOCKER_TAG $TEST
6 changes: 3 additions & 3 deletions yarn-project/end-to-end/src/spartan/4epochs.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { EthCheatCodes, readFieldCompressedString } from '@aztec/aztec.js';
import { AZTEC_SLOT_DURATION } from '@aztec/circuits.js';
import { AZTEC_EPOCH_DURATION } from '@aztec/circuits.js';
import { createDebugLogger } from '@aztec/foundation/log';
import { TokenContract } from '@aztec/noir-contracts.js';

Expand All @@ -17,13 +17,13 @@ if (!ETHEREUM_HOST) {
}

describe('token transfer test', () => {
jest.setTimeout(10 * 60 * 2000); // 20 minutes
jest.setTimeout(10 * 60 * 4000); // 40 minutes

const logger = createDebugLogger(`aztec:spartan:4epochs`);
// We want plenty of minted tokens for a lot of slots that fill up multiple epochs
const MINT_AMOUNT = 2000000n;
const TEST_EPOCHS = 4;
const ROUNDS = BigInt(AZTEC_SLOT_DURATION * TEST_EPOCHS);
const ROUNDS = BigInt(AZTEC_EPOCH_DURATION * TEST_EPOCHS);

let testWallets: TestWallets;

Expand Down
24 changes: 19 additions & 5 deletions yarn-project/sequencer-client/src/publisher/l1-publisher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { areArraysEqual, compactArray, times } from '@aztec/foundation/collectio
import { type Signature } from '@aztec/foundation/eth-signature';
import { Fr } from '@aztec/foundation/fields';
import { createDebugLogger } from '@aztec/foundation/log';
import { type Tuple, serializeToBuffer, toFriendlyJSON } from '@aztec/foundation/serialize';
import { type Tuple, serializeToBuffer } from '@aztec/foundation/serialize';
import { InterruptibleSleep } from '@aztec/foundation/sleep';
import { Timer } from '@aztec/foundation/timer';
import { RollupAbi } from '@aztec/l1-artifacts';
Expand Down Expand Up @@ -221,8 +221,23 @@ export class L1Publisher {
return [slot, blockNumber];
}

public async nextEpochToClaim(): Promise<bigint> {
return await this.rollupContract.read.nextEpochToClaim();
public async getClaimableEpoch(): Promise<bigint | undefined> {
try {
return await this.rollupContract.read.getClaimableEpoch();
} catch (err: any) {
const errorName = tryGetCustomErrorName(err);
// getting the error name from the abi is redundant,
// but it enforces that the error name is correct.
// That is, if the error name is not found, this will not compile.
const acceptedErrors = (['Rollup__NoEpochToProve', 'Rollup__ProofRightAlreadyClaimed'] as const).map(
name => getAbiItem({ abi: RollupAbi, name }).name,
);

if (errorName && acceptedErrors.includes(errorName as any)) {
return undefined;
}
throw err;
}
}

public async getEpochForSlotNumber(slotNumber: bigint): Promise<bigint> {
Expand Down Expand Up @@ -268,7 +283,6 @@ export class L1Publisher {
try {
await this.rollupContract.read.validateEpochProofRightClaim(args, { account: this.account });
} catch (err) {
this.log.verbose(toFriendlyJSON(err as object));
const errorName = tryGetCustomErrorName(err);
this.log.warn(`Proof quote validation failed: ${errorName}`);
return undefined;
Expand Down Expand Up @@ -767,7 +781,7 @@ function getCalldataGasUsage(data: Uint8Array) {
function tryGetCustomErrorName(err: any) {
try {
// See https://viem.sh/docs/contract/simulateContract#handling-custom-errors
if (err.name === 'ViemError') {
if (err.name === 'ViemError' || err.name === 'ContractFunctionExecutionError') {
const baseError = err as BaseError;
const revertError = baseError.walk(err => (err as Error).name === 'ContractFunctionRevertedError');
if (revertError) {
Expand Down
16 changes: 7 additions & 9 deletions yarn-project/sequencer-client/src/sequencer/sequencer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ describe('sequencer', () => {
publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x));

// The previous epoch can be claimed
publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n));
publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n));

await sequencer.work();
expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], proofQuote);
Expand All @@ -599,8 +599,7 @@ describe('sequencer', () => {
publisher.proposeL2Block.mockResolvedValueOnce(true);
publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x));

// The previous epoch can be claimed
publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(0n));
publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(undefined));

await sequencer.work();
expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined);
Expand All @@ -624,7 +623,7 @@ describe('sequencer', () => {
publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x));

// The previous epoch can be claimed
publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n));
publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n));

await sequencer.work();
expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined);
Expand All @@ -646,8 +645,7 @@ describe('sequencer', () => {
publisher.proposeL2Block.mockResolvedValueOnce(true);
publisher.validateProofQuote.mockImplementation((x: EpochProofQuote) => Promise.resolve(x));

// The previous epoch can be claimed
publisher.nextEpochToClaim.mockResolvedValue(currentEpoch);
publisher.getClaimableEpoch.mockResolvedValue(undefined);

await sequencer.work();
expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined);
Expand All @@ -672,7 +670,7 @@ describe('sequencer', () => {
publisher.validateProofQuote.mockImplementation(_ => Promise.resolve(undefined));

// The previous epoch can be claimed
publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n));
publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n));

await sequencer.work();
expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], undefined);
Expand Down Expand Up @@ -727,7 +725,7 @@ describe('sequencer', () => {
);

// The previous epoch can be claimed
publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n));
publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n));

await sequencer.work();
expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], validProofQuote);
Expand Down Expand Up @@ -786,7 +784,7 @@ describe('sequencer', () => {
);

// The previous epoch can be claimed
publisher.nextEpochToClaim.mockImplementation(() => Promise.resolve(currentEpoch - 1n));
publisher.getClaimableEpoch.mockImplementation(() => Promise.resolve(currentEpoch - 1n));

await sequencer.work();
expect(publisher.proposeL2Block).toHaveBeenCalledWith(block, getSignatures(), [txHash], validQuotes[0]);
Expand Down
16 changes: 4 additions & 12 deletions yarn-project/sequencer-client/src/sequencer/sequencer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -548,20 +548,12 @@ export class Sequencer {
protected async createProofClaimForPreviousEpoch(slotNumber: bigint): Promise<EpochProofQuote | undefined> {
try {
// Find out which epoch we are currently in
const epochForBlock = await this.publisher.getEpochForSlotNumber(slotNumber);
if (epochForBlock < 1n) {
// It's the 0th epoch, nothing to be proven yet
this.log.verbose(`First epoch has no claim`);
return undefined;
}
const epochToProve = epochForBlock - 1n;
// Find out the next epoch that can be claimed
const canClaim = await this.publisher.nextEpochToClaim();
if (canClaim != epochToProve) {
// It's not the one we are looking to claim
this.log.verbose(`Unable to claim previous epoch (${canClaim} != ${epochToProve})`);
const epochToProve = await this.publisher.getClaimableEpoch();
if (epochToProve === undefined) {
this.log.verbose(`No epoch to prove`);
return undefined;
}

// Get quotes for the epoch to be proven
const quotes = await this.p2pClient.getEpochProofQuotes(epochToProve);
this.log.verbose(`Retrieved ${quotes.length} quotes, slot: ${slotNumber}, epoch to prove: ${epochToProve}`);
Expand Down
Loading