Skip to content

Commit

Permalink
feat(ctb): PreimageOracle large preimage proposal bonds (ethereum-o…
Browse files Browse the repository at this point in the history
…ptimism#9570)

* Add bonds to `PreimageOracle`

* Update op-challenger/game/fault/contracts/oracle_test.go

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update packages/contracts-bedrock/src/cannon/PreimageOracle.sol

Co-authored-by: refcell <[email protected]>

---------

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: refcell <[email protected]>
  • Loading branch information
3 people authored Feb 16, 2024
1 parent 1e62a0b commit 4354aa8
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 38 deletions.
72 changes: 67 additions & 5 deletions op-bindings/bindings/preimageoracle.go

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions op-bindings/bindings/preimageoracle_more.go

Large diffs are not rendered by default.

17 changes: 17 additions & 0 deletions op-challenger/game/fault/contracts/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const (
methodChallengeLPP = "challengeLPP"
methodChallengePeriod = "challengePeriod"
methodGetTreeRootLPP = "getTreeRootLPP"
methodMinBondSizeLPP = "MIN_BOND_SIZE"
)

var (
Expand All @@ -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.
Expand Down Expand Up @@ -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),
Expand Down
18 changes: 17 additions & 1 deletion op-challenger/game/fault/contracts/oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -332,7 +349,6 @@ func setupPreimageOracleTestWithProposals(t *testing.T, block batching.Block) (*
}

return stubRpc, oracle, proposals

}

func setupPreimageOracleTest(t *testing.T) (*batchingTest.AbiBasedRpc, *PreimageOracleContract) {
Expand Down
5 changes: 5 additions & 0 deletions op-challenger/game/fault/preimages/large.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand Down
5 changes: 5 additions & 0 deletions op-challenger/game/fault/preimages/large_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions op-challenger/game/fault/preimages/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
6 changes: 6 additions & 0 deletions op-e2e/e2eutils/disputegame/preimage/preimage_helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -111,6 +116,7 @@ func (h *Helper) UploadLargePreimage(ctx context.Context, dataSize int, modifier
break
}
}

return types.LargePreimageIdent{
Claimant: h.opts.From,
UUID: uuid,
Expand Down
49 changes: 48 additions & 1 deletion packages/contracts-bedrock/snapshots/abi/PreimageOracle.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,19 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "MIN_BOND_SIZE",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
Expand Down Expand Up @@ -265,7 +278,7 @@
],
"name": "initLPP",
"outputs": [],
"stateMutability": "nonpayable",
"stateMutability": "payable",
"type": "function"
},
{
Expand Down Expand Up @@ -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": [
{
Expand Down Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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[]))"
}
]
35 changes: 32 additions & 3 deletions packages/contracts-bedrock/src/cannon/PreimageOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Loading

0 comments on commit 4354aa8

Please sign in to comment.