From cf9f5bb74148128955ea87a57772fd808d16544b Mon Sep 17 00:00:00 2001 From: nicolas <48695862+merklefruit@users.noreply.github.com> Date: Wed, 25 Sep 2024 12:05:27 +0200 Subject: [PATCH] feat: added stacked preconfs in the same slot by the same sender logic --- bolt-contracts/.gas-snapshot | 16 +- .../src/contracts/BoltChallenger.sol | 302 ++++++++++++------ .../src/interfaces/IBoltChallenger.sol | 48 ++- bolt-contracts/test/BoltChallenger.t.sol | 88 ++--- .../test/testdata/eth_proof_20785011.json | 22 ++ ...eth_proof.json => eth_proof_20785012.json} | 0 .../test/testdata/header_20785011.json | 5 + .../{raw_header.json => header_20785012.json} | 0 ..._proof.json => tx_mpt_proof_20785012.json} | 0 9 files changed, 330 insertions(+), 151 deletions(-) create mode 100644 bolt-contracts/test/testdata/eth_proof_20785011.json rename bolt-contracts/test/testdata/{eth_proof.json => eth_proof_20785012.json} (100%) create mode 100644 bolt-contracts/test/testdata/header_20785011.json rename bolt-contracts/test/testdata/{raw_header.json => header_20785012.json} (100%) rename bolt-contracts/test/testdata/{tx_mpt_proof.json => tx_mpt_proof_20785012.json} (100%) diff --git a/bolt-contracts/.gas-snapshot b/bolt-contracts/.gas-snapshot index 353740bd2..5dffb08e7 100644 --- a/bolt-contracts/.gas-snapshot +++ b/bolt-contracts/.gas-snapshot @@ -1,15 +1,15 @@ BoltChallengerTest:testCommitmentDigestAndSignature() (gas: 4626) BoltChallengerTest:testCommitmentSignature() (gas: 6754) -BoltChallengerTest:testOpenAlreadyExistingChallenge() (gas: 16032) -BoltChallengerTest:testOpenChallenge() (gas: 379747) -BoltChallengerTest:testOpenChallengeInvalidSignature() (gas: 22416) -BoltChallengerTest:testOpenChallengeWithInsufficientBond() (gas: 16738) -BoltChallengerTest:testOpenChallengeWithLargebond() (gas: 388679) -BoltChallengerTest:testOpenChallengeWithSlotInTheFuture() (gas: 17029) +BoltChallengerTest:testOpenAlreadyExistingChallenge() (gas: 111137) +BoltChallengerTest:testOpenChallengeInvalidSignature() (gas: 23952) +BoltChallengerTest:testOpenChallengeSingleTx() (gas: 406261) +BoltChallengerTest:testOpenChallengeWithInsufficientBond() (gas: 17220) +BoltChallengerTest:testOpenChallengeWithLargebond() (gas: 415209) +BoltChallengerTest:testOpenChallengeWithSlotInTheFuture() (gas: 17622) BoltChallengerTest:testProveAccountData() (gas: 355542) -BoltChallengerTest:testProveHeaderData() (gas: 45617) +BoltChallengerTest:testProveHeaderData() (gas: 46228) BoltChallengerTest:testProveTransactionInclusion() (gas: 176543) -BoltChallengerTest:testResolveChallengeFullDefense() (gas: 575605) +BoltChallengerTest:testResolveChallengeFullDefenseSingleTx() (gas: 698603) BoltManagerEigenLayerTest:testGetNonExistentProposerStatus() (gas: 921620) BoltManagerEigenLayerTest:testGetWhitelistedCollaterals() (gas: 99988) BoltManagerEigenLayerTest:testNonWhitelistedCollateral() (gas: 103013) diff --git a/bolt-contracts/src/contracts/BoltChallenger.sol b/bolt-contracts/src/contracts/BoltChallenger.sol index 459f135a6..7bf1eb90e 100644 --- a/bolt-contracts/src/contracts/BoltChallenger.sol +++ b/bolt-contracts/src/contracts/BoltChallenger.sol @@ -31,7 +31,7 @@ contract BoltChallenger is IBoltChallenger { // ========= CONSTANTS ========= /// @notice The challenge bond required to open a challenge. - uint256 public constant CHALLENGE_BOND = 1 ether; + uint256 public constant COMMITMENT_BOND = 1 ether; /// @notice The maximum duration of a challenge to be considered valid. uint256 public constant MAX_CHALLENGE_DURATION = 7 days; @@ -98,52 +98,93 @@ contract BoltChallenger is IBoltChallenger { // ========= CHALLENGE CREATION ========= - // Q: should we add a commit-reveal scheme to prevent frontrunning to steal slashing rewards? + /// @notice Open a challenge against a bundle of committed transactions. + /// @dev The challenge bond must be paid in order to open a challenge. + /// @param commitments The signed commitments to open a challenge for. function openChallenge( - SignedCommitment calldata commitment + SignedCommitment[] calldata commitments ) public payable { + if (commitments.length == 0) { + revert EmptyCommitments(); + } + // Check that the challenge bond is sufficient - if (msg.value < CHALLENGE_BOND) { + uint256 expectedBond = COMMITMENT_BOND * commitments.length; + if (msg.value < expectedBond) { revert InsufficientChallengeBond(); - } else if (msg.value > CHALLENGE_BOND) { + } else if (msg.value > expectedBond) { // Refund the excess value, if any - payable(msg.sender).transfer(msg.value - CHALLENGE_BOND); + payable(msg.sender).transfer(msg.value - expectedBond); } - if (commitment.slot > BeaconChainUtils._getCurrentSlot() - BeaconChainUtils.JUSTIFICATION_DELAY_SLOTS) { + uint256 targetSlot = commitments[0].slot; + if (targetSlot > BeaconChainUtils._getCurrentSlot() - BeaconChainUtils.JUSTIFICATION_DELAY_SLOTS) { // We cannot open challenges for slots that are not finalized by Ethereum consensus yet. // This is admittedly a bit strict, since 32-slot deep reorgs are very unlikely. revert BlockIsNotFinalized(); } - // Reconstruct the commitment digest: `keccak( keccak(signed tx) || le_bytes(slot) )` - bytes32 commitmentID = _computeCommitmentID(commitment); + // Check that all commitments are for the same slot and signed by the same sender + // and store the parsed transaction data for each commitment + TransactionData[] memory transactionsData = new TransactionData[](commitments.length); + (address txSender, address commitmentSigner, TransactionData memory firstTransactionData) = + _recoverCommitmentData(commitments[0]); + + transactionsData[0] = firstTransactionData; + + for (uint256 i = 1; i < commitments.length; i++) { + (address otherTxSender, address otherCommitmentSigner, TransactionData memory otherTransactionData) = + _recoverCommitmentData(commitments[i]); + + transactionsData[i] = otherTransactionData; + + // check that all commitments are for the same slot + if (commitments[i].slot != targetSlot) { + revert UnexpectedMixedSlots(); + } + + // check that all commitments are signed by the same sender + if (otherTxSender != txSender) { + revert UnexpectedMixedSenders(); + } + + // check that all commitments are signed by the same signer (aka "operator") + if (otherCommitmentSigner != commitmentSigner) { + revert UnexpectedMixedSigners(); + } + + // check that the nonces are strictly sequentially increasing in the bundle + if (otherTransactionData.nonce != transactionsData[i - 1].nonce + 1) { + revert UnexpectedNonceOrder(); + } + } - // Verify the commitment signature against the digest - address commitmentSigner = ECDSA.recover(commitmentID, commitment.signature); + // Build the challenge ID: `keccak( keccak(signed tx 1) || keccak(signed tx 2) || ... || le_bytes(slot) )` + bytes32 challengeID = _computeChallengeID(commitments); - // Check that a challenge for this commitment does not already exist - if (challengeIDs.contains(commitmentID)) { + // Check that a challenge for this commitment bundle does not already exist + if (challengeIDs.contains(challengeID)) { revert ChallengeAlreadyExists(); } // Add the challenge to the set of challenges - challengeIDs.add(commitmentID); - challenges[commitmentID] = Challenge({ - id: commitmentID, + challengeIDs.add(challengeID); + challenges[challengeID] = Challenge({ + id: challengeID, openedAt: Time.timestamp(), status: ChallengeStatus.Open, + targetSlot: targetSlot, challenger: msg.sender, commitmentSigner: commitmentSigner, - commitment: commitment + commitmentReceiver: txSender, + committedTxs: transactionsData }); - - emit ChallengeOpened(commitmentID, msg.sender, commitmentSigner); + emit ChallengeOpened(challengeID, msg.sender, commitmentSigner); } // ========= CHALLENGE RESOLUTION ========= - function resolveRecentChallenge(bytes32 challengeID, Proof calldata proof) public { + function resolveChallenge(bytes32 challengeID, Proof calldata proof) public { // Check that the challenge exists if (!challengeIDs.contains(challengeID)) { revert ChallengeDoesNotExist(); @@ -151,31 +192,29 @@ contract BoltChallenger is IBoltChallenger { // The visibility of the BLOCKHASH opcode is limited to the 256 most recent blocks. // For simplicity we restrict this to 256 slots even though 256 blocks would be more accurate. - if (challenges[challengeID].commitment.slot < BeaconChainUtils._getCurrentSlot() - BLOCKHASH_EVM_LOOKBACK) { + if (challenges[challengeID].targetSlot < BeaconChainUtils._getCurrentSlot() - BLOCKHASH_EVM_LOOKBACK) { revert BlockIsTooOld(); } - // Check that the block number is within the EVM lookback window for block hashes - if (proof.blockNumber > block.number || proof.blockNumber < block.number - BLOCKHASH_EVM_LOOKBACK) { + // Check that the previous block is within the EVM lookback window for block hashes. + if ( + (proof.inclusionBlockNumber - 1) > block.number + || (proof.inclusionBlockNumber - 1) < block.number - BLOCKHASH_EVM_LOOKBACK + ) { revert InvalidBlockNumber(); } - // Get the trusted block hash for the block number in which the transaction was included. - bytes32 trustedBlockHash = blockhash(proof.blockNumber); + // Get the trusted block hash for the block number in which the transactions were included. + bytes32 trustedPreviousBlockHash = blockhash(proof.inclusionBlockNumber); // Finally resolve the challenge with the trusted block hash and the provided proofs - _resolve(challengeID, trustedBlockHash, proof); - } - - // Resolving a historical challenge requires acquiring a block hash from an alternative source - // from the EVM. This is because the BLOCKHASH opcode is limited to the 256 most recent blocks. - function resolveChallenge(bytes32 challengeID, Proof calldata proof) public { - // unimplemented!(); + _resolve(challengeID, trustedPreviousBlockHash, proof); } /// @notice Resolve a challenge that has expired without being resolved. - /// @dev This will result in the challenge being considered lost, without need to provide + /// @dev This will result in the challenge being considered breached, without need to provide /// additional proofs of inclusion, as the time window has elapsed. + /// @param challengeID The ID of the challenge to resolve. function resolveExpiredChallenge( bytes32 challengeID ) public { @@ -183,7 +222,6 @@ contract BoltChallenger is IBoltChallenger { revert ChallengeDoesNotExist(); } - // The challenge is assumed to exist at this point, so we can safely access it. Challenge storage challenge = challenges[challengeID]; if (challenge.status != ChallengeStatus.Open) { @@ -194,20 +232,23 @@ contract BoltChallenger is IBoltChallenger { revert ChallengeNotExpired(); } - // If the challenge has expired without being resolved, it is considered lost. - challenge.status = ChallengeStatus.Lost; + // If the challenge has expired without being resolved, it is considered breached. + challenge.status = ChallengeStatus.Breached; _transferFullBond(challenge.challenger); - emit ChallengeLost(challengeID); + emit ChallengeBreached(challengeID); } - /// @notice Resolve a challenge by providing proofs of the inclusion of the committed transaction. - /// @dev Challenges are DEFENDED if the resolver successfully defends the inclusion of the transaction, - /// and LOST if the challenger successfully demonstrates that the inclusion commitment was breached or - /// enough time has passed without proper resolution. - /// - /// q: should we also have a commit-reveal scheme for resolutions to avoid frontrunning to steal bonds? - function _resolve(bytes32 challengeID, bytes32 trustedBlockHash, Proof calldata proof) internal { - // The challenge is assumed to exist at this point, so we can safely access it. + /// @notice Resolve a challenge by providing proofs of the inclusion of the committed transactions. + /// @dev Challenges are DEFENDED if the resolver successfully defends the inclusion of the transactions. + /// In the event of no valid defense in the challenge time window, the challenge is considered BREACHED. + /// @param challengeID The ID of the challenge to resolve. + /// @param trustedPreviousBlockHash The block hash of the block before the inclusion block of the committed txs. + /// @param proof The proof data to resolve the challenge. See `IBoltChallenger.Proof` struct for more details. + function _resolve(bytes32 challengeID, bytes32 trustedPreviousBlockHash, Proof calldata proof) internal { + if (!challengeIDs.contains(challengeID)) { + revert ChallengeDoesNotExist(); + } + Challenge storage challenge = challenges[challengeID]; if (challenge.status != ChallengeStatus.Open) { @@ -215,80 +256,114 @@ contract BoltChallenger is IBoltChallenger { } if (challenge.openedAt + MAX_CHALLENGE_DURATION < Time.timestamp()) { - // If the challenge has expired without being resolved, it is considered lost. - challenge.status = ChallengeStatus.Lost; + // If the challenge has expired without being resolved, it is considered breached. + // It's cheaper to call `resolveExpiredChallenge()` directly in this case, but this + // case should still be handled here for consistency. + challenge.status = ChallengeStatus.Breached; _transferFullBond(challenge.challenger); - emit ChallengeLost(challengeID); + emit ChallengeBreached(challengeID); return; } - // Verify the validity of the header against the trusted block hash. - if (keccak256(proof.blockHeaderRLP) != trustedBlockHash) { - revert InvalidBlockHash(); + // Check theintegrity of the proofs + if ( + proof.txMerkleProofs.length != challenge.committedTxs.length + || proof.txIndexesInBlock.length != challenge.committedTxs.length + ) { + revert InvalidProofsLength(); } - // Decode the RLP-encoded block header fields - BlockHeaderData memory blockHeader = _decodeBlockHeaderRLP(proof.blockHeaderRLP); + // Check the integrity of the trusted block hash + if (keccak256(proof.previousBlockHeaderRLP) != trustedPreviousBlockHash) { + revert InvalidBlockHash(); + } - // Decode the committed raw signed transaction. Its sender will be the account to prove existence of. - TransactionDecoder.Transaction memory decodedTx = challenge.commitment.signedTx.decodeEnveloped(); + // Decode the RLP-encoded block header of the previous block to the inclusion block. + // + // The previous block's state root is necessary to verify the account had the correct balance and + // nonce at the top of the inclusion block (before any transactions were applied). + BlockHeaderData memory previousBlockHeader = _decodeBlockHeaderRLP(proof.previousBlockHeaderRLP); + + // Decode the RLP-encoded block header of the inclusion block. + // + // The inclusion block is necessary to extract the transaction root and verify the inclusion of the + // committed transactions. By checking against the previous block's parent hash we can ensure this + // is the correct block trusting a single block hash. + BlockHeaderData memory inclusionBlockHeader = _decodeBlockHeaderRLP(proof.inclusionBlockHeaderRLP); + + // Check that the inclusion block is a child of the previous block + if (inclusionBlockHeader.parentHash != keccak256(proof.previousBlockHeaderRLP)) { + revert InvalidParentBlockHash(); + } - // Decode the account fields by checking the account proof against the state root of the block header. - // The key in the account trie is the account pubkey (address) which we can recover from the signed tx. + // Decode the account fields by checking the account proof against the state root of the previous block header. + // The key in the account trie is the account pubkey (address) that sent the committed transactions. (bool accountExists, bytes memory accountRLP) = SecureMerkleTrie.get( - abi.encodePacked(decodedTx.recoverSender()), proof.accountMerkleProof, blockHeader.stateRoot + abi.encodePacked(challenge.commitmentReceiver), proof.accountMerkleProof, previousBlockHeader.stateRoot ); if (!accountExists) { revert AccountDoesNotExist(); } + // Extract the nonce and balance of the account from the RLP-encoded data AccountData memory account = _decodeAccountRLP(accountRLP); - if (account.nonce > decodedTx.nonce) { - // The transaction recovered sender has sent a transaction with a higher nonce than the committed - // transaction, before the proposer could include it. Consider the challenge defended, as the - // proposer is not at fault. The bond will be shared between the resolver and commitment signer. - challenge.status = ChallengeStatus.Defended; - _transferHalfBond(msg.sender); - _transferHalfBond(challenge.commitmentSigner); - emit ChallengeDefended(challengeID); - return; - } else if (account.nonce < decodedTx.nonce) { - // Q: is this a valid case? technically the proposer would be at fault for accepting a commitment of an - // already included transaction. TBD. - } + // Loop through each committed transaction and verify its inclusion in the block + // along with the sender's balance and nonce (starting from the account state at the top of the block). + for (uint256 i = 0; i < challenge.committedTxs.length; i++) { + TransactionData memory committedTx = challenge.committedTxs[i]; + + if (account.nonce > committedTx.nonce) { + // The tx sender (aka "challenge.commitmentReceiver") has sent a transaction with a higher nonce + // than the committed transaction, before the proposer could include it. Consider the challenge + // defended, as the proposer is not at fault. The bond will be shared between the resolver and + // commitment signer. + challenge.status = ChallengeStatus.Defended; + _transferHalfBond(msg.sender); + _transferHalfBond(challenge.commitmentSigner); + emit ChallengeDefended(challengeID); + return; + } - if (account.balance < blockHeader.baseFee * decodedTx.gasLimit) { - // The account does not have enough balance to pay for the worst-case base fee of the committed transaction. - // Consider the challenge defended, as the proposer is not at fault. The bond will be shared between the - // resolver and commitment signer. - challenge.status = ChallengeStatus.Defended; - _transferHalfBond(msg.sender); - _transferHalfBond(challenge.commitmentSigner); - emit ChallengeDefended(challengeID); - return; - } + if (account.balance < inclusionBlockHeader.baseFee * committedTx.gasLimit) { + // The tx sender account doesn't have enough balance to pay for the worst-case baseFee of the committed + // transaction. Consider the challenge defended, as the proposer is not at fault. The bond will be + // shared between the resolver and commitment signer. + challenge.status = ChallengeStatus.Defended; + _transferHalfBond(msg.sender); + _transferHalfBond(challenge.commitmentSigner); + emit ChallengeDefended(challengeID); + return; + } - // The key in the transaction trie is the RLP-encoded index of the transaction in the block - bytes memory txLeaf = RLPWriter.writeUint(proof.txIndexInBlock); + // Over/Underflow is checked in the previous if statements. + // This is the same logic applied by the Bolt Sidecar's off-chain checks + // before deciding to sign a new commitment. + account.nonce++; + account.balance -= inclusionBlockHeader.baseFee * committedTx.gasLimit; - // Verify transaction inclusion proof - // Note: the transactions trie is built with raw leaves, without hashing them first. - // This denotes why we use `MerkleTrie.get()` as opposed to `SecureMerkleTrie.get()` here. - (bool txExists, bytes memory txRLP) = MerkleTrie.get(txLeaf, proof.txMerkleProof, blockHeader.txRoot); + // The key in the transaction trie is the RLP-encoded index of the transaction in the block + bytes memory txLeaf = RLPWriter.writeUint(proof.txIndexesInBlock[i]); - if (!txExists) { - revert TransactionNotIncluded(); - } + // Verify transaction inclusion proof + // + // The transactions trie is built with raw leaves, without hashing them first + // (This denotes why we use `MerkleTrie.get()` as opposed to `SecureMerkleTrie.get()`). + (bool txExists, bytes memory txRLP) = + MerkleTrie.get(txLeaf, proof.txMerkleProofs[i], inclusionBlockHeader.txRoot); - // Decode the txRLP and check if it matches the committed transaction - // TODO: q: is txRLP also envelope encoded? if not, this check will fail. - if (keccak256(challenge.commitment.signedTx) != keccak256(txRLP)) { - revert WrongTransactionHashProof(); + if (!txExists) { + revert TransactionNotIncluded(); + } + + // Check if the committed transaction hash matches the hash of the included transaction + if (committedTx.txHash != keccak256(txRLP)) { + revert WrongTransactionHashProof(); + } } - // If all checks pass, the challenge is considered defended as the proposer defended with valid proofs. + // If all checks pass, the challenge is considered DEFENDED as the proposer provided valid proofs. // The bond will be shared between the resolver and commitment signer. challenge.status = ChallengeStatus.Defended; _transferHalfBond(msg.sender); @@ -298,6 +373,38 @@ contract BoltChallenger is IBoltChallenger { // ========= HELPERS ========= + /// @notice Recover the commitment data from a signed commitment. + /// @param commitment The signed commitment to recover the data from. + /// @return txSender The sender of the committed transaction. + /// @return commitmentSigner The signer of the commitment. + /// @return transactionData The decoded transaction data of the committed transaction. + function _recoverCommitmentData( + SignedCommitment calldata commitment + ) internal pure returns (address txSender, address commitmentSigner, TransactionData memory transactionData) { + commitmentSigner = ECDSA.recover(_computeCommitmentID(commitment), commitment.signature); + TransactionDecoder.Transaction memory decodedTx = commitment.signedTx.decodeEnveloped(); + txSender = decodedTx.recoverSender(); + transactionData = TransactionData({ + txHash: keccak256(commitment.signedTx), + nonce: decodedTx.nonce, + gasLimit: decodedTx.gasLimit + }); + } + + /// @notice Compute the challenge ID for a given set of signed commitments. + /// @param commitments The signed commitments to compute the ID for. + /// @return challengeID The computed challenge ID. + function _computeChallengeID( + SignedCommitment[] calldata commitments + ) internal pure returns (bytes32) { + bytes32[] memory txHashes = new bytes32[](commitments.length); + for (uint256 i = 0; i < commitments.length; i++) { + txHashes[i] = keccak256(commitments[i].signedTx); + } + + return keccak256(abi.encodePacked(txHashes, abi.encodePacked(commitments[0].slot))); + } + /// @notice Compute the commitment ID for a given signed commitment. /// @param commitment The signed commitment to compute the ID for. /// @return commitmentID The computed commitment ID. @@ -314,6 +421,7 @@ contract BoltChallenger is IBoltChallenger { ) internal pure returns (BlockHeaderData memory blockHeader) { RLPReader.RLPItem[] memory headerFields = headerRLP.toRLPItem().readList(); + blockHeader.parentHash = headerFields[0].readBytes32(); blockHeader.stateRoot = headerFields[3].readBytes32(); blockHeader.txRoot = headerFields[4].readBytes32(); blockHeader.blockNumber = headerFields[8].readUint256(); @@ -338,7 +446,7 @@ contract BoltChallenger is IBoltChallenger { function _transferFullBond( address recipient ) internal { - (bool success,) = payable(recipient).call{value: CHALLENGE_BOND}(""); + (bool success,) = payable(recipient).call{value: COMMITMENT_BOND}(""); if (!success) { revert BondTransferFailed(); } @@ -349,7 +457,7 @@ contract BoltChallenger is IBoltChallenger { function _transferHalfBond( address recipient ) internal { - (bool success,) = payable(recipient).call{value: CHALLENGE_BOND / 2}(""); + (bool success,) = payable(recipient).call{value: COMMITMENT_BOND / 2}(""); if (!success) { revert BondTransferFailed(); } diff --git a/bolt-contracts/src/interfaces/IBoltChallenger.sol b/bolt-contracts/src/interfaces/IBoltChallenger.sol index de67078bd..fb603de89 100644 --- a/bolt-contracts/src/interfaces/IBoltChallenger.sol +++ b/bolt-contracts/src/interfaces/IBoltChallenger.sol @@ -5,16 +5,18 @@ interface IBoltChallenger { enum ChallengeStatus { Open, Defended, - Lost + Breached } struct Challenge { bytes32 id; uint48 openedAt; ChallengeStatus status; + uint256 targetSlot; address challenger; address commitmentSigner; - SignedCommitment commitment; + address commitmentReceiver; + TransactionData[] committedTxs; } struct SignedCommitment { @@ -23,7 +25,14 @@ interface IBoltChallenger { bytes signedTx; } + struct TransactionData { + bytes32 txHash; + uint256 nonce; + uint256 gasLimit; + } + struct BlockHeaderData { + bytes32 parentHash; bytes32 stateRoot; bytes32 txRoot; uint256 blockNumber; @@ -37,11 +46,23 @@ interface IBoltChallenger { } struct Proof { - bytes blockHeaderRLP; + // block number where the transactions are included + uint256 inclusionBlockNumber; + // RLP-encoded block header of the previous block of the inclusion block + // (for clarity: `previousBlockHeader.number == inclusionBlockNumber - 1`) + bytes previousBlockHeaderRLP; + // RLP-encoded block header where the committed transactions are included + bytes inclusionBlockHeaderRLP; + // merkle inclusion proof of the account in the state trie of the previous block + // (checked against the previousBlockHeader.stateRoot) bytes accountMerkleProof; - bytes txMerkleProof; - uint256 blockNumber; - uint256 txIndexInBlock; + // merkle inclusion proof of the transactions in the transaction trie of the inclusion block + // (checked against the inclusionBlockHeader.txRoot). The order of the proofs should match + // the order of the committed transactions in the challenge: `Challenge.committedTxs`. + bytes[] txMerkleProofs; + // indexes of the committed transactions in the block. The order of the indexes should match + // the order of the committed transactions in the challenge: `Challenge.committedTxs`. + uint256[] txIndexesInBlock; } error SlotInTheFuture(); @@ -52,16 +73,23 @@ interface IBoltChallenger { error ChallengeDoesNotExist(); error BlockIsTooOld(); error InvalidBlockHash(); + error InvalidParentBlockHash(); error AccountDoesNotExist(); error TransactionNotIncluded(); error WrongTransactionHashProof(); error InvalidBlockNumber(); error BondTransferFailed(); error ChallengeNotExpired(); + error EmptyCommitments(); + error UnexpectedMixedSenders(); + error UnexpectedMixedSlots(); + error UnexpectedMixedSigners(); + error UnexpectedNonceOrder(); + error InvalidProofsLength(); event ChallengeOpened(bytes32 indexed challengeId, address indexed challenger, address indexed commitmentSigner); event ChallengeDefended(bytes32 indexed challengeId); - event ChallengeLost(bytes32 indexed challengeId); + event ChallengeBreached(bytes32 indexed challengeId); function getAllChallenges() external view returns (Challenge[] memory); @@ -72,10 +100,12 @@ interface IBoltChallenger { ) external view returns (Challenge memory); function openChallenge( - SignedCommitment calldata commitment + SignedCommitment[] calldata commitments ) external payable; - function resolveRecentChallenge(bytes32 challengeID, Proof calldata proof) external; + function resolveExpiredChallenge( + bytes32 challengeID + ) external; function resolveChallenge(bytes32 challengeID, Proof calldata proof) external; } diff --git a/bolt-contracts/test/BoltChallenger.t.sol b/bolt-contracts/test/BoltChallenger.t.sol index e5a7c6da0..c3f2f0fae 100644 --- a/bolt-contracts/test/BoltChallenger.t.sol +++ b/bolt-contracts/test/BoltChallenger.t.sol @@ -67,7 +67,7 @@ contract BoltChallengerTest is Test { bytes32 trustedBlockHash = 0x0fc7c840f5b4b451e99dc8adb0d475eab2ac7d36278d9601d7f4b2dd05e8022f; // Read the RLP-encoded block header from a file (obtained via `debug_getRawHeader` RPC call) - string memory file = vm.readFile("./test/testdata/raw_header.json"); + string memory file = vm.readFile("./test/testdata/header_20785012.json"); bytes memory headerRLP = vm.parseJsonBytes(file, ".result"); assertEq(keccak256(headerRLP), trustedBlockHash); @@ -94,7 +94,7 @@ contract BoltChallengerTest is Test { // Read the RLP-encoded account proof from a file. This is obtained from the `eth_getProof` // RPC call + ABI-encoding of the resulting accountProof array. - string memory file = vm.readFile("./test/testdata/eth_proof.json"); + string memory file = vm.readFile("./test/testdata/eth_proof_20785012.json"); bytes[] memory accountProofJson = vm.parseJsonBytesArray(file, ".result.accountProof"); bytes memory accountProof = _RLPEncodeList(accountProofJson); @@ -127,7 +127,7 @@ contract BoltChallengerTest is Test { bytes32 txHash = 0x9ec2c56ca36e445a46bc77ca77510f0ef21795d00834269f3752cbd29d63ba1f; // MPT proof, obtained with the `eth-trie-proof` CLI tool - string memory file = vm.readFile("./test/testdata/tx_mpt_proof.json"); + string memory file = vm.readFile("./test/testdata/tx_mpt_proof_20785012.json"); bytes[] memory txProofJson = vm.parseJsonBytesArray(file, ".proof"); bytes memory txProof = _RLPEncodeList(txProofJson); @@ -198,15 +198,16 @@ contract BoltChallengerTest is Test { // =========== Opening a challenge =========== - function testOpenChallenge() public { - IBoltChallenger.SignedCommitment memory commitment = _parseTestCommitment(); + function testOpenChallengeSingleTx() public { + IBoltChallenger.SignedCommitment[] memory commitments = new IBoltChallenger.SignedCommitment[](1); + commitments[0] = _parseTestCommitment(); assertEq(challenger.balance, 100 ether); // Open a challenge with the commitment vm.resumeGasMetering(); vm.prank(challenger); - boltChallenger.openChallenge{value: 1 ether}(commitment); + boltChallenger.openChallenge{value: 1 ether}(commitments); vm.pauseGasMetering(); assertEq(challenger.balance, 99 ether); @@ -220,114 +221,127 @@ contract BoltChallengerTest is Test { assertEq(uint256(challenge.status), 0); assertEq(challenge.challenger, challenger); assertEq(challenge.commitmentSigner, 0x71f7D1B81E297816cf6691B2396060Ede49eFA5e); - assertEq(challenge.commitment.slot, commitment.slot); - assertEq(challenge.commitment.signature, commitment.signature); - assertEq(challenge.commitment.signedTx, commitment.signedTx); + assertEq(challenge.targetSlot, commitments[0].slot); } function testOpenChallengeWithInsufficientBond() public { - IBoltChallenger.SignedCommitment memory commitment = _parseTestCommitment(); + IBoltChallenger.SignedCommitment[] memory commitments = new IBoltChallenger.SignedCommitment[](1); + commitments[0] = _parseTestCommitment(); // Open a challenge with insufficient bond vm.resumeGasMetering(); vm.prank(challenger); vm.expectRevert(IBoltChallenger.InsufficientChallengeBond.selector); - boltChallenger.openChallenge{value: 0.1 ether}(commitment); + boltChallenger.openChallenge{value: 0.1 ether}(commitments); vm.pauseGasMetering(); } function testOpenChallengeWithLargebond() public { - IBoltChallenger.SignedCommitment memory commitment = _parseTestCommitment(); + IBoltChallenger.SignedCommitment[] memory commitments = new IBoltChallenger.SignedCommitment[](1); + commitments[0] = _parseTestCommitment(); // Open a challenge with a large bond, making sure that the rest is refunded vm.resumeGasMetering(); vm.prank(challenger); - boltChallenger.openChallenge{value: 50 ether}(commitment); + boltChallenger.openChallenge{value: 50 ether}(commitments); vm.pauseGasMetering(); assertEq(challenger.balance, 99 ether); } function testOpenAlreadyExistingChallenge() public { - IBoltChallenger.SignedCommitment memory commitment = _parseTestCommitment(); + IBoltChallenger.SignedCommitment[] memory commitments = new IBoltChallenger.SignedCommitment[](1); + commitments[0] = _parseTestCommitment(); // Open a challenge vm.prank(challenger); - boltChallenger.openChallenge{value: 1 ether}(commitment); + boltChallenger.openChallenge{value: 1 ether}(commitments); // Try to open the same challenge again vm.resumeGasMetering(); vm.prank(challenger); vm.expectRevert(IBoltChallenger.ChallengeAlreadyExists.selector); - boltChallenger.openChallenge{value: 1 ether}(commitment); + boltChallenger.openChallenge{value: 1 ether}(commitments); vm.pauseGasMetering(); } function testOpenChallengeWithSlotInTheFuture() public { - IBoltChallenger.SignedCommitment memory commitment = _parseTestCommitment(); - commitment.slot = uint64(BeaconChainUtils._getCurrentSlot()) + 10; + IBoltChallenger.SignedCommitment[] memory commitments = new IBoltChallenger.SignedCommitment[](1); + commitments[0] = _parseTestCommitment(); + + commitments[0].slot = uint64(BeaconChainUtils._getCurrentSlot()) + 10; // Open a challenge with a slot in the future vm.resumeGasMetering(); vm.prank(challenger); vm.expectRevert(IBoltChallenger.BlockIsNotFinalized.selector); - boltChallenger.openChallenge{value: 1 ether}(commitment); + boltChallenger.openChallenge{value: 1 ether}(commitments); vm.pauseGasMetering(); } function testOpenChallengeInvalidSignature() public { - IBoltChallenger.SignedCommitment memory commitment = _parseTestCommitment(); + IBoltChallenger.SignedCommitment[] memory commitments = new IBoltChallenger.SignedCommitment[](1); + commitments[0] = _parseTestCommitment(); // Modify the signature to make it invalid - commitment.signature[0] = bytes1(uint8(commitment.signature[0]) + 5); + commitments[0].signature[0] = bytes1(uint8(commitments[0].signature[0]) + 5); // Open a challenge with an invalid signature vm.resumeGasMetering(); vm.prank(challenger); vm.expectRevert(ECDSA.ECDSAInvalidSignature.selector); - boltChallenger.openChallenge{value: 1 ether}(commitment); + boltChallenger.openChallenge{value: 1 ether}(commitments); vm.pauseGasMetering(); } // =========== Resolving a challenge =========== - function testResolveChallengeFullDefense() public { + function testResolveChallengeFullDefenseSingleTx() public { // Prove the full defense of a challenge: the block header, account proof, and tx proof // are all valid and the proposer has included the transaction in their slot. - IBoltChallenger.SignedCommitment memory commitment = _createRecentBoltCommitment(); + IBoltChallenger.SignedCommitment[] memory commitments = new IBoltChallenger.SignedCommitment[](1); + commitments[0] = _createRecentBoltCommitment(); // Open a challenge vm.prank(challenger); - boltChallenger.openChallenge{value: 1 ether}(commitment); + boltChallenger.openChallenge{value: 1 ether}(commitments); // Get the challenge ID IBoltChallenger.Challenge[] memory challenges = boltChallenger.getAllChallenges(); assertEq(challenges.length, 1); bytes32 challengeID = challenges[0].id; - string memory rawHeader = vm.readFile("./test/testdata/raw_header.json"); - string memory ethProof = vm.readFile("./test/testdata/eth_proof.json"); - string memory txProof = vm.readFile("./test/testdata/tx_mpt_proof.json"); + string memory rawPreviousHeader = vm.readFile("./test/testdata/header_20785011.json"); + string memory rawInclusionHeader = vm.readFile("./test/testdata/header_20785012.json"); + string memory ethProof = vm.readFile("./test/testdata/eth_proof_20785011.json"); + string memory txProof = vm.readFile("./test/testdata/tx_mpt_proof_20785012.json"); + + bytes[] memory txProofs = new bytes[](1); + txProofs[0] = _RLPEncodeList(vm.parseJsonBytesArray(txProof, ".proof")); + + uint256[] memory txIndexesInBlock = new uint256[](1); + txIndexesInBlock[0] = vm.parseJsonUint(txProof, ".index"); IBoltChallenger.Proof memory proof = IBoltChallenger.Proof({ - blockHeaderRLP: vm.parseJsonBytes(rawHeader, ".result"), + inclusionBlockNumber: 20_785_012, + previousBlockHeaderRLP: vm.parseJsonBytes(rawPreviousHeader, ".result"), + inclusionBlockHeaderRLP: vm.parseJsonBytes(rawInclusionHeader, ".result"), accountMerkleProof: _RLPEncodeList(vm.parseJsonBytesArray(ethProof, ".result.accountProof")), - txMerkleProof: _RLPEncodeList(vm.parseJsonBytesArray(txProof, ".proof")), - txIndexInBlock: vm.parseJsonUint(txProof, ".index"), - blockNumber: 20_785_012 + txMerkleProofs: txProofs, + txIndexesInBlock: txIndexesInBlock }); - // check that the block header transactions root matches the root in the tx proof data. - bytes32 txRoot = boltChallenger._decodeBlockHeaderRLPExt(proof.blockHeaderRLP).txRoot; - assertEq(txRoot, vm.parseJsonBytes32(txProof, ".root")); + // check that the inclusion block transactions root matches the root in the tx proof data. + bytes32 inclusionTxRoot = boltChallenger._decodeBlockHeaderRLPExt(proof.inclusionBlockHeaderRLP).txRoot; + assertEq(inclusionTxRoot, vm.parseJsonBytes32(txProof, ".root")); - bytes32 trustedBlockHash = 0x0fc7c840f5b4b451e99dc8adb0d475eab2ac7d36278d9601d7f4b2dd05e8022f; + bytes32 trustedPreviousBlockHash = 0x6be050fe1f6c7ffe8f30a350250a9ecc08ff3c031d129f65e1c10e5119d7a28b; // Resolve the challenge vm.resumeGasMetering(); vm.prank(resolver); - boltChallenger._resolveExt(challengeID, trustedBlockHash, proof); + boltChallenger._resolveExt(challengeID, trustedPreviousBlockHash, proof); vm.pauseGasMetering(); // Check the challenge was resolved diff --git a/bolt-contracts/test/testdata/eth_proof_20785011.json b/bolt-contracts/test/testdata/eth_proof_20785011.json new file mode 100644 index 000000000..bb5045553 --- /dev/null +++ b/bolt-contracts/test/testdata/eth_proof_20785011.json @@ -0,0 +1,22 @@ +{ + "jsonrpc": "2.0", + "id": 1, + "result": { + "address": "0x0d9f5045b604ba0c050b5eb06d0b25d01c525ea5", + "accountProof": [ + "0xf90211a06c5341556789e53964fe2a77e0bf2b540f9c9aef92d508eedbe496183c5fb0d4a088d9fcfc0d596047f887fe0b6e76a22833ed33d90e475a97515c135afc1a358ca0a2d140e4eb080e7154ab4732922dc09ba5e6f7bb66ca085cce4a33a2485cefb3a01550fc55f997ebd8ff3194813c0a83668219d5aab1adea1e4aa8416ed5e086c2a04ce40dbcc710d91c6583912c4bd9e9980998ef0399346a7d488d4807153ffb7fa0a4aed92a1d67dbd794e18c588b0bf690373517a96d1c2028337a17bdb1955522a08a352007fbbbb15880b3ab7f3732a2f0f35513ec3704a97a1c0d6958a855182fa0ab93816f2baef5e33eda09193295d14a76b68ab71b264bbc145f7bbcbab6de44a08c22010d9488cebd377914768e0f105e4468d86843fc8dbe3f0026ea81ea4428a02748a9e18e6560d907ed69f7313239ab489c443e8517a6da153126c1b1f4295ea07e3eac4ab2437783df515ea778b89489991230aa14724147af45cc36027773eda046487f3e599ab8a8d627425936e9490e26c0242526ac83caa777390783efa078a0b9bac1565b8e64fe494b62be9eaf5ba81cfbb477532b5425cb85cc19cdb664f4a024eb0480fbad22741077e53fd0e209b80f721058e000bec1c67904f0a419dc68a09bba4884ab92553333120f0548bc347db14eeb098a903e533b41940a8b3c48d5a0f400d123b1957c29ce7dcab8ffd18a0da9bf7d754c544452391334b05831707280", + "0xf90211a05b4f1080ce2ddb2a07b1093f8248df994bf78d2e0902def84faa02d3e8b4932ba0c75ea41d0704a50f6ef436fede8ff700a2d3c0edbf5c7066f2e7e2346a1b6b15a0a99a258a63cda78ff4fcb4bcdcef99e8f8f125da55ecaba58034408a3c10c03ba08d6ede7b83392f11041c9e9b6403162ddfe0a87de3a26c903b819d81c5d1d68fa09c33fbdc36ea41febe74608a116bd1a6118f9c2a8802d307525e35b31062ed56a0dfd19dec7ac4c6e74eb81bbfe2e9af6dfdb6b8c131f419dcea0bebfba80c4129a06d302c5ad7c218e7772a6fdd426b07ee6a151529f2993e07fa3fe728e03a1016a010184894262fd8c80d8596760e68e39bbe17864fca10d5caf0803c3f9c77ecf1a02c01f8486b37c883b0169b3593a28b70881b03fc4887048a318feb5141140a3da0ecb89cd38ba3499731c19946c2e108cb2977824058bcda585cca702cb231fbada0218c4bba8f633a0c22aa2fe39cae6fc0ddc83c471099709055c4b3953de34685a008948be35bc1607efce7877f13f1cde290b3ec45e026a180d058fe90a314034ca087d839d5da8e87d3fc45f782e128243a4a555b3850ea790f084d2cfc2d775b3da02d09a195dae74c1075bfd63342af74ff5fe4cb6777fc1cc44ea03ab258ba3e43a0219c2f1156d413196b274acb0234c2fcb41ed66aecd1e5011ad83da73b047ce7a090be7be27cf1be666f7835cd80feecb0904d50403adabb86ad1fae00a8cb29da80", + "0xf90211a0a85deaca1adffdb7713b02a33af6ec05b09237d060963af84d7dc1a269178845a0cfd70f233bd4eb4ac42209a0578808f9e24498297af9a3e902233c1839a3a3cda0d6975a90005a1966ddae9b0f978161fb032ea2ca2d8db2453fc6be2710d48388a0d33abb2b6de28462b483a584224b30c079986bed9599023f28c81db7ee324b29a054911488c612caf55eaa494f0cc84ba25485ee49ce40f49b434098951036517da0cc4c54ed2de6d2b8fcc3bcc6d1fc490e8d26e24d4ca1ff3d81c7492fa23e4c3ba06249e67a29f9ed1713043324faf5b666d790977fad710f1a0a9e660ccb48c97da0ed8f59ba4a2766bb4035cc489fbe5f34795c2c36e16bd2d00b7b08a5a6f42507a0d2d4d82c9452c5b78a807c5da31db61357c211c84e90bdb983940f344a9984e3a05c2e1286d8a66758e1c1fcb3c85f28791f95ee85615a39c1d6780f6a54e1b3a6a0ae4bf704ff1b38570d255bb7c2b2917a54b025e6e8185929b4d977f453a8d9e8a0ac9228bf16f7c15155965d5eb5d7aa2e2f31e8f937d52a7203abef348b843632a0a193e1b44b2441cbed3d8cd20862d7f11b0121b957a82d63168b67809dbaf3b2a09aba1e3d0feb612998294bd3645247790107e619bf8728819fbe463fec342f5ca09336f5d7c3d7ddb6e987ac5f7c0e78e3936e05f6da27a7c87813a6e5250e9c38a021c230f247db23bd8d948d68a24891afd58c29864f1d74551060c10067e386a380", + "0xf90211a09d93666285cfbada2d91c81fb96b5e30ff75a51353c69a30c0a1431f51867880a05efd44e1b30767c66f02d79e93b2c45efcd56018050605c08818645ddb7a46c5a05e5bdbdd63d64f58a34f47a42d70b83febf924c08cdfa8d5673db3c6bd71df67a05d67553cf6620c67dead6ee37a48a5dfddddd4adce333c4a7771c25140ecdd92a07d5eb9550d7f807d7e0dd86a52f3d111b4359ac2928ca3e07c34bd6551055537a0fc0e43113716fdd77ec45220d5afcc0953d8357478872de3c6f36fc2d75c9610a03230ce2ec5fa8173fca250ac63df602e3a6d2bff691edc8552ab8bdea458bf07a01e7ad54a56fcd625d6066ec05b6efc30bf90a65b756277f79a65ceb0eb108a32a0089ff36bd32acfeef1b5faf546e54ac7646086d1c2c764239cc9b98da8c460e7a00905f00f493594ce1c13274c2bb5ad018fab388de860f1cae6af64cb3ad3b46fa0ad7d4f9200d8f0ae34bf0cbece04d61b13c6daa0b7ea0e9d732522c5d46feacda075da3fc2991d7d32cb0be70210c7cbe3bd5883cfc52f58e8e04d07b9977f816fa0c7bc6aab1b71efc9ffc459cfda04d930f1652d53179a38b43f8e0980b40ab48ca0ae012ca5c6a3363d5f850e060001c90cd3a022aa44e325d5d832021ebef4fafea06ee58a40e8abba2a3be093fdefd9d9871558f4fa11f6d6721307117a17260b51a07cc44d713e05b70e6749fedd7cb1b4b47d2102638bf93ffd0e8ebf404af6940080", + "0xf90211a0b1689af6f7f511cc59773684cb467f33c3dcc5dc1a08f451c851544dca6b1a3aa039a80eb019a427f8b32dfdd88a0b5d95a21521444c67fca8f1e875f1898e16c4a06652a79c42cf0d23532b84d47d58b6c898076d88147030581ff121d25407e25ba0cd2b62c08885a27becfd0077ccdfdd5d77d4cdc5a0ab1ec2ad1303c93a9aae3ba0333e03e837b7838cc45d8f30b17ec09933b77debdfceea6cc31033c34cb25b88a025c683f0ff00593e3b96caa6a45153e1907e263c2815064492ea29d2ae467d16a0b1f927c724f5f7425c5d47ff064d099a13982ddb528ac5526829ffefb2502c91a086a4177c69624728a862d160c303dda7010e4d317f60b2945a517f2b8cfe71f5a0436081588c2e0ceec3e3b2298c97085410d971d8da9eedcdc48e62fdbd125a2ba02f24c969da88b0cfe6318383edf10cbf2174c883ecd007d6bb79562663723099a0a9cbc30a1d257e3b651b3c304be3c253948c373f6980b22898af778ef9955b0fa0a343404514e213b6e5832f4621e6a6b1f0eb977811d759f06b7ad6da445092fba02eeb6ec27797e59283781b12f56e315f35b16395560a9c34bdd21280e942cfe1a0c874d2114c306baf91f0d43c2c76e30a6ad0da45273fb6d82246565d84c2538ea071a72e0b7d80490d26f95d3714cf8dd0c8b3c7b232980607aee525a9162e6308a04b4d1cf635eab18bbbb9e64109b0d992d759df23a96aab3d4a40e39a7c3becba80", + "0xf90211a023c9514b2fac99cd172f93d2db4d6578ec69fc2e0ef5c79fc76499ded09130eda00c645327db795a8a9c69282d86e3acece2901bdbb8047528ee8c78681b0e14b1a01e76e1109e1936249fdf08a4f583f91c01c6a4672794657bd74cbdd6ab3c2f3aa0a6b7b10c53412201d9ca7461b7210d6457a6b275b95033e2d8ca9778e5071c05a03e6f50fedb2b9f9f963356fc2899800e46a38169a794a1e6906c02ce8e8f0d59a0d4cec6a796cbab7e9cb28063423fcf157bac02de0cf1f0931303a701006019a2a0fb7e5187937b4203cf97bce2f345c9161bfbb9eb431a105356009e6ce18a1fbca0486a40cde9b6a65b5534609ae4f56cc97c00dbc4c4447385f8a42da3b1ac40d3a01c91aad4f4d391df268007fee771caedccc43369e07d6eb22fd45dab6beb0986a0672ca6ccfd8ac83f8974afefc9c9c2b58376991580158b16fd98cb9076cf8ea9a06cb3234503ad7116bb0e66257c646b2bce7603d774474a4f100cf93dde3eb594a085d0406435a6562f3370dd4b5de47a2b7d1084b9f25f0dbf49f54d2f46a2fc32a08bba9249e073397de4c46dd27688e9467b20fb8e61e04fd2594136cae26cd5c7a0a43fd38e14c38b64b84bb579b9a3cfab05cd5cc69c3a812a68e458acf72ea96ca0d848b40a2dbd5d8a4179e89fe8533f21db91982a0b8ecc1542c08ea27aec522aa0e03bc889898881f7ecb7caf0db5fb95125e26ff9e26bb85c33cbe360b50c9f1f80", + "0xf90191a017e9f741d070bfe3d975719f75663dabd5acdeb3678c50bd4872b817d7713d0b80a073811d2e7576e6bdac9804597d153d4e0a4cbf8400b8331f67605f6810ecbd53a0363162c748099d6ed114ead87c481972c1b0c124730b341c4ef1f963e0835a9880a09ef90c2a9ad234a08cbf4ea4ced535f227bc48becd22c1b05f2cbd586f018b73a0ae59576b0c1089b2828db7e54602ad0fabe4eff312ee5e0c21a04ac09b0e04d180a0fdbde0b4dbc02bb2ec472517c6f543de3673d33a6246a602479b43042ae9222da0c8d33ec13b89d8fd0ba487fe9cd61d18deff49bed48dc073384dc086a7361273a021253bb1110de615cede8c708a94cd6b898a61b5b1aaef8760b3341f232d63f0a06e7fbd4a6404e4acae007bb6147c58aebf6f19b3914a8d48b831bc3e50e5c05ea0c72d502ed522a796f823edefb721e64ff9d38904c9913b5be76f1e759c899b9da0fcc3a12a432daa29cb33f4a198624f6391124579ab7cbfae1eb30de076cfdd7980a0ab5019c60fef28581a93f0707739bbb24dcfbc2ffc550fb57aea59d91c79c04780", + "0xf86f9d3a1698215d94a53d304dacf1ae39aec65065a3307b0d96cd2acd29de94b84ff84d81eb88033ee43ef5fd164da056e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421a0c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470" + ], + "balance": "0x33ee43ef5fd164d", + "codeHash": "0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470", + "nonce": "0xeb", + "storageHash": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421", + "storageProof": [] + } +} diff --git a/bolt-contracts/test/testdata/eth_proof.json b/bolt-contracts/test/testdata/eth_proof_20785012.json similarity index 100% rename from bolt-contracts/test/testdata/eth_proof.json rename to bolt-contracts/test/testdata/eth_proof_20785012.json diff --git a/bolt-contracts/test/testdata/header_20785011.json b/bolt-contracts/test/testdata/header_20785011.json new file mode 100644 index 000000000..c4d63aa8e --- /dev/null +++ b/bolt-contracts/test/testdata/header_20785011.json @@ -0,0 +1,5 @@ +{ + "jsonrpc": "2.0", + "result": "0xf9025aa00595a77df480da86f42f0f339fc55fc6a40886cd320de3a3ec3df6286c867503a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347941f9090aae28b8a3dceadf281b0f12828e676c326a0d6b6ae210398562833e4ac414eb120db14aed11fb8adf5ff5939a20bdd9fbc6fa0a9c4dc0b0e382f1f800d1581d4001bfc33bd384a6f50978e19ab60e92a283f37a020fcda2771063409aefb2669c4e7579d270234d44055c996de915e95ec9caf01b90100d0f9414739b00184f00e04f08d310c80718d32249441f88082897d3830b2a854950fc41563202a9022080028471a29204227c0a8af36a184dae6a6662def6988186c1e0a609449e97ab949b9a07134a1a6210ac1d5651e4114a8c6389926140162011995324058a1346953f321800cf3202e50a05c066d218641101d171b10860450625e0b0c67488d4c09fe03e272d75c514595113100c8112984e2fd90a26a3385185d1f8020044a2190c0ac199d070479854502981a2522046e10d58f1a7b7b583c82005cb3cb29a1823a811124d0328101175569421f64d64002d9b1e630b871202e43622604014484b4810b82dccb057441009700f809c07d01903816558084013d27738401c9c38083a09ebf8466ec2a63917273796e632d6275696c6465722e78797aa0cfe02ed4afd27cd0a42cb0f802a57672daac70511e3b860d7a7ea6b69d4e0d6d880000000000000000850537c96c32a073cb76da1e69871e3400088fa509333d96c3ac5fa307e6f64892202222885f6280830a0000a0ede6709250535a0c48e856b76e415c907d018c3c5f4d1b34e2ae14aa61a8eba1", + "id": 1 +} diff --git a/bolt-contracts/test/testdata/raw_header.json b/bolt-contracts/test/testdata/header_20785012.json similarity index 100% rename from bolt-contracts/test/testdata/raw_header.json rename to bolt-contracts/test/testdata/header_20785012.json diff --git a/bolt-contracts/test/testdata/tx_mpt_proof.json b/bolt-contracts/test/testdata/tx_mpt_proof_20785012.json similarity index 100% rename from bolt-contracts/test/testdata/tx_mpt_proof.json rename to bolt-contracts/test/testdata/tx_mpt_proof_20785012.json