Skip to content

Commit

Permalink
feat: added stacked preconfs in the same slot by the same sender logic
Browse files Browse the repository at this point in the history
  • Loading branch information
merklefruit committed Sep 25, 2024
1 parent 3f7a268 commit cf9f5bb
Show file tree
Hide file tree
Showing 9 changed files with 330 additions and 151 deletions.
16 changes: 8 additions & 8 deletions bolt-contracts/.gas-snapshot
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
302 changes: 205 additions & 97 deletions bolt-contracts/src/contracts/BoltChallenger.sol

Large diffs are not rendered by default.

48 changes: 39 additions & 9 deletions bolt-contracts/src/interfaces/IBoltChallenger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand All @@ -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();
Expand All @@ -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);

Expand All @@ -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;
}
88 changes: 51 additions & 37 deletions bolt-contracts/test/BoltChallenger.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand Down
Loading

0 comments on commit cf9f5bb

Please sign in to comment.