Commit 036850b8 authored by refcell.eth's avatar refcell.eth Committed by GitHub

feat(op-challenger): Preimage Oracle Contract Calls (#9032)

parent db28a833
......@@ -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"
)
......@@ -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 {
......@@ -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}
}
......@@ -6,6 +6,7 @@ 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"
......@@ -13,12 +14,7 @@ import (
)
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(),
......@@ -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
}
......@@ -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"
......@@ -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)
......@@ -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)
......
......@@ -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}
}
......
......@@ -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)
......@@ -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
......
......@@ -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"
)
......@@ -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)
})
}
......@@ -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
}
......@@ -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")
......@@ -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)
}
......@@ -23,7 +23,6 @@ type GameContract interface {
AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
DefendTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error)
StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error)
UpdateOracleTx(ctx context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error)
GetRequiredBond(ctx context.Context, position types.Position) (*big.Int, error)
}
......
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