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 (
methodChallengeLPP = "challengeLPP"
methodChallengePeriod = "challengePeriod"
methodGetTreeRootLPP = "getTreeRootLPP"
methodMinBondSizeLPP = "MIN_BOND_SIZE"
)
var (
......@@ -55,6 +56,9 @@ type PreimageOracleContract struct {
// challengePeriod caches the challenge period from the contract once it has been loaded.
// 0 indicates the period has not been loaded yet.
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.
......@@ -318,6 +322,19 @@ func (c *PreimageOracleContract) ChallengeTx(ident keccakTypes.LargePreimageIden
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 {
return keccakTypes.LargePreimageIdent{
Claimant: result.GetAddress(0),
......
......@@ -113,6 +113,23 @@ func TestPreimageOracleContract_MinLargePreimageSize(t *testing.T) {
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) {
t.Run("exists", func(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t)
......@@ -332,7 +349,6 @@ func setupPreimageOracleTestWithProposals(t *testing.T, block batching.Block) (*
}
return stubRpc, oracle, proposals
}
func setupPreimageOracleTest(t *testing.T) (*batchingTest.AbiBasedRpc, *PreimageOracleContract) {
......
......@@ -165,6 +165,11 @@ func (p *LargePreimageUploader) initLargePreimage(uuid *big.Int, partOffset uint
if err != nil {
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 {
return fmt.Errorf("failed to populate pre-image oracle: %w", err)
}
......
......@@ -338,6 +338,11 @@ func (s *mockPreimageOracleContract) GetProposalMetadata(_ context.Context, _ ba
s.squeezeCallClaimSize = 1
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 {
if s.squeezeCallFails {
return mockSqueezeCallError
......
......@@ -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
GetProposalMetadata(ctx context.Context, block batching.Block, idents ...keccakTypes.LargePreimageIdent) ([]keccakTypes.LargePreimageMetaData, 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
data := testutils.RandomData(rand.New(rand.NewSource(1234)), dataSize)
s := matrix.NewStateMatrix()
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)))
h.require.NoError(err)
_, err = wait.ForReceiptOK(ctx, h.client, tx.Hash())
h.require.NoError(err)
h.opts.Value = big.NewInt(0)
startBlock := big.NewInt(0)
totalBlocks := len(data) / types.BlockSize
in := bytes.NewReader(data)
......@@ -111,6 +116,7 @@ func (h *Helper) UploadLargePreimage(ctx context.Context, dataSize int, modifier
break
}
}
return types.LargePreimageIdent{
Claimant: h.opts.From,
UUID: uuid,
......
......@@ -46,6 +46,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MIN_BOND_SIZE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
......@@ -265,7 +278,7 @@
],
"name": "initLPP",
"outputs": [],
"stateMutability": "nonpayable",
"stateMutability": "payable",
"type": "function"
},
{
......@@ -522,6 +535,30 @@
"stateMutability": "view",
"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": [
{
......@@ -783,11 +820,21 @@
"name": "BadProposal",
"type": "error"
},
{
"inputs": [],
"name": "BondTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "CancunNotActive",
"type": "error"
},
{
"inputs": [],
"name": "InsufficientBond",
"type": "error"
},
{
"inputs": [],
"name": "InvalidInputSize",
......
......@@ -50,16 +50,23 @@
},
{
"bytes": "32",
"label": "proposalParts",
"label": "proposalBonds",
"offset": 0,
"slot": "22",
"type": "mapping(address => mapping(uint256 => uint256))"
},
{
"bytes": "32",
"label": "proposalParts",
"offset": 0,
"slot": "23",
"type": "mapping(address => mapping(uint256 => bytes32))"
},
{
"bytes": "32",
"label": "proposalBlocks",
"offset": 0,
"slot": "23",
"slot": "24",
"type": "mapping(address => mapping(uint256 => uint64[]))"
}
]
\ No newline at end of file
......@@ -23,6 +23,8 @@ contract PreimageOracle is IPreimageOracle {
uint256 internal immutable CHALLENGE_PERIOD;
/// @notice The minimum size of a preimage that can be proposed in the large preimage path.
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.
uint256 public constant KECCAK_TREE_DEPTH = 16;
/// @notice The maximum number of keccak blocks that can fit into the merkle tree.
......@@ -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
/// challenged status.
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.
mapping(address => mapping(uint256 => bytes32)) public proposalParts;
/// @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 {
}
/// @notice Initialize a large preimage proposal. Must be called before adding any leaves.
function initLPP(uint256 _uuid, uint32 _partOffset, uint32 _claimedSize) external {
// The caller of `addLeavesLPP` must be an EOA.
function initLPP(uint256 _uuid, uint32 _partOffset, uint32 _claimedSize) external payable {
// 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();
// The part offset must be within the bounds of the claimed size + 8.
......@@ -404,9 +411,13 @@ contract PreimageOracle is IPreimageOracle {
// The claimed size must be at least `MIN_LPP_SIZE_BYTES`.
if (_claimedSize < MIN_LPP_SIZE_BYTES) revert InvalidInputSize();
// Initialize the proposal metadata.
LPPMetaData metaData = proposalMetadata[msg.sender][_uuid];
proposalMetadata[msg.sender][_uuid] = metaData.setPartOffset(_partOffset).setClaimedSize(_claimedSize);
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.
......@@ -563,6 +574,9 @@ contract PreimageOracle is IPreimageOracle {
// Mark the keccak claim as countered.
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.
......@@ -591,6 +605,9 @@ contract PreimageOracle is IPreimageOracle {
// Mark the keccak claim as countered.
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.
......@@ -644,6 +661,9 @@ contract PreimageOracle is IPreimageOracle {
preimagePartOk[finalDigest][partOffset] = true;
preimageParts[finalDigest][partOffset] = proposalParts[_claimant][_uuid];
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.
......@@ -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
function _verify(
bytes32[] calldata _proof,
......@@ -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
function _hashLeaf(Leaf memory _leaf) internal pure returns (bytes32 leaf_) {
leaf_ = keccak256(abi.encodePacked(_leaf.input, _leaf.index, _leaf.stateCommitment));
......
......@@ -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.
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