Skip to content

Commit

Permalink
feat(MSFDG): add getSubValues
Browse files Browse the repository at this point in the history
  • Loading branch information
dajuguan committed Nov 3, 2024
1 parent 9aef5fb commit ad007d7
Show file tree
Hide file tree
Showing 16 changed files with 628 additions and 2 deletions.
3 changes: 2 additions & 1 deletion op-challenger2/game/fault/agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type Responder interface {

type ClaimLoader interface {
GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error)
GetAllClaimsWithSubValues(ctx context.Context) ([]types.Claim, error)
IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error)
GetMaxGameDepth(ctx context.Context) (types.Depth, error)
GetSplitDepth(ctx context.Context) (types.Depth, error)
Expand Down Expand Up @@ -235,7 +236,7 @@ func (a *Agent) resolveClaims(ctx context.Context) error {

// newGameFromContracts initializes a new game state from the state in the contract
func (a *Agent) newGameFromContracts(ctx context.Context) (types.Game, error) {
claims, err := a.loader.GetAllClaims(ctx, rpcblock.Latest)
claims, err := a.loader.GetAllClaimsWithSubValues(ctx)
if err != nil {
return nil, fmt.Errorf("failed to fetch claims: %w", err)
}
Expand Down
8 changes: 8 additions & 0 deletions op-challenger2/game/fault/agent_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,14 @@ func (s *stubClaimLoader) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]t
return s.claims, nil
}

func (s *stubClaimLoader) GetAllClaimsWithSubValues(_ context.Context) ([]types.Claim, error) {
s.callCount++
if s.callCount > s.maxLoads && s.maxLoads != 0 {
return []types.Claim{}, nil
}
return s.claims, nil
}

func (s *stubClaimLoader) GetMaxGameDepth(_ context.Context) (types.Depth, error) {
return s.maxDepth, nil
}
Expand Down
97 changes: 97 additions & 0 deletions op-challenger2/game/fault/contracts/faultdisputegame.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger2/game/fault/contracts/metrics"
"github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types"
gameTypes "github.com/ethereum-optimism/optimism/op-challenger2/game/types"
"github.com/ethereum-optimism/optimism/op-e2e/bindings"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)

Expand Down Expand Up @@ -58,6 +61,9 @@ var (
methodMaxAttackBranch = "maxAttackBranch"
methodAttackV2 = "attackV2"
methodStepV2 = "stepV2"
fieldSubValues = "_claims"
fieldAttackBranch = "_attackBranch"
eventMove = "Move"
)

var (
Expand Down Expand Up @@ -140,6 +146,23 @@ func mustParseAbi(json []byte) *abi.ABI {
return &loaded
}

func SubValuesHash(values []common.Hash) common.Hash {
nelem := len(values)
hashes := make([]common.Hash, nelem)
copy(hashes, values)
for nelem != 1 {
for i := 0; i < nelem/2; i++ {
hashes[i] = crypto.Keccak256Hash(hashes[i*2][:], hashes[i*2+1][:])
}
// directly copy the last item
if nelem%2 == 1 {
hashes[nelem/2] = hashes[nelem-1]
}
nelem = (nelem + 1) / 2
}
return hashes[0]
}

// GetBalance returns the total amount of ETH controlled by this contract.
// Note that the ETH is actually held by the DelayedWETH contract which may be shared by multiple games.
// Returns the balance and the address of the contract that actually holds the balance.
Expand Down Expand Up @@ -447,6 +470,79 @@ func (f *FaultDisputeGameContractLatest) GetAllClaims(ctx context.Context, block
return claims, nil
}

func (f *FaultDisputeGameContractLatest) GetAllClaimsWithSubValues(ctx context.Context) ([]types.Claim, error) {
claims, err := f.GetAllClaims(ctx, rpcblock.Latest)
if err != nil {
return nil, err
}
for idx, claim := range claims {
subValues, attackBranch, err := f.getSubValuesAndAttackBranch(ctx, &claim)
if err != nil {
return nil, fmt.Errorf("failed to load subValues: %w", err)
}
claim.SubValues = &subValues
claim.AttackBranch = attackBranch
claims[idx] = claim
}
return claims, nil
}

func (f *FaultDisputeGameContractLatest) getSubValuesAndAttackBranch(ctx context.Context, aggClaim *types.Claim) ([]common.Hash, uint64, error) {
defer f.metrics.StartContractRequest("GetSubValues")()

filter, err := bindings.NewFaultDisputeGameFilterer(f.contract.Addr(), f.multiCaller)
if err != nil {
return nil, 0, err
}

parentIndex := [...]*big.Int{big.NewInt(int64(aggClaim.ParentContractIndex))}
claim := [...][32]byte{aggClaim.ClaimData.ValueBytes()}
claimant := [...]common.Address{aggClaim.Claimant}
moveIter, err := filter.FilterMove(&bind.FilterOpts{Context: ctx}, parentIndex[:], claim[:], claimant[:])
if err != nil {
return nil, 0, fmt.Errorf("failed to filter move event log: %w", err)
}
ok := moveIter.Next()
if !ok {
return nil, 0, fmt.Errorf("failed to get move event log: %w", moveIter.Error())
}
txHash := moveIter.Event.Raw.TxHash

getTxByHashCall := batching.NewTxGetByHash(f.contract.Abi(), txHash, methodAttackV2)
result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, getTxByHashCall)
if err != nil {
return nil, 0, fmt.Errorf("failed to load attackV2's calldata: %w", err)
}

txn := result.GetTx()
var subValues []common.Hash
var attackBranch uint64
if len(txn.BlobHashes()) > 0 {
// todo: fetch Blobs and unpack it into subValues and attackBranch
return nil, 0, fmt.Errorf("blob tx hasn't been supported")
} else {
inputMap, err := getTxByHashCall.UnpackCallData(&txn)
if err != nil {
return nil, 0, fmt.Errorf("failed to unpack tx resp: %w", err)
}
attackBranchU256 := *abi.ConvertType(inputMap[fieldAttackBranch], new(big.Int)).(*big.Int)
attackBranch = attackBranchU256.Uint64()

maxAttackBranch, err := f.GetMaxAttackBranch(ctx)
if err != nil {
return nil, 0, err
}
valuesBytesLen := uint(32 * maxAttackBranch)
bytes := abi.ConvertType(inputMap[fieldSubValues], make([]byte, valuesBytesLen)).([]byte)
for i := uint64(0); i < maxAttackBranch; i++ {
hash := common.BytesToHash(bytes[i*32 : (i+1)*32])
subValues = append(subValues, hash)
}
}

return subValues, attackBranch, nil
}

func (f *FaultDisputeGameContractLatest) IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error) {
defer f.metrics.StartContractRequest("IsResolved")()
calls := make([]batching.Call, 0, len(claims))
Expand Down Expand Up @@ -670,4 +766,5 @@ type FaultDisputeGameContract interface {
StepV2Tx(claimIdx uint64, attackBranch uint64, stateData []byte, proof types.StepProof) (txmgr.TxCandidate, error)
GetNBits(ctx context.Context) (uint64, error)
GetMaxAttackBranch(ctx context.Context) (uint64, error)
GetAllClaimsWithSubValues(ctx context.Context) ([]types.Claim, error)
}
125 changes: 125 additions & 0 deletions op-challenger2/game/fault/contracts/faultdisputegame_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
coreTypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -851,3 +853,126 @@ func TestStepV2Tx(t *testing.T) {
})
}
}

func TestGetAllClaimsWithSubValues(t *testing.T) {
for _, version := range versions {
t.Run(version.version, func(t *testing.T) {
stubRpc, game := setupFaultDisputeGameTest(t, version)
block := rpcblock.Latest

claimant := common.Address{0xbb}
bond := big.NewInt(1044)
nBits := uint64(2)
claimsBytes := make([]byte, ((1<<nBits)-1)*32)
subValues := []common.Hash{{0x10}, {0x11}, {0x12}}
for i := range claimsBytes {
claimsBytes[i] = subValues[i/32][i%32]
}
daType := faultTypes.CallDataType
parentPos := faultTypes.NewPosition(0, big.NewInt(0))
// attackBranch must be 0, because we can only attach the first branch of rootClaim
attackBranch := big.NewInt(0)
parent := faultTypes.Claim{ClaimData: faultTypes.ClaimData{Value: common.Hash{0xbb}, Position: parentPos}, ContractIndex: 111}
stubRpc.SetResponse(fdgAddr, methodNBits, block, nil, []interface{}{new(big.Int).SetUint64(nBits)})
stubRpc.SetResponse(fdgAddr, methodRequiredBond, block, []interface{}{parent.Position.MoveN(nBits, attackBranch.Uint64()).ToGIndex()}, []interface{}{bond})
stubRpc.SetResponse(fdgAddr, methodAttackV2, block, []interface{}{parent.Value, big.NewInt(111), attackBranch, daType, claimsBytes[:]}, nil)
txCandidate, err := game.AttackV2Tx(context.Background(), parent, attackBranch.Uint64(), daType.Uint64(), claimsBytes[:])
require.NoError(t, err)
tx := coreTypes.NewTx(&coreTypes.LegacyTx{
Nonce: 0,
GasPrice: big.NewInt(11111),
Gas: 1111,
To: &claimant,
Value: big.NewInt(111),
Data: txCandidate.TxData,
})
packed, err := tx.MarshalBinary()
require.NoError(t, err)
txHash := tx.Hash()
stubRpc.SetGetTxByHashResponse(txHash, packed)

// mock eventLog
eventName := eventMove
fdgAbi := version.loadAbi()
challgenIndex := []interface{}{big.NewInt(int64(parent.ContractIndex))}
claim := []interface{}{SubValuesHash(subValues)}
address := []interface{}{claimant}
query := [][]interface{}{challgenIndex, claim, address}

query = append([][]interface{}{{fdgAbi.Events[eventName].ID}}, query...)
topics, err := abi.MakeTopics(query...)
require.NoError(t, err)
var queryTopics []common.Hash
for _, item := range topics {
queryTopics = append(queryTopics, item[0])
}
out := []coreTypes.Log{
{
Address: fdgAddr,
Topics: queryTopics,
Data: []byte{},
TxHash: txHash,
},
}

stubRpc.SetFilterLogResponse(topics, fdgAddr, block, out)
stubRpc.SetResponse(fdgAddr, methodClaimCount, block, nil, []interface{}{big.NewInt(1)})
stubRpc.SetResponse(fdgAddr, methodMaxAttackBranch, block, nil, []interface{}{big.NewInt(1<<nBits - 1)})
claim0 := faultTypes.Claim{
ClaimData: faultTypes.ClaimData{
Value: SubValuesHash(subValues),
Position: parent.Position.MoveN(nBits, attackBranch.Uint64()),
Bond: bond,
},
CounteredBy: common.Address{0x00},
Claimant: claimant,
Clock: decodeClock(big.NewInt(1234)),
// contractIndex is mocked to 0, because there is only one claim for GetAllClaimsWithSubValues
ContractIndex: 0,
ParentContractIndex: parent.ContractIndex,
SubValues: &subValues,
AttackBranch: attackBranch.Uint64(),
}
expectedClaims := []faultTypes.Claim{claim0}
for _, claim := range expectedClaims {
expectGetClaim(stubRpc, block, claim)
}

claims, err := game.GetAllClaimsWithSubValues(context.Background())
require.NoError(t, err)
require.Equal(t, 1, len(claims))
require.Equal(t, claim0.SubValues, claims[0].SubValues)
claim0.SubValues = nil
claims[0].SubValues = nil
require.Equal(t, 0, claim0.ClaimData.IndexAtDepth().Cmp(claims[0].ClaimData.IndexAtDepth()))
claim0.ClaimData = faultTypes.ClaimData{}
claims[0].ClaimData = faultTypes.ClaimData{}
require.Equal(t, claim0, claims[0])
})
}
}

