diff --git a/arbnode/node.go b/arbnode/node.go index e52a698e4e..32f1c1492e 100644 --- a/arbnode/node.go +++ b/arbnode/node.go @@ -611,7 +611,7 @@ func createNodeImpl( confirmedNotifiers = append(confirmedNotifiers, messagePruner) } - stakerObj, err = staker.NewStaker(l1Reader, wallet, bind.CallOpts{}, config.Staker, blockValidator, statelessBlockValidator, nil, confirmedNotifiers, deployInfo.ValidatorUtils, fatalErrChan) + stakerObj, err = staker.NewStaker(l1Reader, wallet, bind.CallOpts{}, config.Staker, blockValidator, statelessBlockValidator, nil, confirmedNotifiers, deployInfo.ValidatorUtils, deployInfo.Bridge, fatalErrChan) if err != nil { return nil, err } diff --git a/staker/manager.go b/staker/manager.go index 72731261d8..bef8a62882 100644 --- a/staker/manager.go +++ b/staker/manager.go @@ -7,7 +7,6 @@ import ( solimpl "github.com/OffchainLabs/bold/chain-abstraction/sol-implementation" challengemanager "github.com/OffchainLabs/bold/challenge-manager" - "github.com/OffchainLabs/bold/challenge-manager/types" l2stateprovider "github.com/OffchainLabs/bold/layer2-state-provider" "github.com/OffchainLabs/bold/solgen/go/challengeV2gen" "github.com/OffchainLabs/bold/solgen/go/rollupgen" @@ -25,8 +24,7 @@ func NewManager( callOpts bind.CallOpts, client arbutil.L1Interface, statelessBlockValidator *StatelessBlockValidator, - historyCacheBaseDir, - validatorName string, + config *BoldConfig, ) (*challengemanager.Manager, error) { chain, err := solimpl.NewAssertionChain( ctx, @@ -68,9 +66,9 @@ func NewManager( stateManager, err := NewStateManager( statelessBlockValidator, - historyCacheBaseDir, + config.MachineLeavesCachePath, challengeLeafHeights, - validatorName, + config.ValidatorName, ) if err != nil { return nil, err @@ -88,7 +86,13 @@ func NewManager( client, provider, rollupAddress, - challengemanager.WithMode(types.MakeMode), + challengemanager.WithName(config.ValidatorName), + challengemanager.WithMode(BoldModes[config.Mode]), + challengemanager.WithAssertionPostingInterval(config.AssertionPostingInterval), + challengemanager.WithAssertionScanningInterval(config.AssertionScanningInterval), + challengemanager.WithAssertionConfirmingInterval(config.AssertionConfirmingInterval), + challengemanager.WithEdgeTrackerWakeInterval(config.EdgeTrackerWakeInterval), + challengemanager.WithAddress(txOpts.From), ) if err != nil { return nil, err diff --git a/staker/staker.go b/staker/staker.go index 4f35c1bc9a..bb8773437d 100644 --- a/staker/staker.go +++ b/staker/staker.go @@ -24,6 +24,8 @@ import ( "github.com/offchainlabs/nitro/arbnode/redislock" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker/txbuilder" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/headerreader" @@ -74,6 +76,7 @@ func L1PostingStrategyAddOptions(prefix string, f *flag.FlagSet) { type L1ValidatorConfig struct { Enable bool `koanf:"enable"` + Bold BoldConfig `koanf:"bold"` Strategy string `koanf:"strategy"` StakerInterval time.Duration `koanf:"staker-interval"` MakeAssertionInterval time.Duration `koanf:"make-assertion-interval"` @@ -141,6 +144,7 @@ func (c *L1ValidatorConfig) Validate() error { var DefaultL1ValidatorConfig = L1ValidatorConfig{ Enable: true, + Bold: DefaultBoldConfig, Strategy: "Watchtower", StakerInterval: time.Minute, MakeAssertionInterval: time.Hour, @@ -191,6 +195,7 @@ var DefaultValidatorL1WalletConfig = genericconf.WalletConfig{ func L1ValidatorConfigAddOptions(prefix string, f *flag.FlagSet) { f.Bool(prefix+".enable", DefaultL1ValidatorConfig.Enable, "enable validator") + BoldConfigAddOptions(prefix+".bold", f) f.String(prefix+".strategy", DefaultL1ValidatorConfig.Strategy, "L1 validator strategy, either watchtower, defensive, stakeLatest, or makeNodes") f.Duration(prefix+".staker-interval", DefaultL1ValidatorConfig.StakerInterval, "how often the L1 validator should check the status of the L1 rollup and maybe take action with its stake") f.Duration(prefix+".make-assertion-interval", DefaultL1ValidatorConfig.MakeAssertionInterval, "if configured with the makeNodes strategy, how often to create new assertions (bypassed in case of a dispute)") @@ -253,6 +258,7 @@ type Staker struct { bringActiveUntilNode uint64 inboxReader InboxReaderInterface statelessBlockValidator *StatelessBlockValidator + bridge *bridgegen.IBridge fatalErr chan<- error } @@ -287,6 +293,7 @@ func NewStaker( stakedNotifiers []LatestStakedNotifier, confirmedNotifiers []LatestConfirmedNotifier, validatorUtilsAddress common.Address, + bridgeAddress common.Address, fatalErr chan<- error, ) (*Staker, error) { @@ -303,6 +310,13 @@ func NewStaker( if config.StartValidationFromStaked && blockValidator != nil { stakedNotifiers = append(stakedNotifiers, blockValidator) } + var bridge *bridgegen.IBridge + if config.Bold.Enable { + bridge, err = bridgegen.NewIBridge(bridgeAddress, client) + if err != nil { + return nil, err + } + } return &Staker{ L1Validator: val, l1Reader: l1Reader, @@ -314,6 +328,7 @@ func NewStaker( lastActCalledBlock: nil, inboxReader: statelessBlockValidator.inboxReader, statelessBlockValidator: statelessBlockValidator, + bridge: bridge, fatalErr: fatalErr, }, nil } @@ -428,6 +443,13 @@ func (s *Staker) Start(ctxIn context.Context) { if err != nil { log.Warn("error updating latest wasm module root", "err", err) } + switchedToBoldProtocol, err := s.checkAndSwitchToBoldStaker(ctxIn) + if err != nil { + log.Error("staker: error in checking switch to bold staker", "err", err) + } + if switchedToBoldProtocol { + s.StopAndWait() + } arbTx, err := s.Act(ctx) if err == nil && arbTx != nil { _, err = s.l1Reader.WaitForTxApproval(ctx, arbTx) @@ -486,6 +508,48 @@ func (s *Staker) Start(ctxIn context.Context) { } return s.config.StakerInterval }) + s.CallIteratively(func(ctx context.Context) time.Duration { + // Using ctxIn instead of ctx since, ctxIn will be passed on to bold staker + // and ctx will be cancelled after the switch to bold staker. + switchedToBoldProtocol, err := s.checkAndSwitchToBoldStaker(ctxIn) + if err != nil { + log.Error("staker: error in checking switch to bold staker", "err", err) + } + if switchedToBoldProtocol { + s.StopAndWait() + } + return s.config.StakerInterval + }) +} + +func (s *Staker) checkAndSwitchToBoldStaker(ctx context.Context) (bool, error) { + switchedToBoldProtocol := false + if s.config.Bold.Enable { + callOpts := s.getCallOpts(ctx) + rollupAddress, err := s.bridge.Rollup(callOpts) + if err != nil { + return false, err + } + userLogic, err := rollupgen.NewRollupUserLogic(rollupAddress, s.client) + if err != nil { + return false, err + } + _, err = userLogic.ExtraChallengeTimeBlocks(callOpts) + if err != nil { + // Switch to Bold protocol since ExtraChallengeTimeBlocks does not exist in bold protocol. + auth, err := s.builder.Auth(ctx) + if err != nil { + return false, err + } + boldManager, err := NewManager(ctx, rollupAddress, auth, *callOpts, s.client, s.statelessBlockValidator, &s.config.Bold) + if err != nil { + return false, err + } + boldManager.Start(ctx) + switchedToBoldProtocol = true + } + } + return switchedToBoldProtocol, nil } func (s *Staker) IsWhitelisted(ctx context.Context) (bool, error) { diff --git a/staker/state_provider.go b/staker/state_provider.go index 274e554a90..98ac353866 100644 --- a/staker/state_provider.go +++ b/staker/state_provider.go @@ -8,13 +8,18 @@ import ( "fmt" "strings" "sync" + "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" + flag "github.com/spf13/pflag" + protocol "github.com/OffchainLabs/bold/chain-abstraction" + "github.com/OffchainLabs/bold/challenge-manager/types" "github.com/OffchainLabs/bold/containers/option" l2stateprovider "github.com/OffchainLabs/bold/layer2-state-provider" + "github.com/offchainlabs/nitro/arbutil" challengecache "github.com/offchainlabs/nitro/staker/challenge-cache" "github.com/offchainlabs/nitro/validator" @@ -32,33 +37,43 @@ var ( ) type BoldConfig struct { - Enable bool `koanf:"enable"` - Mode string `koanf:"mode"` - BlockChallengeLeafHeight uint64 `koanf:"block-challenge-leaf-height"` - BigStepLeafHeight uint64 `koanf:"big-step-leaf-height"` - SmallStepLeafHeight uint64 `koanf:"small-step-leaf-height"` - NumBigSteps uint64 `koanf:"num-big-steps"` - ValidatorName string `koanf:"validator-name"` - MachineLeavesCachePath string `koanf:"machine-leaves-cache-path"` - AssertionPostingIntervalSeconds uint64 `koanf:"assertion-posting-interval-seconds"` - AssertionScanningIntervalSeconds uint64 `koanf:"assertion-scanning-interval-seconds"` - AssertionConfirmingIntervalSeconds uint64 `koanf:"assertion-confirming-interval-seconds"` - EdgeTrackerWakeIntervalSeconds uint64 `koanf:"edge-tracker-wake-interval-seconds"` + Enable bool `koanf:"enable"` + Mode string `koanf:"mode"` + ValidatorName string `koanf:"validator-name"` + MachineLeavesCachePath string `koanf:"machine-leaves-cache-path"` + AssertionPostingInterval time.Duration `koanf:"assertion-posting-interval"` + AssertionScanningInterval time.Duration `koanf:"assertion-scanning-interval"` + AssertionConfirmingInterval time.Duration `koanf:"assertion-confirming-interval"` + EdgeTrackerWakeInterval time.Duration `koanf:"edge-tracker-wake-interval"` } var DefaultBoldConfig = BoldConfig{ - Enable: false, - Mode: "make-mode", - BlockChallengeLeafHeight: 1 << 5, - BigStepLeafHeight: 1 << 5, - SmallStepLeafHeight: 1 << 7, - NumBigSteps: 5, - ValidatorName: "default-validator", - MachineLeavesCachePath: "/tmp/machine-leaves-cache", - AssertionPostingIntervalSeconds: 30, - AssertionScanningIntervalSeconds: 30, - AssertionConfirmingIntervalSeconds: 60, - EdgeTrackerWakeIntervalSeconds: 1, + Enable: false, + Mode: "make-mode", + ValidatorName: "default-validator", + MachineLeavesCachePath: "/tmp/machine-leaves-cache", + AssertionPostingInterval: 30 * time.Second, + AssertionScanningInterval: 30 * time.Second, + AssertionConfirmingInterval: 60 * time.Second, + EdgeTrackerWakeInterval: 1 * time.Second, +} + +var BoldModes = map[string]types.Mode{ + "watch-tower-mode": types.WatchTowerMode, + "resolve-mode": types.ResolveMode, + "defensive-mode": types.DefensiveMode, + "make-mode": types.MakeMode, +} + +func BoldConfigAddOptions(prefix string, f *flag.FlagSet) { + f.Bool(prefix+".enable", DefaultBoldConfig.Enable, "enable bold protocol") + f.String(prefix+".mode", DefaultBoldConfig.Mode, "mode for bold protocol") + f.String(prefix+".validator-name", DefaultBoldConfig.ValidatorName, "name of validator") + f.String(prefix+".machine-leaves-cache-path", DefaultBoldConfig.MachineLeavesCachePath, "path to machine leaves cache") + f.Duration(prefix+".assertion-posting-interval", DefaultBoldConfig.AssertionPostingInterval, "interval for posting assertions") + f.Duration(prefix+".assertion-scanning-interval", DefaultBoldConfig.AssertionScanningInterval, "interval for scanning assertions") + f.Duration(prefix+".assertion-confirming-interval", DefaultBoldConfig.AssertionConfirmingInterval, "interval for confirming assertions") + f.Duration(prefix+".edge-tracker-wake-interval", DefaultBoldConfig.EdgeTrackerWakeInterval, "interval for waking edge tracker") } type StateManager struct { diff --git a/system_tests/staker_test.go b/system_tests/staker_test.go index 6ac8dfddcb..9d56eac356 100644 --- a/system_tests/staker_test.go +++ b/system_tests/staker_test.go @@ -9,6 +9,7 @@ package arbtest import ( "context" + "encoding/json" "errors" "fmt" "math/big" @@ -22,12 +23,20 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" + mocksgen_bold "github.com/OffchainLabs/bold/solgen/go/mocksgen" + rollupgen_bold "github.com/OffchainLabs/bold/solgen/go/rollupgen" + challenge_testing "github.com/OffchainLabs/bold/testing" + "github.com/OffchainLabs/bold/testing/setup" + "github.com/offchainlabs/nitro/arbnode" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/arbos/l2pricing" + "github.com/offchainlabs/nitro/cmd/chaininfo" + "github.com/offchainlabs/nitro/solgen/go/bridgegen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen" @@ -36,6 +45,7 @@ import ( "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/arbmath" "github.com/offchainlabs/nitro/util/colors" + "github.com/offchainlabs/nitro/validator/server_common" "github.com/offchainlabs/nitro/validator/valnode" ) @@ -190,6 +200,7 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) nil, nil, l2nodeA.DeployInfo.ValidatorUtils, + l2nodeA.DeployInfo.Bridge, nil, ) Require(t, err) @@ -230,6 +241,7 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) nil, nil, l2nodeB.DeployInfo.ValidatorUtils, + l2nodeB.DeployInfo.Bridge, nil, ) Require(t, err) @@ -251,6 +263,7 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) nil, nil, l2nodeA.DeployInfo.ValidatorUtils, + l2nodeA.DeployInfo.Bridge, nil, ) Require(t, err) @@ -429,3 +442,189 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) func TestStakersCooperative(t *testing.T) { stakerTestImpl(t, false, false) } + +func TestStakerSwitchDuringRollupUpgrade(t *testing.T) { + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + stakerImpl, l1info, l1client, l2chainConfig, l2node, deployAuth := setupNonBoldStaker(t, ctx) + defer l2node.StopAndWait() + + err := stakerImpl.Initialize(ctx) + Require(t, err) + stakerImpl.Start(ctx) + if stakerImpl.Stopped() { + t.Fatal("Old protocol staker not started") + } + + rollupAddresses := deployBoldContracts(t, ctx, l1info, l1client, l2chainConfig.ChainID, deployAuth) + + bridge, err := bridgegen.NewBridge(l2node.DeployInfo.Bridge, l1client) + Require(t, err) + tx, err := bridge.UpdateRollupAddress(&deployAuth, rollupAddresses.Rollup) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l1client, tx) + Require(t, err) + + time.Sleep(time.Second) + + if !stakerImpl.Stopped() { + t.Fatal("Old protocol staker not stopped after rollup upgrade") + } +} + +func setupNonBoldStaker(t *testing.T, ctx context.Context) (*staker.Staker, info, *ethclient.Client, *params.ChainConfig, *arbnode.Node, bind.TransactOpts) { + var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs + l2chainConfig := params.ArbitrumDevTestChainConfig() + l2info := NewBlockChainTestInfo( + t, + types.NewArbitrumSigner(types.NewLondonSigner(l2chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), + transferGas, + ) + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.Build(t) + l2node := builder.L2.ConsensusNode + l2client := builder.L2.Client + l1info := builder.L1Info + l1client := builder.L1.Client + + BridgeBalance(t, "Faucet", big.NewInt(1).Mul(big.NewInt(params.Ether), big.NewInt(10000)), l1info, l2info, l1client, l2client, ctx) + + deployAuth := l1info.GetDefaultTransactOpts("RollupOwner", ctx) + + balance := big.NewInt(params.Ether) + balance.Mul(balance, big.NewInt(100)) + l1info.GenerateAccount("Validator") + TransferBalance(t, "Faucet", "Validator", balance, l1info, l1client, ctx) + l1auth := l1info.GetDefaultTransactOpts("Validator", ctx) + + rollup, err := rollupgen.NewRollupAdminLogic(l2node.DeployInfo.Rollup, l1client) + Require(t, err) + + tx, err := rollup.SetMinimumAssertionPeriod(&deployAuth, big.NewInt(1)) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, l1client, tx) + Require(t, err) + valConfig := staker.DefaultL1ValidatorConfig + valConfig.Strategy = "WatchTower" + valConfig.Bold = staker.DefaultBoldConfig + valConfig.StakerInterval = 100 * time.Millisecond + + dp, err := arbnode.StakerDataposter(ctx, rawdb.NewTable(l2node.ArbDB, storage.StakerPrefix), l2node.L1Reader, &l1auth, NewFetcherFromConfig(arbnode.ConfigDefaultL1NonSequencerTest()), nil) + if err != nil { + t.Fatalf("Error creating validator dataposter: %v", err) + } + valWallet, err := validatorwallet.NewContract(dp, nil, l2node.DeployInfo.ValidatorWalletCreator, l2node.DeployInfo.Rollup, l2node.L1Reader, &l1auth, 0, func(common.Address) {}, func() uint64 { return valConfig.ExtraGas }) + Require(t, err) + _, valStack := createTestValidationNode(t, ctx, &valnode.TestValidationConfig) + blockValidatorConfig := staker.TestBlockValidatorConfig + + stateless, err := staker.NewStatelessBlockValidator( + l2node.InboxReader, + l2node.InboxTracker, + l2node.TxStreamer, + l2node.Execution, + l2node.ArbDB, + nil, + StaticFetcherFrom(t, &blockValidatorConfig), + valStack, + ) + Require(t, err) + err = stateless.Start(ctx) + Require(t, err) + stakerImpl, err := staker.NewStaker( + l2node.L1Reader, + valWallet, + bind.CallOpts{}, + valConfig, + nil, + stateless, + nil, + nil, + l2node.DeployInfo.ValidatorUtils, + l2node.DeployInfo.Bridge, + nil, + ) + Require(t, err) + return stakerImpl, l1info, l1client, l2chainConfig, l2node, deployAuth +} + +func deployBoldContracts( + t *testing.T, + ctx context.Context, + l1info info, + backend *ethclient.Client, + chainId *big.Int, + deployAuth bind.TransactOpts, +) *chaininfo.RollupAddresses { + stakeToken, tx, tokenBindings, err := mocksgen_bold.DeployTestWETH9( + &deployAuth, + backend, + "Weth", + "WETH", + ) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, backend, tx) + Require(t, err) + value, _ := new(big.Int).SetString("1000000", 10) + deployAuth.Value = value + tx, err = tokenBindings.Deposit(&deployAuth) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, backend, tx) + Require(t, err) + deployAuth.Value = nil + Require(t, err) + _, err = EnsureTxSucceeded(ctx, backend, tx) + Require(t, err) + + initialBalance := new(big.Int).Lsh(big.NewInt(1), 200) + l1info.GenerateGenesisAccount("deployer", initialBalance) + l1info.GenerateGenesisAccount("asserter", initialBalance) + l1info.GenerateGenesisAccount("sequencer", initialBalance) + SendWaitTestTransactions(t, ctx, backend, []*types.Transaction{ + l1info.PrepareTx("Faucet", "RollupOwner", 30000, initialBalance, nil)}) + l1TransactionOpts := l1info.GetDefaultTransactOpts("RollupOwner", ctx) + locator, err := server_common.NewMachineLocator("") + Require(t, err) + + cfg := challenge_testing.GenerateRollupConfig( + false, + locator.LatestWasmModuleRoot(), + l1TransactionOpts.From, + chainId, + common.Address{}, + big.NewInt(1), + stakeToken, + rollupgen_bold.ExecutionState{ + GlobalState: rollupgen_bold.GlobalState{}, + MachineStatus: 1, + }, + big.NewInt(0), + common.Address{}, + ) + config, err := json.Marshal(params.ArbitrumDevTestChainConfig()) + if err != nil { + return nil + } + cfg.ChainConfig = string(config) + + addresses, err := setup.DeployFullRollupStack( + ctx, + backend, + &l1TransactionOpts, + l1info.GetAddress("sequencer"), + cfg, + false, + false, + ) + Require(t, err) + + return &chaininfo.RollupAddresses{ + Bridge: addresses.Bridge, + Inbox: addresses.Inbox, + SequencerInbox: addresses.SequencerInbox, + Rollup: addresses.Rollup, + ValidatorUtils: addresses.ValidatorUtils, + ValidatorWalletCreator: addresses.ValidatorWalletCreator, + DeployedAt: addresses.DeployedAt, + } +}