Commit 4354aa88 authored by clabby's avatar clabby Committed by GitHub

feat(ctb): `PreimageOracle` large preimage proposal bonds (#9570)

* Add bonds to `PreimageOracle`

* Update op-challenger/game/fault/contracts/oracle_test.go
Co-authored-by: default avatarcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update packages/contracts-bedrock/src/cannon/PreimageOracle.sol
Co-authored-by: default avatarrefcell <abigger87@gmail.com>

---------
Co-authored-by: default avatarcoderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: default avatarrefcell <abigger87@gmail.com>
parent 1e62a0b7
This diff is collapsed.
This diff is collapsed.
...@@ -38,6 +38,7 @@ const ( ...@@ -38,6 +38,7 @@ const (
methodChallengeLPP = "challengeLPP" methodChallengeLPP = "challengeLPP"
methodChallengePeriod = "challengePeriod" methodChallengePeriod = "challengePeriod"
methodGetTreeRootLPP = "getTreeRootLPP" methodGetTreeRootLPP = "getTreeRootLPP"
methodMinBondSizeLPP = "MIN_BOND_SIZE"
) )
var ( var (
...@@ -55,6 +56,9 @@ type PreimageOracleContract struct { ...@@ -55,6 +56,9 @@ type PreimageOracleContract struct {
// challengePeriod caches the challenge period from the contract once it has been loaded. // challengePeriod caches the challenge period from the contract once it has been loaded.
// 0 indicates the period has not been loaded yet. // 0 indicates the period has not been loaded yet.
challengePeriod atomic.Uint64 challengePeriod atomic.Uint64
// minBondSizeLPP caches the minimum bond size for large preimages from the contract once it has been loaded.
// 0 indicates the value has not been loaded yet.
minBondSizeLPP atomic.Uint64
} }
// toPreimageOracleLeaf converts a Leaf to the contract [bindings.PreimageOracleLeaf] type. // toPreimageOracleLeaf converts a Leaf to the contract [bindings.PreimageOracleLeaf] type.
...@@ -318,6 +322,19 @@ func (c *PreimageOracleContract) ChallengeTx(ident keccakTypes.LargePreimageIden ...@@ -318,6 +322,19 @@ func (c *PreimageOracleContract) ChallengeTx(ident keccakTypes.LargePreimageIden
return call.ToTxCandidate() return call.ToTxCandidate()
} }
func (c *PreimageOracleContract) GetMinBondLPP(ctx context.Context) (*big.Int, error) {
if bondSize := c.minBondSizeLPP.Load(); bondSize != 0 {
return big.NewInt(int64(bondSize)), nil
}
result, err := c.multiCaller.SingleCall(ctx, batching.BlockLatest, c.contract.Call(methodMinBondSizeLPP))
if err != nil {
return nil, fmt.Errorf("failed to fetch min bond size for LPPs: %w", err)
}
period := result.GetBigInt(0)
c.minBondSizeLPP.Store(period.Uint64())
return period, nil
}
func (c *PreimageOracleContract) decodePreimageIdent(result *batching.CallResult) keccakTypes.LargePreimageIdent { func (c *PreimageOracleContract) decodePreimageIdent(result *batching.CallResult) keccakTypes.LargePreimageIdent {
return keccakTypes.LargePreimageIdent{ return keccakTypes.LargePreimageIdent{
Claimant: result.GetAddress(0), Claimant: result.GetAddress(0),
......
...@@ -113,6 +113,23 @@ func TestPreimageOracleContract_MinLargePreimageSize(t *testing.T) { ...@@ -113,6 +113,23 @@ func TestPreimageOracleContract_MinLargePreimageSize(t *testing.T) {
require.Equal(t, uint64(123), minProposalSize) require.Equal(t, uint64(123), minProposalSize)
} }
func TestPreimageOracleContract_MinBondSizeLPP(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t)
stubRpc.SetResponse(oracleAddr, methodMinBondSizeLPP, batching.BlockLatest,
[]interface{}{},
[]interface{}{big.NewInt(123)},
)
minBond, err := oracle.GetMinBondLPP(context.Background())
require.NoError(t, err)
require.Equal(t, big.NewInt(123), minBond)
// Should cache responses
stubRpc.ClearResponses(methodMinBondSizeLPP)
minBond, err = oracle.GetMinBondLPP(context.Background())
require.NoError(t, err)
require.Equal(t, big.NewInt(123), minBond)
}
func TestPreimageOracleContract_PreimageDataExists(t *testing.T) { func TestPreimageOracleContract_PreimageDataExists(t *testing.T) {
t.Run("exists", func(t *testing.T) { t.Run("exists", func(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t) stubRpc, oracle := setupPreimageOracleTest(t)
...@@ -332,7 +349,6 @@ func setupPreimageOracleTestWithProposals(t *testing.T, block batching.Block) (* ...@@ -332,7 +349,6 @@ func setupPreimageOracleTestWithProposals(t *testing.T, block batching.Block) (*
} }
return stubRpc, oracle, proposals return stubRpc, oracle, proposals
} }
func setupPreimageOracleTest(t *testing.T) (*batchingTest.AbiBasedRpc, *PreimageOracleContract) { func setupPreimageOracleTest(t *testing.T) (*batchingTest.AbiBasedRpc, *PreimageOracleContract) {
......
...@@ -165,6 +165,11 @@ func (p *LargePreimageUploader) initLargePreimage(uuid *big.Int, partOffset uint ...@@ -165,6 +165,11 @@ func (p *LargePreimageUploader) initLargePreimage(uuid *big.Int, partOffset uint
if err != nil { if err != nil {
return fmt.Errorf("failed to create pre-image oracle tx: %w", err) return fmt.Errorf("failed to create pre-image oracle tx: %w", err)
} }
bond, err := p.contract.GetMinBondLPP(context.Background())
if err != nil {
return fmt.Errorf("failed to get min bond for large preimage proposal: %w", err)
}
candidate.Value = bond
if _, err := p.txSender.SendAndWait("init large preimage", candidate); err != nil { if _, err := p.txSender.SendAndWait("init large preimage", candidate); err != nil {
return fmt.Errorf("failed to populate pre-image oracle: %w", err) return fmt.Errorf("failed to populate pre-image oracle: %w", err)
} }
......
...@@ -338,6 +338,11 @@ func (s *mockPreimageOracleContract) GetProposalMetadata(_ context.Context, _ ba ...@@ -338,6 +338,11 @@ func (s *mockPreimageOracleContract) GetProposalMetadata(_ context.Context, _ ba
s.squeezeCallClaimSize = 1 s.squeezeCallClaimSize = 1
return []keccakTypes.LargePreimageMetaData{{LargePreimageIdent: idents[0]}}, nil return []keccakTypes.LargePreimageMetaData{{LargePreimageIdent: idents[0]}}, nil
} }
func (s *mockPreimageOracleContract) GetMinBondLPP(_ context.Context) (*big.Int, error) {
return big.NewInt(0), nil
}
func (s *mockPreimageOracleContract) CallSqueeze(_ context.Context, _ common.Address, _ *big.Int, _ keccakTypes.StateSnapshot, _ keccakTypes.Leaf, _ merkle.Proof, _ keccakTypes.Leaf, _ merkle.Proof) error { func (s *mockPreimageOracleContract) CallSqueeze(_ context.Context, _ common.Address, _ *big.Int, _ keccakTypes.StateSnapshot, _ keccakTypes.Leaf, _ merkle.Proof, _ keccakTypes.Leaf, _ merkle.Proof) error {
if s.squeezeCallFails { if s.squeezeCallFails {
return mockSqueezeCallError return mockSqueezeCallError
......
...@@ -29,4 +29,5 @@ type PreimageOracleContract interface { ...@@ -29,4 +29,5 @@ type PreimageOracleContract interface {
CallSqueeze(ctx context.Context, claimant common.Address, uuid *big.Int, prestateMatrix keccakTypes.StateSnapshot, preState keccakTypes.Leaf, preStateProof merkle.Proof, postState keccakTypes.Leaf, postStateProof merkle.Proof) error CallSqueeze(ctx context.Context, claimant common.Address, uuid *big.Int, prestateMatrix keccakTypes.StateSnapshot, preState keccakTypes.Leaf, preStateProof merkle.Proof, postState keccakTypes.Leaf, postStateProof merkle.Proof) error
GetProposalMetadata(ctx context.Context, block batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error) GetProposalMetadata(ctx context.Context, block batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, error)
ChallengePeriod(ctx context.Context) (uint64, error) ChallengePeriod(ctx context.Context) (uint64, error)
GetMinBondLPP(ctx context.Context) (*big.Int, error)
} }
...@@ -82,10 +82,15 @@ func (h *Helper) UploadLargePreimage(ctx context.Context, dataSize int, modifier ...@@ -82,10 +82,15 @@ func (h *Helper) UploadLargePreimage(ctx context.Context, dataSize int, modifier
data := testutils.RandomData(rand.New(rand.NewSource(1234)), dataSize) data := testutils.RandomData(rand.New(rand.NewSource(1234)), dataSize)
s := matrix.NewStateMatrix() s := matrix.NewStateMatrix()
uuid := big.NewInt(h.uuidProvider.Add(1)) uuid := big.NewInt(h.uuidProvider.Add(1))
bondValue, err := h.oracleBindings.MINBONDSIZE(&bind.CallOpts{})
h.require.NoError(err)
h.opts.Value = bondValue
tx, err := h.oracleBindings.InitLPP(h.opts, uuid, 32, uint32(len(data))) tx, err := h.oracleBindings.InitLPP(h.opts, uuid, 32, uint32(len(data)))
h.require.NoError(err) h.require.NoError(err)
_, err = wait.ForReceiptOK(ctx, h.client, tx.Hash()) _, err = wait.ForReceiptOK(ctx, h.client, tx.Hash())
h.require.NoError(err) h.require.NoError(err)
h.opts.Value = big.NewInt(0)
startBlock := big.NewInt(0) startBlock := big.NewInt(0)
totalBlocks := len(data) / types.BlockSize totalBlocks := len(data) / types.BlockSize
in := bytes.NewReader(data) in := bytes.NewReader(data)
...@@ -111,6 +116,7 @@ func (h *Helper) UploadLargePreimage(ctx context.Context, dataSize int, modifier ...@@ -111,6 +116,7 @@ func (h *Helper) UploadLargePreimage(ctx context.Context, dataSize int, modifier
break break
} }
} }
return types.LargePreimageIdent{ return types.LargePreimageIdent{
Claimant: h.opts.From, Claimant: h.opts.From,
UUID: uuid, UUID: uuid,
......
...@@ -46,6 +46,19 @@ ...@@ -46,6 +46,19 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [],
"name": "MIN_BOND_SIZE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -265,7 +278,7 @@ ...@@ -265,7 +278,7 @@
], ],
"name": "initLPP", "name": "initLPP",
"outputs": [], "outputs": [],
"stateMutability": "nonpayable", "stateMutability": "payable",
"type": "function" "type": "function"
}, },
{ {
...@@ -522,6 +535,30 @@ ...@@ -522,6 +535,30 @@
"stateMutability": "view", "stateMutability": "view",
"type": "function" "type": "function"
}, },
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "proposalBonds",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{ {
"inputs": [ "inputs": [
{ {
...@@ -783,11 +820,21 @@ ...@@ -783,11 +820,21 @@
"name": "BadProposal", "name": "BadProposal",
"type": "error" "type": "error"
}, },
{
"inputs": [],
"name": "BondTransferFailed",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "CancunNotActive", "name": "CancunNotActive",
"type": "error" "type": "error"
}, },
{
"inputs": [],
"name": "InsufficientBond",
"type": "error"
},
{ {
"inputs": [], "inputs": [],
"name": "InvalidInputSize", "name": "InvalidInputSize",
......
...@@ -50,16 +50,23 @@ ...@@ -50,16 +50,23 @@
}, },
{ {
"bytes": "32", "bytes": "32",
"label": "proposalParts", "label": "proposalBonds",
"offset": 0, "offset": 0,
"slot": "22", "slot": "22",
"type": "mapping(address => mapping(uint256 => uint256))"
},
{
"bytes": "32",
"label": "proposalParts",
"offset": 0,
"slot": "23",
"type": "mapping(address => mapping(uint256 => bytes32))" "type": "mapping(address => mapping(uint256 => bytes32))"
}, },
{ {
"bytes": "32", "bytes": "32",
"label": "proposalBlocks", "label": "proposalBlocks",
"offset": 0, "offset": 0,
"slot": "23", "slot": "24",
"type": "mapping(address => mapping(uint256 => uint64[]))" "type": "mapping(address => mapping(uint256 => uint64[]))"
} }
] ]
\ No newline at end of file
...@@ -23,6 +23,8 @@ contract PreimageOracle is IPreimageOracle { ...@@ -23,6 +23,8 @@ contract PreimageOracle is IPreimageOracle {
uint256 internal immutable CHALLENGE_PERIOD; uint256 internal immutable CHALLENGE_PERIOD;
/// @notice The minimum size of a preimage that can be proposed in the large preimage path. /// @notice The minimum size of a preimage that can be proposed in the large preimage path.
uint256 internal immutable MIN_LPP_SIZE_BYTES; uint256 internal immutable MIN_LPP_SIZE_BYTES;
/// @notice The minimum bond size for large preimage proposals.
uint256 public constant MIN_BOND_SIZE = 0.25 ether;
/// @notice The depth of the keccak256 merkle tree. Supports up to 65,536 keccak blocks, or ~8.91MB preimages. /// @notice The depth of the keccak256 merkle tree. Supports up to 65,536 keccak blocks, or ~8.91MB preimages.
uint256 public constant KECCAK_TREE_DEPTH = 16; uint256 public constant KECCAK_TREE_DEPTH = 16;
/// @notice The maximum number of keccak blocks that can fit into the merkle tree. /// @notice The maximum number of keccak blocks that can fit into the merkle tree.
...@@ -71,6 +73,8 @@ contract PreimageOracle is IPreimageOracle { ...@@ -71,6 +73,8 @@ contract PreimageOracle is IPreimageOracle {
/// @notice Mapping of claimants to proposal UUIDs to the timestamp of creation of the proposal as well as the /// @notice Mapping of claimants to proposal UUIDs to the timestamp of creation of the proposal as well as the
/// challenged status. /// challenged status.
mapping(address => mapping(uint256 => LPPMetaData)) public proposalMetadata; mapping(address => mapping(uint256 => LPPMetaData)) public proposalMetadata;
/// @notice Mapping of claimants to proposal UUIDs to bond amounts.
mapping(address => mapping(uint256 => uint256)) public proposalBonds;
/// @notice Mapping of claimants to proposal UUIDs to the preimage part picked up during the absorbtion process. /// @notice Mapping of claimants to proposal UUIDs to the preimage part picked up during the absorbtion process.
mapping(address => mapping(uint256 => bytes32)) public proposalParts; mapping(address => mapping(uint256 => bytes32)) public proposalParts;
/// @notice Mapping of claimants to proposal UUIDs to blocks which leaves were added to the merkle tree. /// @notice Mapping of claimants to proposal UUIDs to blocks which leaves were added to the merkle tree.
...@@ -394,8 +398,11 @@ contract PreimageOracle is IPreimageOracle { ...@@ -394,8 +398,11 @@ contract PreimageOracle is IPreimageOracle {
} }
/// @notice Initialize a large preimage proposal. Must be called before adding any leaves. /// @notice Initialize a large preimage proposal. Must be called before adding any leaves.
function initLPP(uint256 _uuid, uint32 _partOffset, uint32 _claimedSize) external { function initLPP(uint256 _uuid, uint32 _partOffset, uint32 _claimedSize) external payable {
// The caller of `addLeavesLPP` must be an EOA. // The bond provided must be at least `MIN_BOND_SIZE`.
if (msg.value < MIN_BOND_SIZE) revert InsufficientBond();
// The caller of `addLeavesLPP` must be an EOA, so that the call inputs are always available in block bodies.
if (msg.sender != tx.origin) revert NotEOA(); if (msg.sender != tx.origin) revert NotEOA();
// The part offset must be within the bounds of the claimed size + 8. // The part offset must be within the bounds of the claimed size + 8.
...@@ -404,9 +411,13 @@ contract PreimageOracle is IPreimageOracle { ...@@ -404,9 +411,13 @@ contract PreimageOracle is IPreimageOracle {
// The claimed size must be at least `MIN_LPP_SIZE_BYTES`. // The claimed size must be at least `MIN_LPP_SIZE_BYTES`.
if (_claimedSize < MIN_LPP_SIZE_BYTES) revert InvalidInputSize(); if (_claimedSize < MIN_LPP_SIZE_BYTES) revert InvalidInputSize();
// Initialize the proposal metadata.
LPPMetaData metaData = proposalMetadata[msg.sender][_uuid]; LPPMetaData metaData = proposalMetadata[msg.sender][_uuid];
proposalMetadata[msg.sender][_uuid] = metaData.setPartOffset(_partOffset).setClaimedSize(_claimedSize); proposalMetadata[msg.sender][_uuid] = metaData.setPartOffset(_partOffset).setClaimedSize(_claimedSize);
proposals.push(LargePreimageProposalKeys(msg.sender, _uuid)); proposals.push(LargePreimageProposalKeys(msg.sender, _uuid));
// Assign the bond to the proposal.
proposalBonds[msg.sender][_uuid] = msg.value;
} }
/// @notice Adds a contiguous list of keccak state matrices to the merkle tree. /// @notice Adds a contiguous list of keccak state matrices to the merkle tree.
...@@ -563,6 +574,9 @@ contract PreimageOracle is IPreimageOracle { ...@@ -563,6 +574,9 @@ contract PreimageOracle is IPreimageOracle {
// Mark the keccak claim as countered. // Mark the keccak claim as countered.
proposalMetadata[_claimant][_uuid] = proposalMetadata[_claimant][_uuid].setCountered(true); proposalMetadata[_claimant][_uuid] = proposalMetadata[_claimant][_uuid].setCountered(true);
// Pay out the bond to the challenger.
_payoutBond(_claimant, _uuid, msg.sender);
} }
/// @notice Challenge the first keccak256 block that was absorbed. /// @notice Challenge the first keccak256 block that was absorbed.
...@@ -591,6 +605,9 @@ contract PreimageOracle is IPreimageOracle { ...@@ -591,6 +605,9 @@ contract PreimageOracle is IPreimageOracle {
// Mark the keccak claim as countered. // Mark the keccak claim as countered.
proposalMetadata[_claimant][_uuid] = proposalMetadata[_claimant][_uuid].setCountered(true); proposalMetadata[_claimant][_uuid] = proposalMetadata[_claimant][_uuid].setCountered(true);
// Pay out the bond to the challenger.
_payoutBond(_claimant, _uuid, msg.sender);
} }
/// @notice Finalize a large preimage proposal after the challenge period has passed. /// @notice Finalize a large preimage proposal after the challenge period has passed.
...@@ -644,6 +661,9 @@ contract PreimageOracle is IPreimageOracle { ...@@ -644,6 +661,9 @@ contract PreimageOracle is IPreimageOracle {
preimagePartOk[finalDigest][partOffset] = true; preimagePartOk[finalDigest][partOffset] = true;
preimageParts[finalDigest][partOffset] = proposalParts[_claimant][_uuid]; preimageParts[finalDigest][partOffset] = proposalParts[_claimant][_uuid];
preimageLengths[finalDigest] = metaData.claimedSize(); preimageLengths[finalDigest] = metaData.claimedSize();
// Pay out the bond to the claimant.
_payoutBond(_claimant, _uuid, _claimant);
} }
/// @notice Gets the current merkle root of the large preimage proposal tree. /// @notice Gets the current merkle root of the large preimage proposal tree.
...@@ -705,7 +725,7 @@ contract PreimageOracle is IPreimageOracle { ...@@ -705,7 +725,7 @@ contract PreimageOracle is IPreimageOracle {
} }
} }
/// Check if leaf` at `index` verifies against the Merkle `root` and `branch`. /// @notice Check if leaf` at `index` verifies against the Merkle `root` and `branch`.
/// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#is_valid_merkle_branch /// https://github.com/ethereum/consensus-specs/blob/dev/specs/phase0/beacon-chain.md#is_valid_merkle_branch
function _verify( function _verify(
bytes32[] calldata _proof, bytes32[] calldata _proof,
...@@ -738,6 +758,15 @@ contract PreimageOracle is IPreimageOracle { ...@@ -738,6 +758,15 @@ contract PreimageOracle is IPreimageOracle {
} }
} }
/// @notice Pay out a proposal's bond. Reverts if the transfer fails.
function _payoutBond(address _claimant, uint256 _uuid, address _to) internal {
// Pay out the bond to the claimant.
uint256 bond = proposalBonds[_claimant][_uuid];
proposalBonds[_claimant][_uuid] = 0;
(bool success,) = _to.call{ value: bond }("");
if (!success) revert BondTransferFailed();
}
/// @notice Hashes leaf data for the preimage proposals tree /// @notice Hashes leaf data for the preimage proposals tree
function _hashLeaf(Leaf memory _leaf) internal pure returns (bytes32 leaf_) { function _hashLeaf(Leaf memory _leaf) internal pure returns (bytes32 leaf_) {
leaf_ = keccak256(abi.encodePacked(_leaf.input, _leaf.index, _leaf.stateCommitment)); leaf_ = keccak256(abi.encodePacked(_leaf.input, _leaf.index, _leaf.stateCommitment));
......
...@@ -42,3 +42,9 @@ error NotEOA(); ...@@ -42,3 +42,9 @@ error NotEOA();
/// @notice Thrown when a function that requires Cancun EVM features is called on at a time where Cancun is not enabled. /// @notice Thrown when a function that requires Cancun EVM features is called on at a time where Cancun is not enabled.
error CancunNotActive(); error CancunNotActive();
/// @notice Thrown when an insufficient bond is provided for a large preimage proposal.
error InsufficientBond();
/// @notice Thrown when a bond transfer fails.
error BondTransferFailed();
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment