diff --git a/packages/contracts/contracts/optimistic-ethereum/chain/CanonicalTransactionChain.sol b/packages/contracts/contracts/optimistic-ethereum/chain/CanonicalTransactionChain.sol index 71c61bfdca83a..884a5be7657e3 100644 --- a/packages/contracts/contracts/optimistic-ethereum/chain/CanonicalTransactionChain.sol +++ b/packages/contracts/contracts/optimistic-ethereum/chain/CanonicalTransactionChain.sol @@ -29,9 +29,11 @@ contract CanonicalTransactionChain is ContractResolver { address public sequencer; uint public forceInclusionPeriodSeconds; + uint public forceInclusionPeriodBlocks; uint public cumulativeNumElements; bytes32[] public batches; uint public lastOVMTimestamp; + uint public lastOVMBlockNumber; /* @@ -53,6 +55,7 @@ contract CanonicalTransactionChain is ContractResolver { { sequencer = _sequencer; forceInclusionPeriodSeconds = _forceInclusionPeriodSeconds; + forceInclusionPeriodBlocks = _forceInclusionPeriodSeconds / 13; lastOVMTimestamp = 0; } @@ -86,6 +89,7 @@ contract CanonicalTransactionChain is ContractResolver { { return keccak256(abi.encodePacked( _batchHeader.timestamp, + _batchHeader.blockNumber, _batchHeader.isL1ToL2Tx, // TODO REPLACE WITH QUEUE ORIGIN (if you are a PR reviewer please lmk!) _batchHeader.elementsMerkleRoot, _batchHeader.numElementsInBatch, @@ -94,11 +98,11 @@ contract CanonicalTransactionChain is ContractResolver { } /** - * Checks whether a sender is allowed to append to the chain. + * Checks whether an address is the sequencer. * @param _sender Address to check. - * @return Whether or not the address can append. + * @return Whether or not the address is the sequencer. */ - function authenticateAppend( + function isSequencer( address _sender ) public @@ -121,7 +125,7 @@ contract CanonicalTransactionChain is ContractResolver { require( safetyQueue.isEmpty() || l1ToL2Header.timestamp <= safetyQueue.peekTimestamp(), - "Must process older SafetyQueue batches first to enforce timestamp monotonicity" + "Must process older SafetyQueue batches first to enforce OVM timestamp monotonicity" ); _appendQueueBatch(l1ToL2Header, true); @@ -141,7 +145,7 @@ contract CanonicalTransactionChain is ContractResolver { require( l1ToL2Queue.isEmpty() || safetyHeader.timestamp <= l1ToL2Queue.peekTimestamp(), - "Must process older L1ToL2Queue batches first to enforce timestamp monotonicity" + "Must process older L1ToL2Queue batches first to enforce OVM timestamp monotonicity" ); _appendQueueBatch(safetyHeader, false); @@ -155,7 +159,8 @@ contract CanonicalTransactionChain is ContractResolver { */ function appendSequencerBatch( bytes[] memory _txBatch, - uint _timestamp + uint _timestamp, + uint _blockNumber ) public { @@ -163,7 +168,7 @@ contract CanonicalTransactionChain is ContractResolver { SafetyTransactionQueue safetyQueue = resolveSafetyTransactionQueue(); require( - authenticateAppend(msg.sender), + isSequencer(msg.sender), "Message sender does not have permission to append a batch" ); @@ -178,31 +183,62 @@ contract CanonicalTransactionChain is ContractResolver { ); require( - _timestamp <= now, - "Cannot submit a batch with a timestamp in the future" + _blockNumber + forceInclusionPeriodBlocks > block.number, + "Cannot submit a batch with a blockNumber older than the sequencer inclusion period" ); require( - l1ToL2Queue.isEmpty() || _timestamp <= l1ToL2Queue.peekTimestamp(), - "Must process older L1ToL2Queue batches first to enforce timestamp monotonicity" + _timestamp <= now, + "Cannot submit a batch with a timestamp in the future" ); require( - safetyQueue.isEmpty() || _timestamp <= safetyQueue.peekTimestamp(), - "Must process older SafetyQueue batches first to enforce timestamp monotonicity" + _blockNumber <= block.number, + "Cannot submit a batch with a blockNumber in the future" ); + if (!l1ToL2Queue.isEmpty()) { + require( + _timestamp <= l1ToL2Queue.peekTimestamp(), + "Must process older L1ToL2Queue batches first to enforce OVM timestamp monotonicity" + ); + + require( + _blockNumber <= l1ToL2Queue.peekBlockNumber(), + "Must process older L1ToL2Queue batches first to enforce OVM blockNumber monotonicity" + ); + } + + if (!safetyQueue.isEmpty()) { + require( + _timestamp <= safetyQueue.peekTimestamp(), + "Must process older SafetyQueue batches first to enforce OVM timestamp monotonicity" + ); + + require( + _blockNumber <= safetyQueue.peekBlockNumber(), + "Must process older SafetyQueue batches first to enforce OVM blockNumber monotonicity" + ); + } + require( _timestamp >= lastOVMTimestamp, "Timestamps must monotonically increase" ); + require( + _blockNumber >= lastOVMBlockNumber, + "BlockNumbers must monotonically increase" + ); + lastOVMTimestamp = _timestamp; + lastOVMBlockNumber = _blockNumber; RollupMerkleUtils merkleUtils = resolveRollupMerkleUtils(); bytes32 batchHeaderHash = keccak256(abi.encodePacked( _timestamp, - false, // isL1ToL2Tx + _blockNumber, + false, // isL1ToL2Tx TODO: replace with queue origin merkleUtils.getMerkleRoot(_txBatch), // elementsMerkleRoot _txBatch.length, // numElementsInBatch cumulativeNumElements // cumulativeNumElements @@ -270,18 +306,21 @@ contract CanonicalTransactionChain is ContractResolver { internal { uint timestamp = _timestampedHash.timestamp; + uint blockNumber = _timestampedHash.blockNumber; require( - timestamp + forceInclusionPeriodSeconds <= now || authenticateAppend(msg.sender), + timestamp + forceInclusionPeriodSeconds <= now || isSequencer(msg.sender), "Message sender does not have permission to append this batch" ); lastOVMTimestamp = timestamp; + lastOVMBlockNumber = blockNumber; bytes32 elementsMerkleRoot = _timestampedHash.txHash; uint numElementsInBatch = 1; bytes32 batchHeaderHash = keccak256(abi.encodePacked( timestamp, + blockNumber, _isL1ToL2Tx, elementsMerkleRoot, numElementsInBatch, diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol index 9cce1081993dd..c591f6426b164 100644 --- a/packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/ExecutionManager.sol @@ -200,6 +200,7 @@ contract ExecutionManager is ContractResolver { // Make the EOA call for the account executeTransaction( _timestamp, + 0, // note: since executeEOACall is soon to be deprecated, not bothering to add blockNumber here. _queueOrigin, _ovmEntrypoint, _callBytes, @@ -214,6 +215,7 @@ contract ExecutionManager is ContractResolver { * Execute a transaction. Note that unsigned EOA calls are unauthenticated. * This means that they should not be allowed for normal execution. * @param _timestamp The timestamp which should be used for this call's context. + * @param _blockNumber The blockNumber which should be used for this call's context. * @param _queueOrigin The parent-chain queue from which this call originated. * @param _ovmEntrypoint The contract which this transaction should be executed against. * @param _callBytes The calldata for this ovm transaction. @@ -223,6 +225,7 @@ contract ExecutionManager is ContractResolver { */ function executeTransaction( uint _timestamp, + uint _blockNumber, uint _queueOrigin, address _ovmEntrypoint, bytes memory _callBytes, @@ -238,7 +241,7 @@ contract ExecutionManager is ContractResolver { require(_timestamp > 0, "Timestamp must be greater than 0"); // Initialize our context - initializeContext(_timestamp, _queueOrigin, _fromAddress, _l1MsgSenderAddress, _ovmTxGasLimit); + initializeContext(_timestamp, _blockNumber, _queueOrigin, _fromAddress, _l1MsgSenderAddress, _ovmTxGasLimit); // Set the active contract to be our EOA address switchActiveContract(_fromAddress); @@ -479,6 +482,28 @@ contract ExecutionManager is ContractResolver { } } + /** + * @notice NUMBER opcode + * This gets the current blockNumber. Since the L2 value for this + * will necessarily be different than L1, this needs to be overridden for the OVM. + * Note: This is a raw function, so there are no listed (ABI-encoded) inputs / outputs. + * Below format of the bytes expected as input and written as output: + * calldata: 4 bytes: [methodID (bytes4)] + * returndata: uint256 representing the current blockNumber. + */ + function ovmNUMBER() + public + view + { + uint t = executionContext.blockNumber; + + assembly { + let timestampMemory := mload(0x40) + mstore(timestampMemory, t) + return(timestampMemory, 32) + } + } + /** * @notice CHAINID opcode * This gets the chain id. Since the L2 value for this @@ -1315,6 +1340,7 @@ contract ExecutionManager is ContractResolver { */ function initializeContext( uint _timestamp, + uint _blockNumber, uint _queueOrigin, address _ovmTxOrigin, address _l1MsgSender, @@ -1326,9 +1352,10 @@ contract ExecutionManager is ContractResolver { // reserved for the genesis contract & initial msgSender). restoreContractContext(ZERO_ADDRESS, ZERO_ADDRESS); - // And finally set the timestamp, queue origin, tx origin, and + // And finally set the timestamp, blockNumber, queue origin, tx origin, and // l1MessageSender. executionContext.timestamp = _timestamp; + executionContext.blockNumber = _blockNumber; executionContext.queueOrigin = _queueOrigin; executionContext.ovmTxOrigin = _ovmTxOrigin; executionContext.l1MessageSender = _l1MsgSender; diff --git a/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol b/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol index 0292f5a1f7b63..45e9fa7c912eb 100644 --- a/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol +++ b/packages/contracts/contracts/optimistic-ethereum/ovm/StateTransitioner.sol @@ -232,6 +232,7 @@ contract StateTransitioner is IStateTransitioner, ContractResolver { // Execute the transaction via the execution manager. executionManager.executeTransaction( _transactionData.timestamp, + _transactionData.blockNumber, _transactionData.queueOrigin, _transactionData.ovmEntrypoint, _transactionData.callBytes, diff --git a/packages/contracts/contracts/optimistic-ethereum/queue/RollupQueue.sol b/packages/contracts/contracts/optimistic-ethereum/queue/RollupQueue.sol index c6fde28f870cc..f6df2b029bdcc 100644 --- a/packages/contracts/contracts/optimistic-ethereum/queue/RollupQueue.sol +++ b/packages/contracts/contracts/optimistic-ethereum/queue/RollupQueue.sol @@ -58,7 +58,7 @@ contract RollupQueue { /** * Peeks the timestamp of the front element on the queue. - * @return Front queue element timestamp. + * @return Front queue element timestamp (lowest in queue). */ function peekTimestamp() public @@ -70,14 +70,16 @@ contract RollupQueue { } /** - * Checks if this is a calldata transaction queue. - * @return Whether or not this is a calldata tx queue. + * Peeks the blockNumber of the front element on the queue. + * @return Front queue element blockNumber (lowest in queue). */ - function isCalldataTxQueue() + function peekBlockNumber() public - returns (bool) + view + returns (uint) { - return true; + DataTypes.TimestampedHash memory frontBatch = peek(); + return frontBatch.blockNumber; } /* @@ -96,8 +98,9 @@ contract RollupQueue { bytes32 txHash = keccak256(_data); batchHeaders.push(DataTypes.TimestampedHash({ + txHash: txHash, timestamp: now, - txHash: txHash + blockNumber: block.number })); } diff --git a/packages/contracts/contracts/optimistic-ethereum/utils/libraries/DataTypes.sol b/packages/contracts/contracts/optimistic-ethereum/utils/libraries/DataTypes.sol index b2deca359acba..5df0f887ddd89 100644 --- a/packages/contracts/contracts/optimistic-ethereum/utils/libraries/DataTypes.sol +++ b/packages/contracts/contracts/optimistic-ethereum/utils/libraries/DataTypes.sol @@ -26,6 +26,7 @@ library DataTypes { bool inStaticContext; uint chainId; uint timestamp; + uint blockNumber; uint queueOrigin; address ovmActiveContract; address ovmMsgSender; @@ -64,6 +65,7 @@ library DataTypes { struct TxChainBatchHeader { uint timestamp; + uint blockNumber; bool isL1ToL2Tx; bytes32 elementsMerkleRoot; uint numElementsInBatch; @@ -71,8 +73,9 @@ library DataTypes { } struct TimestampedHash { - uint timestamp; bytes32 txHash; + uint timestamp; + uint blockNumber; } struct AccountState { @@ -91,6 +94,7 @@ library DataTypes { struct OVMTransactionData { uint256 timestamp; + uint256 blockNumber; uint256 queueOrigin; address ovmEntrypoint; bytes callBytes; diff --git a/packages/contracts/contracts/test-helpers/ContextContract.sol b/packages/contracts/contracts/test-helpers/ContextContract.sol index 53eeef34e9673..8e56a651dda4e 100644 --- a/packages/contracts/contracts/test-helpers/ContextContract.sol +++ b/packages/contracts/contracts/test-helpers/ContextContract.sol @@ -67,6 +67,33 @@ contract ContextContract { } } + function getNUMBER() public { + // bitwise right shift 28 * 8 bits so the 4 method ID bytes are in the right-most bytes + bytes32 methodId = keccak256("ovmNUMBER()") >> 224; + address addr = executionManagerAddress; + + assembly { + let callBytes := mload(0x40) + calldatacopy(callBytes, 0, calldatasize) + + // replace the first 4 bytes with the right methodID + mstore8(callBytes, shr(24, methodId)) + mstore8(add(callBytes, 1), shr(16, methodId)) + mstore8(add(callBytes, 2), shr(8, methodId)) + mstore8(add(callBytes, 3), methodId) + + // overwrite call params + let result := mload(0x40) + let success := call(gas, addr, 0, callBytes, calldatasize, result, 32) + + if eq(success, 0) { + revert(result, returndatasize) + } + + return(result, returndatasize) + } + } + function getADDRESS() public { // bitwise right shift 28 * 8 bits so the 4 method ID bytes are in the right-most bytes bytes32 methodId = keccak256("ovmADDRESS()") >> 224; diff --git a/packages/contracts/src/deployment/contract-deploy.ts b/packages/contracts/src/deployment/contract-deploy.ts index a02b5b3414822..79c09b4b4fdf1 100644 --- a/packages/contracts/src/deployment/contract-deploy.ts +++ b/packages/contracts/src/deployment/contract-deploy.ts @@ -28,7 +28,7 @@ const deployContract = async ( // Can't use this because it fails on ExecutionManager & FraudVerifier // return config.factory.deploy(...config.params) - const res = await config.signer.sendTransaction({ + const deployResult = await config.signer.sendTransaction({ data: rawTx.data, gasLimit: 9_500_000, gasPrice: 2_000_000_000, @@ -37,14 +37,17 @@ const deployContract = async ( }) const receipt: ethers.providers.TransactionReceipt = await config.signer.provider.waitForTransaction( - res.hash + deployResult.hash ) - return new Contract( + const contract = new Contract( receipt.contractAddress, config.factory.interface, config.signer - ) + ) as any // set as any so we can override read-only deployTransaction field + contract.deployTransaction = deployResult + + return contract as Contract } /** @@ -59,9 +62,16 @@ export const deployAndRegister = async ( name: string, deployConfig: ContractDeployOptions ): Promise => { - log.debug(`Deploying ${name}...`) + log.debug(`Deploying ${name} with params: [${[...deployConfig.params]}]...`) const deployedContract = await deployContract(deployConfig) + const deployTxHash = deployedContract.deployTransaction.hash + const deployTxReceipt = await addressResolver.provider.getTransactionReceipt( + deployTxHash + ) log.info(`Deployed ${name} at address ${deployedContract.address}.`) + log.debug( + `${name} deploy tx (hash: ${deployTxHash}) used ${deployTxReceipt.gasUsed.toNumber()} gas.` + ) log.debug(`Registering ${name} with AddressResolver`) const res: ethers.providers.TransactionResponse = await addressResolver.setAddress( @@ -83,6 +93,11 @@ export const deployAndRegister = async ( export const deployAllContracts = async ( config: RollupDeployConfig ): Promise => { + log.debug( + `Attempting to deploy all L1 rollup contracts with the following rollup options: \n${JSON.stringify( + config.rollupOptions + )}` + ) let addressResolver: Contract if (!config.addressResolverContractAddress) { if (!config.addressResolverConfig) { diff --git a/packages/contracts/test/contracts/chain/CanonicalTransactionChain.spec.ts b/packages/contracts/test/contracts/chain/CanonicalTransactionChain.spec.ts index 8b3277f879d47..e0b2896f21988 100644 --- a/packages/contracts/test/contracts/chain/CanonicalTransactionChain.spec.ts +++ b/packages/contracts/test/contracts/chain/CanonicalTransactionChain.spec.ts @@ -30,7 +30,7 @@ const abi = new ethers.utils.AbiCoder() /* Tests */ describe('CanonicalTransactionChain', () => { const provider = ethers.provider - const FORCE_INCLUSION_PERIOD = 600 //600 seconds = 10 minutes + const FORCE_INCLUSION_PERIOD = 4000 const DEFAULT_BATCH = [ GET_DUMMY_TX_WITH_OVM_GAS_LIMIT(30_000), GET_DUMMY_TX_WITH_OVM_GAS_LIMIT(35_000), @@ -55,13 +55,14 @@ describe('CanonicalTransactionChain', () => { let l1ToL2Queue: Contract let safetyQueue: Contract - const appendSequencerBatch = async (batch: string[]): Promise => { + const appendSequencerBatch = async (batch: string[]): Promise => { + const blockNumber = await provider.getBlockNumber() const timestamp = Math.floor(Date.now() / 1000) // Submit the rollup batch on-chain await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(batch, timestamp) - return timestamp + .appendSequencerBatch(batch, timestamp, blockNumber) + return [timestamp, blockNumber] } const appendAndGenerateSequencerBatch = async ( @@ -69,10 +70,11 @@ describe('CanonicalTransactionChain', () => { batchIndex: number = 0, cumulativePrevElements: number = 0 ): Promise => { - const timestamp = await appendSequencerBatch(batch) + const [timestamp, blockNumber] = await appendSequencerBatch(batch) return createTxChainBatch( batch, timestamp, + blockNumber, false, batchIndex, cumulativePrevElements @@ -82,12 +84,14 @@ describe('CanonicalTransactionChain', () => { const createTxChainBatch = async ( batch: string[], timestamp: number, + blockNumber, isL1ToL2Tx: boolean, batchIndex: number = 0, cumulativePrevElements: number = 0 ): Promise => { const localBatch = new TxChainBatch( timestamp, + blockNumber, isL1ToL2Tx, batchIndex, cumulativePrevElements, @@ -120,7 +124,11 @@ describe('CanonicalTransactionChain', () => { ) // Generate a local version of the rollup batch const timestamp = (await provider.getBlock(txReceipt.blockNumber)).timestamp - const localBatch = new TxQueueBatch(rolledupData, timestamp) + const localBatch = new TxQueueBatch( + rolledupData, + timestamp, + txReceipt.blockNumber + ) await localBatch.generateTree() return localBatch } @@ -140,7 +148,7 @@ describe('CanonicalTransactionChain', () => { const txReceipt = await provider.getTransactionReceipt(_txHash) const timestamp = (await provider.getBlock(txReceipt.blockNumber)).timestamp // Generate a local version of the rollup batch - const localBatch = new TxQueueBatch(_tx, timestamp) + const localBatch = new TxQueueBatch(_tx, timestamp, txReceipt.blockNumber) await localBatch.generateTree() return localBatch } @@ -216,53 +224,105 @@ describe('CanonicalTransactionChain', () => { ) }) - it('should revert if submitting a batch older than the inclusion period', async () => { + it('should revert if submitting a batch with timestamp older than the inclusion period', async () => { const timestamp = Math.floor(Date.now() / 1000) - const oldTimestamp = timestamp - (FORCE_INCLUSION_PERIOD + 1) + const blockNumber = Math.floor(timestamp / 15) + const oldTimestamp = timestamp - (FORCE_INCLUSION_PERIOD + 1000) await TestUtils.assertRevertsAsync( 'Cannot submit a batch with a timestamp older than the sequencer inclusion period', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp) + .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp, blockNumber) } ) }) - it('should not revert if submitting a 5 minute old batch', async () => { + it('should revert if submitting a batch with blockNumber older than the inclusion period', async () => { const timestamp = Math.floor(Date.now() / 1000) + const FORCE_INCLUSION_PERIOD_BLOCKS = await canonicalTxChain.forceInclusionPeriodBlocks() + for (let i = 0; i < FORCE_INCLUSION_PERIOD_BLOCKS + 1; i++) { + await provider.send('evm_mine', []) + } + const currentBlockNumber = await canonicalTxChain.provider.getBlockNumber() + await TestUtils.assertRevertsAsync( + 'Cannot submit a batch with a blockNumber older than the sequencer inclusion period', + async () => { + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch( + DEFAULT_BATCH, + timestamp, + currentBlockNumber - FORCE_INCLUSION_PERIOD_BLOCKS + ) + } + ) + }) + + it('should not revert if submitting an INCLUSION_PERIOD/2 old batch', async () => { + const blockNumber = await provider.getBlockNumber() + const timestamp = (await provider.getBlock(blockNumber)).timestamp const oldTimestamp = timestamp - FORCE_INCLUSION_PERIOD / 2 await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp) + .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp, blockNumber) }) it('should revert if submitting a batch with a future timestamp', async () => { + const blockNumber = await provider.getBlockNumber() const timestamp = Math.floor(Date.now() / 1000) - const futureTimestamp = timestamp + 100 + const futureTimestamp = timestamp + 30_000 await TestUtils.assertRevertsAsync( 'Cannot submit a batch with a timestamp in the future', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, futureTimestamp) + .appendSequencerBatch(DEFAULT_BATCH, futureTimestamp, blockNumber) + } + ) + }) + + it('should revert if submitting a batch with a future blockNumber', async () => { + const timestamp = Math.floor(Date.now() / 1000) + const blockNumber = Math.floor(timestamp / 15) + const futureBlockNumber = blockNumber + 100 + await TestUtils.assertRevertsAsync( + 'Cannot submit a batch with a blockNumber in the future', + async () => { + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch(DEFAULT_BATCH, timestamp, futureBlockNumber) } ) }) it('should revert if submitting a new batch with a timestamp older than last batch timestamp', async () => { - const timestamp = await appendSequencerBatch(DEFAULT_BATCH) + const [timestamp, blockNumber] = await appendSequencerBatch(DEFAULT_BATCH) + const oldTimestamp = timestamp - 1 await TestUtils.assertRevertsAsync( 'Timestamps must monotonically increase', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp) + .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp, blockNumber) } ) }) + it('should revert if submitting a new batch with a blockNumber older than last batch blockNumber', async () => { + const [timestamp, blockNumber] = await appendSequencerBatch(DEFAULT_BATCH) + + const oldBlockNumber = blockNumber - 1 + await TestUtils.assertRevertsAsync( + 'BlockNumbers must monotonically increase', + async () => { + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch(DEFAULT_BATCH, timestamp, oldBlockNumber) + } + ) + }) it('should add to batches array', async () => { await appendSequencerBatch(DEFAULT_BATCH) const batchesLength = await canonicalTxChain.getBatchesLength() @@ -277,10 +337,16 @@ describe('CanonicalTransactionChain', () => { it('should not allow appendSequencerBatch from non-sequencer', async () => { const timestamp = Math.floor(Date.now() / 1000) + const blockNumber = Math.floor(timestamp / 15) + await TestUtils.assertRevertsAsync( 'Message sender does not have permission to append a batch', async () => { - await canonicalTxChain.appendSequencerBatch(DEFAULT_BATCH, timestamp) + await canonicalTxChain.appendSequencerBatch( + DEFAULT_BATCH, + timestamp, + blockNumber + ) } ) }) @@ -324,17 +390,22 @@ describe('CanonicalTransactionChain', () => { ) }) - it('should successfully append a batch with an older timestamp', async () => { + it('should successfully append a batch with an older timestamp and blockNumber', async () => { const oldTimestamp = localBatch.timestamp - 1 + const oldBlockNumber = localBatch.blockNumber - 1 await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp) + .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp, oldBlockNumber) }) it('should successfully append a batch with an equal timestamp', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, localBatch.timestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + localBatch.timestamp, + localBatch.blockNumber + ) }) it('should revert when there is an older batch in the L1ToL2Queue', async () => { @@ -342,11 +413,15 @@ describe('CanonicalTransactionChain', () => { await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD]) const newTimestamp = localBatch.timestamp + 60 await TestUtils.assertRevertsAsync( - 'Must process older L1ToL2Queue batches first to enforce timestamp monotonicity', + 'Must process older L1ToL2Queue batches first to enforce OVM timestamp monotonicity', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, newTimestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + newTimestamp, + localBatch.blockNumber + ) } ) await provider.send('evm_revert', [snapshotID]) @@ -363,43 +438,75 @@ describe('CanonicalTransactionChain', () => { const oldTimestamp = localBatch.timestamp - 1 await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + oldTimestamp, + localBatch.blockNumber + ) }) it('should successfully append a batch with an equal timestamp', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, localBatch.timestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + localBatch.timestamp, + localBatch.blockNumber + ) }) - it('should revert when there is an older batch in the SafetyQueue', async () => { + it('should revert when there is an older-timestamp batch in the SafetyQueue', async () => { const snapshotID = await provider.send('evm_snapshot', []) await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD]) const newTimestamp = localBatch.timestamp + 60 await TestUtils.assertRevertsAsync( - 'Must process older SafetyQueue batches first to enforce timestamp monotonicity', + 'Must process older SafetyQueue batches first to enforce OVM timestamp monotonicity', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, newTimestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + newTimestamp, + localBatch.blockNumber + ) } ) await provider.send('evm_revert', [snapshotID]) }) + + it('should revert when there is an older-blockNumber batch in the SafetyQueue', async () => { + await provider.send(`evm_mine`, []) + await TestUtils.assertRevertsAsync( + 'Must process older SafetyQueue batches first to enforce OVM blockNumber monotonicity', + async () => { + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch( + DEFAULT_BATCH, + localBatch.timestamp, + localBatch.blockNumber + 1 + ) + } + ) + }) }) describe('when there is an old batch in the safetyQueue and a recent batch in the l1ToL2Queue', async () => { let safetyTimestamp + let safetyBlockNumber let l1ToL2Timestamp + let l1ToL2BlockNumber let snapshotID beforeEach(async () => { const localSafetyBatch = await enqueueAndGenerateSafetyBatch(DEFAULT_TX) safetyTimestamp = localSafetyBatch.timestamp + safetyBlockNumber = localSafetyBatch.blockNumber snapshotID = await provider.send('evm_snapshot', []) await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD / 2]) const localL1ToL2Batch = await enqueueAndGenerateL1ToL2Batch( DEFAULT_L1_L2_MESSAGE_PARAMS ) l1ToL2Timestamp = localL1ToL2Batch.timestamp + l1ToL2BlockNumber = localL1ToL2Batch.blockNumber }) afterEach(async () => { await provider.send('evm_revert', [snapshotID]) @@ -409,36 +516,88 @@ describe('CanonicalTransactionChain', () => { const oldTimestamp = safetyTimestamp - 1 await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp) + .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp, safetyBlockNumber) + }) + + it('should successfully append a batch with an older blockNumber than the oldest batch', async () => { + const oldBlockNumber = safetyBlockNumber - 1 + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch(DEFAULT_BATCH, safetyTimestamp, oldBlockNumber) }) - it('should successfully append a batch with a timestamp equal to the oldest batch', async () => { + it('should successfully append a batch with a timestamp and blockNumber equal to the oldest batch', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, safetyTimestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + safetyTimestamp, + safetyBlockNumber + ) }) it('should revert when appending a batch with a timestamp in between the two batches', async () => { const middleTimestamp = safetyTimestamp + 1 await TestUtils.assertRevertsAsync( - 'Must process older SafetyQueue batches first to enforce timestamp monotonicity', + 'Must process older SafetyQueue batches first to enforce OVM timestamp monotonicity', + async () => { + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch( + DEFAULT_BATCH, + middleTimestamp, + safetyBlockNumber + ) + } + ) + }) + + it('should revert when appending a batch with a timestamp in between the two batches', async () => { + const middleBlockNumber = safetyBlockNumber + 1 + await TestUtils.assertRevertsAsync( + 'Must process older SafetyQueue batches first to enforce OVM blockNumber monotonicity', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, middleTimestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + safetyTimestamp, + middleBlockNumber + ) } ) }) it('should revert when appending a batch with a timestamp newer than both batches', async () => { await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD / 10]) // increase time by 60 seconds - const oldTimestamp = l1ToL2Timestamp + 1 + const newTimestamp = l1ToL2Timestamp + 1 await TestUtils.assertRevertsAsync( - 'Must process older L1ToL2Queue batches first to enforce timestamp monotonicity', + 'Must process older L1ToL2Queue batches first to enforce OVM timestamp monotonicity', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + newTimestamp, + safetyBlockNumber + ) + } + ) + }) + + it('should revert when appending a batch with a blockNumber newer than both batches', async () => { + await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD / 10]) // increase time by 60 seconds + const newBlockNumber = l1ToL2BlockNumber + 1 + await TestUtils.assertRevertsAsync( + 'Must process older L1ToL2Queue batches first to enforce OVM blockNumber monotonicity', + async () => { + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch( + DEFAULT_BATCH, + safetyTimestamp, + newBlockNumber + ) } ) }) @@ -446,17 +605,21 @@ describe('CanonicalTransactionChain', () => { describe('when there is an old batch in the l1ToL2Queue and a recent batch in the safetyQueue', async () => { let l1ToL2Timestamp + let l1ToL2BlockNumber let safetyTimestamp + let safetyBlockNumber let snapshotID beforeEach(async () => { const localL1ToL2Batch = await enqueueAndGenerateL1ToL2Batch( DEFAULT_L1_L2_MESSAGE_PARAMS ) l1ToL2Timestamp = localL1ToL2Batch.timestamp + l1ToL2BlockNumber = localL1ToL2Batch.blockNumber snapshotID = await provider.send('evm_snapshot', []) await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD / 2]) const localSafetyBatch = await enqueueAndGenerateSafetyBatch(DEFAULT_TX) safetyTimestamp = localSafetyBatch.timestamp + safetyBlockNumber = localSafetyBatch.blockNumber }) afterEach(async () => { await provider.send('evm_revert', [snapshotID]) @@ -466,23 +629,54 @@ describe('CanonicalTransactionChain', () => { const oldTimestamp = l1ToL2Timestamp - 1 await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp) + .appendSequencerBatch(DEFAULT_BATCH, oldTimestamp, l1ToL2BlockNumber) + }) + + it('should successfully append a batch with an older blockNumber than both batches', async () => { + const oldBlockNumber = l1ToL2BlockNumber - 1 + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch(DEFAULT_BATCH, l1ToL2Timestamp, oldBlockNumber) }) - it('should successfully append a batch with a timestamp equal to the older batch', async () => { + it('should successfully append a batch with a timestamp and blockNumber equal to the older batch', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, l1ToL2Timestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + l1ToL2Timestamp, + l1ToL2BlockNumber + ) }) it('should revert when appending a batch with a timestamp in between the two batches', async () => { const middleTimestamp = l1ToL2Timestamp + 1 await TestUtils.assertRevertsAsync( - 'Must process older L1ToL2Queue batches first to enforce timestamp monotonicity', + 'Must process older L1ToL2Queue batches first to enforce OVM timestamp monotonicity', + async () => { + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch( + DEFAULT_BATCH, + middleTimestamp, + safetyBlockNumber + ) + } + ) + }) + + it('should revert when appending a batch with a blockNumber in between the two batches', async () => { + const middleBlockNumber = l1ToL2BlockNumber + 1 + await TestUtils.assertRevertsAsync( + 'Must process older L1ToL2Queue batches first to enforce OVM timestamp monotonicity', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, middleTimestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + safetyTimestamp, + middleBlockNumber + ) } ) }) @@ -491,11 +685,32 @@ describe('CanonicalTransactionChain', () => { await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD / 10]) // increase time by 60 seconds const newTimestamp = safetyTimestamp + 1 await TestUtils.assertRevertsAsync( - 'Must process older L1ToL2Queue batches first to enforce timestamp monotonicity', + 'Must process older L1ToL2Queue batches first to enforce OVM timestamp monotonicity', async () => { await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(DEFAULT_BATCH, newTimestamp) + .appendSequencerBatch( + DEFAULT_BATCH, + newTimestamp, + safetyBlockNumber + ) + } + ) + }) + + it('should revert when appending a batch with a blockNumber newer than both batches', async () => { + await provider.send('evm_increaseTime', [FORCE_INCLUSION_PERIOD / 10]) // increase time by 60 seconds + const newBlockNumber = safetyBlockNumber + 1 + await TestUtils.assertRevertsAsync( + 'Must process older L1ToL2Queue batches first to enforce OVM blockNumber monotonicity', + async () => { + await canonicalTxChain + .connect(sequencer) + .appendSequencerBatch( + DEFAULT_BATCH, + l1ToL2Timestamp, + newBlockNumber + ) } ) }) @@ -520,9 +735,14 @@ describe('CanonicalTransactionChain', () => { }) it('should successfully append a L1ToL2Batch', async () => { - const { timestamp, txHash } = await l1ToL2Queue.batchHeaders(0) + const { + timestamp, + txHash, + blockNumber, + } = await l1ToL2Queue.batchHeaders(0) const localBatch = new TxChainBatch( timestamp, + blockNumber, true, // isL1ToL2Tx 0, //batchIndex 0, // cumulativePrevElements @@ -560,7 +780,7 @@ describe('CanonicalTransactionChain', () => { await provider.send('evm_increaseTime', [10]) await enqueueAndGenerateL1ToL2Batch(DEFAULT_L1_L2_MESSAGE_PARAMS) await TestUtils.assertRevertsAsync( - 'Must process older SafetyQueue batches first to enforce timestamp monotonicity', + 'Must process older SafetyQueue batches first to enforce OVM timestamp monotonicity', async () => { await canonicalTxChain.appendL1ToL2Batch() } @@ -606,9 +826,14 @@ describe('CanonicalTransactionChain', () => { }) it('should successfully append a SafetyBatch', async () => { - const { timestamp, txHash } = await safetyQueue.batchHeaders(0) + const { + timestamp, + txHash, + blockNumber, + } = await safetyQueue.batchHeaders(0) const localBatch = new TxChainBatch( timestamp, + blockNumber, false, // isL1ToL2Tx 0, //batchIndex 0, // cumulativePrevElements @@ -644,7 +869,7 @@ describe('CanonicalTransactionChain', () => { await provider.send('evm_increaseTime', [10]) await enqueueAndGenerateSafetyBatch(DEFAULT_TX) await TestUtils.assertRevertsAsync( - 'Must process older L1ToL2Queue batches first to enforce timestamp monotonicity', + 'Must process older L1ToL2Queue batches first to enforce OVM timestamp monotonicity', async () => { await canonicalTxChain.appendSafetyBatch() } @@ -712,6 +937,7 @@ describe('CanonicalTransactionChain', () => { await canonicalTxChain.connect(sequencer).appendL1ToL2Batch() const localBatch = new TxChainBatch( l1ToL2Batch.timestamp, //timestamp + l1ToL2Batch.blockNumber, true, //isL1ToL2Tx 0, //batchIndex 0, //cumulativePrevElements @@ -742,6 +968,7 @@ describe('CanonicalTransactionChain', () => { await canonicalTxChain.connect(sequencer).appendSafetyBatch() const localBatch = new TxChainBatch( safetyBatch.timestamp, //timestamp + safetyBatch.blockNumber, false, //isL1ToL2Tx 0, //batchIndex 0, //cumulativePrevElements diff --git a/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts b/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts index ab24715130940..aff670325680d 100644 --- a/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts +++ b/packages/contracts/test/contracts/chain/StateCommitmentChain.spec.ts @@ -40,7 +40,7 @@ describe('StateCommitmentChain', () => { '0x1234', '0x5678', ] - const FORCE_INCLUSION_PERIOD = 600 + const FORCE_INCLUSION_PERIOD = 4000 let wallet: Signer let sequencer: Signer @@ -77,11 +77,12 @@ describe('StateCommitmentChain', () => { } const appendTxBatch = async (batch: string[]): Promise => { + const blockNumber = await canonicalTxChain.provider.getBlockNumber() const timestamp = Math.floor(Date.now() / 1000) // Submit the rollup batch on-chain await canonicalTxChain .connect(sequencer) - .appendSequencerBatch(batch, timestamp) + .appendSequencerBatch(batch, timestamp, blockNumber) } let resolver: AddressResolverMapping diff --git a/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts b/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts index 9fb801640ef67..6fa09ceed1772 100644 --- a/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts +++ b/packages/contracts/test/contracts/ovm/FraudVerifier.spec.ts @@ -19,6 +19,7 @@ import { interface OVMTransactionData { timestamp: number + blockNumber: number queueOrigin: number ovmEntrypoint: string callBytes: string @@ -34,6 +35,7 @@ const FORCE_INCLUSION_PERIOD = 600 const makeDummyTransaction = (calldata: string): OVMTransactionData => { return { timestamp: Math.floor(Date.now() / 1000), + blockNumber: 0, queueOrigin: 0, ovmEntrypoint: NULL_ADDRESS, callBytes: calldata, @@ -63,14 +65,15 @@ const appendTransactionBatch = async ( canonicalTransactionChain: Contract, sequencer: Signer, batch: string[] -): Promise => { +): Promise => { + const blockNumber = await canonicalTransactionChain.provider.getBlockNumber() const timestamp = Math.floor(Date.now() / 1000) await canonicalTransactionChain .connect(sequencer) - .appendSequencerBatch(batch, timestamp) + .appendSequencerBatch(batch, timestamp, blockNumber) - return timestamp + return [timestamp, blockNumber] } const appendAndGenerateTransactionBatch = async ( @@ -80,7 +83,7 @@ const appendAndGenerateTransactionBatch = async ( batchIndex: number = 0, cumulativePrevElements: number = 0 ): Promise => { - const timestamp = await appendTransactionBatch( + const [timestamp, blockNumber] = await appendTransactionBatch( canonicalTransactionChain, sequencer, batch @@ -88,6 +91,7 @@ const appendAndGenerateTransactionBatch = async ( const localBatch = new TxChainBatch( timestamp, + blockNumber, false, batchIndex, cumulativePrevElements, diff --git a/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts b/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts index 7bd94c33d6645..4e675dcdfac6c 100644 --- a/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts +++ b/packages/contracts/test/contracts/ovm/StateTransitioner.spec.ts @@ -53,6 +53,7 @@ const DEFAULT_TX_NUM_STORAGE_UPDATES: number = 4 interface OVMTransactionData { timestamp: number + blockNumber: number queueOrigin: number ovmEntrypoint: string callBytes: string @@ -65,6 +66,7 @@ interface OVMTransactionData { const makeDummyTransaction = (calldata: string): OVMTransactionData => { return { timestamp: Math.floor(Date.now() / 1000), + blockNumber: 0, queueOrigin: 0, ovmEntrypoint: NULL_ADDRESS, callBytes: calldata, @@ -252,6 +254,7 @@ const makeTransactionData = async ( return { timestamp: 1, + blockNumber: 1, queueOrigin: 1, ovmEntrypoint: target.address, callBytes: calldata, diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts index d82326f46de8c..6ad521a213194 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.call-opcodes.spec.ts @@ -456,6 +456,7 @@ describe('Execution Manager -- Call opcodes', () => { [ getCurrentTime(), 0, + 0, callContractAddress, callBytes, ZERO_ADDRESS, @@ -485,6 +486,7 @@ describe('Execution Manager -- Call opcodes', () => { [ getCurrentTime(), 0, + 0, contractAddress, callBytes, ZERO_ADDRESS, diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts index 6c359a6f2839a..df7b68b4a3ccb 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.context-opcodes.spec.ts @@ -46,6 +46,7 @@ const methodIds = fromPairs( 'getCHAINID', 'ovmADDRESS', 'ovmCALLER', + 'getNUMBER', ].map((methodId) => [methodId, encodeMethodId(methodId)]) ) @@ -164,15 +165,36 @@ describe('Execution Manager -- Context opcodes', () => { const result = await executeTransaction( contractAddress, methodIds.callThroughExecutionManager, - [contract2Address32, methodIds.getTIMESTAMP] + [contract2Address32, methodIds.getTIMESTAMP], + '0x00', + timestamp ) log.debug(`TIMESTAMP result: ${result}`) should.exist(result, 'Result should exist!') - hexStrToNumber(result).should.be.gte( - timestamp, - 'Timestamps do not match.' + hexStrToNumber(result).should.equal(timestamp, 'Timestamps do not match.') + }) + }) + + describe('ovmNUMBER', async () => { + it('properly retrieves NUMBER', async () => { + const blockNumber: number = 15 + const result = await executeTransaction( + contractAddress, + methodIds.callThroughExecutionManager, + [contract2Address32, methodIds.getNUMBER], + '0x00', + 1, + blockNumber + ) + + log.debug(`NUMBER result: ${result}`) + + should.exist(result, 'Result should exist!') + hexStrToNumber(result).should.equal( + blockNumber, + 'blockNumbers do not match.' ) }) }) @@ -243,13 +265,16 @@ describe('Execution Manager -- Context opcodes', () => { address: string, methodId: string, args: any[], - queueOrigin = ZERO_ADDRESS + queueOrigin = ZERO_ADDRESS, + timestamp = getCurrentTime(), + blockNumber = 0 ): Promise => { const callBytes = add0x(methodId + encodeRawArguments(args)) const data = executionManager.interface.encodeFunctionData( 'executeTransaction', [ - getCurrentTime(), + timestamp, + blockNumber, queueOrigin, address, callBytes, diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts index 3dd97998e5f10..b59aa9c150cef 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.executeCall.spec.ts @@ -155,6 +155,7 @@ describe('Execution Manager -- TX/Call Execution Functions', () => { const tx = await executionManager.executeTransaction( getCurrentTime(), 0, + 0, transaction.to, transaction.data, wallet.address, @@ -291,6 +292,7 @@ describe('Execution Manager -- TX/Call Execution Functions', () => { const calldata = ExecutionManager.interface.encodeFunctionData( 'executeTransaction', [ + ZERO_UINT, ZERO_UINT, ZERO_UINT, dummyContractAddress, @@ -332,6 +334,7 @@ describe('Execution Manager -- TX/Call Execution Functions', () => { const calldata = ExecutionManager.interface.encodeFunctionData( 'executeTransaction', [ + ZERO_UINT, ZERO_UINT, ZERO_UINT, dummyContractAddress, diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.gas-metering.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.gas-metering.spec.ts index 8978642de15e3..f740291e6b551 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.gas-metering.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.gas-metering.spec.ts @@ -42,7 +42,7 @@ const abi = new ethers.utils.AbiCoder() // Empirically determined constant which is some extra gas the EM records due to running CALL, gasAfter - gasBefore, etc. // This is unfortunately not always the same--it will differ based on the size of calldata into the CALL. // However, that size is constant for these tests, since we only call consumeGas() below. -const CONSUME_GAS_EXECUTION_OVERHEAD = 39967 +const CONSUME_GAS_EXECUTION_OVERHEAD = 39945 /********* * TESTS * @@ -153,7 +153,8 @@ describe('Execution Manager -- Gas Metering', () => { timestamp: number, queueOrigin: number, gasToConsume: number, - gasLimit: any = false + gasLimit: any = false, + blockNumber: number = 0 ) => { const internalCallBytes = GasConsumer.interface.encodeFunctionData( 'consumeGasInternalCall', @@ -170,6 +171,7 @@ describe('Execution Manager -- Gas Metering', () => { 'executeTransaction', [ timestamp, + blockNumber, queueOrigin, gasConsumerAddress, internalCallBytes, diff --git a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts index e824b7a4c637e..8e518ae1511c0 100644 --- a/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts +++ b/packages/contracts/test/contracts/ovm/execution-manager/ExecutionManager.l1-l2-opcodes.spec.ts @@ -161,6 +161,7 @@ describe('Execution Manager -- L1 <-> L2 Opcodes', () => { [ getCurrentTime(), 0, + 0, callContractAddress, callBytes, ZERO_ADDRESS, @@ -210,6 +211,7 @@ describe('Execution Manager -- L1 <-> L2 Opcodes', () => { [ getCurrentTime(), 0, + 0, l1MessageSenderPrecompileAddr, getL1MessageSenderMethodId, ZERO_ADDRESS, diff --git a/packages/contracts/test/contracts/queue/RollupQueue.spec.ts b/packages/contracts/test/contracts/queue/RollupQueue.spec.ts index 5ddd009241103..0108b33e9c42d 100644 --- a/packages/contracts/test/contracts/queue/RollupQueue.spec.ts +++ b/packages/contracts/test/contracts/queue/RollupQueue.spec.ts @@ -37,9 +37,10 @@ describe('RollupQueue', () => { // Submit the rollup batch on-chain const enqueueTx = await rollupQueue.enqueueTx(tx) const txReceipt = await provider.getTransactionReceipt(enqueueTx.hash) - const timestamp = (await provider.getBlock(txReceipt.blockNumber)).timestamp + const blockNumber = txReceipt.blockNumber + const timestamp = (await provider.getBlock(blockNumber)).timestamp // Generate a local version of the rollup batch - const localBatch = new TxQueueBatch(tx, timestamp) + const localBatch = new TxQueueBatch(tx, timestamp, blockNumber) await localBatch.generateTree() return localBatch } @@ -53,10 +54,13 @@ describe('RollupQueue', () => { it('should set the TimestampedHash correctly', async () => { const localBatch = await enqueueAndGenerateBatch(DEFAULT_TX) - const { txHash, timestamp } = await rollupQueue.batchHeaders(0) + const { txHash, timestamp, blockNumber } = await rollupQueue.batchHeaders( + 0 + ) const expectedBatchHeaderHash = await localBatch.getMerkleRoot() txHash.should.equal(expectedBatchHeaderHash) timestamp.should.equal(localBatch.timestamp) + blockNumber.should.equal(localBatch.blockNumber) }) it('should add multiple batches correctly', async () => { @@ -105,6 +109,7 @@ describe('RollupQueue', () => { const expectedTxHash = await localFrontBatch.getMerkleRoot() frontBatch.txHash.should.equal(expectedTxHash) frontBatch.timestamp.should.equal(localFrontBatch.timestamp) + frontBatch.blockNumber.should.equal(localFrontBatch.blockNumber) await rollupQueue.dequeue() @@ -147,16 +152,19 @@ describe('RollupQueue', () => { }) }) - describe('peek() and peekTimestamp()', async () => { + describe('peek(), peekTimestamp(), and peekBlockNumber()', async () => { it('should peek successfully with single element', async () => { const localBatch = await enqueueAndGenerateBatch(DEFAULT_TX) - const { txHash, timestamp } = await rollupQueue.peek() + const { txHash, timestamp, blockNumber } = await rollupQueue.peek() const expectedBatchHeaderHash = await localBatch.getMerkleRoot() txHash.should.equal(expectedBatchHeaderHash) timestamp.should.equal(localBatch.timestamp) const peekTimestamp = await rollupQueue.peekTimestamp() peekTimestamp.should.equal(timestamp) + + const peekBlockNumber = await rollupQueue.peekBlockNumber() + peekBlockNumber.should.equal(blockNumber) }) it('should revert when peeking at an empty queue', async () => { diff --git a/packages/contracts/test/test-helpers/ovm-helpers.ts b/packages/contracts/test/test-helpers/ovm-helpers.ts index d86bedcbbcb0a..5f5bcb32049f7 100644 --- a/packages/contracts/test/test-helpers/ovm-helpers.ts +++ b/packages/contracts/test/test-helpers/ovm-helpers.ts @@ -264,7 +264,8 @@ export const executeTransaction = async ( to: Address, data: string, allowRevert: boolean, - timestamp: number = getCurrentTime() + timestamp: number = getCurrentTime(), + blockNumber: number = 0 ): Promise => { // Verify that the transaction is not accidentally sending to the ZERO_ADDRESS if (to === ZERO_ADDRESS) { @@ -287,6 +288,7 @@ export const executeTransaction = async ( // Actually make the call const tx = await executionManager.executeTransaction( timestamp, + blockNumber, 0, ovmTo, data, diff --git a/packages/contracts/test/test-helpers/types/batch.ts b/packages/contracts/test/test-helpers/types/batch.ts index a21a0cb6ddf22..3962cdc93abb3 100644 --- a/packages/contracts/test/test-helpers/types/batch.ts +++ b/packages/contracts/test/test-helpers/types/batch.ts @@ -13,6 +13,7 @@ const abi = new utils.AbiCoder() interface TxChainBatchHeader { timestamp: number + blockNumber: number isL1ToL2Tx: boolean elementsMerkleRoot: string numElementsInBatch: number @@ -135,10 +136,12 @@ export function getL1ToL2MessageTxData( */ export class TxChainBatch extends ChainBatch { public timestamp: number + public blockNumber: number public isL1ToL2Tx: boolean constructor( - timestamp: number, // Ethereum batch this batch was submitted in + timestamp: number, // Ethereum timestamp this batch was submitted in + blockNumber: number, // Same as above w/ blockNumber isL1ToL2Tx: boolean, batchIndex: number, // index in batchs array (first batch has batchIndex of 0) cumulativePrevElements: number, @@ -166,14 +169,16 @@ export class TxChainBatch extends ChainBatch { super(batchIndex, cumulativePrevElements, elementsToMerklize) this.isL1ToL2Tx = isL1ToL2Tx this.timestamp = timestamp + this.blockNumber = blockNumber } public async hashBatchHeader(): Promise { const bufferRoot = await this.elementsMerkleTree.getRootHash() return utils.solidityKeccak256( - ['uint', 'bool', 'bytes32', 'uint', 'uint'], + ['uint', 'uint', 'bool', 'bytes32', 'uint', 'uint'], [ this.timestamp, + this.blockNumber, this.isL1ToL2Tx, bufToHexString(bufferRoot), this.elements.length, @@ -194,6 +199,7 @@ export class TxChainBatch extends ChainBatch { batchIndex: this.batchIndex, batchHeader: { timestamp: this.timestamp, + blockNumber: this.blockNumber, isL1ToL2Tx: this.isL1ToL2Tx, elementsMerkleRoot: bufToHexString(bufferRoot), numElementsInBatch: this.elements.length, @@ -236,10 +242,13 @@ export class TxQueueBatch { public elements: string[] public elementsMerkleTree: SparseMerkleTreeImpl public timestamp: number + public blockNumber: number - constructor(tx: string, timestamp: number) { + // TODO remove blockNumber optionality, just here for testing + constructor(tx: string, timestamp: number, blockNumber: number) { this.elements = [tx] this.timestamp = timestamp + this.blockNumber = blockNumber } /* * Generate the elements merkle tree from this.elements @@ -269,8 +278,15 @@ export class TxQueueBatch { ): Promise { const txHash = await this.getMerkleRoot() return utils.solidityKeccak256( - ['uint', 'bool', 'bytes32', 'uint', 'uint'], - [this.timestamp, isL1ToL2Tx, txHash, 1, cumulativePrevElements] + ['uint', 'uint', 'bool', 'bytes32', 'uint', 'uint'], + [ + this.timestamp, + this.blockNumber, + isL1ToL2Tx, + txHash, + 1, + cumulativePrevElements, + ] ) } } diff --git a/packages/rollup-core/src/app/data/consumers/canonical-chain-batch-submitter.ts b/packages/rollup-core/src/app/data/consumers/canonical-chain-batch-submitter.ts index 35b1247cc7bcc..1f84071a6192e 100644 --- a/packages/rollup-core/src/app/data/consumers/canonical-chain-batch-submitter.ts +++ b/packages/rollup-core/src/app/data/consumers/canonical-chain-batch-submitter.ts @@ -91,13 +91,18 @@ export class CanonicalChainBatchSubmitter extends ScheduledTask { try { const txsCalldata: string[] = this.getTransactionBatchCalldata(l2Batch) + // TODO: update this to work with geth-persisted timestamp/block number that updates based on L1 actions const timestamp = l2Batch.transactions[0].timestamp + const blockNumber = + (await this.canonicalTransactionChain.provider.getBlockNumber()) - 10 // broken for any prod setting but works for now + log.debug( `Submitting tx batch ${l2Batch.batchNumber} with ${l2Batch.transactions.length} transactions to canonical chain. Timestamp: ${timestamp}` ) const txRes: TransactionResponse = await this.canonicalTransactionChain.appendSequencerBatch( txsCalldata, - timestamp + timestamp, + blockNumber ) log.debug( `Tx batch ${l2Batch.batchNumber} appended with at least one confirmation! Tx Hash: ${txRes.hash}` diff --git a/packages/rollup-core/test/app/canonical-chain-batch-submitter.spec.ts b/packages/rollup-core/test/app/canonical-chain-batch-submitter.spec.ts index 7c67d762d318d..23d33559eefdd 100644 --- a/packages/rollup-core/test/app/canonical-chain-batch-submitter.spec.ts +++ b/packages/rollup-core/test/app/canonical-chain-batch-submitter.spec.ts @@ -64,6 +64,10 @@ class MockProvider { } return this.txReceipts.get(hash) } + + public async getBlockNumber(): Promise { + return this.txReceipts.size + } } class MockCanonicalTransactionChain { @@ -73,7 +77,8 @@ class MockCanonicalTransactionChain { public async appendSequencerBatch( calldata: string, - timestamp: number + timestamp: number, + blockNumber: number ): Promise { const response: TransactionResponse = this.responses.shift() if (!response) { diff --git a/packages/test-rollup-workflow/test/test-submit-to-l2.spec.ts b/packages/test-rollup-workflow/test/test-submit-to-l2.spec.ts index 65f63f99437d0..4cab25b7b991d 100644 --- a/packages/test-rollup-workflow/test/test-submit-to-l2.spec.ts +++ b/packages/test-rollup-workflow/test/test-submit-to-l2.spec.ts @@ -60,9 +60,10 @@ describe('Test Sending Transactions Directly To L2', () => { ) }) - it('Sets storage N times', async () => { + const numTxsToSend: number = 50 + it(`Sets storage ${numTxsToSend} times`, async () => { const key: string = 'test' - for (let i = 0; i < 20; i++) { + for (let i = 0; i < numTxsToSend; i++) { log.debug(`Sending tx to set storage key ${key}`) const res = await simpleStorage.setStorage(key, `${key}${i}`) await sleep(1)