Skip to content

Commit

Permalink
feat(op-challenger): Preimage Oracle Contract Calls (ethereum-optimis…
Browse files Browse the repository at this point in the history
  • Loading branch information
refcell authored Jan 18, 2024
1 parent db28a83 commit 036850b
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 20 deletions.
94 changes: 94 additions & 0 deletions op-challenger/game/fault/contracts/oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ import (

"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)

const (
methodInitLPP = "initLPP"
methodAddLeavesLPP = "addLeavesLPP"
methodSqueezeLPP = "squeezeLPP"
methodLoadKeccak256PreimagePart = "loadKeccak256PreimagePart"
)

Expand All @@ -22,6 +26,44 @@ type PreimageOracleContract struct {
contract *batching.BoundContract
}

// Leaf is the keccak state matrix added to the large preimage merkle tree.
type Leaf struct {
// Input is the data absorbed for the block, exactly 136 bytes
Input [136]byte
// Index of the block in the absorption process
Index *big.Int
// StateCommitment is the hash of the internal state after absorbing the input.
StateCommitment common.Hash
}

// toPreimageOracleLeaf converts a Leaf to the contract [bindings.PreimageOracleLeaf] type.
func (l Leaf) toPreimageOracleLeaf() bindings.PreimageOracleLeaf {
commitment := ([32]byte)(l.StateCommitment.Bytes())
return bindings.PreimageOracleLeaf{
Input: l.Input[:],
Index: l.Index,
StateCommitment: commitment,
}
}

// MerkleProof is a place holder for the actual type we use for merkle proofs
// TODO(client-pod#481): Move this somewhere better and add useful functionality
type MerkleProof [][]byte

// toSized converts a [][]byte to a [][32]byte
func (p MerkleProof) toSized() [][32]byte {
var sized [][32]byte
for _, proof := range p {
// SAFETY: if the proof is less than 32 bytes, it will be padded with 0s
if len(proof) < 32 {
proof = append(proof, make([]byte, 32-len(proof))...)
}
// SAFETY: the proof is 32 or more bytes here, so it will be truncated to 32 bytes
sized = append(sized, [32]byte(proof[:32]))
}
return sized
}

func NewPreimageOracleContract(addr common.Address, caller *batching.MultiCaller) (*PreimageOracleContract, error) {
mipsAbi, err := bindings.PreimageOracleMetaData.GetAbi()
if err != nil {
Expand All @@ -43,3 +85,55 @@ func (c *PreimageOracleContract) AddGlobalDataTx(data *types.PreimageOracleData)
call := c.contract.Call(methodLoadKeccak256PreimagePart, new(big.Int).SetUint64(uint64(data.OracleOffset)), data.GetPreimageWithoutSize())
return call.ToTxCandidate()
}

func (c *PreimageOracleContract) InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error) {
call := c.contract.Call(methodInitLPP, uuid, partOffset, claimedSize)
return call.ToTxCandidate()
}

func (c *PreimageOracleContract) AddLeaves(uuid *big.Int, leaves []Leaf, finalize bool) ([]txmgr.TxCandidate, error) {
var txs []txmgr.TxCandidate
for _, leaf := range leaves {
commitments := [][32]byte{([32]byte)(leaf.StateCommitment.Bytes())}
call := c.contract.Call(methodAddLeavesLPP, uuid, leaf.Input[:], commitments, finalize)
tx, err := call.ToTxCandidate()
if err != nil {
return nil, err
}
txs = append(txs, tx)
}
return txs, nil
}

func (c *PreimageOracleContract) Squeeze(
claimant common.Address,
uuid *big.Int,
stateMatrix *matrix.StateMatrix,
preState Leaf,
preStateProof MerkleProof,
postState Leaf,
postStateProof MerkleProof,
) (txmgr.TxCandidate, error) {
call := c.contract.Call(
methodSqueezeLPP,
claimant,
uuid,
abiEncodeStateMatrix(stateMatrix),
preState.toPreimageOracleLeaf(),
preStateProof.toSized(),
postState.toPreimageOracleLeaf(),
postStateProof.toSized(),
)
return call.ToTxCandidate()
}

// abiEncodeStateMatrix encodes the state matrix for the contract ABI
func abiEncodeStateMatrix(stateMatrix *matrix.StateMatrix) bindings.LibKeccakStateMatrix {
packedState := stateMatrix.PackState()
var stateSlice = new([25]uint64)
// SAFETY: a maximum of 25 * 8 bytes will be read from packedState and written to stateSlice
for i := 0; i < min(len(packedState), 25*8); i += 8 {
stateSlice[i/8] = new(big.Int).SetBytes(packedState[i : i+8]).Uint64()
}
return bindings.LibKeccakStateMatrix{State: *stateSlice}
}
94 changes: 87 additions & 7 deletions op-challenger/game/fault/contracts/oracle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,15 @@ import (

"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)

func TestPreimageOracleContract_LoadKeccak256(t *testing.T) {
oracleAbi, err := bindings.PreimageOracleMetaData.GetAbi()
require.NoError(t, err)

stubRpc := batchingTest.NewAbiBasedRpc(t, oracleAddr, oracleAbi)
oracleContract, err := NewPreimageOracleContract(oracleAddr, batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize))
require.NoError(t, err)
stubRpc, oracle := setupPreimageOracleTest(t)

data := &types.PreimageOracleData{
OracleKey: common.Hash{0xcc}.Bytes(),
Expand All @@ -30,7 +26,91 @@ func TestPreimageOracleContract_LoadKeccak256(t *testing.T) {
data.GetPreimageWithoutSize(),
}, nil)

tx, err := oracleContract.AddGlobalDataTx(data)
tx, err := oracle.AddGlobalDataTx(data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}

func TestPreimageOracleContract_InitLargePreimage(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t)

uuid := big.NewInt(123)
partOffset := uint32(1)
claimedSize := uint32(2)
stubRpc.SetResponse(oracleAddr, methodInitLPP, batching.BlockLatest, []interface{}{
uuid,
partOffset,
claimedSize,
}, nil)

tx, err := oracle.InitLargePreimage(uuid, partOffset, claimedSize)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}

func TestPreimageOracleContract_AddLeaves(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t)

uuid := big.NewInt(123)
leaves := []Leaf{{
Input: [136]byte{0x12},
Index: big.NewInt(123),
StateCommitment: common.Hash{0x34},
}}
finalize := true
stubRpc.SetResponse(oracleAddr, methodAddLeavesLPP, batching.BlockLatest, []interface{}{
uuid,
leaves[0].Input[:],
[][32]byte{([32]byte)(leaves[0].StateCommitment.Bytes())},
finalize,
}, nil)

txs, err := oracle.AddLeaves(uuid, leaves, finalize)
require.NoError(t, err)
require.Len(t, txs, 1)
stubRpc.VerifyTxCandidate(txs[0])
}

func TestPreimageOracleContract_Squeeze(t *testing.T) {
stubRpc, oracle := setupPreimageOracleTest(t)

claimant := common.Address{0x12}
uuid := big.NewInt(123)
stateMatrix := matrix.NewStateMatrix()
preState := Leaf{
Input: [136]byte{0x12},
Index: big.NewInt(123),
StateCommitment: common.Hash{0x34},
}
preStateProof := MerkleProof{{0x34}}
postState := Leaf{
Input: [136]byte{0x34},
Index: big.NewInt(456),
StateCommitment: common.Hash{0x56},
}
postStateProof := MerkleProof{{0x56}}
stubRpc.SetResponse(oracleAddr, methodSqueezeLPP, batching.BlockLatest, []interface{}{
claimant,
uuid,
abiEncodeStateMatrix(stateMatrix),
preState.toPreimageOracleLeaf(),
preStateProof.toSized(),
postState.toPreimageOracleLeaf(),
postStateProof.toSized(),
}, nil)

tx, err := oracle.Squeeze(claimant, uuid, stateMatrix, preState, preStateProof, postState, postStateProof)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}

func setupPreimageOracleTest(t *testing.T) (*batchingTest.AbiBasedRpc, *PreimageOracleContract) {
oracleAbi, err := bindings.PreimageOracleMetaData.GetAbi()
require.NoError(t, err)

stubRpc := batchingTest.NewAbiBasedRpc(t, oracleAddr, oracleAbi)
oracleContract, err := NewPreimageOracleContract(oracleAddr, batching.NewMultiCaller(stubRpc, batching.DefaultBatchSize))
require.NoError(t, err)

return stubRpc, oracleContract
}
9 changes: 8 additions & 1 deletion op-challenger/game/fault/player.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"

"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/preimages"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/responder"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
Expand All @@ -30,11 +31,13 @@ type GamePlayer struct {
}

type GameContract interface {
preimages.PreimageGameContract
responder.GameContract
GameInfo
ClaimLoader
GetStatus(ctx context.Context) (gameTypes.GameStatus, error)
GetMaxGameDepth(ctx context.Context) (types.Depth, error)
GetOracle(ctx context.Context) (*contracts.PreimageOracleContract, error)
}

type resourceCreator func(ctx context.Context, logger log.Logger, gameDepth types.Depth, dir string) (types.TraceAccessor, error)
Expand Down Expand Up @@ -81,8 +84,12 @@ func NewGamePlayer(
return nil, fmt.Errorf("failed to create trace accessor: %w", err)
}

oracle, err := loader.GetOracle(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load oracle: %w", err)
}
direct := preimages.NewDirectPreimageUploader(logger, txMgr, loader)
large := preimages.NewLargePreimageUploader(logger, txMgr, loader)
large := preimages.NewLargePreimageUploader(logger, txMgr, oracle)
uploader := preimages.NewSplitPreimageUploader(direct, large)

responder, err := responder.NewFaultResponder(logger, txMgr, loader, uploader)
Expand Down
8 changes: 6 additions & 2 deletions op-challenger/game/fault/preimages/direct.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,20 @@ import (

var _ PreimageUploader = (*DirectPreimageUploader)(nil)

type PreimageGameContract interface {
UpdateOracleTx(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error)
}

// DirectPreimageUploader uploads the provided [types.PreimageOracleData]
// directly to the PreimageOracle contract in a single transaction.
type DirectPreimageUploader struct {
log log.Logger

txMgr txmgr.TxManager
contract PreimageOracleContract
contract PreimageGameContract
}

func NewDirectPreimageUploader(logger log.Logger, txMgr txmgr.TxManager, contract PreimageOracleContract) *DirectPreimageUploader {
func NewDirectPreimageUploader(logger log.Logger, txMgr txmgr.TxManager, contract PreimageGameContract) *DirectPreimageUploader {
return &DirectPreimageUploader{logger, txMgr, contract}
}

Expand Down
14 changes: 7 additions & 7 deletions op-challenger/game/fault/preimages/direct_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ var (
func TestDirectPreimageUploader_UploadPreimage(t *testing.T) {
t.Run("UpdateOracleTxFails", func(t *testing.T) {
oracle, txMgr, contract := newTestDirectPreimageUploader(t)
contract.uploadFails = true
contract.updateFails = true
err := oracle.UploadPreimage(context.Background(), 0, &types.PreimageOracleData{})
require.ErrorIs(t, err, mockUpdateOracleTxError)
require.Equal(t, 1, contract.updates)
Expand Down Expand Up @@ -77,21 +77,21 @@ func TestDirectPreimageUploader_SendTxAndWait(t *testing.T) {
})
}

func newTestDirectPreimageUploader(t *testing.T) (*DirectPreimageUploader, *mockTxMgr, *mockPreimageOracleContract) {
func newTestDirectPreimageUploader(t *testing.T) (*DirectPreimageUploader, *mockTxMgr, *mockPreimageGameContract) {
logger := testlog.Logger(t, log.LvlError)
txMgr := &mockTxMgr{}
contract := &mockPreimageOracleContract{}
contract := &mockPreimageGameContract{}
return NewDirectPreimageUploader(logger, txMgr, contract), txMgr, contract
}

type mockPreimageOracleContract struct {
type mockPreimageGameContract struct {
updates int
uploadFails bool
updateFails bool
}

func (s *mockPreimageOracleContract) UpdateOracleTx(_ context.Context, _ uint64, _ *types.PreimageOracleData) (txmgr.TxCandidate, error) {
func (s *mockPreimageGameContract) UpdateOracleTx(_ context.Context, _ uint64, _ *types.PreimageOracleData) (txmgr.TxCandidate, error) {
s.updates++
if s.uploadFails {
if s.updateFails {
return txmgr.TxCandidate{}, mockUpdateOracleTxError
}
return txmgr.TxCandidate{}, nil
Expand Down
19 changes: 18 additions & 1 deletion op-challenger/game/fault/preimages/large_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@ package preimages

import (
"context"
"math/big"
"testing"

"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
Expand All @@ -14,7 +19,7 @@ func TestLargePreimageUploader_UploadPreimage(t *testing.T) {
t.Run("Success", func(t *testing.T) {
oracle, _, _ := newTestLargePreimageUploader(t)
err := oracle.UploadPreimage(context.Background(), 0, &types.PreimageOracleData{})
// todo(proofs#467): fix this to not error. See LargePreimageUploader.UploadPreimage.
// TODO(proofs#467): fix this to not error. See LargePreimageUploader.UploadPreimage.
require.ErrorIs(t, err, errNotSupported)
})
}
Expand All @@ -25,3 +30,15 @@ func newTestLargePreimageUploader(t *testing.T) (*LargePreimageUploader, *mockTx
contract := &mockPreimageOracleContract{}
return NewLargePreimageUploader(logger, txMgr, contract), txMgr, contract
}

type mockPreimageOracleContract struct{}

func (s *mockPreimageOracleContract) InitLargePreimage(_ *big.Int, _ uint32, _ uint32) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, nil
}
func (s *mockPreimageOracleContract) AddLeaves(_ *big.Int, _ []contracts.Leaf, _ bool) ([]txmgr.TxCandidate, error) {
return []txmgr.TxCandidate{}, nil
}
func (s *mockPreimageOracleContract) Squeeze(_ common.Address, _ *big.Int, _ *matrix.StateMatrix, _ contracts.Leaf, _ contracts.MerkleProof, _ contracts.Leaf, _ contracts.MerkleProof) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, nil
}
8 changes: 7 additions & 1 deletion op-challenger/game/fault/preimages/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ package preimages
import (
"context"
"fmt"
"math/big"

"github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/keccak/matrix"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
)

var ErrNilPreimageData = fmt.Errorf("cannot upload nil preimage data")
Expand All @@ -18,5 +22,7 @@ type PreimageUploader interface {

// PreimageOracleContract is the interface for interacting with the PreimageOracle contract.
type PreimageOracleContract interface {
UpdateOracleTx(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error)
InitLargePreimage(uuid *big.Int, partOffset uint32, claimedSize uint32) (txmgr.TxCandidate, error)
AddLeaves(uuid *big.Int, leaves []contracts.Leaf, finalize bool) ([]txmgr.TxCandidate, error)
Squeeze(claimant common.Address, uuid *big.Int, stateMatrix *matrix.StateMatrix, preState contracts.Leaf, preStateProof contracts.MerkleProof, postState contracts.Leaf, postStateProof contracts.MerkleProof) (txmgr.TxCandidate, error)
}
Loading

0 comments on commit 036850b

Please sign in to comment.