Commit 888e8c1c authored by Adrian Sutton's avatar Adrian Sutton

op-challenger: Add global data to preimage contract

Use new contract bindings to generate transactions when adding local preimage data.
parent 23ba7576
......@@ -27,6 +27,7 @@ const (
methodAttack = "attack"
methodDefend = "defend"
methodStep = "step"
methodAddLocalData = "addLocalData"
)
type FaultDisputeGameContract struct {
......@@ -195,6 +196,16 @@ func (f *FaultDisputeGameContract) resolveCall() *batching.ContractCall {
return f.contract.Call(methodResolve)
}
func (f *FaultDisputeGameContract) AddLocalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
call := f.contract.Call(
methodAddLocalData,
data.GetIdent(),
new(big.Int).SetUint64(data.LocalContext),
new(big.Int).SetUint64(uint64(data.OracleOffset)),
)
return call.ToTxCandidate()
}
func (f *FaultDisputeGameContract) decodeClaim(result *batching.CallResult, contractIndex int) types.Claim {
parentIndex := result.GetUint32(0)
countered := result.GetBool(1)
......
......@@ -234,6 +234,25 @@ func TestStepTx(t *testing.T) {
stubRpc.VerifyTxCandidate(tx)
}
func TestAddLocalDataTx(t *testing.T) {
stubRpc, game := setup(t)
data := &faultTypes.PreimageOracleData{
IsLocal: true,
LocalContext: 2,
OracleKey: common.Hash{0xbc}.Bytes(),
OracleData: []byte{1, 2, 3, 4, 5, 6, 7},
OracleOffset: 16,
}
stubRpc.SetResponse(methodAddLocalData, batching.BlockLatest, []interface{}{
data.GetIdent(),
new(big.Int).SetUint64(data.LocalContext),
new(big.Int).SetUint64(uint64(data.OracleOffset)),
}, nil)
tx, err := game.AddLocalDataTx(data)
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
}
func expectGetClaim(stubRpc *batchingTest.AbiBasedRpc, claim faultTypes.Claim) {
stubRpc.SetResponse(
methodClaim,
......
......@@ -43,7 +43,7 @@ func RegisterGameTypes(
if err != nil {
return nil, nil, nil, fmt.Errorf("create cannon trace provider: %w", err)
}
updater, err := cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client)
updater, err := cannon.NewOracleUpdater(ctx, logger, txMgr, addr, client, contract)
if err != nil {
return nil, nil, nil, fmt.Errorf("failed to create the cannon updater: %w", err)
}
......
......@@ -16,14 +16,17 @@ import (
"github.com/ethereum/go-ethereum/log"
)
type GameContract interface {
AddLocalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error)
}
// cannonUpdater is a [types.OracleUpdater] that exposes a method
// to update onchain cannon oracles with required data.
type cannonUpdater struct {
log log.Logger
txMgr txmgr.TxManager
fdgAbi abi.ABI
fdgAddr common.Address
gameContract GameContract
preimageOracleAbi abi.ABI
preimageOracleAddr common.Address
......@@ -36,6 +39,7 @@ func NewOracleUpdater(
txMgr txmgr.TxManager,
fdgAddr common.Address,
client bind.ContractCaller,
gameContract GameContract,
) (*cannonUpdater, error) {
gameCaller, err := bindings.NewFaultDisputeGameCaller(fdgAddr, client)
if err != nil {
......@@ -54,20 +58,16 @@ func NewOracleUpdater(
if err != nil {
return nil, fmt.Errorf("failed to load pre-image oracle address from game %v: %w", fdgAddr, err)
}
return NewOracleUpdaterWithOracle(logger, txMgr, fdgAddr, oracleAddr)
return NewOracleUpdaterWithOracle(logger, txMgr, gameContract, oracleAddr)
}
// NewOracleUpdaterWithOracle returns a new updater using a specified pre-image oracle address.
func NewOracleUpdaterWithOracle(
logger log.Logger,
txMgr txmgr.TxManager,
fdgAddr common.Address,
gameContract GameContract,
preimageOracleAddr common.Address,
) (*cannonUpdater, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil {
return nil, err
}
preimageOracleAbi, err := bindings.PreimageOracleMetaData.GetAbi()
if err != nil {
return nil, err
......@@ -77,8 +77,7 @@ func NewOracleUpdaterWithOracle(
log: logger,
txMgr: txMgr,
fdgAbi: *fdgAbi,
fdgAddr: fdgAddr,
gameContract: gameContract,
preimageOracleAbi: *preimageOracleAbi,
preimageOracleAddr: preimageOracleAddr,
......@@ -95,11 +94,11 @@ func (u *cannonUpdater) UpdateOracle(ctx context.Context, data *types.PreimageOr
// sendLocalOracleData sends the local oracle data to the [txmgr].
func (u *cannonUpdater) sendLocalOracleData(ctx context.Context, data *types.PreimageOracleData) error {
txData, err := u.BuildLocalOracleData(data)
tx, err := u.gameContract.AddLocalDataTx(data)
if err != nil {
return fmt.Errorf("local oracle tx data build: %w", err)
}
return u.sendTxAndWait(ctx, u.fdgAddr, txData)
return u.sendTxAndWait(ctx, tx)
}
// sendGlobalOracleData sends the global oracle data to the [txmgr].
......@@ -108,19 +107,11 @@ func (u *cannonUpdater) sendGlobalOracleData(ctx context.Context, data *types.Pr
if err != nil {
return fmt.Errorf("global oracle tx data build: %w", err)
}
return u.sendTxAndWait(ctx, u.fdgAddr, txData)
}
// BuildLocalOracleData takes the local preimage key and data
// and creates tx data to load the key, data pair into the
// PreimageOracle contract from the FaultDisputeGame contract call.
func (u *cannonUpdater) BuildLocalOracleData(data *types.PreimageOracleData) ([]byte, error) {
return u.fdgAbi.Pack(
"addLocalData",
data.GetIdent(),
big.NewInt(int64(data.LocalContext)),
big.NewInt(int64(data.OracleOffset)),
)
return u.sendTxAndWait(ctx, txmgr.TxCandidate{
To: &u.preimageOracleAddr,
TxData: txData,
GasLimit: 0,
})
}
// BuildGlobalOracleData takes the global preimage key and data
......@@ -136,12 +127,8 @@ func (u *cannonUpdater) BuildGlobalOracleData(data *types.PreimageOracleData) ([
// sendTxAndWait sends a transaction through the [txmgr] and waits for a receipt.
// This sets the tx GasLimit to 0, performing gas estimation online through the [txmgr].
func (u *cannonUpdater) sendTxAndWait(ctx context.Context, addr common.Address, txData []byte) error {
receipt, err := u.txMgr.Send(ctx, txmgr.TxCandidate{
To: &addr,
TxData: txData,
GasLimit: 0,
})
func (u *cannonUpdater) sendTxAndWait(ctx context.Context, tx txmgr.TxCandidate) error {
receipt, err := u.txMgr.Send(ctx, tx)
if err != nil {
return err
}
......
......@@ -5,7 +5,6 @@ import (
"errors"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
......@@ -20,29 +19,29 @@ import (
)
var (
mockFdgAddress = common.HexToAddress("0x1234")
mockPreimageOracleAddress = common.HexToAddress("0x12345")
mockSendError = errors.New("mock send error")
)
type mockTxManager struct {
from common.Address
sends int
sent []txmgr.TxCandidate
failedSends int
sendFails bool
}
func (m *mockTxManager) Send(ctx context.Context, candidate txmgr.TxCandidate) (*ethtypes.Receipt, error) {
m.sent = append(m.sent, candidate)
if m.sendFails {
m.failedSends++
return nil, mockSendError
}
m.sends++
return ethtypes.NewReceipt(
[]byte{},
false,
0,
), nil
return &ethtypes.Receipt{
Type: ethtypes.LegacyTxType,
PostState: []byte{},
CumulativeGasUsed: 0,
Status: ethtypes.ReceiptStatusSuccessful,
}, nil
}
func (m *mockTxManager) BlockNumber(ctx context.Context) (uint64, error) {
......@@ -53,70 +52,80 @@ func (m *mockTxManager) From() common.Address {
return m.from
}
func newTestCannonUpdater(t *testing.T, sendFails bool) (*cannonUpdater, *mockTxManager) {
func newTestCannonUpdater(t *testing.T, sendFails bool) (*cannonUpdater, *mockTxManager, *mockGameContract) {
logger := testlog.Logger(t, log.LvlInfo)
txMgr := &mockTxManager{
from: mockFdgAddress,
from: common.HexToAddress("0x1234"),
sendFails: sendFails,
}
updater, err := NewOracleUpdaterWithOracle(logger, txMgr, mockFdgAddress, mockPreimageOracleAddress)
gameContract := &mockGameContract{}
updater, err := NewOracleUpdaterWithOracle(logger, txMgr, gameContract, mockPreimageOracleAddress)
require.NoError(t, err)
return updater, txMgr
return updater, txMgr, gameContract
}
// TestCannonUpdater_UpdateOracle tests the [cannonUpdater]
// UpdateOracle function.
func TestCannonUpdater_UpdateOracle(t *testing.T) {
t.Run("succeeds", func(t *testing.T) {
updater, mockTxMgr := newTestCannonUpdater(t, false)
t.Run("local_succeeds", func(t *testing.T) {
updater, mockTxMgr, gameContract := newTestCannonUpdater(t, false)
gameContract.tx = txmgr.TxCandidate{
TxData: []byte{5, 6, 7, 8},
}
require.NoError(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
IsLocal: true,
LocalContext: 3,
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
}))
require.Equal(t, 1, mockTxMgr.sends)
require.Len(t, mockTxMgr.sent, 1)
require.Equal(t, gameContract.tx, mockTxMgr.sent[0])
})
t.Run("send fails", func(t *testing.T) {
updater, mockTxMgr := newTestCannonUpdater(t, true)
t.Run("local_fails", func(t *testing.T) {
updater, mockTxMgr, gameContract := newTestCannonUpdater(t, true)
gameContract.tx = txmgr.TxCandidate{
TxData: []byte{5, 6, 7, 8},
}
require.Error(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
IsLocal: true,
LocalContext: 3,
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
}))
require.Len(t, mockTxMgr.sent, 1)
require.Equal(t, gameContract.tx, mockTxMgr.sent[0])
require.Equal(t, 1, mockTxMgr.failedSends)
})
}
// TestCannonUpdater_BuildLocalOracleData tests the [cannonUpdater]
// builds a valid tx candidate for a local oracle update.
func TestCannonUpdater_BuildLocalOracleData(t *testing.T) {
updater, _ := newTestCannonUpdater(t, false)
oracleData := &types.PreimageOracleData{
OracleKey: common.Hex2Bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
t.Run("global_succeeds", func(t *testing.T) {
updater, mockTxMgr, _ := newTestCannonUpdater(t, false)
require.NoError(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
IsLocal: false,
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
OracleOffset: 7,
}
txData, err := updater.BuildLocalOracleData(oracleData)
require.NoError(t, err)
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
require.NoError(t, err)
addLocalDataBytes4 := fdgAbi.Methods["addLocalData"].ID[:4]
// Pack the tx data manually.
var expected []byte
expected = append(expected, addLocalDataBytes4...)
expected = append(expected, common.Hex2Bytes("00aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")...)
expected = append(expected, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")...)
expected = append(expected, common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000007")...)
}))
require.Len(t, mockTxMgr.sent, 1)
require.Equal(t, mockPreimageOracleAddress, *mockTxMgr.sent[0].To)
})
require.Equal(t, expected, txData)
t.Run("local_fails", func(t *testing.T) {
updater, mockTxMgr, _ := newTestCannonUpdater(t, true)
require.Error(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{
IsLocal: false,
OracleKey: common.Hash{0xaa}.Bytes(),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
}))
require.Len(t, mockTxMgr.sent, 1)
require.Equal(t, mockPreimageOracleAddress, *mockTxMgr.sent[0].To)
require.Equal(t, 1, mockTxMgr.failedSends)
})
}
// TestCannonUpdater_BuildGlobalOracleData tests the [cannonUpdater]
// builds a valid tx candidate for a global oracle update.
func TestCannonUpdater_BuildGlobalOracleData(t *testing.T) {
updater, _ := newTestCannonUpdater(t, false)
updater, _, _ := newTestCannonUpdater(t, false)
oracleData := &types.PreimageOracleData{
OracleKey: common.Hex2Bytes("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"),
OracleData: common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc"),
......@@ -138,3 +147,12 @@ func TestCannonUpdater_BuildGlobalOracleData(t *testing.T) {
require.Equal(t, expected, txData)
}
type mockGameContract struct {
tx txmgr.TxCandidate
err error
}
func (m *mockGameContract) AddLocalDataTx(data *types.PreimageOracleData) (txmgr.TxCandidate, error) {
return m.tx, m.err
}
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