func TestSubValuesHash(t *testing.T) {
allClaims := [][]common.Hash{
{{0x01}, {0x02}, {0x03}},
{{0x01}, {0x02}, {0x03}, {0x04}},
}
tests := []struct {
claims []common.Hash
expectedHash common.Hash
}{
{
claims: allClaims[0],
expectedHash: crypto.Keccak256Hash(crypto.Keccak256Hash(allClaims[0][0][:], allClaims[0][1][:]).Bytes(), allClaims[0][2][:]),
},
{
claims: allClaims[1],
expectedHash: crypto.Keccak256Hash(crypto.Keccak256Hash(allClaims[1][0][:], allClaims[1][1][:]).Bytes(), crypto.Keccak256Hash(allClaims[1][2][:], allClaims[1][3][:]).Bytes()),
},
}

for _, test := range tests {
actualHash := SubValuesHash(test.claims)
require.Equal(t, test.expectedHash, actualHash)
}
}
2 changes: 1 addition & 1 deletion op-challenger2/game/fault/types/position.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func bigMSB(x *big.Int) Depth {
func (p Position) MoveN(nbits uint64, attackingBranch uint64) Position {
return Position{
depth: p.depth + Depth(nbits),
indexAtDepth: new(big.Int).Lsh(new(big.Int).Add(p.indexAtDepth, big.NewInt(int64(attackingBranch))), uint(nbits)),
indexAtDepth: new(big.Int).Lsh(new(big.Int).Add(p.IndexAtDepth(), big.NewInt(int64(attackingBranch))), uint(nbits)),
}
}

Expand Down
4 changes: 4 additions & 0 deletions op-service/sources/batching/bound.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,10 @@ func (b *BoundContract) Addr() common.Address {
return b.addr
}

func (b *BoundContract) Abi() *abi.ABI {
return b.abi
}

func (b *BoundContract) Call(method string, args ...interface{}) *ContractCall {
return NewContractCall(b.abi, b.addr, method, args...)
}
Expand Down
5 changes: 5 additions & 0 deletions op-service/sources/batching/call.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"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/rpc"
)

Expand Down Expand Up @@ -67,3 +68,7 @@ func (c *CallResult) GetBytes32Slice(i int) [][32]byte {
func (c *CallResult) GetString(i int) string {
return *abi.ConvertType(c.out[i], new(string)).(*string)
}

func (c *CallResult) GetTx() types.Transaction {
return *abi.ConvertType(c.out[0], new(types.Transaction)).(*types.Transaction)
}
37 changes: 37 additions & 0 deletions op-service/sources/batching/event_call.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package batching

import (
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rpc"
)

type EventCall struct {
topics [][]common.Hash
to []common.Address
}

func NewEventCall(q ethereum.FilterQuery) *EventCall {
return &EventCall{
topics: q.Topics,
to: q.Addresses,
}
}

func (b *EventCall) ToBatchElemCreator() (BatchElementCreator, error) {
return func(block rpcblock.Block) (any, rpc.BatchElem) {
out := new([]types.Log)
return out, rpc.BatchElem{
Method: "eth_getFilterLogs",
Args: []interface{}{b.topics, b.to[0], block.ArgValue()},
Result: &out,
}
}, nil
}

func (c *EventCall) HandleResult(result interface{}) (*CallResult, error) {
res := result.(*[]types.Log)
return &CallResult{out: []interface{}{*res}}, nil
}
Loading

0 comments on commit ad007d7

Please sign in to comment.