From edf818cc51b98056c48a4adb234ae4544f330216 Mon Sep 17 00:00:00 2001 From: Po Date: Sun, 3 Nov 2024 12:18:51 +0800 Subject: [PATCH] feat(MSFDG): add getSubValues --- op-challenger2/game/fault/agent.go | 3 +- op-challenger2/game/fault/agent_test.go | 8 ++ .../game/fault/contracts/faultdisputegame.go | 97 ++++++++++++++ .../fault/contracts/faultdisputegame_test.go | 126 ++++++++++++++++++ op-challenger2/game/fault/types/position.go | 2 +- op-service/sources/batching/bound.go | 4 + op-service/sources/batching/call.go | 5 + op-service/sources/batching/event_call.go | 37 +++++ .../sources/batching/event_call_test.go | 75 +++++++++++ op-service/sources/batching/multicall.go | 20 +++ op-service/sources/batching/test/abi_stub.go | 21 +++ .../sources/batching/test/event_stub.go | 66 +++++++++ .../sources/batching/test/generic_stub.go | 8 ++ op-service/sources/batching/test/tx_stub.go | 45 +++++++ op-service/sources/batching/tx_call.go | 64 +++++++++ op-service/sources/batching/tx_call_test.go | 50 +++++++ 16 files changed, 629 insertions(+), 2 deletions(-) create mode 100644 op-service/sources/batching/event_call.go create mode 100644 op-service/sources/batching/event_call_test.go create mode 100644 op-service/sources/batching/test/event_stub.go create mode 100644 op-service/sources/batching/test/tx_stub.go create mode 100644 op-service/sources/batching/tx_call.go create mode 100644 op-service/sources/batching/tx_call_test.go diff --git a/op-challenger2/game/fault/agent.go b/op-challenger2/game/fault/agent.go index b3865dbb2a7f..9a82250f5921 100644 --- a/op-challenger2/game/fault/agent.go +++ b/op-challenger2/game/fault/agent.go @@ -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) @@ -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) } diff --git a/op-challenger2/game/fault/agent_test.go b/op-challenger2/game/fault/agent_test.go index 0acf8ce32d25..f3e9ccccfde2 100644 --- a/op-challenger2/game/fault/agent_test.go +++ b/op-challenger2/game/fault/agent_test.go @@ -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 } diff --git a/op-challenger2/game/fault/contracts/faultdisputegame.go b/op-challenger2/game/fault/contracts/faultdisputegame.go index 28fd34d68269..de945aad5ced 100644 --- a/op-challenger2/game/fault/contracts/faultdisputegame.go +++ b/op-challenger2/game/fault/contracts/faultdisputegame.go @@ -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" ) @@ -58,6 +61,9 @@ var ( methodMaxAttackBranch = "maxAttackBranch" methodAttackV2 = "attackV2" methodStepV2 = "stepV2" + fieldSubValues = "_claims" + fieldAttackBranch = "_attackBranch" + eventMove = "Move" ) var ( @@ -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. @@ -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)) @@ -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) } diff --git a/op-challenger2/game/fault/contracts/faultdisputegame_test.go b/op-challenger2/game/fault/contracts/faultdisputegame_test.go index 2c699b27223c..db9581b2a43f 100644 --- a/op-challenger2/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger2/game/fault/contracts/faultdisputegame_test.go @@ -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" ) @@ -851,3 +853,127 @@ 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<