Commit 1dd35b7c authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-challenger: Implement block number challenge calls in contract bindings (#10462)

parent 0ad08a37
[
{
"inputs": [
{
"internalType": "GameType",
"name": "_gameType",
"type": "uint32"
},
{
"internalType": "Claim",
"name": "_absolutePrestate",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "_maxGameDepth",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_splitDepth",
"type": "uint256"
},
{
"internalType": "Duration",
"name": "_clockExtension",
"type": "uint64"
},
{
"internalType": "Duration",
"name": "_maxClockDuration",
"type": "uint64"
},
{
"internalType": "contract IBigStepper",
"name": "_vm",
"type": "address"
},
{
"internalType": "contract IDelayedWETH",
"name": "_weth",
"type": "address"
},
{
"internalType": "contract IAnchorStateRegistry",
"name": "_anchorStateRegistry",
"type": "address"
},
{
"internalType": "uint256",
"name": "_l2ChainId",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "absolutePrestate",
"outputs": [
{
"internalType": "Claim",
"name": "absolutePrestate_",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_ident",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_execLeafIdx",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_partOffset",
"type": "uint256"
}
],
"name": "addLocalData",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "anchorStateRegistry",
"outputs": [
{
"internalType": "contract IAnchorStateRegistry",
"name": "registry_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_parentIndex",
"type": "uint256"
},
{
"internalType": "Claim",
"name": "_claim",
"type": "bytes32"
}
],
"name": "attack",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "_recipient",
"type": "address"
}
],
"name": "claimCredit",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "claimData",
"outputs": [
{
"internalType": "uint32",
"name": "parentIndex",
"type": "uint32"
},
{
"internalType": "address",
"name": "counteredBy",
"type": "address"
},
{
"internalType": "address",
"name": "claimant",
"type": "address"
},
{
"internalType": "uint128",
"name": "bond",
"type": "uint128"
},
{
"internalType": "Claim",
"name": "claim",
"type": "bytes32"
},
{
"internalType": "Position",
"name": "position",
"type": "uint128"
},
{
"internalType": "Clock",
"name": "clock",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "claimDataLen",
"outputs": [
{
"internalType": "uint256",
"name": "len_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "Hash",
"name": "",
"type": "bytes32"
}
],
"name": "claims",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "clockExtension",
"outputs": [
{
"internalType": "Duration",
"name": "clockExtension_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "createdAt",
"outputs": [
{
"internalType": "Timestamp",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"name": "credit",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_parentIndex",
"type": "uint256"
},
{
"internalType": "Claim",
"name": "_claim",
"type": "bytes32"
}
],
"name": "defend",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "extraData",
"outputs": [
{
"internalType": "bytes",
"name": "extraData_",
"type": "bytes"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "gameCreator",
"outputs": [
{
"internalType": "address",
"name": "creator_",
"type": "address"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "gameData",
"outputs": [
{
"internalType": "GameType",
"name": "gameType_",
"type": "uint32"
},
{
"internalType": "Claim",
"name": "rootClaim_",
"type": "bytes32"
},
{
"internalType": "bytes",
"name": "extraData_",
"type": "bytes"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "gameType",
"outputs": [
{
"internalType": "GameType",
"name": "gameType_",
"type": "uint32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_claimIndex",
"type": "uint256"
}
],
"name": "getChallengerDuration",
"outputs": [
{
"internalType": "Duration",
"name": "duration_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_claimIndex",
"type": "uint256"
}
],
"name": "getNumToResolve",
"outputs": [
{
"internalType": "uint256",
"name": "numRemainingChildren_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "Position",
"name": "_position",
"type": "uint128"
}
],
"name": "getRequiredBond",
"outputs": [
{
"internalType": "uint256",
"name": "requiredBond_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "initialize",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "l1Head",
"outputs": [
{
"internalType": "Hash",
"name": "l1Head_",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "l2BlockNumber",
"outputs": [
{
"internalType": "uint256",
"name": "l2BlockNumber_",
"type": "uint256"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "l2ChainId",
"outputs": [
{
"internalType": "uint256",
"name": "l2ChainId_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxClockDuration",
"outputs": [
{
"internalType": "Duration",
"name": "maxClockDuration_",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "maxGameDepth",
"outputs": [
{
"internalType": "uint256",
"name": "maxGameDepth_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_challengeIndex",
"type": "uint256"
},
{
"internalType": "Claim",
"name": "_claim",
"type": "bytes32"
},
{
"internalType": "bool",
"name": "_isAttack",
"type": "bool"
}
],
"name": "move",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "resolutionCheckpoints",
"outputs": [
{
"internalType": "bool",
"name": "initialCheckpointComplete",
"type": "bool"
},
{
"internalType": "uint32",
"name": "subgameIndex",
"type": "uint32"
},
{
"internalType": "Position",
"name": "leftmostPosition",
"type": "uint128"
},
{
"internalType": "address",
"name": "counteredBy",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "resolve",
"outputs": [
{
"internalType": "enum GameStatus",
"name": "status_",
"type": "uint8"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_claimIndex",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_numToResolve",
"type": "uint256"
}
],
"name": "resolveClaim",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "resolvedAt",
"outputs": [
{
"internalType": "Timestamp",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "resolvedSubgames",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "rootClaim",
"outputs": [
{
"internalType": "Claim",
"name": "rootClaim_",
"type": "bytes32"
}
],
"stateMutability": "pure",
"type": "function"
},
{
"inputs": [],
"name": "splitDepth",
"outputs": [
{
"internalType": "uint256",
"name": "splitDepth_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "startingBlockNumber",
"outputs": [
{
"internalType": "uint256",
"name": "startingBlockNumber_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "startingOutputRoot",
"outputs": [
{
"internalType": "Hash",
"name": "root",
"type": "bytes32"
},
{
"internalType": "uint256",
"name": "l2BlockNumber",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "startingRootHash",
"outputs": [
{
"internalType": "Hash",
"name": "startingRootHash_",
"type": "bytes32"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "status",
"outputs": [
{
"internalType": "enum GameStatus",
"name": "",
"type": "uint8"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_claimIndex",
"type": "uint256"
},
{
"internalType": "bool",
"name": "_isAttack",
"type": "bool"
},
{
"internalType": "bytes",
"name": "_stateData",
"type": "bytes"
},
{
"internalType": "bytes",
"name": "_proof",
"type": "bytes"
}
],
"name": "step",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"name": "subgames",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "vm",
"outputs": [
{
"internalType": "contract IBigStepper",
"name": "vm_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "weth",
"outputs": [
{
"internalType": "contract IDelayedWETH",
"name": "weth_",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint256",
"name": "parentIndex",
"type": "uint256"
},
{
"indexed": true,
"internalType": "Claim",
"name": "claim",
"type": "bytes32"
},
{
"indexed": true,
"internalType": "address",
"name": "claimant",
"type": "address"
}
],
"name": "Move",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "enum GameStatus",
"name": "status",
"type": "uint8"
}
],
"name": "Resolved",
"type": "event"
},
{
"inputs": [],
"name": "AlreadyInitialized",
"type": "error"
},
{
"inputs": [],
"name": "AnchorRootNotFound",
"type": "error"
},
{
"inputs": [],
"name": "BondTransferFailed",
"type": "error"
},
{
"inputs": [],
"name": "CannotDefendRootClaim",
"type": "error"
},
{
"inputs": [],
"name": "ClaimAboveSplit",
"type": "error"
},
{
"inputs": [],
"name": "ClaimAlreadyExists",
"type": "error"
},
{
"inputs": [],
"name": "ClaimAlreadyResolved",
"type": "error"
},
{
"inputs": [],
"name": "ClockNotExpired",
"type": "error"
},
{
"inputs": [],
"name": "ClockTimeExceeded",
"type": "error"
},
{
"inputs": [],
"name": "DuplicateStep",
"type": "error"
},
{
"inputs": [],
"name": "GameDepthExceeded",
"type": "error"
},
{
"inputs": [],
"name": "GameNotInProgress",
"type": "error"
},
{
"inputs": [],
"name": "IncorrectBondAmount",
"type": "error"
},
{
"inputs": [],
"name": "InvalidClockExtension",
"type": "error"
},
{
"inputs": [],
"name": "InvalidLocalIdent",
"type": "error"
},
{
"inputs": [],
"name": "InvalidParent",
"type": "error"
},
{
"inputs": [],
"name": "InvalidPrestate",
"type": "error"
},
{
"inputs": [],
"name": "InvalidSplitDepth",
"type": "error"
},
{
"inputs": [],
"name": "MaxDepthTooLarge",
"type": "error"
},
{
"inputs": [],
"name": "NoCreditToClaim",
"type": "error"
},
{
"inputs": [],
"name": "OutOfOrderResolution",
"type": "error"
},
{
"inputs": [
{
"internalType": "Claim",
"name": "rootClaim",
"type": "bytes32"
}
],
"name": "UnexpectedRootClaim",
"type": "error"
},
{
"inputs": [],
"name": "ValidStep",
"type": "error"
}
]
\ No newline at end of file
...@@ -19,37 +19,40 @@ import ( ...@@ -19,37 +19,40 @@ import (
"github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
) )
// The maximum number of children that will be processed during a call to `resolveClaim` // The maximum number of children that will be processed during a call to `resolveClaim`
var maxChildChecks = big.NewInt(512) var maxChildChecks = big.NewInt(512)
var ( var (
methodVersion = "version" methodVersion = "version"
methodMaxClockDuration = "maxClockDuration" methodMaxClockDuration = "maxClockDuration"
methodMaxGameDepth = "maxGameDepth" methodMaxGameDepth = "maxGameDepth"
methodAbsolutePrestate = "absolutePrestate" methodAbsolutePrestate = "absolutePrestate"
methodStatus = "status" methodStatus = "status"
methodRootClaim = "rootClaim" methodRootClaim = "rootClaim"
methodClaimCount = "claimDataLen" methodClaimCount = "claimDataLen"
methodClaim = "claimData" methodClaim = "claimData"
methodL1Head = "l1Head" methodL1Head = "l1Head"
methodResolvedSubgames = "resolvedSubgames" methodResolvedSubgames = "resolvedSubgames"
methodResolve = "resolve" methodResolve = "resolve"
methodResolveClaim = "resolveClaim" methodResolveClaim = "resolveClaim"
methodAttack = "attack" methodAttack = "attack"
methodDefend = "defend" methodDefend = "defend"
methodStep = "step" methodStep = "step"
methodAddLocalData = "addLocalData" methodAddLocalData = "addLocalData"
methodVM = "vm" methodVM = "vm"
methodStartingBlockNumber = "startingBlockNumber" methodStartingBlockNumber = "startingBlockNumber"
methodStartingRootHash = "startingRootHash" methodStartingRootHash = "startingRootHash"
methodSplitDepth = "splitDepth" methodSplitDepth = "splitDepth"
methodL2BlockNumber = "l2BlockNumber" methodL2BlockNumber = "l2BlockNumber"
methodRequiredBond = "getRequiredBond" methodRequiredBond = "getRequiredBond"
methodClaimCredit = "claimCredit" methodClaimCredit = "claimCredit"
methodCredit = "credit" methodCredit = "credit"
methodWETH = "weth" methodWETH = "weth"
methodL2BlockNumberChallenged = "l2BlockNumberChallenged"
methodChallengeRootL2Block = "challengeRootL2Block"
) )
var ( var (
...@@ -68,6 +71,14 @@ type Proposal struct { ...@@ -68,6 +71,14 @@ type Proposal struct {
OutputRoot common.Hash OutputRoot common.Hash
} }
// outputRootProof is designed to match the solidity OutputRootProof struct.
type outputRootProof struct {
Version [32]byte
StateRoot [32]byte
MessagePasserStorageRoot [32]byte
LatestBlockhash [32]byte
}
func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) { func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMetricer, addr common.Address, caller *batching.MultiCaller) (FaultDisputeGameContract, error) {
contractAbi := snapshots.LoadFaultDisputeGameABI() contractAbi := snapshots.LoadFaultDisputeGameABI()
...@@ -87,6 +98,16 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe ...@@ -87,6 +98,16 @@ func NewFaultDisputeGameContract(ctx context.Context, metrics metrics.ContractMe
contract: batching.NewBoundContract(legacyAbi, addr), contract: batching.NewBoundContract(legacyAbi, addr),
}, },
}, nil }, nil
} else if strings.HasPrefix(version, "0.18.") {
// Detected an older version of contracts, use a compatibility shim.
legacyAbi := mustParseAbi(faultDisputeGameAbi0180)
return &FaultDisputeGameContract0180{
FaultDisputeGameContractLatest: FaultDisputeGameContractLatest{
metrics: metrics,
multiCaller: caller,
contract: batching.NewBoundContract(legacyAbi, addr),
},
}, nil
} else { } else {
return &FaultDisputeGameContractLatest{ return &FaultDisputeGameContractLatest{
metrics: metrics, metrics: metrics,
...@@ -414,11 +435,25 @@ func (f *FaultDisputeGameContractLatest) vm(ctx context.Context) (*VMContract, e ...@@ -414,11 +435,25 @@ func (f *FaultDisputeGameContractLatest) vm(ctx context.Context) (*VMContract, e
} }
func (f *FaultDisputeGameContractLatest) IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) { func (f *FaultDisputeGameContractLatest) IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) {
return false, nil defer f.metrics.StartContractRequest("IsL2BlockNumberChallenged")()
result, err := f.multiCaller.SingleCall(ctx, block, f.contract.Call(methodL2BlockNumberChallenged))
if err != nil {
return false, fmt.Errorf("failed to fetch block number challenged: %w", err)
}
return result.GetBool(0), nil
} }
func (f *FaultDisputeGameContractLatest) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) { func (f *FaultDisputeGameContractLatest) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, ErrChallengeL2BlockNotSupported headerRlp, err := rlp.EncodeToBytes(challenge.Header)
if err != nil {
return txmgr.TxCandidate{}, fmt.Errorf("failed to serialize header: %w", err)
}
return f.contract.Call(methodChallengeRootL2Block, outputRootProof{
Version: challenge.Output.Version,
StateRoot: challenge.Output.StateRoot,
MessagePasserStorageRoot: challenge.Output.WithdrawalStorageRoot,
LatestBlockhash: challenge.Output.BlockRef.Hash,
}, headerRlp).ToTxCandidate()
} }
func (f *FaultDisputeGameContractLatest) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) { func (f *FaultDisputeGameContractLatest) AttackTx(parentContractIndex uint64, pivot common.Hash) (txmgr.TxCandidate, error) {
......
package contracts
import (
"context"
_ "embed"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock"
"github.com/ethereum-optimism/optimism/op-service/txmgr"
)
//go:embed abis/FaultDisputeGame-0.18.1.json
var faultDisputeGameAbi0180 []byte
type FaultDisputeGameContract0180 struct {
FaultDisputeGameContractLatest
}
func (f *FaultDisputeGameContract0180) IsL2BlockNumberChallenged(_ context.Context, _ rpcblock.Block) (bool, error) {
return false, nil
}
func (f *FaultDisputeGameContract0180) ChallengeL2BlockNumberTx(_ *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, ErrChallengeL2BlockNotSupported
}
...@@ -132,3 +132,11 @@ func (f *FaultDisputeGameContract080) ResolveClaimTx(claimIdx uint64) (txmgr.TxC ...@@ -132,3 +132,11 @@ func (f *FaultDisputeGameContract080) ResolveClaimTx(claimIdx uint64) (txmgr.TxC
func (f *FaultDisputeGameContract080) resolveClaimCall(claimIdx uint64) *batching.ContractCall { func (f *FaultDisputeGameContract080) resolveClaimCall(claimIdx uint64) *batching.ContractCall {
return f.contract.Call(methodResolveClaim, new(big.Int).SetUint64(claimIdx)) return f.contract.Call(methodResolveClaim, new(big.Int).SetUint64(claimIdx))
} }
func (f *FaultDisputeGameContract080) IsL2BlockNumberChallenged(_ context.Context, _ rpcblock.Block) (bool, error) {
return false, nil
}
func (f *FaultDisputeGameContract080) ChallengeL2BlockNumberTx(_ *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) {
return txmgr.TxCandidate{}, ErrChallengeL2BlockNotSupported
}
...@@ -3,21 +3,26 @@ package contracts ...@@ -3,21 +3,26 @@ package contracts
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"math" "math"
"math/big" "math/big"
"math/rand"
"testing" "testing"
"time" "time"
contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics" contractMetrics "github.com/ethereum-optimism/optimism/op-challenger/game/fault/contracts/metrics"
faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types" faultTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-challenger/game/types" "github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/sources/batching" "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/sources/batching/rpcblock"
batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test" batchingTest "github.com/ethereum-optimism/optimism/op-service/sources/batching/test"
"github.com/ethereum-optimism/optimism/op-service/testutils"
"github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/op-service/txmgr"
"github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots"
"github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -34,7 +39,8 @@ type contractVersion struct { ...@@ -34,7 +39,8 @@ type contractVersion struct {
const ( const (
vers080 = "0.8.0" vers080 = "0.8.0"
versLatest = "0.18.0" vers0180 = "0.18.0"
versLatest = "1.1.0"
) )
var versions = []contractVersion{ var versions = []contractVersion{
...@@ -44,6 +50,12 @@ var versions = []contractVersion{ ...@@ -44,6 +50,12 @@ var versions = []contractVersion{
return mustParseAbi(faultDisputeGameAbi020) return mustParseAbi(faultDisputeGameAbi020)
}, },
}, },
{
version: vers0180,
loadAbi: func() *abi.ABI {
return mustParseAbi(faultDisputeGameAbi0180)
},
},
{ {
version: versLatest, version: versLatest,
loadAbi: snapshots.LoadFaultDisputeGameABI, loadAbi: snapshots.LoadFaultDisputeGameABI,
...@@ -655,12 +667,22 @@ func TestFaultDisputeGame_IsResolved(t *testing.T) { ...@@ -655,12 +667,22 @@ func TestFaultDisputeGame_IsResolved(t *testing.T) {
func TestFaultDisputeGameContractLatest_IsL2BlockNumberChallenged(t *testing.T) { func TestFaultDisputeGameContractLatest_IsL2BlockNumberChallenged(t *testing.T) {
for _, version := range versions { for _, version := range versions {
version := version version := version
t.Run(version.version, func(t *testing.T) { for _, expected := range []bool{true, false} {
_, game := setupFaultDisputeGameTest(t, version) expected := expected
challenged, err := game.IsL2BlockNumberChallenged(context.Background(), rpcblock.Latest) t.Run(fmt.Sprintf("%v-%v", version.version, expected), func(t *testing.T) {
require.NoError(t, err) block := rpcblock.ByHash(common.Hash{0x43})
require.False(t, challenged) stubRpc, game := setupFaultDisputeGameTest(t, version)
}) supportsL2BlockNumChallenge := version.version != vers080 && version.version != vers0180
if supportsL2BlockNumChallenge {
stubRpc.SetResponse(fdgAddr, methodL2BlockNumberChallenged, block, nil, []interface{}{expected})
} else if expected {
t.Skip("Can't have challenged L2 block number on this contract version")
}
challenged, err := game.IsL2BlockNumberChallenged(context.Background(), block)
require.NoError(t, err)
require.Equal(t, expected, challenged)
})
}
} }
} }
...@@ -668,10 +690,40 @@ func TestFaultDisputeGameContractLatest_ChallengeL2BlockNumberTx(t *testing.T) { ...@@ -668,10 +690,40 @@ func TestFaultDisputeGameContractLatest_ChallengeL2BlockNumberTx(t *testing.T) {
for _, version := range versions { for _, version := range versions {
version := version version := version
t.Run(version.version, func(t *testing.T) { t.Run(version.version, func(t *testing.T) {
_, game := setupFaultDisputeGameTest(t, version) rng := rand.New(rand.NewSource(0))
tx, err := game.ChallengeL2BlockNumberTx(&faultTypes.InvalidL2BlockNumberChallenge{}) stubRpc, game := setupFaultDisputeGameTest(t, version)
require.ErrorIs(t, err, ErrChallengeL2BlockNotSupported) challenge := &faultTypes.InvalidL2BlockNumberChallenge{
require.Equal(t, txmgr.TxCandidate{}, tx) Output: &eth.OutputResponse{
Version: eth.Bytes32{},
OutputRoot: eth.Bytes32{0xaa},
BlockRef: eth.L2BlockRef{Hash: common.Hash{0xbb}},
WithdrawalStorageRoot: common.Hash{0xcc},
StateRoot: common.Hash{0xdd},
},
Header: testutils.RandomHeader(rng),
}
supportsL2BlockNumChallenge := version.version != vers080 && version.version != vers0180
if supportsL2BlockNumChallenge {
headerRlp, err := rlp.EncodeToBytes(challenge.Header)
require.NoError(t, err)
stubRpc.SetResponse(fdgAddr, methodChallengeRootL2Block, rpcblock.Latest, []interface{}{
outputRootProof{
Version: challenge.Output.Version,
StateRoot: challenge.Output.StateRoot,
MessagePasserStorageRoot: challenge.Output.WithdrawalStorageRoot,
LatestBlockhash: challenge.Output.BlockRef.Hash,
},
headerRlp,
}, nil)
}
tx, err := game.ChallengeL2BlockNumberTx(challenge)
if supportsL2BlockNumChallenge {
require.NoError(t, err)
stubRpc.VerifyTxCandidate(tx)
} else {
require.ErrorIs(t, err, ErrChallengeL2BlockNotSupported)
require.Equal(t, txmgr.TxCandidate{}, tx)
}
}) })
} }
} }
......
...@@ -442,6 +442,19 @@ func (g *OutputGameHelper) WaitForInactivity(ctx context.Context, numInactiveBlo ...@@ -442,6 +442,19 @@ func (g *OutputGameHelper) WaitForInactivity(ctx context.Context, numInactiveBlo
} }
} }
func (g *OutputGameHelper) WaitForL2BlockNumberChallenged(ctx context.Context) {
g.T.Logf("Waiting for game %v to have L2 block number challenged", g.Addr)
caller := batching.NewMultiCaller(g.System.NodeClient("l1").Client(), batching.DefaultBatchSize)
contract, err := contracts.NewFaultDisputeGameContract(ctx, contractMetrics.NoopContractMetrics, g.Addr, caller)
g.Require.NoError(err)
timedCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
defer cancel()
err = wait.For(timedCtx, time.Second, func() (bool, error) {
return contract.IsL2BlockNumberChallenged(ctx, rpcblock.Latest)
})
g.Require.NoError(err, "L2 block number was not challenged in time")
}
// Mover is a function that either attacks or defends the claim at parentClaimIdx // Mover is a function that either attacks or defends the claim at parentClaimIdx
type Mover func(parent *ClaimHelper) *ClaimHelper type Mover func(parent *ClaimHelper) *ClaimHelper
......
...@@ -784,3 +784,35 @@ func TestInvalidateProposalForFutureBlock(t *testing.T) { ...@@ -784,3 +784,35 @@ func TestInvalidateProposalForFutureBlock(t *testing.T) {
}) })
} }
} }
func TestInvalidateCorrectProposalFutureBlock(t *testing.T) {
op_e2e.InitParallel(t, op_e2e.UsesCannon)
ctx := context.Background()
// Spin up the system without the batcher so the safe head doesn't advance
sys, l1Client := StartFaultDisputeSystem(t, WithBatcherStopped(), WithSequencerWindowSize(100000))
t.Cleanup(sys.Close)
// Create a dispute game factory helper.
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys)
// No batches submitted so safe head is genesis
output, err := sys.RollupClient("sequencer").OutputAtBlock(ctx, 0)
require.NoError(t, err, "Failed to get output at safe head")
// Create a dispute game with an output root that is valid at `safeHead`, but that claims to correspond to block
// `safeHead.Number + 10000`. This is dishonest, because this block does not exist yet.
game := disputeGameFactory.StartOutputCannonGame(ctx, "sequencer", 10_000, common.Hash(output.OutputRoot), disputegame.WithFutureProposal())
// Start the honest challenger.
game.StartChallenger(ctx, "Honest", challenger.WithPrivKey(sys.Cfg.Secrets.Bob))
game.WaitForL2BlockNumberChallenged(ctx)
// Time travel past when the game will be resolvable.
sys.TimeTravelClock.AdvanceTime(game.MaxClockDuration(ctx))
require.NoError(t, wait.ForNextBlock(ctx, l1Client))
// The game should resolve as `CHALLENGER_WINS` always, because the root claim signifies a claim that does not exist
// yet in the L2 chain.
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx)
}
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