Commit 13c710c7 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #6183 from ethereum-optimism/refcell/challengertxbuilder

feat(op-challenger): FaultResponder
parents 69ba4ea1 174458d5
package fault
import (
"context"
"math/big"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
)
// faultResponder implements the [Responder] interface to send onchain transactions.
type faultResponder struct {
log log.Logger
txMgr txmgr.TxManager
fdgAddr common.Address
fdgAbi *abi.ABI
}
// NewFaultResponder returns a new [faultResponder].
func NewFaultResponder(logger log.Logger, txManagr txmgr.TxManager, fdgAddr common.Address) (*faultResponder, error) {
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
if err != nil {
return nil, err
}
return &faultResponder{
log: logger,
txMgr: txManagr,
fdgAddr: fdgAddr,
fdgAbi: fdgAbi,
}, nil
}
// buildFaultDefendData creates the transaction data for the Defend function.
func (r *faultResponder) buildFaultDefendData(parentContractIndex int, pivot [32]byte) ([]byte, error) {
return r.fdgAbi.Pack(
"defend",
big.NewInt(int64(parentContractIndex)),
pivot,
)
}
// buildFaultAttackData creates the transaction data for the Attack function.
func (r *faultResponder) buildFaultAttackData(parentContractIndex int, pivot [32]byte) ([]byte, error) {
return r.fdgAbi.Pack(
"attack",
big.NewInt(int64(parentContractIndex)),
pivot,
)
}
// BuildTx builds the transaction for the [faultResponder].
func (r *faultResponder) BuildTx(ctx context.Context, response Claim) ([]byte, error) {
if response.DefendsParent() {
txData, err := r.buildFaultDefendData(response.ParentContractIndex, response.ValueBytes())
if err != nil {
return nil, err
}
return txData, nil
} else {
txData, err := r.buildFaultAttackData(response.ParentContractIndex, response.ValueBytes())
if err != nil {
return nil, err
}
return txData, nil
}
}
// Respond takes a [Claim] and executes the response action.
func (r *faultResponder) Respond(ctx context.Context, response Claim) error {
// Build the transaction data.
txData, err := r.BuildTx(ctx, response)
if err != nil {
return err
}
// Send the transaction through the [txmgr].
receipt, err := r.txMgr.Send(ctx, txmgr.TxCandidate{
To: &r.fdgAddr,
TxData: txData,
// Setting GasLimit to 0 performs gas estimation online through the [txmgr].
GasLimit: 0,
})
if err != nil {
return err
}
if receipt.Status == types.ReceiptStatusFailed {
r.log.Error("responder tx successfully published but reverted", "tx_hash", receipt.TxHash)
} else {
r.log.Info("responder tx successfully published", "tx_hash", receipt.TxHash)
}
return nil
}
package fault
import (
"context"
"errors"
"math/big"
"testing"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
var (
mockFdgAddress = common.HexToAddress("0x1234")
mockSendError = errors.New("mock send error")
)
type mockTxManager struct {
from common.Address
sends int
sendFails bool
}
func (m *mockTxManager) Send(ctx context.Context, candidate txmgr.TxCandidate) (*types.Receipt, error) {
if m.sendFails {
return nil, mockSendError
}
m.sends++
return types.NewReceipt(
[]byte{},
false,
0,
), nil
}
func (m *mockTxManager) From() common.Address {
return m.from
}
func newTestFaultResponder(t *testing.T, sendFails bool) (*faultResponder, *mockTxManager) {
log := testlog.Logger(t, log.LvlError)
mockTxMgr := &mockTxManager{}
mockTxMgr.sendFails = sendFails
responder, err := NewFaultResponder(log, mockTxMgr, mockFdgAddress)
require.NoError(t, err)
return responder, mockTxMgr
}
// TestResponder_Respond_SendFails tests the [Responder.Respond] method
// bubbles up the error returned by the [txmgr.Send] method.
func TestResponder_Respond_SendFails(t *testing.T) {
responder, mockTxMgr := newTestFaultResponder(t, true)
err := responder.Respond(context.Background(), Claim{
ClaimData: ClaimData{
Value: common.Hash{0x01},
Position: NewPositionFromGIndex(2),
},
Parent: ClaimData{
Value: common.Hash{0x02},
Position: NewPositionFromGIndex(1),
},
ContractIndex: 0,
ParentContractIndex: 0,
})
require.ErrorIs(t, err, mockSendError)
require.Equal(t, 0, mockTxMgr.sends)
}
// TestResponder_Respond_Success tests the [Responder.Respond] method
// succeeds when the tx candidate is successfully sent through the txmgr.
func TestResponder_Respond_Success(t *testing.T) {
responder, mockTxMgr := newTestFaultResponder(t, false)
err := responder.Respond(context.Background(), Claim{
ClaimData: ClaimData{
Value: common.Hash{0x01},
Position: NewPositionFromGIndex(2),
},
Parent: ClaimData{
Value: common.Hash{0x02},
Position: NewPositionFromGIndex(1),
},
ContractIndex: 0,
ParentContractIndex: 0,
})
require.NoError(t, err)
require.Equal(t, 1, mockTxMgr.sends)
}
// TestResponder_BuildTx_Attack tests the [Responder.BuildTx] method
// returns a tx candidate with the correct data for an attack tx.
func TestResponder_BuildTx_Attack(t *testing.T) {
responder, _ := newTestFaultResponder(t, false)
responseClaim := Claim{
ClaimData: ClaimData{
Value: common.Hash{0x01},
Position: NewPositionFromGIndex(2),
},
Parent: ClaimData{
Value: common.Hash{0x02},
Position: NewPositionFromGIndex(1),
},
ContractIndex: 0,
ParentContractIndex: 7,
}
tx, err := responder.BuildTx(context.Background(), responseClaim)
require.NoError(t, err)
// Pack the tx data manually.
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
require.NoError(t, err)
expected, err := fdgAbi.Pack(
"attack",
big.NewInt(int64(7)),
responseClaim.ValueBytes(),
)
require.NoError(t, err)
require.Equal(t, expected, tx)
}
// TestResponder_BuildTx_Defend tests the [Responder.BuildTx] method
// returns a tx candidate with the correct data for a defend tx.
func TestResponder_BuildTx_Defend(t *testing.T) {
responder, _ := newTestFaultResponder(t, false)
responseClaim := Claim{
ClaimData: ClaimData{
Value: common.Hash{0x01},
Position: NewPositionFromGIndex(3),
},
Parent: ClaimData{
Value: common.Hash{0x02},
Position: NewPositionFromGIndex(6),
},
ContractIndex: 0,
ParentContractIndex: 7,
}
tx, err := responder.BuildTx(context.Background(), responseClaim)
require.NoError(t, err)
// Pack the tx data manually.
fdgAbi, err := bindings.FaultDisputeGameMetaData.GetAbi()
require.NoError(t, err)
expected, err := fdgAbi.Pack(
"defend",
big.NewInt(int64(7)),
responseClaim.ValueBytes(),
)
require.NoError(t, err)
require.Equal(t, expected, tx)
}
...@@ -25,6 +25,13 @@ type ClaimData struct { ...@@ -25,6 +25,13 @@ type ClaimData struct {
Position Position
} }
func (c *ClaimData) ValueBytes() [32]byte {
responseBytes := c.Value.Bytes()
var responseArr [32]byte
copy(responseArr[:], responseBytes[:32])
return responseArr
}
// Claim extends ClaimData with information about the relationship between two claims. // Claim extends ClaimData with information about the relationship between two claims.
// It uses ClaimData to break cyclicity without using pointers. // It uses ClaimData to break cyclicity without using pointers.
// If the position of the game is Depth 0, IndexAtDepth 0 it is the root claim // If the position of the game is Depth 0, IndexAtDepth 0 it is the root claim
......
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