From 7f5f406b15ec1abfae80e0f3f38e89883fd4a5c5 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Wed, 26 Jun 2024 19:32:04 +0530 Subject: [PATCH 01/24] fast confirmation --- contracts | 2 +- staker/staker.go | 34 ++++++- system_tests/staker_test.go | 182 ++++++++++++++++++++++++++++++++++++ 3 files changed, 213 insertions(+), 5 deletions(-) diff --git a/contracts b/contracts index 7a41cd59cd..ccd2c2b5b3 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 7a41cd59cdf2eb01cf31c2351b8d1ff6fbf52178 +Subproject commit ccd2c2b5b3dbeaa93df3be20841322b54ab2b045 diff --git a/staker/staker.go b/staker/staker.go index da6413e122..ff0f00fee2 100644 --- a/staker/staker.go +++ b/staker/staker.go @@ -90,6 +90,7 @@ type L1ValidatorConfig struct { ExtraGas uint64 `koanf:"extra-gas" reload:"hot"` Dangerous DangerousConfig `koanf:"dangerous"` ParentChainWallet genericconf.WalletConfig `koanf:"parent-chain-wallet"` + EnableFastConfirmation bool `koanf:"enable-fast-confirmation"` strategy StakerStrategy gasRefunder common.Address @@ -156,6 +157,7 @@ var DefaultL1ValidatorConfig = L1ValidatorConfig{ ExtraGas: 50000, Dangerous: DefaultDangerousConfig, ParentChainWallet: DefaultValidatorL1WalletConfig, + EnableFastConfirmation: false, } var TestL1ValidatorConfig = L1ValidatorConfig{ @@ -176,6 +178,7 @@ var TestL1ValidatorConfig = L1ValidatorConfig{ ExtraGas: 50000, Dangerous: DefaultDangerousConfig, ParentChainWallet: DefaultValidatorL1WalletConfig, + EnableFastConfirmation: false, } var DefaultValidatorL1WalletConfig = genericconf.WalletConfig{ @@ -204,6 +207,7 @@ func L1ValidatorConfigAddOptions(prefix string, f *flag.FlagSet) { dataposter.DataPosterConfigAddOptions(prefix+".data-poster", f, dataposter.DefaultDataPosterConfigForValidator) DangerousConfigAddOptions(prefix+".dangerous", f) genericconf.WalletConfigAddOptions(prefix+".parent-chain-wallet", f, DefaultL1ValidatorConfig.ParentChainWallet.Pathname) + f.Bool(prefix+".enable-fast-confirmation", DefaultL1ValidatorConfig.EnableFastConfirmation, "enable fast confirmation") } type DangerousConfig struct { @@ -864,7 +868,7 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv if err != nil { return fmt.Errorf("error staking on new node: %w", err) } - return nil + return s.tryFastConfirmation(ctx, action.assertion.AfterState.GlobalState.BlockHash, action.assertion.AfterState.GlobalState.SendRoot) } // If we have no stake yet, we'll put one down @@ -886,7 +890,7 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv return fmt.Errorf("error placing new stake on new node: %w", err) } info.StakeExists = true - return nil + return s.tryFastConfirmation(ctx, action.assertion.AfterState.GlobalState.BlockHash, action.assertion.AfterState.GlobalState.SendRoot) case existingNodeAction: info.LatestStakedNode = action.number info.LatestStakedNodeHash = action.hash @@ -914,7 +918,7 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv if err != nil { return fmt.Errorf("error staking on existing node: %w", err) } - return nil + return s.tryFastConfirmationNodeNumber(ctx, action.number) } // If we have no stake yet, we'll put one down @@ -935,12 +939,34 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv return fmt.Errorf("error placing new stake on existing node: %w", err) } info.StakeExists = true - return nil + return s.tryFastConfirmationNodeNumber(ctx, action.number) default: panic("invalid action type") } } +func (s *Staker) tryFastConfirmationNodeNumber(ctx context.Context, number uint64) error { + if !s.config.EnableFastConfirmation { + return nil + } + nodeInfo, err := s.rollup.LookupNode(ctx, number) + if err != nil { + return err + } + return s.tryFastConfirmation(ctx, nodeInfo.AfterState().GlobalState.BlockHash, nodeInfo.AfterState().GlobalState.SendRoot) +} + +func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error { + if !s.config.EnableFastConfirmation { + return nil + } + auth, err := s.builder.Auth(ctx) + if err != nil { + return err + } + _, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot) + return err +} func (s *Staker) createConflict(ctx context.Context, info *StakerInfo) error { if info.CurrentChallenge != nil { return nil diff --git a/system_tests/staker_test.go b/system_tests/staker_test.go index 4afe2e8ccd..14fcb49a9e 100644 --- a/system_tests/staker_test.go +++ b/system_tests/staker_test.go @@ -464,3 +464,185 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) func TestStakersCooperative(t *testing.T) { stakerTestImpl(t, false, false) } + +func TestFastConfirmation(t *testing.T) { + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + srv := externalsignertest.NewServer(t) + go func() { + if err := srv.Start(); err != nil { + log.Error("Failed to start external signer server:", err) + return + } + }() + var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.L2Info = NewBlockChainTestInfo( + t, + types.NewArbitrumSigner(types.NewLondonSigner(builder.chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), + transferGas, + ) + + builder.nodeConfig.BatchPoster.MaxDelay = -1000 * time.Hour + cleanup := builder.Build(t) + defer cleanup() + + addNewBatchPoster(ctx, t, builder, srv.Address) + + builder.L1.SendWaitTestTransactions(t, []*types.Transaction{ + builder.L1Info.PrepareTxTo("Faucet", &srv.Address, 30000, big.NewInt(1).Mul(big.NewInt(1e18), big.NewInt(1e18)), nil)}) + + l2node := builder.L2.ConsensusNode + execNode := builder.L2.ExecNode + + config := arbnode.ConfigDefaultL1Test() + config.Sequencer = false + config.DelayedSequencer.Enable = false + config.BatchPoster.Enable = false + builder.execConfig.Sequencer.Enable = false + + builder.BridgeBalance(t, "Faucet", big.NewInt(1).Mul(big.NewInt(params.Ether), big.NewInt(10000))) + + deployAuth := builder.L1Info.GetDefaultTransactOpts("RollupOwner", ctx) + + balance := big.NewInt(params.Ether) + balance.Mul(balance, big.NewInt(100)) + builder.L1.TransferBalance(t, "Faucet", "Validator", balance, builder.L1Info) + l1auth := builder.L1Info.GetDefaultTransactOpts("Validator", ctx) + + valWalletAddrPtr, err := validatorwallet.GetValidatorWalletContract(ctx, l2node.DeployInfo.ValidatorWalletCreator, 0, &l1auth, l2node.L1Reader, true) + Require(t, err) + valWalletAddr := *valWalletAddrPtr + valWalletAddrCheck, err := validatorwallet.GetValidatorWalletContract(ctx, l2node.DeployInfo.ValidatorWalletCreator, 0, &l1auth, l2node.L1Reader, true) + Require(t, err) + if valWalletAddr == *valWalletAddrCheck { + Require(t, err, "didn't cache validator wallet address", valWalletAddr.String(), "vs", valWalletAddrCheck.String()) + } + + rollup, err := rollupgen.NewRollupAdminLogic(l2node.DeployInfo.Rollup, builder.L1.Client) + Require(t, err) + + upgradeExecutor, err := upgrade_executorgen.NewUpgradeExecutor(l2node.DeployInfo.UpgradeExecutor, builder.L1.Client) + Require(t, err, "unable to bind upgrade executor") + rollupABI, err := abi.JSON(strings.NewReader(rollupgen.RollupAdminLogicABI)) + Require(t, err, "unable to parse rollup ABI") + + setValidatorCalldata, err := rollupABI.Pack("setValidator", []common.Address{valWalletAddr, srv.Address}, []bool{true, true}) + Require(t, err, "unable to generate setValidator calldata") + tx, err := upgradeExecutor.ExecuteCall(&deployAuth, l2node.DeployInfo.Rollup, setValidatorCalldata) + Require(t, err, "unable to set validators") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + setMinAssertPeriodCalldata, err := rollupABI.Pack("setMinimumAssertionPeriod", big.NewInt(1)) + Require(t, err, "unable to generate setMinimumAssertionPeriod calldata") + tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2node.DeployInfo.Rollup, setMinAssertPeriodCalldata) + Require(t, err, "unable to set minimum assertion period") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + setAnyTrustFastConfirmerCalldata, err := rollupABI.Pack("setAnyTrustFastConfirmer", valWalletAddr) + Require(t, err, "unable to generate setAnyTrustFastConfirmer calldata") + tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2node.DeployInfo.Rollup, setAnyTrustFastConfirmerCalldata) + Require(t, err, "unable to set anytrust fast confirmer") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + valConfig := staker.TestL1ValidatorConfig + valConfig.EnableFastConfirmation = true + parentChainID, err := builder.L1.Client.ChainID(ctx) + if err != nil { + t.Fatalf("Failed to get parent chain id: %v", err) + } + dp, err := arbnode.StakerDataposter( + ctx, + rawdb.NewTable(l2node.ArbDB, storage.StakerPrefix), + l2node.L1Reader, + &l1auth, NewFetcherFromConfig(arbnode.ConfigDefaultL1NonSequencerTest()), + nil, + parentChainID, + ) + 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) + valConfig.Strategy = "MakeNodes" + + _, valStack := createTestValidationNode(t, ctx, &valnode.TestValidationConfig) + blockValidatorConfig := staker.TestBlockValidatorConfig + + stateless, err := staker.NewStatelessBlockValidator( + l2node.InboxReader, + l2node.InboxTracker, + l2node.TxStreamer, + execNode, + l2node.ArbDB, + nil, + StaticFetcherFrom(t, &blockValidatorConfig), + valStack, + ) + Require(t, err) + err = stateless.Start(ctx) + Require(t, err) + stakerA, err := staker.NewStaker( + l2node.L1Reader, + valWallet, + bind.CallOpts{}, + valConfig, + nil, + stateless, + nil, + nil, + l2node.DeployInfo.ValidatorUtils, + nil, + ) + Require(t, err) + err = stakerA.Initialize(ctx) + if stakerA.Strategy() != staker.WatchtowerStrategy { + err = valWallet.Initialize(ctx) + Require(t, err) + } + Require(t, err) + cfg := arbnode.ConfigDefaultL1NonSequencerTest() + signerCfg, err := externalSignerTestCfg(srv.Address, srv.URL()) + if err != nil { + t.Fatalf("Error getting external signer config: %v", err) + } + cfg.Staker.DataPoster.ExternalSigner = *signerCfg + + builder.L2Info.GenerateAccount("BackgroundUser") + tx = builder.L2Info.PrepareTx("Faucet", "BackgroundUser", builder.L2Info.TransferGas, balance, nil) + err = builder.L2.Client.SendTransaction(ctx, tx) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + // Continually make L2 transactions in a background thread + backgroundTxsCtx, cancelBackgroundTxs := context.WithCancel(ctx) + backgroundTxsShutdownChan := make(chan struct{}) + defer (func() { + cancelBackgroundTxs() + <-backgroundTxsShutdownChan + })() + go (func() { + defer close(backgroundTxsShutdownChan) + err := makeBackgroundTxs(backgroundTxsCtx, builder) + if !errors.Is(err, context.Canceled) { + log.Warn("error making background txs", "err", err) + } + })() + + latestConfirmBeforeAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) + Require(t, err) + tx, err = stakerA.Act(ctx) + Require(t, err) + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + latestConfirmAfterAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) + Require(t, err) + if latestConfirmAfterAct <= latestConfirmBeforeAct { + Fatal(t, "staker A didn't advance the latest confirmed node") + } +} From aaaa297c1962750f7661484d6f3daaacb807aa9e Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Thu, 27 Jun 2024 18:31:18 +0530 Subject: [PATCH 02/24] fast confirmation with safe --- .gitmodules | 3 + contracts | 2 +- safe-smart-account | 1 + solgen/gen.go | 12 ++ staker/staker.go | 225 +++++++++++++++++++++++++++ system_tests/staker_test.go | 301 +++++++++++++++++++++++++++++++++++- 6 files changed, 541 insertions(+), 3 deletions(-) create mode 160000 safe-smart-account diff --git a/.gitmodules b/.gitmodules index d7b61d862b..d4d26282ae 100644 --- a/.gitmodules +++ b/.gitmodules @@ -32,3 +32,6 @@ [submodule "arbitrator/langs/bf"] path = arbitrator/langs/bf url = https://github.com/OffchainLabs/stylus-sdk-bf.git +[submodule "safe-smart-account"] + path = safe-smart-account + url = https://github.com/safe-global/safe-smart-account.git diff --git a/contracts b/contracts index ccd2c2b5b3..56ae8045eb 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit ccd2c2b5b3dbeaa93df3be20841322b54ab2b045 +Subproject commit 56ae8045eb511824474f675326d32716ca26326b diff --git a/safe-smart-account b/safe-smart-account new file mode 160000 index 0000000000..46f7c60ef4 --- /dev/null +++ b/safe-smart-account @@ -0,0 +1 @@ +Subproject commit 46f7c60ef4ba68cd2ed2bad8ab9f606fe9dab40f diff --git a/solgen/gen.go b/solgen/gen.go index 92511595d7..2ad71b0c79 100644 --- a/solgen/gen.go +++ b/solgen/gen.go @@ -73,6 +73,18 @@ func main() { log.Fatal(err) } + filePathsSafeSmartAccount, err := filepath.Glob(filepath.Join(parent, "safe-smart-account", "build", "artifacts", "contracts", "*", "*.sol", "*.json")) + if err != nil { + log.Fatal(err) + } + filePathsSafeSmartAccountOuter, err := filepath.Glob(filepath.Join(parent, "safe-smart-account", "build", "artifacts", "contracts", "*.sol", "*.json")) + if err != nil { + log.Fatal(err) + } + + filePaths = append(filePaths, filePathsSafeSmartAccount...) + filePaths = append(filePaths, filePathsSafeSmartAccountOuter...) + modules := make(map[string]*moduleInfo) for _, path := range filePaths { diff --git a/staker/staker.go b/staker/staker.go index ff0f00fee2..f031623a6d 100644 --- a/staker/staker.go +++ b/staker/staker.go @@ -9,9 +9,11 @@ import ( "fmt" "math/big" "runtime/debug" + "sort" "strings" "time" + "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/core/types" @@ -23,6 +25,8 @@ import ( "github.com/offchainlabs/nitro/arbnode/dataposter" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/cmd/genericconf" + "github.com/offchainlabs/nitro/solgen/go/contractsgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker/txbuilder" "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/arbmath" @@ -91,6 +95,7 @@ type L1ValidatorConfig struct { Dangerous DangerousConfig `koanf:"dangerous"` ParentChainWallet genericconf.WalletConfig `koanf:"parent-chain-wallet"` EnableFastConfirmation bool `koanf:"enable-fast-confirmation"` + FastConfirmSafeAddress string `koanf:"fast-confirm-safe-address"` strategy StakerStrategy gasRefunder common.Address @@ -158,6 +163,7 @@ var DefaultL1ValidatorConfig = L1ValidatorConfig{ Dangerous: DefaultDangerousConfig, ParentChainWallet: DefaultValidatorL1WalletConfig, EnableFastConfirmation: false, + FastConfirmSafeAddress: "", } var TestL1ValidatorConfig = L1ValidatorConfig{ @@ -179,6 +185,7 @@ var TestL1ValidatorConfig = L1ValidatorConfig{ Dangerous: DefaultDangerousConfig, ParentChainWallet: DefaultValidatorL1WalletConfig, EnableFastConfirmation: false, + FastConfirmSafeAddress: "", } var DefaultValidatorL1WalletConfig = genericconf.WalletConfig{ @@ -208,6 +215,7 @@ func L1ValidatorConfigAddOptions(prefix string, f *flag.FlagSet) { DangerousConfigAddOptions(prefix+".dangerous", f) genericconf.WalletConfigAddOptions(prefix+".parent-chain-wallet", f, DefaultL1ValidatorConfig.ParentChainWallet.Pathname) f.Bool(prefix+".enable-fast-confirmation", DefaultL1ValidatorConfig.EnableFastConfirmation, "enable fast confirmation") + f.String(prefix+".fast-confirm-safe-address", DefaultL1ValidatorConfig.FastConfirmSafeAddress, "safe address for fast confirmation") } type DangerousConfig struct { @@ -254,6 +262,20 @@ type Staker struct { inboxReader InboxReaderInterface statelessBlockValidator *StatelessBlockValidator fatalErr chan<- error + fastConfirmSafe *FastConfirmSafe +} + +type FastConfirmSafe struct { + rollupAddress common.Address + safe *contractsgen.Safe + owners []common.Address + approvedHashesOwners map[common.Hash]map[common.Address]bool + threshold uint64 + fastConfirmNextNodeMethod abi.Method + builder *txbuilder.Builder + wallet ValidatorWalletInterface + gasRefunder common.Address + l1Reader *headerreader.HeaderReader } type ValidatorWalletInterface interface { @@ -303,6 +325,21 @@ func NewStaker( if config.StartValidationFromStaked && blockValidator != nil { stakedNotifiers = append(stakedNotifiers, blockValidator) } + var fastConfirmSafe *FastConfirmSafe + if config.EnableFastConfirmation && config.FastConfirmSafeAddress != "" { + fastConfirmSafe, err = NewFastConfirmSafe( + callOpts, + wallet.RollupAddress(), + common.HexToAddress(config.FastConfirmSafeAddress), + val.builder, + val.wallet, + config.gasRefunder, + l1Reader, + ) + if err != nil { + return nil, err + } + } return &Staker{ L1Validator: val, l1Reader: l1Reader, @@ -315,9 +352,59 @@ func NewStaker( inboxReader: statelessBlockValidator.inboxReader, statelessBlockValidator: statelessBlockValidator, fatalErr: fatalErr, + fastConfirmSafe: fastConfirmSafe, }, nil } +func NewFastConfirmSafe( + callOpts bind.CallOpts, + rollupAddress common.Address, + fastConfirmSafeAddress common.Address, + builder *txbuilder.Builder, + wallet ValidatorWalletInterface, + gasRefunder common.Address, + l1Reader *headerreader.HeaderReader, +) (*FastConfirmSafe, error) { + fastConfirmSafe := &FastConfirmSafe{ + builder: builder, + rollupAddress: rollupAddress, + wallet: wallet, + gasRefunder: gasRefunder, + l1Reader: l1Reader, + } + safe, err := contractsgen.NewSafe(fastConfirmSafeAddress, builder) + if err != nil { + return nil, err + } + fastConfirmSafe.safe = safe + owners, err := safe.GetOwners(&callOpts) + if err != nil { + return nil, err + } + + // This is needed because safe contract needs owners to be sorted. + sort.Slice(owners, func(i, j int) bool { + return owners[i].Cmp(owners[j]) < 0 + }) + fastConfirmSafe.owners = owners + threshold, err := safe.GetThreshold(&callOpts) + if err != nil { + return nil, err + } + fastConfirmSafe.threshold = threshold.Uint64() + fastConfirmSafe.approvedHashesOwners = make(map[common.Hash]map[common.Address]bool) + rollupUserLogicAbi, err := rollupgen.RollupUserLogicMetaData.GetAbi() + if err != nil { + return nil, err + } + fastConfirmNextNodeMethod, ok := rollupUserLogicAbi.Methods["fastConfirmNextNode"] + if !ok { + return nil, errors.New("RollupUserLogic ABI missing fastConfirmNextNode method") + } + fastConfirmSafe.fastConfirmNextNodeMethod = fastConfirmNextNodeMethod + return fastConfirmSafe, nil +} + func (s *Staker) Initialize(ctx context.Context) error { err := s.L1Validator.Initialize(ctx) if err != nil { @@ -960,6 +1047,9 @@ func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash, if !s.config.EnableFastConfirmation { return nil } + if s.fastConfirmSafe != nil { + return s.fastConfirmSafe.tryFastConfirmation(ctx, blockHash, sendRoot) + } auth, err := s.builder.Auth(ctx) if err != nil { return err @@ -967,6 +1057,141 @@ func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash, _, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot) return err } + +func (f *FastConfirmSafe) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error { + fastConfirmCallData, err := f.createFastConfirmCalldata(blockHash, sendRoot) + if err != nil { + return err + } + callOpts := &bind.CallOpts{Context: ctx} + // Current nonce of the safe. + nonce, err := f.safe.Nonce(callOpts) + if err != nil { + return err + } + // Hash of the safe transaction. + safeTxHash, err := f.safe.GetTransactionHash( + callOpts, + f.rollupAddress, + big.NewInt(0), + fastConfirmCallData, + 0, + big.NewInt(0), + big.NewInt(0), + big.NewInt(0), + common.Address{}, + common.Address{}, + nonce, + ) + if err != nil { + return err + } + arbTx, err := f.wallet.ExecuteTransactions(ctx, f.builder, f.gasRefunder) + if err != nil { + return err + } + if arbTx != nil { + _, err = f.l1Reader.WaitForTxApproval(ctx, arbTx) + if err == nil { + log.Info("successfully executed staker transaction", "hash", arbTx.Hash()) + } else { + return fmt.Errorf("error waiting for tx receipt: %w", err) + } + } + f.builder.ClearTransactions() + auth, err := f.builder.Auth(ctx) + if err != nil { + return err + } + _, err = f.safe.ApproveHash(auth, safeTxHash) + if err != nil { + return err + } + arbTx, err = f.wallet.ExecuteTransactions(ctx, f.builder, f.gasRefunder) + if err != nil { + return err + } + if arbTx != nil { + _, err = f.l1Reader.WaitForTxApproval(ctx, arbTx) + if err == nil { + log.Info("successfully executed staker transaction", "hash", arbTx.Hash()) + } else { + return fmt.Errorf("error waiting for tx receipt: %w", err) + } + } + f.builder.ClearTransactions() + if _, ok := f.approvedHashesOwners[safeTxHash]; !ok { + f.approvedHashesOwners[safeTxHash] = make(map[common.Address]bool) + } + return f.checkApprovedHashAndExecTransaction(ctx, fastConfirmCallData, safeTxHash) +} + +func (f *FastConfirmSafe) createFastConfirmCalldata( + blockHash common.Hash, sendRoot common.Hash, +) ([]byte, error) { + calldata, err := f.fastConfirmNextNodeMethod.Inputs.Pack( + blockHash, + sendRoot, + ) + if err != nil { + return nil, err + } + fullCalldata := append([]byte{}, f.fastConfirmNextNodeMethod.ID...) + fullCalldata = append(fullCalldata, calldata...) + return fullCalldata, nil +} + +func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Context, fastConfirmCallData []byte, safeTxHash [32]byte) error { + var signatures []byte + for _, owner := range f.owners { + if _, ok := f.approvedHashesOwners[safeTxHash][owner]; !ok { + iter, err := f.safe.FilterApproveHash(&bind.FilterOpts{Context: ctx}, [][32]byte{safeTxHash}, []common.Address{owner}) + if err != nil { + return err + } + for iter.Next() { + f.approvedHashesOwners[iter.Event.ApprovedHash][iter.Event.Owner] = true + } + } + // If the owner has approved the hash, we add the signature to the transaction. + // We add the signature in the format r, s, v. + // We set v to 1, as it is the only possible value for a approved hash. + // We set r to the owner's address. + // We set s to the empty hash. + // Refer to the Safe contract for more information. + if _, ok := f.approvedHashesOwners[safeTxHash][owner]; ok { + v := uint8(1) + r := common.BytesToHash(owner.Bytes()) + s := common.Hash{} + signatures = append(signatures, r.Bytes()...) + signatures = append(signatures, s.Bytes()...) + signatures = append(signatures, v) + } + } + if uint64(len(f.approvedHashesOwners[safeTxHash])) >= f.threshold { + auth, err := f.builder.Auth(ctx) + if err != nil { + return err + } + _, err = f.safe.ExecTransaction( + auth, + f.rollupAddress, + big.NewInt(0), + fastConfirmCallData, + 0, + big.NewInt(0), + big.NewInt(0), + big.NewInt(0), + common.Address{}, + common.Address{}, + signatures, + ) + if err != nil { + return err + } + } + return nil +} func (s *Staker) createConflict(ctx context.Context, info *StakerInfo) error { if info.CurrentChallenge != nil { return nil diff --git a/system_tests/staker_test.go b/system_tests/staker_test.go index 14fcb49a9e..955ccc6e9f 100644 --- a/system_tests/staker_test.go +++ b/system_tests/staker_test.go @@ -29,7 +29,9 @@ import ( "github.com/offchainlabs/nitro/arbnode/dataposter/externalsignertest" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/arbos/l2pricing" + "github.com/offchainlabs/nitro/solgen/go/contractsgen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/solgen/go/proxiesgen" "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen" "github.com/offchainlabs/nitro/staker" @@ -638,11 +640,306 @@ func TestFastConfirmation(t *testing.T) { Require(t, err) tx, err = stakerA.Act(ctx) Require(t, err) - _, err = builder.L1.EnsureTxSucceeded(tx) - Require(t, err) + if tx != nil { + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + } latestConfirmAfterAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) Require(t, err) if latestConfirmAfterAct <= latestConfirmBeforeAct { Fatal(t, "staker A didn't advance the latest confirmed node") } } + +func TestFastConfirmationWithSafe(t *testing.T) { + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + srv := externalsignertest.NewServer(t) + go func() { + if err := srv.Start(); err != nil { + log.Error("Failed to start external signer server:", err) + return + } + }() + var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true) + builder.L2Info = NewBlockChainTestInfo( + t, + types.NewArbitrumSigner(types.NewLondonSigner(builder.chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), + transferGas, + ) + + builder.nodeConfig.BatchPoster.MaxDelay = -1000 * time.Hour + cleanupA := builder.Build(t) + defer cleanupA() + + addNewBatchPoster(ctx, t, builder, srv.Address) + + builder.L1.SendWaitTestTransactions(t, []*types.Transaction{ + builder.L1Info.PrepareTxTo("Faucet", &srv.Address, 30000, big.NewInt(1).Mul(big.NewInt(1e18), big.NewInt(1e18)), nil)}) + + l2nodeA := builder.L2.ConsensusNode + execNodeA := builder.L2.ExecNode + + config := arbnode.ConfigDefaultL1Test() + config.Sequencer = false + config.DelayedSequencer.Enable = false + config.BatchPoster.Enable = false + builder.execConfig.Sequencer.Enable = false + testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: config}) + defer cleanupB() + + l2nodeB := testClientB.ConsensusNode + execNodeB := testClientB.ExecNode + + nodeAGenesis := execNodeA.Backend.APIBackend().CurrentHeader().Hash() + nodeBGenesis := execNodeB.Backend.APIBackend().CurrentHeader().Hash() + if nodeAGenesis != nodeBGenesis { + Fatal(t, "node A L2 genesis hash", nodeAGenesis, "!= node B L2 genesis hash", nodeBGenesis) + } + + builder.BridgeBalance(t, "Faucet", big.NewInt(1).Mul(big.NewInt(params.Ether), big.NewInt(10000))) + + deployAuth := builder.L1Info.GetDefaultTransactOpts("RollupOwner", ctx) + + balance := big.NewInt(params.Ether) + balance.Mul(balance, big.NewInt(100)) + builder.L1Info.GenerateAccount("ValidatorA") + builder.L1.TransferBalance(t, "Faucet", "ValidatorA", balance, builder.L1Info) + l1authA := builder.L1Info.GetDefaultTransactOpts("ValidatorA", ctx) + + builder.L1Info.GenerateAccount("ValidatorB") + builder.L1.TransferBalance(t, "Faucet", "ValidatorB", balance, builder.L1Info) + l1authB := builder.L1Info.GetDefaultTransactOpts("ValidatorB", ctx) + + valWalletAddrAPtr, err := validatorwallet.GetValidatorWalletContract(ctx, l2nodeA.DeployInfo.ValidatorWalletCreator, 0, &l1authA, l2nodeA.L1Reader, true) + Require(t, err) + valWalletAddrA := *valWalletAddrAPtr + valWalletAddrCheck, err := validatorwallet.GetValidatorWalletContract(ctx, l2nodeA.DeployInfo.ValidatorWalletCreator, 0, &l1authA, l2nodeA.L1Reader, true) + Require(t, err) + if valWalletAddrA == *valWalletAddrCheck { + Require(t, err, "didn't cache validator wallet address", valWalletAddrA.String(), "vs", valWalletAddrCheck.String()) + } + + rollup, err := rollupgen.NewRollupAdminLogic(l2nodeA.DeployInfo.Rollup, builder.L1.Client) + Require(t, err) + + upgradeExecutor, err := upgrade_executorgen.NewUpgradeExecutor(l2nodeA.DeployInfo.UpgradeExecutor, builder.L1.Client) + Require(t, err, "unable to bind upgrade executor") + rollupABI, err := abi.JSON(strings.NewReader(rollupgen.RollupAdminLogicABI)) + Require(t, err, "unable to parse rollup ABI") + + safeAddress := deploySafe(t, builder.L1, builder.L1.Client, deployAuth, []common.Address{valWalletAddrA, srv.Address}) + setValidatorCalldata, err := rollupABI.Pack("setValidator", []common.Address{valWalletAddrA, l1authB.From, srv.Address, safeAddress}, []bool{true, true, true, true}) + Require(t, err, "unable to generate setValidator calldata") + tx, err := upgradeExecutor.ExecuteCall(&deployAuth, l2nodeA.DeployInfo.Rollup, setValidatorCalldata) + Require(t, err, "unable to set validators") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + setMinAssertPeriodCalldata, err := rollupABI.Pack("setMinimumAssertionPeriod", big.NewInt(1)) + Require(t, err, "unable to generate setMinimumAssertionPeriod calldata") + tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2nodeA.DeployInfo.Rollup, setMinAssertPeriodCalldata) + Require(t, err, "unable to set minimum assertion period") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + setAnyTrustFastConfirmerCalldata, err := rollupABI.Pack("setAnyTrustFastConfirmer", safeAddress) + Require(t, err, "unable to generate setAnyTrustFastConfirmer calldata") + tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2nodeA.DeployInfo.Rollup, setAnyTrustFastConfirmerCalldata) + Require(t, err, "unable to set anytrust fast confirmer") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + valConfig := staker.TestL1ValidatorConfig + valConfig.EnableFastConfirmation = true + valConfig.FastConfirmSafeAddress = safeAddress.String() + parentChainID, err := builder.L1.Client.ChainID(ctx) + if err != nil { + t.Fatalf("Failed to get parent chain id: %v", err) + } + dpA, err := arbnode.StakerDataposter( + ctx, + rawdb.NewTable(l2nodeB.ArbDB, storage.StakerPrefix), + l2nodeA.L1Reader, + &l1authA, NewFetcherFromConfig(arbnode.ConfigDefaultL1NonSequencerTest()), + nil, + parentChainID, + ) + if err != nil { + t.Fatalf("Error creating validator dataposter: %v", err) + } + valWalletA, err := validatorwallet.NewContract(dpA, nil, l2nodeA.DeployInfo.ValidatorWalletCreator, l2nodeA.DeployInfo.Rollup, l2nodeA.L1Reader, &l1authA, 0, func(common.Address) {}, func() uint64 { return valConfig.ExtraGas }) + Require(t, err) + valConfig.Strategy = "MakeNodes" + + _, valStack := createTestValidationNode(t, ctx, &valnode.TestValidationConfig) + blockValidatorConfig := staker.TestBlockValidatorConfig + + statelessA, err := staker.NewStatelessBlockValidator( + l2nodeA.InboxReader, + l2nodeA.InboxTracker, + l2nodeA.TxStreamer, + execNodeA, + l2nodeA.ArbDB, + nil, + StaticFetcherFrom(t, &blockValidatorConfig), + valStack, + ) + Require(t, err) + err = statelessA.Start(ctx) + Require(t, err) + stakerA, err := staker.NewStaker( + l2nodeA.L1Reader, + valWalletA, + bind.CallOpts{}, + valConfig, + nil, + statelessA, + nil, + nil, + l2nodeA.DeployInfo.ValidatorUtils, + nil, + ) + Require(t, err) + err = stakerA.Initialize(ctx) + if stakerA.Strategy() != staker.WatchtowerStrategy { + err = valWalletA.Initialize(ctx) + Require(t, err) + } + Require(t, err) + cfg := arbnode.ConfigDefaultL1NonSequencerTest() + signerCfg, err := externalSignerTestCfg(srv.Address, srv.URL()) + if err != nil { + t.Fatalf("Error getting external signer config: %v", err) + } + cfg.Staker.DataPoster.ExternalSigner = *signerCfg + dpB, err := arbnode.StakerDataposter( + ctx, + rawdb.NewTable(l2nodeB.ArbDB, storage.StakerPrefix), + l2nodeB.L1Reader, + &l1authB, NewFetcherFromConfig(cfg), + nil, + parentChainID, + ) + if err != nil { + t.Fatalf("Error creating validator dataposter: %v", err) + } + valWalletB, err := validatorwallet.NewEOA(dpB, l2nodeB.DeployInfo.Rollup, l2nodeB.L1Reader.Client(), func() uint64 { return 0 }) + Require(t, err) + valConfig.Strategy = "MakeNodes" + statelessB, err := staker.NewStatelessBlockValidator( + l2nodeB.InboxReader, + l2nodeB.InboxTracker, + l2nodeB.TxStreamer, + execNodeB, + l2nodeB.ArbDB, + nil, + StaticFetcherFrom(t, &blockValidatorConfig), + valStack, + ) + Require(t, err) + err = statelessB.Start(ctx) + Require(t, err) + stakerB, err := staker.NewStaker( + l2nodeB.L1Reader, + valWalletB, + bind.CallOpts{}, + valConfig, + nil, + statelessB, + nil, + nil, + l2nodeB.DeployInfo.ValidatorUtils, + nil, + ) + Require(t, err) + err = stakerB.Initialize(ctx) + Require(t, err) + if stakerB.Strategy() != staker.WatchtowerStrategy { + err = valWalletB.Initialize(ctx) + Require(t, err) + } + + builder.L2Info.GenerateAccount("BackgroundUser") + tx = builder.L2Info.PrepareTx("Faucet", "BackgroundUser", builder.L2Info.TransferGas, balance, nil) + err = builder.L2.Client.SendTransaction(ctx, tx) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + // Continually make L2 transactions in a background thread + backgroundTxsCtx, cancelBackgroundTxs := context.WithCancel(ctx) + backgroundTxsShutdownChan := make(chan struct{}) + defer (func() { + cancelBackgroundTxs() + <-backgroundTxsShutdownChan + })() + go (func() { + defer close(backgroundTxsShutdownChan) + err := makeBackgroundTxs(backgroundTxsCtx, builder) + if !errors.Is(err, context.Canceled) { + log.Warn("error making background txs", "err", err) + } + })() + + latestConfirmBeforeAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) + Require(t, err) + tx, err = stakerA.Act(ctx) + Require(t, err) + if tx != nil { + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + } + latestConfirmAfterStakerAAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) + Require(t, err) + if latestConfirmAfterStakerAAct != latestConfirmBeforeAct { + Fatal(t, "staker A alone advanced the latest confirmed node", latestConfirmAfterStakerAAct, "when it shouldn't have") + } + for j := 0; j < 5; j++ { + builder.L1.TransferBalance(t, "Faucet", "Faucet", common.Big0, builder.L1Info) + } + tx, err = stakerB.Act(ctx) + Require(t, err) + if tx != nil { + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + } + latestConfirmAfterStakerBAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) + Require(t, err) + if latestConfirmAfterStakerBAct <= latestConfirmBeforeAct { + Fatal(t, "staker A and B together didn't advance the latest confirmed node") + } +} + +func deploySafe(t *testing.T, l1 *TestClient, backend bind.ContractBackend, deployAuth bind.TransactOpts, owners []common.Address) common.Address { + safeAddress, tx, _, err := contractsgen.DeploySafeL2(&deployAuth, backend) + Require(t, err) + _, err = l1.EnsureTxSucceeded(tx) + Require(t, err) + safeProxyAddress, tx, _, err := proxiesgen.DeploySafeProxy(&deployAuth, backend, safeAddress) + Require(t, err) + _, err = l1.EnsureTxSucceeded(tx) + Require(t, err) + var safe *contractsgen.Safe + safe, err = contractsgen.NewSafe(safeProxyAddress, backend) + Require(t, err) + _, err = l1.EnsureTxSucceeded(tx) + Require(t, err) + tx, err = safe.Setup( + &deployAuth, + owners, + big.NewInt(2), + common.Address{}, + nil, + common.Address{}, + common.Address{}, + big.NewInt(0), + common.Address{}, + ) + Require(t, err) + _, err = l1.EnsureTxSucceeded(tx) + Require(t, err) + return safeProxyAddress +} From 97a3dbad253a36ae6babce7271699cacdc00163c Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Thu, 27 Jun 2024 18:43:51 +0530 Subject: [PATCH 03/24] fix makefile --- Makefile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Makefile b/Makefile index 53b89c8d72..c924fddeb5 100644 --- a/Makefile +++ b/Makefile @@ -494,11 +494,13 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin) @touch $@ .make/solidity: $(DEP_PREDICATE) contracts/src/*/*.sol .make/yarndeps $(ORDER_ONLY_PREDICATE) .make + (cd safe-smart-account && npm run build) yarn --cwd contracts build yarn --cwd contracts build:forge:yul @touch $@ .make/yarndeps: $(DEP_PREDICATE) contracts/package.json contracts/yarn.lock $(ORDER_ONLY_PREDICATE) .make + (cd safe-smart-account && npm i) yarn --cwd contracts install @touch $@ From 7a675c158efc561164deae76373559202e00b3b3 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Thu, 27 Jun 2024 18:55:44 +0530 Subject: [PATCH 04/24] fix docker --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 2863b76dd1..75a4a3470e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,7 @@ WORKDIR /workspace COPY contracts/package.json contracts/yarn.lock contracts/ RUN cd contracts && yarn install COPY contracts contracts/ +COPY safe-smart-account safe-smart-account/ COPY Makefile . RUN . ~/.bashrc && NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-solidity From 32bb178a9bc2512b086a49187f1c95359d614d6f Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Thu, 27 Jun 2024 19:06:50 +0530 Subject: [PATCH 05/24] fix docker --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 75a4a3470e..45ac32e18e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,6 +32,7 @@ WORKDIR /workspace COPY contracts/package.json contracts/yarn.lock contracts/ RUN cd contracts && yarn install COPY contracts contracts/ +RUN cd safe-smart-account && npm i && npm run build COPY safe-smart-account safe-smart-account/ COPY Makefile . RUN . ~/.bashrc && NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-solidity From da7f1769ae2eca854ef0271f4bb947a79e717e9a Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Thu, 27 Jun 2024 19:16:46 +0530 Subject: [PATCH 06/24] fix docker --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 45ac32e18e..72581029a9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -32,8 +32,8 @@ WORKDIR /workspace COPY contracts/package.json contracts/yarn.lock contracts/ RUN cd contracts && yarn install COPY contracts contracts/ -RUN cd safe-smart-account && npm i && npm run build COPY safe-smart-account safe-smart-account/ +RUN cd safe-smart-account && npm i && npm run build COPY Makefile . RUN . ~/.bashrc && NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-solidity From c0ef5f186f05a46433f1ee40e49567d3a00e3ac8 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Thu, 27 Jun 2024 19:37:52 +0530 Subject: [PATCH 07/24] fix docker --- Dockerfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Dockerfile b/Dockerfile index 72581029a9..d2b5dbc209 100644 --- a/Dockerfile +++ b/Dockerfile @@ -227,6 +227,7 @@ COPY . ./ COPY --from=contracts-builder workspace/contracts/build/ contracts/build/ COPY --from=contracts-builder workspace/contracts/out/ contracts/out/ COPY --from=contracts-builder workspace/contracts/node_modules/@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json contracts/node_modules/@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/ +COPY --from=contracts-builder workspace/safe-smart-account/build/ workspace/safe-smart-account/build/ COPY --from=contracts-builder workspace/.make/ .make/ COPY --from=prover-header-export / target/ COPY --from=brotli-library-export / target/ From c8797fa770e6a869066e2cc03a2f0477a577aa82 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Thu, 27 Jun 2024 20:06:40 +0530 Subject: [PATCH 08/24] fix makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index c924fddeb5..3635eefaf2 100644 --- a/Makefile +++ b/Makefile @@ -493,7 +493,7 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin) go run solgen/gen.go @touch $@ -.make/solidity: $(DEP_PREDICATE) contracts/src/*/*.sol .make/yarndeps $(ORDER_ONLY_PREDICATE) .make +.make/solidity: $(DEP_PREDICATE) safe-smart-account/contracts/*/*.sol safe-smart-account/contracts/*.sol contracts/src/*/*.sol .make/yarndeps $(ORDER_ONLY_PREDICATE) .make (cd safe-smart-account && npm run build) yarn --cwd contracts build yarn --cwd contracts build:forge:yul From b66830638e0ca69e7d7a245ce894e87a829208f0 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 8 Jul 2024 17:09:55 +0530 Subject: [PATCH 09/24] fix contracts --- contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts b/contracts index 56ae8045eb..61204dd455 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 56ae8045eb511824474f675326d32716ca26326b +Subproject commit 61204dd455966cb678192427a07aa9795ff91c14 From f66590c1575a7f83bfd69503062c52c5ba48b746 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 8 Jul 2024 18:03:14 +0530 Subject: [PATCH 10/24] cherry pick fast confrimation commits --- contracts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts b/contracts index 61204dd455..448e30d6de 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 61204dd455966cb678192427a07aa9795ff91c14 +Subproject commit 448e30d6de0eda9efd6a4c7c2311ec0b89f5cb60 From ca9d8690cd673c4dfbd83deb280175d1a4bca3dc Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 8 Jul 2024 19:25:45 +0530 Subject: [PATCH 11/24] fix docker --- Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index e003bb493b..6194581546 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,7 +33,7 @@ COPY contracts/package.json contracts/yarn.lock contracts/ RUN cd contracts && yarn install COPY contracts contracts/ COPY safe-smart-account safe-smart-account/ -RUN cd safe-smart-account && npm i && npm run build +RUN cd safe-smart-account && npm i COPY Makefile . RUN . ~/.bashrc && NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-solidity @@ -227,7 +227,7 @@ COPY . ./ COPY --from=contracts-builder workspace/contracts/build/ contracts/build/ COPY --from=contracts-builder workspace/contracts/out/ contracts/out/ COPY --from=contracts-builder workspace/contracts/node_modules/@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/UpgradeExecutor.json contracts/node_modules/@offchainlabs/upgrade-executor/build/contracts/src/UpgradeExecutor.sol/ -COPY --from=contracts-builder workspace/safe-smart-account/build/ workspace/safe-smart-account/build/ +COPY --from=contracts-builder workspace/safe-smart-account/build/ safe-smart-account/build/ COPY --from=contracts-builder workspace/.make/ .make/ COPY --from=prover-header-export / target/ COPY --from=brotli-library-export / target/ From 5e2881535526c8cf14a33336f6bc82b242c20e6a Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 8 Jul 2024 20:29:40 +0530 Subject: [PATCH 12/24] fix --- .dockerignore | 1 + Dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/.dockerignore b/.dockerignore index e142afd073..21fe19b1fc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,6 +9,7 @@ go-ethereum/tests **/*.yml contracts/build contracts/cache/ +safe-smart-account/ solgen/go **/node_modules diff --git a/Dockerfile b/Dockerfile index 6194581546..6d3662a455 100644 --- a/Dockerfile +++ b/Dockerfile @@ -84,6 +84,7 @@ COPY ./wavmio ./wavmio COPY ./zeroheavy ./zeroheavy COPY ./contracts/src/precompiles/ ./contracts/src/precompiles/ COPY ./contracts/package.json ./contracts/yarn.lock ./contracts/ +COPY ./safe-smart-account ./safe-smart-account COPY ./solgen/gen.go ./solgen/ COPY ./fastcache ./fastcache COPY ./go-ethereum ./go-ethereum From a736fd81199ff8e78180663b834291ea6252a1a7 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Mon, 8 Jul 2024 20:32:25 +0530 Subject: [PATCH 13/24] fix --- .dockerignore | 2 +- Dockerfile | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 21fe19b1fc..51424900e8 100644 --- a/.dockerignore +++ b/.dockerignore @@ -9,7 +9,7 @@ go-ethereum/tests **/*.yml contracts/build contracts/cache/ -safe-smart-account/ +safe-smart-account/build/ solgen/go **/node_modules diff --git a/Dockerfile b/Dockerfile index 6d3662a455..f58b46fff7 100644 --- a/Dockerfile +++ b/Dockerfile @@ -182,6 +182,7 @@ COPY ./Makefile ./ COPY ./arbitrator ./arbitrator COPY ./solgen ./solgen COPY ./contracts ./contracts +COPY ./safe-smart-account ./safe-smart-account RUN NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-replay-env FROM debian:bookworm-slim as machine-versions From 89227b14f0e02c1773b9d5e742a39f3ede439a0f Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Tue, 9 Jul 2024 15:57:10 +0530 Subject: [PATCH 14/24] update test --- system_tests/staker_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/system_tests/staker_test.go b/system_tests/staker_test.go index 955ccc6e9f..1ae2d07610 100644 --- a/system_tests/staker_test.go +++ b/system_tests/staker_test.go @@ -828,7 +828,7 @@ func TestFastConfirmationWithSafe(t *testing.T) { } valWalletB, err := validatorwallet.NewEOA(dpB, l2nodeB.DeployInfo.Rollup, l2nodeB.L1Reader.Client(), func() uint64 { return 0 }) Require(t, err) - valConfig.Strategy = "MakeNodes" + valConfig.Strategy = "stakelatest" statelessB, err := staker.NewStatelessBlockValidator( l2nodeB.InboxReader, l2nodeB.InboxTracker, From 08320f68f58c060c358748b928cfd22bf0e1d135 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Tue, 9 Jul 2024 20:38:25 +0530 Subject: [PATCH 15/24] pin safe module to 192c7dc --- safe-smart-account | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/safe-smart-account b/safe-smart-account index 46f7c60ef4..192c7dc672 160000 --- a/safe-smart-account +++ b/safe-smart-account @@ -1 +1 @@ -Subproject commit 46f7c60ef4ba68cd2ed2bad8ab9f606fe9dab40f +Subproject commit 192c7dc67290940fcbc75165522bb86a37187069 From bc279c2808b17c8ef940f9e4b3617c19e533a4eb Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Tue, 9 Jul 2024 21:02:18 +0530 Subject: [PATCH 16/24] fix ci --- Dockerfile | 2 +- Makefile | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dockerfile b/Dockerfile index 37013d6d73..89f1b07da4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -33,7 +33,7 @@ COPY contracts/package.json contracts/yarn.lock contracts/ RUN cd contracts && yarn install COPY contracts contracts/ COPY safe-smart-account safe-smart-account/ -RUN cd safe-smart-account && npm i +RUN cd safe-smart-account && yarn install COPY Makefile . RUN . ~/.bashrc && NITRO_BUILD_IGNORE_TIMESTAMPS=1 make build-solidity diff --git a/Makefile b/Makefile index 931d504996..b0d8116c97 100644 --- a/Makefile +++ b/Makefile @@ -495,13 +495,13 @@ contracts/test/prover/proofs/%.json: $(arbitrator_cases)/%.wasm $(prover_bin) @touch $@ .make/solidity: $(DEP_PREDICATE) safe-smart-account/contracts/*/*.sol safe-smart-account/contracts/*.sol contracts/src/*/*.sol .make/yarndeps $(ORDER_ONLY_PREDICATE) .make - (cd safe-smart-account && npm run build) + yarn --cwd safe-smart-account build yarn --cwd contracts build yarn --cwd contracts build:forge:yul @touch $@ .make/yarndeps: $(DEP_PREDICATE) contracts/package.json contracts/yarn.lock $(ORDER_ONLY_PREDICATE) .make - (cd safe-smart-account && npm i) + yarn --cwd safe-smart-account install yarn --cwd contracts install @touch $@ From 29b51d2abce6521680e76b31f29e3366e954315a Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Wed, 10 Jul 2024 00:25:13 +0530 Subject: [PATCH 17/24] fix --- staker/staker.go | 4 ++-- system_tests/staker_test.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/staker/staker.go b/staker/staker.go index f031623a6d..51647f343f 100644 --- a/staker/staker.go +++ b/staker/staker.go @@ -937,7 +937,7 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv s.bringActiveUntilNode = info.LatestStakedNode + 1 } info.CanProgress = false - return nil + return s.tryFastConfirmation(ctx, action.assertion.AfterState.GlobalState.BlockHash, action.assertion.AfterState.GlobalState.SendRoot) } // Details are already logged with more details in generateNodeAction @@ -992,7 +992,7 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv hash: action.hash, } } - return nil + return s.tryFastConfirmationNodeNumber(ctx, action.number) } log.Info("staking on existing node", "node", action.number) // We'll return early if we already havea stake diff --git a/system_tests/staker_test.go b/system_tests/staker_test.go index 1ae2d07610..5a1cb57fc0 100644 --- a/system_tests/staker_test.go +++ b/system_tests/staker_test.go @@ -828,7 +828,7 @@ func TestFastConfirmationWithSafe(t *testing.T) { } valWalletB, err := validatorwallet.NewEOA(dpB, l2nodeB.DeployInfo.Rollup, l2nodeB.L1Reader.Client(), func() uint64 { return 0 }) Require(t, err) - valConfig.Strategy = "stakelatest" + valConfig.Strategy = "watchtower" statelessB, err := staker.NewStatelessBlockValidator( l2nodeB.InboxReader, l2nodeB.InboxTracker, From 95981b60e6d76c9f4b810bf8be3e753a6097a3c8 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Fri, 19 Jul 2024 15:18:22 +0530 Subject: [PATCH 18/24] Changes based on PR comments --- staker/fast_confirm.go | 249 +++++++++++++++ staker/staker.go | 231 +------------- system_tests/common_test.go | 24 +- system_tests/fast_confirm_test.go | 511 ++++++++++++++++++++++++++++++ system_tests/staker_test.go | 479 ---------------------------- 5 files changed, 780 insertions(+), 714 deletions(-) create mode 100644 staker/fast_confirm.go create mode 100644 system_tests/fast_confirm_test.go diff --git a/staker/fast_confirm.go b/staker/fast_confirm.go new file mode 100644 index 0000000000..72945043f0 --- /dev/null +++ b/staker/fast_confirm.go @@ -0,0 +1,249 @@ +package staker + +import ( + "context" + "errors" + "fmt" + "math/big" + "sort" + + "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/log" + + "github.com/offchainlabs/nitro/solgen/go/contractsgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" + "github.com/offchainlabs/nitro/staker/txbuilder" + "github.com/offchainlabs/nitro/util/headerreader" +) + +type FastConfirmSafe struct { + rollupAddress common.Address + safe *contractsgen.Safe + owners []common.Address + threshold uint64 + fastConfirmNextNodeMethod abi.Method + builder *txbuilder.Builder + wallet ValidatorWalletInterface + gasRefunder common.Address + l1Reader *headerreader.HeaderReader + fastConfirmApprover common.Address +} + +func NewFastConfirmSafe( + callOpts bind.CallOpts, + rollupAddress common.Address, + fastConfirmSafeAddress common.Address, + builder *txbuilder.Builder, + wallet ValidatorWalletInterface, + gasRefunder common.Address, + l1Reader *headerreader.HeaderReader, + fastConfirmApprover common.Address, +) (*FastConfirmSafe, error) { + fastConfirmSafe := &FastConfirmSafe{ + builder: builder, + rollupAddress: rollupAddress, + wallet: wallet, + gasRefunder: gasRefunder, + l1Reader: l1Reader, + fastConfirmApprover: fastConfirmApprover, + } + safe, err := contractsgen.NewSafe(fastConfirmSafeAddress, builder) + if err != nil { + return nil, err + } + fastConfirmSafe.safe = safe + owners, err := safe.GetOwners(&callOpts) + if err != nil { + return nil, err + } + + // This is needed because safe contract needs owners to be sorted. + sort.Slice(owners, func(i, j int) bool { + return owners[i].Cmp(owners[j]) < 0 + }) + fastConfirmSafe.owners = owners + threshold, err := safe.GetThreshold(&callOpts) + if err != nil { + return nil, err + } + fastConfirmSafe.threshold = threshold.Uint64() + rollupUserLogicAbi, err := rollupgen.RollupUserLogicMetaData.GetAbi() + if err != nil { + return nil, err + } + fastConfirmNextNodeMethod, ok := rollupUserLogicAbi.Methods["fastConfirmNextNode"] + if !ok { + return nil, errors.New("RollupUserLogic ABI missing fastConfirmNextNode method") + } + fastConfirmSafe.fastConfirmNextNodeMethod = fastConfirmNextNodeMethod + return fastConfirmSafe, nil +} + +func (s *Staker) tryFastConfirmationNodeNumber(ctx context.Context, number uint64) error { + if !s.config.EnableFastConfirmation { + return nil + } + nodeInfo, err := s.rollup.LookupNode(ctx, number) + if err != nil { + return err + } + return s.tryFastConfirmation(ctx, nodeInfo.AfterState().GlobalState.BlockHash, nodeInfo.AfterState().GlobalState.SendRoot) +} + +func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error { + if !s.config.EnableFastConfirmation { + return nil + } + if s.fastConfirmSafe != nil { + return s.fastConfirmSafe.tryFastConfirmation(ctx, blockHash, sendRoot) + } + auth, err := s.builder.Auth(ctx) + if err != nil { + return err + } + _, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot) + return err +} + +func (f *FastConfirmSafe) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error { + fastConfirmCallData, err := f.createFastConfirmCalldata(blockHash, sendRoot) + if err != nil { + return err + } + callOpts := &bind.CallOpts{Context: ctx} + // Current nonce of the safe. + nonce, err := f.safe.Nonce(callOpts) + if err != nil { + return err + } + // Hash of the safe transaction. + safeTxHash, err := f.safe.GetTransactionHash( + callOpts, + f.rollupAddress, + big.NewInt(0), + fastConfirmCallData, + 0, + big.NewInt(0), + big.NewInt(0), + big.NewInt(0), + common.Address{}, + common.Address{}, + nonce, + ) + if err != nil { + return err + } + if !f.wallet.CanBatchTxs() { + err = f.flushTransactions(ctx) + if err != nil { + return err + } + } + auth, err := f.builder.Auth(ctx) + if err != nil { + return err + } + _, err = f.safe.ApproveHash(auth, safeTxHash) + if err != nil { + return err + } + if !f.wallet.CanBatchTxs() { + err = f.flushTransactions(ctx) + if err != nil { + return err + } + } + return f.checkApprovedHashAndExecTransaction(ctx, fastConfirmCallData, safeTxHash) +} + +func (f *FastConfirmSafe) flushTransactions(ctx context.Context) error { + arbTx, err := f.wallet.ExecuteTransactions(ctx, f.builder, f.gasRefunder) + if err != nil { + return err + } + if arbTx != nil { + _, err = f.l1Reader.WaitForTxApproval(ctx, arbTx) + if err == nil { + log.Info("successfully executed staker transaction", "hash", arbTx.Hash()) + } else { + return fmt.Errorf("error waiting for tx receipt: %w", err) + } + } + f.builder.ClearTransactions() + return nil +} + +func (f *FastConfirmSafe) createFastConfirmCalldata( + blockHash common.Hash, sendRoot common.Hash, +) ([]byte, error) { + calldata, err := f.fastConfirmNextNodeMethod.Inputs.Pack( + blockHash, + sendRoot, + ) + if err != nil { + return nil, err + } + fullCalldata := append([]byte{}, f.fastConfirmNextNodeMethod.ID...) + fullCalldata = append(fullCalldata, calldata...) + return fullCalldata, nil +} + +func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Context, fastConfirmCallData []byte, safeTxHash [32]byte) error { + var signatures []byte + approvedHashCount := uint64(0) + for _, owner := range f.owners { + var approved *big.Int + // No need check if fastConfirmApprover has approved the hash, + // since checkApprovedHashAndExecTransaction is called only after fastConfirmApprover has approved the hash. + if f.fastConfirmApprover == owner { + approved = common.Big1 + } else { + var err error + approved, err = f.safe.ApprovedHashes(&bind.CallOpts{Context: ctx}, owner, safeTxHash) + if err != nil { + return err + } + } + + // If the owner has approved the hash, we add the signature to the transaction. + // We add the signature in the format r, s, v. + // We set v to 1, as it is the only possible value for a approved hash. + // We set r to the owner's address. + // We set s to the empty hash. + // Refer to the Safe contract for more information. + if approved.Cmp(common.Big1) == 0 { + approvedHashCount++ + v := uint8(1) + r := common.BytesToHash(owner.Bytes()) + s := common.Hash{} + signatures = append(signatures, r.Bytes()...) + signatures = append(signatures, s.Bytes()...) + signatures = append(signatures, v) + } + } + if approvedHashCount >= f.threshold { + auth, err := f.builder.Auth(ctx) + if err != nil { + return err + } + _, err = f.safe.ExecTransaction( + auth, + f.rollupAddress, + big.NewInt(0), + fastConfirmCallData, + 0, + big.NewInt(0), + big.NewInt(0), + big.NewInt(0), + common.Address{}, + common.Address{}, + signatures, + ) + if err != nil { + return err + } + } + return nil +} diff --git a/staker/staker.go b/staker/staker.go index c2add6a390..5917559079 100644 --- a/staker/staker.go +++ b/staker/staker.go @@ -9,11 +9,9 @@ import ( "fmt" "math/big" "runtime/debug" - "sort" "strings" "time" - "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/core/types" @@ -25,8 +23,6 @@ import ( "github.com/offchainlabs/nitro/arbnode/dataposter" "github.com/offchainlabs/nitro/arbutil" "github.com/offchainlabs/nitro/cmd/genericconf" - "github.com/offchainlabs/nitro/solgen/go/contractsgen" - "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker/txbuilder" "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/arbmath" @@ -96,6 +92,7 @@ type L1ValidatorConfig struct { ParentChainWallet genericconf.WalletConfig `koanf:"parent-chain-wallet"` EnableFastConfirmation bool `koanf:"enable-fast-confirmation"` FastConfirmSafeAddress string `koanf:"fast-confirm-safe-address"` + FastConfirmApprover string `koanf:"fast-confirm-approver"` LogQueryBatchSize uint64 `koanf:"log-query-batch-size" reload:"hot"` strategy StakerStrategy @@ -165,6 +162,7 @@ var DefaultL1ValidatorConfig = L1ValidatorConfig{ ParentChainWallet: DefaultValidatorL1WalletConfig, EnableFastConfirmation: false, FastConfirmSafeAddress: "", + FastConfirmApprover: "", LogQueryBatchSize: 0, } @@ -188,6 +186,7 @@ var TestL1ValidatorConfig = L1ValidatorConfig{ ParentChainWallet: DefaultValidatorL1WalletConfig, EnableFastConfirmation: false, FastConfirmSafeAddress: "", + FastConfirmApprover: "", LogQueryBatchSize: 0, } @@ -220,6 +219,7 @@ func L1ValidatorConfigAddOptions(prefix string, f *flag.FlagSet) { genericconf.WalletConfigAddOptions(prefix+".parent-chain-wallet", f, DefaultL1ValidatorConfig.ParentChainWallet.Pathname) f.Bool(prefix+".enable-fast-confirmation", DefaultL1ValidatorConfig.EnableFastConfirmation, "enable fast confirmation") f.String(prefix+".fast-confirm-safe-address", DefaultL1ValidatorConfig.FastConfirmSafeAddress, "safe address for fast confirmation") + f.String(prefix+".fast-confirm-approver", DefaultL1ValidatorConfig.FastConfirmApprover, "approver address for fast confirmation") } type DangerousConfig struct { @@ -269,19 +269,6 @@ type Staker struct { fastConfirmSafe *FastConfirmSafe } -type FastConfirmSafe struct { - rollupAddress common.Address - safe *contractsgen.Safe - owners []common.Address - approvedHashesOwners map[common.Hash]map[common.Address]bool - threshold uint64 - fastConfirmNextNodeMethod abi.Method - builder *txbuilder.Builder - wallet ValidatorWalletInterface - gasRefunder common.Address - l1Reader *headerreader.HeaderReader -} - type ValidatorWalletInterface interface { Initialize(context.Context) error // Address must be able to be called concurrently with other functions @@ -339,6 +326,7 @@ func NewStaker( val.wallet, config.gasRefunder, l1Reader, + common.HexToAddress(config.FastConfirmApprover), ) if err != nil { return nil, err @@ -360,55 +348,6 @@ func NewStaker( }, nil } -func NewFastConfirmSafe( - callOpts bind.CallOpts, - rollupAddress common.Address, - fastConfirmSafeAddress common.Address, - builder *txbuilder.Builder, - wallet ValidatorWalletInterface, - gasRefunder common.Address, - l1Reader *headerreader.HeaderReader, -) (*FastConfirmSafe, error) { - fastConfirmSafe := &FastConfirmSafe{ - builder: builder, - rollupAddress: rollupAddress, - wallet: wallet, - gasRefunder: gasRefunder, - l1Reader: l1Reader, - } - safe, err := contractsgen.NewSafe(fastConfirmSafeAddress, builder) - if err != nil { - return nil, err - } - fastConfirmSafe.safe = safe - owners, err := safe.GetOwners(&callOpts) - if err != nil { - return nil, err - } - - // This is needed because safe contract needs owners to be sorted. - sort.Slice(owners, func(i, j int) bool { - return owners[i].Cmp(owners[j]) < 0 - }) - fastConfirmSafe.owners = owners - threshold, err := safe.GetThreshold(&callOpts) - if err != nil { - return nil, err - } - fastConfirmSafe.threshold = threshold.Uint64() - fastConfirmSafe.approvedHashesOwners = make(map[common.Hash]map[common.Address]bool) - rollupUserLogicAbi, err := rollupgen.RollupUserLogicMetaData.GetAbi() - if err != nil { - return nil, err - } - fastConfirmNextNodeMethod, ok := rollupUserLogicAbi.Methods["fastConfirmNextNode"] - if !ok { - return nil, errors.New("RollupUserLogic ABI missing fastConfirmNextNode method") - } - fastConfirmSafe.fastConfirmNextNodeMethod = fastConfirmNextNodeMethod - return fastConfirmSafe, nil -} - func (s *Staker) Initialize(ctx context.Context) error { err := s.L1Validator.Initialize(ctx) if err != nil { @@ -1036,166 +975,6 @@ func (s *Staker) advanceStake(ctx context.Context, info *OurStakerInfo, effectiv } } -func (s *Staker) tryFastConfirmationNodeNumber(ctx context.Context, number uint64) error { - if !s.config.EnableFastConfirmation { - return nil - } - nodeInfo, err := s.rollup.LookupNode(ctx, number) - if err != nil { - return err - } - return s.tryFastConfirmation(ctx, nodeInfo.AfterState().GlobalState.BlockHash, nodeInfo.AfterState().GlobalState.SendRoot) -} - -func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error { - if !s.config.EnableFastConfirmation { - return nil - } - if s.fastConfirmSafe != nil { - return s.fastConfirmSafe.tryFastConfirmation(ctx, blockHash, sendRoot) - } - auth, err := s.builder.Auth(ctx) - if err != nil { - return err - } - _, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot) - return err -} - -func (f *FastConfirmSafe) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error { - fastConfirmCallData, err := f.createFastConfirmCalldata(blockHash, sendRoot) - if err != nil { - return err - } - callOpts := &bind.CallOpts{Context: ctx} - // Current nonce of the safe. - nonce, err := f.safe.Nonce(callOpts) - if err != nil { - return err - } - // Hash of the safe transaction. - safeTxHash, err := f.safe.GetTransactionHash( - callOpts, - f.rollupAddress, - big.NewInt(0), - fastConfirmCallData, - 0, - big.NewInt(0), - big.NewInt(0), - big.NewInt(0), - common.Address{}, - common.Address{}, - nonce, - ) - if err != nil { - return err - } - arbTx, err := f.wallet.ExecuteTransactions(ctx, f.builder, f.gasRefunder) - if err != nil { - return err - } - if arbTx != nil { - _, err = f.l1Reader.WaitForTxApproval(ctx, arbTx) - if err == nil { - log.Info("successfully executed staker transaction", "hash", arbTx.Hash()) - } else { - return fmt.Errorf("error waiting for tx receipt: %w", err) - } - } - f.builder.ClearTransactions() - auth, err := f.builder.Auth(ctx) - if err != nil { - return err - } - _, err = f.safe.ApproveHash(auth, safeTxHash) - if err != nil { - return err - } - arbTx, err = f.wallet.ExecuteTransactions(ctx, f.builder, f.gasRefunder) - if err != nil { - return err - } - if arbTx != nil { - _, err = f.l1Reader.WaitForTxApproval(ctx, arbTx) - if err == nil { - log.Info("successfully executed staker transaction", "hash", arbTx.Hash()) - } else { - return fmt.Errorf("error waiting for tx receipt: %w", err) - } - } - f.builder.ClearTransactions() - if _, ok := f.approvedHashesOwners[safeTxHash]; !ok { - f.approvedHashesOwners[safeTxHash] = make(map[common.Address]bool) - } - return f.checkApprovedHashAndExecTransaction(ctx, fastConfirmCallData, safeTxHash) -} - -func (f *FastConfirmSafe) createFastConfirmCalldata( - blockHash common.Hash, sendRoot common.Hash, -) ([]byte, error) { - calldata, err := f.fastConfirmNextNodeMethod.Inputs.Pack( - blockHash, - sendRoot, - ) - if err != nil { - return nil, err - } - fullCalldata := append([]byte{}, f.fastConfirmNextNodeMethod.ID...) - fullCalldata = append(fullCalldata, calldata...) - return fullCalldata, nil -} - -func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Context, fastConfirmCallData []byte, safeTxHash [32]byte) error { - var signatures []byte - for _, owner := range f.owners { - if _, ok := f.approvedHashesOwners[safeTxHash][owner]; !ok { - iter, err := f.safe.FilterApproveHash(&bind.FilterOpts{Context: ctx}, [][32]byte{safeTxHash}, []common.Address{owner}) - if err != nil { - return err - } - for iter.Next() { - f.approvedHashesOwners[iter.Event.ApprovedHash][iter.Event.Owner] = true - } - } - // If the owner has approved the hash, we add the signature to the transaction. - // We add the signature in the format r, s, v. - // We set v to 1, as it is the only possible value for a approved hash. - // We set r to the owner's address. - // We set s to the empty hash. - // Refer to the Safe contract for more information. - if _, ok := f.approvedHashesOwners[safeTxHash][owner]; ok { - v := uint8(1) - r := common.BytesToHash(owner.Bytes()) - s := common.Hash{} - signatures = append(signatures, r.Bytes()...) - signatures = append(signatures, s.Bytes()...) - signatures = append(signatures, v) - } - } - if uint64(len(f.approvedHashesOwners[safeTxHash])) >= f.threshold { - auth, err := f.builder.Auth(ctx) - if err != nil { - return err - } - _, err = f.safe.ExecTransaction( - auth, - f.rollupAddress, - big.NewInt(0), - fastConfirmCallData, - 0, - big.NewInt(0), - big.NewInt(0), - big.NewInt(0), - common.Address{}, - common.Address{}, - signatures, - ) - if err != nil { - return err - } - } - return nil -} func (s *Staker) createConflict(ctx context.Context, info *StakerInfo) error { if info.CurrentChallenge != nil { return nil diff --git a/system_tests/common_test.go b/system_tests/common_test.go index b0748f8639..ff184340ab 100644 --- a/system_tests/common_test.go +++ b/system_tests/common_test.go @@ -164,12 +164,13 @@ type NodeBuilder struct { L2Info info // L1, L2 Node parameters - dataDir string - isSequencer bool - takeOwnership bool - withL1 bool - addresses *chaininfo.RollupAddresses - initMessage *arbostypes.ParsedInitMessage + dataDir string + isSequencer bool + takeOwnership bool + withL1 bool + addresses *chaininfo.RollupAddresses + initMessage *arbostypes.ParsedInitMessage + withProdConfirmPeriodBlocks bool // Created nodes L1 *TestClient @@ -209,6 +210,11 @@ func (b *NodeBuilder) WithArbOSVersion(arbosVersion uint64) *NodeBuilder { return b } +func (b *NodeBuilder) WithProdConfirmPeriodBlocks() *NodeBuilder { + b.withProdConfirmPeriodBlocks = true + return b +} + func (b *NodeBuilder) WithWasmRootDir(wasmRootDir string) *NodeBuilder { b.valnodeConfig.Wasm.RootPath = wasmRootDir return b @@ -253,7 +259,7 @@ func (b *NodeBuilder) BuildL1(t *testing.T) { b.L1Info, b.L1.Client, b.L1.L1Backend, b.L1.Stack = createTestL1BlockChain(t, b.L1Info) locator, err := server_common.NewMachineLocator(b.valnodeConfig.Wasm.RootPath) Require(t, err) - b.addresses, b.initMessage = DeployOnTestL1(t, b.ctx, b.L1Info, b.L1.Client, b.chainConfig, locator.LatestWasmModuleRoot()) + b.addresses, b.initMessage = DeployOnTestL1(t, b.ctx, b.L1Info, b.L1.Client, b.chainConfig, locator.LatestWasmModuleRoot(), b.withProdConfirmPeriodBlocks) b.L1.cleanup = func() { requireClose(t, b.L1.Stack) } } @@ -883,7 +889,7 @@ func getInitMessage(ctx context.Context, t *testing.T, l1client client, addresse } func DeployOnTestL1( - t *testing.T, ctx context.Context, l1info info, l1client client, chainConfig *params.ChainConfig, wasmModuleRoot common.Hash, + t *testing.T, ctx context.Context, l1info info, l1client client, chainConfig *params.ChainConfig, wasmModuleRoot common.Hash, prodConfirmPeriodBlocks bool, ) (*chaininfo.RollupAddresses, *arbostypes.ParsedInitMessage) { l1info.GenerateAccount("RollupOwner") l1info.GenerateAccount("Sequencer") @@ -915,7 +921,7 @@ func DeployOnTestL1( []common.Address{l1info.GetAddress("Sequencer")}, l1info.GetAddress("RollupOwner"), 0, - arbnode.GenerateRollupConfig(false, wasmModuleRoot, l1info.GetAddress("RollupOwner"), chainConfig, serializedChainConfig, common.Address{}), + arbnode.GenerateRollupConfig(prodConfirmPeriodBlocks, wasmModuleRoot, l1info.GetAddress("RollupOwner"), chainConfig, serializedChainConfig, common.Address{}), nativeToken, maxDataSize, false, diff --git a/system_tests/fast_confirm_test.go b/system_tests/fast_confirm_test.go new file mode 100644 index 0000000000..750175ba1b --- /dev/null +++ b/system_tests/fast_confirm_test.go @@ -0,0 +1,511 @@ +package arbtest + +import ( + "context" + "errors" + "math/big" + "strings" + "testing" + "time" + + "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/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/params" + + "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbnode/dataposter/externalsignertest" + "github.com/offchainlabs/nitro/arbnode/dataposter/storage" + "github.com/offchainlabs/nitro/arbos/l2pricing" + "github.com/offchainlabs/nitro/solgen/go/contractsgen" + "github.com/offchainlabs/nitro/solgen/go/proxiesgen" + "github.com/offchainlabs/nitro/solgen/go/rollupgen" + "github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen" + "github.com/offchainlabs/nitro/staker" + "github.com/offchainlabs/nitro/staker/validatorwallet" + "github.com/offchainlabs/nitro/util" + "github.com/offchainlabs/nitro/validator/valnode" +) + +func TestFastConfirmation(t *testing.T) { + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + srv := externalsignertest.NewServer(t) + go func() { + if err := srv.Start(); err != nil { + log.Error("Failed to start external signer server:", err) + return + } + }() + var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs + + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).WithProdConfirmPeriodBlocks() + builder.L2Info = NewBlockChainTestInfo( + t, + types.NewArbitrumSigner(types.NewLondonSigner(builder.chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), + transferGas, + ) + + builder.nodeConfig.BatchPoster.MaxDelay = -1000 * time.Hour + cleanup := builder.Build(t) + defer cleanup() + + addNewBatchPoster(ctx, t, builder, srv.Address) + + builder.L1.SendWaitTestTransactions(t, []*types.Transaction{ + builder.L1Info.PrepareTxTo("Faucet", &srv.Address, 30000, big.NewInt(1).Mul(big.NewInt(1e18), big.NewInt(1e18)), nil)}) + + l2node := builder.L2.ConsensusNode + execNode := builder.L2.ExecNode + + config := arbnode.ConfigDefaultL1Test() + config.Sequencer = false + config.DelayedSequencer.Enable = false + config.BatchPoster.Enable = false + builder.execConfig.Sequencer.Enable = false + + builder.BridgeBalance(t, "Faucet", big.NewInt(1).Mul(big.NewInt(params.Ether), big.NewInt(10000))) + + deployAuth := builder.L1Info.GetDefaultTransactOpts("RollupOwner", ctx) + + balance := big.NewInt(params.Ether) + balance.Mul(balance, big.NewInt(100)) + builder.L1.TransferBalance(t, "Faucet", "Validator", balance, builder.L1Info) + l1auth := builder.L1Info.GetDefaultTransactOpts("Validator", ctx) + + valWalletAddrPtr, err := validatorwallet.GetValidatorWalletContract(ctx, l2node.DeployInfo.ValidatorWalletCreator, 0, &l1auth, l2node.L1Reader, true) + Require(t, err) + valWalletAddr := *valWalletAddrPtr + valWalletAddrCheck, err := validatorwallet.GetValidatorWalletContract(ctx, l2node.DeployInfo.ValidatorWalletCreator, 0, &l1auth, l2node.L1Reader, true) + Require(t, err) + if valWalletAddr == *valWalletAddrCheck { + Require(t, err, "didn't cache validator wallet address", valWalletAddr.String(), "vs", valWalletAddrCheck.String()) + } + + rollup, err := rollupgen.NewRollupAdminLogic(l2node.DeployInfo.Rollup, builder.L1.Client) + Require(t, err) + + upgradeExecutor, err := upgrade_executorgen.NewUpgradeExecutor(l2node.DeployInfo.UpgradeExecutor, builder.L1.Client) + Require(t, err, "unable to bind upgrade executor") + rollupABI, err := abi.JSON(strings.NewReader(rollupgen.RollupAdminLogicABI)) + Require(t, err, "unable to parse rollup ABI") + + setValidatorCalldata, err := rollupABI.Pack("setValidator", []common.Address{valWalletAddr, srv.Address}, []bool{true, true}) + Require(t, err, "unable to generate setValidator calldata") + tx, err := upgradeExecutor.ExecuteCall(&deployAuth, l2node.DeployInfo.Rollup, setValidatorCalldata) + Require(t, err, "unable to set validators") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + setMinAssertPeriodCalldata, err := rollupABI.Pack("setMinimumAssertionPeriod", big.NewInt(1)) + Require(t, err, "unable to generate setMinimumAssertionPeriod calldata") + tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2node.DeployInfo.Rollup, setMinAssertPeriodCalldata) + Require(t, err, "unable to set minimum assertion period") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + setAnyTrustFastConfirmerCalldata, err := rollupABI.Pack("setAnyTrustFastConfirmer", valWalletAddr) + Require(t, err, "unable to generate setAnyTrustFastConfirmer calldata") + tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2node.DeployInfo.Rollup, setAnyTrustFastConfirmerCalldata) + Require(t, err, "unable to set anytrust fast confirmer") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + valConfig := staker.TestL1ValidatorConfig + valConfig.EnableFastConfirmation = true + parentChainID, err := builder.L1.Client.ChainID(ctx) + if err != nil { + t.Fatalf("Failed to get parent chain id: %v", err) + } + dp, err := arbnode.StakerDataposter( + ctx, + rawdb.NewTable(l2node.ArbDB, storage.StakerPrefix), + l2node.L1Reader, + &l1auth, NewFetcherFromConfig(arbnode.ConfigDefaultL1NonSequencerTest()), + nil, + parentChainID, + ) + 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) + valConfig.Strategy = "MakeNodes" + + _, valStack := createTestValidationNode(t, ctx, &valnode.TestValidationConfig) + blockValidatorConfig := staker.TestBlockValidatorConfig + + stateless, err := staker.NewStatelessBlockValidator( + l2node.InboxReader, + l2node.InboxTracker, + l2node.TxStreamer, + execNode, + l2node.ArbDB, + nil, + StaticFetcherFrom(t, &blockValidatorConfig), + valStack, + ) + Require(t, err) + err = stateless.Start(ctx) + Require(t, err) + stakerA, err := staker.NewStaker( + l2node.L1Reader, + valWallet, + bind.CallOpts{}, + valConfig, + nil, + stateless, + nil, + nil, + l2node.DeployInfo.ValidatorUtils, + nil, + ) + Require(t, err) + err = stakerA.Initialize(ctx) + if stakerA.Strategy() != staker.WatchtowerStrategy { + err = valWallet.Initialize(ctx) + Require(t, err) + } + Require(t, err) + cfg := arbnode.ConfigDefaultL1NonSequencerTest() + signerCfg, err := externalSignerTestCfg(srv.Address, srv.URL()) + if err != nil { + t.Fatalf("Error getting external signer config: %v", err) + } + cfg.Staker.DataPoster.ExternalSigner = *signerCfg + + builder.L2Info.GenerateAccount("BackgroundUser") + tx = builder.L2Info.PrepareTx("Faucet", "BackgroundUser", builder.L2Info.TransferGas, balance, nil) + err = builder.L2.Client.SendTransaction(ctx, tx) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + // Continually make L2 transactions in a background thread + backgroundTxsCtx, cancelBackgroundTxs := context.WithCancel(ctx) + backgroundTxsShutdownChan := make(chan struct{}) + defer (func() { + cancelBackgroundTxs() + <-backgroundTxsShutdownChan + })() + go (func() { + defer close(backgroundTxsShutdownChan) + err := makeBackgroundTxs(backgroundTxsCtx, builder) + if !errors.Is(err, context.Canceled) { + log.Warn("error making background txs", "err", err) + } + })() + + latestConfirmBeforeAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) + Require(t, err) + tx, err = stakerA.Act(ctx) + Require(t, err) + if tx != nil { + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + } + latestConfirmAfterAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) + Require(t, err) + if latestConfirmAfterAct <= latestConfirmBeforeAct { + Fatal(t, "staker A didn't advance the latest confirmed node") + } +} + +func TestFastConfirmationWithSafe(t *testing.T) { + ctx, cancelCtx := context.WithCancel(context.Background()) + defer cancelCtx() + srv := externalsignertest.NewServer(t) + go func() { + if err := srv.Start(); err != nil { + log.Error("Failed to start external signer server:", err) + return + } + }() + var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs + + // Create a node with a large confirm period to ensure that the staker can't confirm without the fast confirmer. + builder := NewNodeBuilder(ctx).DefaultConfig(t, true).WithProdConfirmPeriodBlocks() + builder.L2Info = NewBlockChainTestInfo( + t, + types.NewArbitrumSigner(types.NewLondonSigner(builder.chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), + transferGas, + ) + + builder.nodeConfig.BatchPoster.MaxDelay = -1000 * time.Hour + cleanupA := builder.Build(t) + defer cleanupA() + + addNewBatchPoster(ctx, t, builder, srv.Address) + + builder.L1.SendWaitTestTransactions(t, []*types.Transaction{ + builder.L1Info.PrepareTxTo("Faucet", &srv.Address, 30000, big.NewInt(1).Mul(big.NewInt(1e18), big.NewInt(1e18)), nil)}) + + l2nodeA := builder.L2.ConsensusNode + execNodeA := builder.L2.ExecNode + + config := arbnode.ConfigDefaultL1Test() + config.Sequencer = false + config.DelayedSequencer.Enable = false + config.BatchPoster.Enable = false + builder.execConfig.Sequencer.Enable = false + testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: config}) + defer cleanupB() + + l2nodeB := testClientB.ConsensusNode + execNodeB := testClientB.ExecNode + + nodeAGenesis := execNodeA.Backend.APIBackend().CurrentHeader().Hash() + nodeBGenesis := execNodeB.Backend.APIBackend().CurrentHeader().Hash() + if nodeAGenesis != nodeBGenesis { + Fatal(t, "node A L2 genesis hash", nodeAGenesis, "!= node B L2 genesis hash", nodeBGenesis) + } + + builder.BridgeBalance(t, "Faucet", big.NewInt(1).Mul(big.NewInt(params.Ether), big.NewInt(10000))) + + deployAuth := builder.L1Info.GetDefaultTransactOpts("RollupOwner", ctx) + + balance := big.NewInt(params.Ether) + balance.Mul(balance, big.NewInt(100)) + builder.L1Info.GenerateAccount("ValidatorA") + builder.L1.TransferBalance(t, "Faucet", "ValidatorA", balance, builder.L1Info) + l1authA := builder.L1Info.GetDefaultTransactOpts("ValidatorA", ctx) + + builder.L1Info.GenerateAccount("ValidatorB") + builder.L1.TransferBalance(t, "Faucet", "ValidatorB", balance, builder.L1Info) + l1authB := builder.L1Info.GetDefaultTransactOpts("ValidatorB", ctx) + + valWalletAddrAPtr, err := validatorwallet.GetValidatorWalletContract(ctx, l2nodeA.DeployInfo.ValidatorWalletCreator, 0, &l1authA, l2nodeA.L1Reader, true) + Require(t, err) + valWalletAddrA := *valWalletAddrAPtr + valWalletAddrCheck, err := validatorwallet.GetValidatorWalletContract(ctx, l2nodeA.DeployInfo.ValidatorWalletCreator, 0, &l1authA, l2nodeA.L1Reader, true) + Require(t, err) + if valWalletAddrA == *valWalletAddrCheck { + Require(t, err, "didn't cache validator wallet address", valWalletAddrA.String(), "vs", valWalletAddrCheck.String()) + } + + rollup, err := rollupgen.NewRollupAdminLogic(l2nodeA.DeployInfo.Rollup, builder.L1.Client) + Require(t, err) + + upgradeExecutor, err := upgrade_executorgen.NewUpgradeExecutor(l2nodeA.DeployInfo.UpgradeExecutor, builder.L1.Client) + Require(t, err, "unable to bind upgrade executor") + rollupABI, err := abi.JSON(strings.NewReader(rollupgen.RollupAdminLogicABI)) + Require(t, err, "unable to parse rollup ABI") + + safeAddress := deploySafe(t, builder.L1, builder.L1.Client, deployAuth, []common.Address{valWalletAddrA, srv.Address}) + setValidatorCalldata, err := rollupABI.Pack("setValidator", []common.Address{valWalletAddrA, l1authB.From, srv.Address, safeAddress}, []bool{true, true, true, true}) + Require(t, err, "unable to generate setValidator calldata") + tx, err := upgradeExecutor.ExecuteCall(&deployAuth, l2nodeA.DeployInfo.Rollup, setValidatorCalldata) + Require(t, err, "unable to set validators") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + setMinAssertPeriodCalldata, err := rollupABI.Pack("setMinimumAssertionPeriod", big.NewInt(1)) + Require(t, err, "unable to generate setMinimumAssertionPeriod calldata") + tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2nodeA.DeployInfo.Rollup, setMinAssertPeriodCalldata) + Require(t, err, "unable to set minimum assertion period") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + setAnyTrustFastConfirmerCalldata, err := rollupABI.Pack("setAnyTrustFastConfirmer", safeAddress) + Require(t, err, "unable to generate setAnyTrustFastConfirmer calldata") + tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2nodeA.DeployInfo.Rollup, setAnyTrustFastConfirmerCalldata) + Require(t, err, "unable to set anytrust fast confirmer") + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + + valConfig := staker.TestL1ValidatorConfig + valConfig.EnableFastConfirmation = true + valConfig.FastConfirmSafeAddress = safeAddress.String() + valConfig.FastConfirmApprover = valWalletAddrA.String() + parentChainID, err := builder.L1.Client.ChainID(ctx) + if err != nil { + t.Fatalf("Failed to get parent chain id: %v", err) + } + dpA, err := arbnode.StakerDataposter( + ctx, + rawdb.NewTable(l2nodeB.ArbDB, storage.StakerPrefix), + l2nodeA.L1Reader, + &l1authA, NewFetcherFromConfig(arbnode.ConfigDefaultL1NonSequencerTest()), + nil, + parentChainID, + ) + if err != nil { + t.Fatalf("Error creating validator dataposter: %v", err) + } + valWalletA, err := validatorwallet.NewContract(dpA, nil, l2nodeA.DeployInfo.ValidatorWalletCreator, l2nodeA.DeployInfo.Rollup, l2nodeA.L1Reader, &l1authA, 0, func(common.Address) {}, func() uint64 { return valConfig.ExtraGas }) + Require(t, err) + valConfig.Strategy = "MakeNodes" + + _, valStack := createTestValidationNode(t, ctx, &valnode.TestValidationConfig) + blockValidatorConfig := staker.TestBlockValidatorConfig + + statelessA, err := staker.NewStatelessBlockValidator( + l2nodeA.InboxReader, + l2nodeA.InboxTracker, + l2nodeA.TxStreamer, + execNodeA, + l2nodeA.ArbDB, + nil, + StaticFetcherFrom(t, &blockValidatorConfig), + valStack, + ) + Require(t, err) + err = statelessA.Start(ctx) + Require(t, err) + stakerA, err := staker.NewStaker( + l2nodeA.L1Reader, + valWalletA, + bind.CallOpts{}, + valConfig, + nil, + statelessA, + nil, + nil, + l2nodeA.DeployInfo.ValidatorUtils, + nil, + ) + Require(t, err) + err = stakerA.Initialize(ctx) + if stakerA.Strategy() != staker.WatchtowerStrategy { + err = valWalletA.Initialize(ctx) + Require(t, err) + } + Require(t, err) + cfg := arbnode.ConfigDefaultL1NonSequencerTest() + signerCfg, err := externalSignerTestCfg(srv.Address, srv.URL()) + if err != nil { + t.Fatalf("Error getting external signer config: %v", err) + } + cfg.Staker.DataPoster.ExternalSigner = *signerCfg + dpB, err := arbnode.StakerDataposter( + ctx, + rawdb.NewTable(l2nodeB.ArbDB, storage.StakerPrefix), + l2nodeB.L1Reader, + &l1authB, NewFetcherFromConfig(cfg), + nil, + parentChainID, + ) + if err != nil { + t.Fatalf("Error creating validator dataposter: %v", err) + } + valWalletB, err := validatorwallet.NewEOA(dpB, l2nodeB.DeployInfo.Rollup, l2nodeB.L1Reader.Client(), func() uint64 { return 0 }) + Require(t, err) + valConfig.Strategy = "watchtower" + valConfig.FastConfirmApprover = srv.Address.String() + statelessB, err := staker.NewStatelessBlockValidator( + l2nodeB.InboxReader, + l2nodeB.InboxTracker, + l2nodeB.TxStreamer, + execNodeB, + l2nodeB.ArbDB, + nil, + StaticFetcherFrom(t, &blockValidatorConfig), + valStack, + ) + Require(t, err) + err = statelessB.Start(ctx) + Require(t, err) + stakerB, err := staker.NewStaker( + l2nodeB.L1Reader, + valWalletB, + bind.CallOpts{}, + valConfig, + nil, + statelessB, + nil, + nil, + l2nodeB.DeployInfo.ValidatorUtils, + nil, + ) + Require(t, err) + err = stakerB.Initialize(ctx) + Require(t, err) + if stakerB.Strategy() != staker.WatchtowerStrategy { + err = valWalletB.Initialize(ctx) + Require(t, err) + } + + builder.L2Info.GenerateAccount("BackgroundUser") + tx = builder.L2Info.PrepareTx("Faucet", "BackgroundUser", builder.L2Info.TransferGas, balance, nil) + err = builder.L2.Client.SendTransaction(ctx, tx) + Require(t, err) + _, err = builder.L2.EnsureTxSucceeded(tx) + Require(t, err) + + // Continually make L2 transactions in a background thread + backgroundTxsCtx, cancelBackgroundTxs := context.WithCancel(ctx) + backgroundTxsShutdownChan := make(chan struct{}) + defer (func() { + cancelBackgroundTxs() + <-backgroundTxsShutdownChan + })() + go (func() { + defer close(backgroundTxsShutdownChan) + err := makeBackgroundTxs(backgroundTxsCtx, builder) + if !errors.Is(err, context.Canceled) { + log.Warn("error making background txs", "err", err) + } + })() + + latestConfirmBeforeAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) + Require(t, err) + tx, err = stakerA.Act(ctx) + Require(t, err) + if tx != nil { + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + } + latestConfirmAfterStakerAAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) + Require(t, err) + if latestConfirmAfterStakerAAct != latestConfirmBeforeAct { + Fatal(t, "staker A alone advanced the latest confirmed node", latestConfirmAfterStakerAAct, "when it shouldn't have") + } + for j := 0; j < 5; j++ { + builder.L1.TransferBalance(t, "Faucet", "Faucet", common.Big0, builder.L1Info) + } + tx, err = stakerB.Act(ctx) + Require(t, err) + if tx != nil { + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + } + latestConfirmAfterStakerBAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) + Require(t, err) + if latestConfirmAfterStakerBAct <= latestConfirmBeforeAct { + Fatal(t, "staker A and B together didn't advance the latest confirmed node") + } +} + +func deploySafe(t *testing.T, l1 *TestClient, backend bind.ContractBackend, deployAuth bind.TransactOpts, owners []common.Address) common.Address { + safeAddress, tx, _, err := contractsgen.DeploySafeL2(&deployAuth, backend) + Require(t, err) + _, err = l1.EnsureTxSucceeded(tx) + Require(t, err) + safeProxyAddress, tx, _, err := proxiesgen.DeploySafeProxy(&deployAuth, backend, safeAddress) + Require(t, err) + _, err = l1.EnsureTxSucceeded(tx) + Require(t, err) + var safe *contractsgen.Safe + safe, err = contractsgen.NewSafe(safeProxyAddress, backend) + Require(t, err) + _, err = l1.EnsureTxSucceeded(tx) + Require(t, err) + tx, err = safe.Setup( + &deployAuth, + owners, + big.NewInt(2), + common.Address{}, + nil, + common.Address{}, + common.Address{}, + big.NewInt(0), + common.Address{}, + ) + Require(t, err) + _, err = l1.EnsureTxSucceeded(tx) + Require(t, err) + return safeProxyAddress +} diff --git a/system_tests/staker_test.go b/system_tests/staker_test.go index 75e902933b..52f16614f7 100644 --- a/system_tests/staker_test.go +++ b/system_tests/staker_test.go @@ -29,9 +29,7 @@ import ( "github.com/offchainlabs/nitro/arbnode/dataposter/externalsignertest" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/arbos/l2pricing" - "github.com/offchainlabs/nitro/solgen/go/contractsgen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" - "github.com/offchainlabs/nitro/solgen/go/proxiesgen" "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/solgen/go/upgrade_executorgen" "github.com/offchainlabs/nitro/staker" @@ -466,480 +464,3 @@ func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) func TestStakersCooperative(t *testing.T) { stakerTestImpl(t, false, false) } - -func TestFastConfirmation(t *testing.T) { - ctx, cancelCtx := context.WithCancel(context.Background()) - defer cancelCtx() - srv := externalsignertest.NewServer(t) - go func() { - if err := srv.Start(); err != nil { - log.Error("Failed to start external signer server:", err) - return - } - }() - var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs - - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) - builder.L2Info = NewBlockChainTestInfo( - t, - types.NewArbitrumSigner(types.NewLondonSigner(builder.chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), - transferGas, - ) - - builder.nodeConfig.BatchPoster.MaxDelay = -1000 * time.Hour - cleanup := builder.Build(t) - defer cleanup() - - addNewBatchPoster(ctx, t, builder, srv.Address) - - builder.L1.SendWaitTestTransactions(t, []*types.Transaction{ - builder.L1Info.PrepareTxTo("Faucet", &srv.Address, 30000, big.NewInt(1).Mul(big.NewInt(1e18), big.NewInt(1e18)), nil)}) - - l2node := builder.L2.ConsensusNode - execNode := builder.L2.ExecNode - - config := arbnode.ConfigDefaultL1Test() - config.Sequencer = false - config.DelayedSequencer.Enable = false - config.BatchPoster.Enable = false - builder.execConfig.Sequencer.Enable = false - - builder.BridgeBalance(t, "Faucet", big.NewInt(1).Mul(big.NewInt(params.Ether), big.NewInt(10000))) - - deployAuth := builder.L1Info.GetDefaultTransactOpts("RollupOwner", ctx) - - balance := big.NewInt(params.Ether) - balance.Mul(balance, big.NewInt(100)) - builder.L1.TransferBalance(t, "Faucet", "Validator", balance, builder.L1Info) - l1auth := builder.L1Info.GetDefaultTransactOpts("Validator", ctx) - - valWalletAddrPtr, err := validatorwallet.GetValidatorWalletContract(ctx, l2node.DeployInfo.ValidatorWalletCreator, 0, &l1auth, l2node.L1Reader, true) - Require(t, err) - valWalletAddr := *valWalletAddrPtr - valWalletAddrCheck, err := validatorwallet.GetValidatorWalletContract(ctx, l2node.DeployInfo.ValidatorWalletCreator, 0, &l1auth, l2node.L1Reader, true) - Require(t, err) - if valWalletAddr == *valWalletAddrCheck { - Require(t, err, "didn't cache validator wallet address", valWalletAddr.String(), "vs", valWalletAddrCheck.String()) - } - - rollup, err := rollupgen.NewRollupAdminLogic(l2node.DeployInfo.Rollup, builder.L1.Client) - Require(t, err) - - upgradeExecutor, err := upgrade_executorgen.NewUpgradeExecutor(l2node.DeployInfo.UpgradeExecutor, builder.L1.Client) - Require(t, err, "unable to bind upgrade executor") - rollupABI, err := abi.JSON(strings.NewReader(rollupgen.RollupAdminLogicABI)) - Require(t, err, "unable to parse rollup ABI") - - setValidatorCalldata, err := rollupABI.Pack("setValidator", []common.Address{valWalletAddr, srv.Address}, []bool{true, true}) - Require(t, err, "unable to generate setValidator calldata") - tx, err := upgradeExecutor.ExecuteCall(&deployAuth, l2node.DeployInfo.Rollup, setValidatorCalldata) - Require(t, err, "unable to set validators") - _, err = builder.L1.EnsureTxSucceeded(tx) - Require(t, err) - - setMinAssertPeriodCalldata, err := rollupABI.Pack("setMinimumAssertionPeriod", big.NewInt(1)) - Require(t, err, "unable to generate setMinimumAssertionPeriod calldata") - tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2node.DeployInfo.Rollup, setMinAssertPeriodCalldata) - Require(t, err, "unable to set minimum assertion period") - _, err = builder.L1.EnsureTxSucceeded(tx) - Require(t, err) - - setAnyTrustFastConfirmerCalldata, err := rollupABI.Pack("setAnyTrustFastConfirmer", valWalletAddr) - Require(t, err, "unable to generate setAnyTrustFastConfirmer calldata") - tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2node.DeployInfo.Rollup, setAnyTrustFastConfirmerCalldata) - Require(t, err, "unable to set anytrust fast confirmer") - _, err = builder.L1.EnsureTxSucceeded(tx) - Require(t, err) - - valConfig := staker.TestL1ValidatorConfig - valConfig.EnableFastConfirmation = true - parentChainID, err := builder.L1.Client.ChainID(ctx) - if err != nil { - t.Fatalf("Failed to get parent chain id: %v", err) - } - dp, err := arbnode.StakerDataposter( - ctx, - rawdb.NewTable(l2node.ArbDB, storage.StakerPrefix), - l2node.L1Reader, - &l1auth, NewFetcherFromConfig(arbnode.ConfigDefaultL1NonSequencerTest()), - nil, - parentChainID, - ) - 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) - valConfig.Strategy = "MakeNodes" - - _, valStack := createTestValidationNode(t, ctx, &valnode.TestValidationConfig) - blockValidatorConfig := staker.TestBlockValidatorConfig - - stateless, err := staker.NewStatelessBlockValidator( - l2node.InboxReader, - l2node.InboxTracker, - l2node.TxStreamer, - execNode, - l2node.ArbDB, - nil, - StaticFetcherFrom(t, &blockValidatorConfig), - valStack, - ) - Require(t, err) - err = stateless.Start(ctx) - Require(t, err) - stakerA, err := staker.NewStaker( - l2node.L1Reader, - valWallet, - bind.CallOpts{}, - valConfig, - nil, - stateless, - nil, - nil, - l2node.DeployInfo.ValidatorUtils, - nil, - ) - Require(t, err) - err = stakerA.Initialize(ctx) - if stakerA.Strategy() != staker.WatchtowerStrategy { - err = valWallet.Initialize(ctx) - Require(t, err) - } - Require(t, err) - cfg := arbnode.ConfigDefaultL1NonSequencerTest() - signerCfg, err := externalSignerTestCfg(srv.Address, srv.URL()) - if err != nil { - t.Fatalf("Error getting external signer config: %v", err) - } - cfg.Staker.DataPoster.ExternalSigner = *signerCfg - - builder.L2Info.GenerateAccount("BackgroundUser") - tx = builder.L2Info.PrepareTx("Faucet", "BackgroundUser", builder.L2Info.TransferGas, balance, nil) - err = builder.L2.Client.SendTransaction(ctx, tx) - Require(t, err) - _, err = builder.L2.EnsureTxSucceeded(tx) - Require(t, err) - - // Continually make L2 transactions in a background thread - backgroundTxsCtx, cancelBackgroundTxs := context.WithCancel(ctx) - backgroundTxsShutdownChan := make(chan struct{}) - defer (func() { - cancelBackgroundTxs() - <-backgroundTxsShutdownChan - })() - go (func() { - defer close(backgroundTxsShutdownChan) - err := makeBackgroundTxs(backgroundTxsCtx, builder) - if !errors.Is(err, context.Canceled) { - log.Warn("error making background txs", "err", err) - } - })() - - latestConfirmBeforeAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) - Require(t, err) - tx, err = stakerA.Act(ctx) - Require(t, err) - if tx != nil { - _, err = builder.L1.EnsureTxSucceeded(tx) - Require(t, err) - } - latestConfirmAfterAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) - Require(t, err) - if latestConfirmAfterAct <= latestConfirmBeforeAct { - Fatal(t, "staker A didn't advance the latest confirmed node") - } -} - -func TestFastConfirmationWithSafe(t *testing.T) { - ctx, cancelCtx := context.WithCancel(context.Background()) - defer cancelCtx() - srv := externalsignertest.NewServer(t) - go func() { - if err := srv.Start(); err != nil { - log.Error("Failed to start external signer server:", err) - return - } - }() - var transferGas = util.NormalizeL2GasForL1GasInitial(800_000, params.GWei) // include room for aggregator L1 costs - - builder := NewNodeBuilder(ctx).DefaultConfig(t, true) - builder.L2Info = NewBlockChainTestInfo( - t, - types.NewArbitrumSigner(types.NewLondonSigner(builder.chainConfig.ChainID)), big.NewInt(l2pricing.InitialBaseFeeWei*2), - transferGas, - ) - - builder.nodeConfig.BatchPoster.MaxDelay = -1000 * time.Hour - cleanupA := builder.Build(t) - defer cleanupA() - - addNewBatchPoster(ctx, t, builder, srv.Address) - - builder.L1.SendWaitTestTransactions(t, []*types.Transaction{ - builder.L1Info.PrepareTxTo("Faucet", &srv.Address, 30000, big.NewInt(1).Mul(big.NewInt(1e18), big.NewInt(1e18)), nil)}) - - l2nodeA := builder.L2.ConsensusNode - execNodeA := builder.L2.ExecNode - - config := arbnode.ConfigDefaultL1Test() - config.Sequencer = false - config.DelayedSequencer.Enable = false - config.BatchPoster.Enable = false - builder.execConfig.Sequencer.Enable = false - testClientB, cleanupB := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: config}) - defer cleanupB() - - l2nodeB := testClientB.ConsensusNode - execNodeB := testClientB.ExecNode - - nodeAGenesis := execNodeA.Backend.APIBackend().CurrentHeader().Hash() - nodeBGenesis := execNodeB.Backend.APIBackend().CurrentHeader().Hash() - if nodeAGenesis != nodeBGenesis { - Fatal(t, "node A L2 genesis hash", nodeAGenesis, "!= node B L2 genesis hash", nodeBGenesis) - } - - builder.BridgeBalance(t, "Faucet", big.NewInt(1).Mul(big.NewInt(params.Ether), big.NewInt(10000))) - - deployAuth := builder.L1Info.GetDefaultTransactOpts("RollupOwner", ctx) - - balance := big.NewInt(params.Ether) - balance.Mul(balance, big.NewInt(100)) - builder.L1Info.GenerateAccount("ValidatorA") - builder.L1.TransferBalance(t, "Faucet", "ValidatorA", balance, builder.L1Info) - l1authA := builder.L1Info.GetDefaultTransactOpts("ValidatorA", ctx) - - builder.L1Info.GenerateAccount("ValidatorB") - builder.L1.TransferBalance(t, "Faucet", "ValidatorB", balance, builder.L1Info) - l1authB := builder.L1Info.GetDefaultTransactOpts("ValidatorB", ctx) - - valWalletAddrAPtr, err := validatorwallet.GetValidatorWalletContract(ctx, l2nodeA.DeployInfo.ValidatorWalletCreator, 0, &l1authA, l2nodeA.L1Reader, true) - Require(t, err) - valWalletAddrA := *valWalletAddrAPtr - valWalletAddrCheck, err := validatorwallet.GetValidatorWalletContract(ctx, l2nodeA.DeployInfo.ValidatorWalletCreator, 0, &l1authA, l2nodeA.L1Reader, true) - Require(t, err) - if valWalletAddrA == *valWalletAddrCheck { - Require(t, err, "didn't cache validator wallet address", valWalletAddrA.String(), "vs", valWalletAddrCheck.String()) - } - - rollup, err := rollupgen.NewRollupAdminLogic(l2nodeA.DeployInfo.Rollup, builder.L1.Client) - Require(t, err) - - upgradeExecutor, err := upgrade_executorgen.NewUpgradeExecutor(l2nodeA.DeployInfo.UpgradeExecutor, builder.L1.Client) - Require(t, err, "unable to bind upgrade executor") - rollupABI, err := abi.JSON(strings.NewReader(rollupgen.RollupAdminLogicABI)) - Require(t, err, "unable to parse rollup ABI") - - safeAddress := deploySafe(t, builder.L1, builder.L1.Client, deployAuth, []common.Address{valWalletAddrA, srv.Address}) - setValidatorCalldata, err := rollupABI.Pack("setValidator", []common.Address{valWalletAddrA, l1authB.From, srv.Address, safeAddress}, []bool{true, true, true, true}) - Require(t, err, "unable to generate setValidator calldata") - tx, err := upgradeExecutor.ExecuteCall(&deployAuth, l2nodeA.DeployInfo.Rollup, setValidatorCalldata) - Require(t, err, "unable to set validators") - _, err = builder.L1.EnsureTxSucceeded(tx) - Require(t, err) - - setMinAssertPeriodCalldata, err := rollupABI.Pack("setMinimumAssertionPeriod", big.NewInt(1)) - Require(t, err, "unable to generate setMinimumAssertionPeriod calldata") - tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2nodeA.DeployInfo.Rollup, setMinAssertPeriodCalldata) - Require(t, err, "unable to set minimum assertion period") - _, err = builder.L1.EnsureTxSucceeded(tx) - Require(t, err) - - setAnyTrustFastConfirmerCalldata, err := rollupABI.Pack("setAnyTrustFastConfirmer", safeAddress) - Require(t, err, "unable to generate setAnyTrustFastConfirmer calldata") - tx, err = upgradeExecutor.ExecuteCall(&deployAuth, l2nodeA.DeployInfo.Rollup, setAnyTrustFastConfirmerCalldata) - Require(t, err, "unable to set anytrust fast confirmer") - _, err = builder.L1.EnsureTxSucceeded(tx) - Require(t, err) - - valConfig := staker.TestL1ValidatorConfig - valConfig.EnableFastConfirmation = true - valConfig.FastConfirmSafeAddress = safeAddress.String() - parentChainID, err := builder.L1.Client.ChainID(ctx) - if err != nil { - t.Fatalf("Failed to get parent chain id: %v", err) - } - dpA, err := arbnode.StakerDataposter( - ctx, - rawdb.NewTable(l2nodeB.ArbDB, storage.StakerPrefix), - l2nodeA.L1Reader, - &l1authA, NewFetcherFromConfig(arbnode.ConfigDefaultL1NonSequencerTest()), - nil, - parentChainID, - ) - if err != nil { - t.Fatalf("Error creating validator dataposter: %v", err) - } - valWalletA, err := validatorwallet.NewContract(dpA, nil, l2nodeA.DeployInfo.ValidatorWalletCreator, l2nodeA.DeployInfo.Rollup, l2nodeA.L1Reader, &l1authA, 0, func(common.Address) {}, func() uint64 { return valConfig.ExtraGas }) - Require(t, err) - valConfig.Strategy = "MakeNodes" - - _, valStack := createTestValidationNode(t, ctx, &valnode.TestValidationConfig) - blockValidatorConfig := staker.TestBlockValidatorConfig - - statelessA, err := staker.NewStatelessBlockValidator( - l2nodeA.InboxReader, - l2nodeA.InboxTracker, - l2nodeA.TxStreamer, - execNodeA, - l2nodeA.ArbDB, - nil, - StaticFetcherFrom(t, &blockValidatorConfig), - valStack, - ) - Require(t, err) - err = statelessA.Start(ctx) - Require(t, err) - stakerA, err := staker.NewStaker( - l2nodeA.L1Reader, - valWalletA, - bind.CallOpts{}, - valConfig, - nil, - statelessA, - nil, - nil, - l2nodeA.DeployInfo.ValidatorUtils, - nil, - ) - Require(t, err) - err = stakerA.Initialize(ctx) - if stakerA.Strategy() != staker.WatchtowerStrategy { - err = valWalletA.Initialize(ctx) - Require(t, err) - } - Require(t, err) - cfg := arbnode.ConfigDefaultL1NonSequencerTest() - signerCfg, err := externalSignerTestCfg(srv.Address, srv.URL()) - if err != nil { - t.Fatalf("Error getting external signer config: %v", err) - } - cfg.Staker.DataPoster.ExternalSigner = *signerCfg - dpB, err := arbnode.StakerDataposter( - ctx, - rawdb.NewTable(l2nodeB.ArbDB, storage.StakerPrefix), - l2nodeB.L1Reader, - &l1authB, NewFetcherFromConfig(cfg), - nil, - parentChainID, - ) - if err != nil { - t.Fatalf("Error creating validator dataposter: %v", err) - } - valWalletB, err := validatorwallet.NewEOA(dpB, l2nodeB.DeployInfo.Rollup, l2nodeB.L1Reader.Client(), func() uint64 { return 0 }) - Require(t, err) - valConfig.Strategy = "watchtower" - statelessB, err := staker.NewStatelessBlockValidator( - l2nodeB.InboxReader, - l2nodeB.InboxTracker, - l2nodeB.TxStreamer, - execNodeB, - l2nodeB.ArbDB, - nil, - StaticFetcherFrom(t, &blockValidatorConfig), - valStack, - ) - Require(t, err) - err = statelessB.Start(ctx) - Require(t, err) - stakerB, err := staker.NewStaker( - l2nodeB.L1Reader, - valWalletB, - bind.CallOpts{}, - valConfig, - nil, - statelessB, - nil, - nil, - l2nodeB.DeployInfo.ValidatorUtils, - nil, - ) - Require(t, err) - err = stakerB.Initialize(ctx) - Require(t, err) - if stakerB.Strategy() != staker.WatchtowerStrategy { - err = valWalletB.Initialize(ctx) - Require(t, err) - } - - builder.L2Info.GenerateAccount("BackgroundUser") - tx = builder.L2Info.PrepareTx("Faucet", "BackgroundUser", builder.L2Info.TransferGas, balance, nil) - err = builder.L2.Client.SendTransaction(ctx, tx) - Require(t, err) - _, err = builder.L2.EnsureTxSucceeded(tx) - Require(t, err) - - // Continually make L2 transactions in a background thread - backgroundTxsCtx, cancelBackgroundTxs := context.WithCancel(ctx) - backgroundTxsShutdownChan := make(chan struct{}) - defer (func() { - cancelBackgroundTxs() - <-backgroundTxsShutdownChan - })() - go (func() { - defer close(backgroundTxsShutdownChan) - err := makeBackgroundTxs(backgroundTxsCtx, builder) - if !errors.Is(err, context.Canceled) { - log.Warn("error making background txs", "err", err) - } - })() - - latestConfirmBeforeAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) - Require(t, err) - tx, err = stakerA.Act(ctx) - Require(t, err) - if tx != nil { - _, err = builder.L1.EnsureTxSucceeded(tx) - Require(t, err) - } - latestConfirmAfterStakerAAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) - Require(t, err) - if latestConfirmAfterStakerAAct != latestConfirmBeforeAct { - Fatal(t, "staker A alone advanced the latest confirmed node", latestConfirmAfterStakerAAct, "when it shouldn't have") - } - for j := 0; j < 5; j++ { - builder.L1.TransferBalance(t, "Faucet", "Faucet", common.Big0, builder.L1Info) - } - tx, err = stakerB.Act(ctx) - Require(t, err) - if tx != nil { - _, err = builder.L1.EnsureTxSucceeded(tx) - Require(t, err) - } - latestConfirmAfterStakerBAct, err := rollup.LatestConfirmed(&bind.CallOpts{}) - Require(t, err) - if latestConfirmAfterStakerBAct <= latestConfirmBeforeAct { - Fatal(t, "staker A and B together didn't advance the latest confirmed node") - } -} - -func deploySafe(t *testing.T, l1 *TestClient, backend bind.ContractBackend, deployAuth bind.TransactOpts, owners []common.Address) common.Address { - safeAddress, tx, _, err := contractsgen.DeploySafeL2(&deployAuth, backend) - Require(t, err) - _, err = l1.EnsureTxSucceeded(tx) - Require(t, err) - safeProxyAddress, tx, _, err := proxiesgen.DeploySafeProxy(&deployAuth, backend, safeAddress) - Require(t, err) - _, err = l1.EnsureTxSucceeded(tx) - Require(t, err) - var safe *contractsgen.Safe - safe, err = contractsgen.NewSafe(safeProxyAddress, backend) - Require(t, err) - _, err = l1.EnsureTxSucceeded(tx) - Require(t, err) - tx, err = safe.Setup( - &deployAuth, - owners, - big.NewInt(2), - common.Address{}, - nil, - common.Address{}, - common.Address{}, - big.NewInt(0), - common.Address{}, - ) - Require(t, err) - _, err = l1.EnsureTxSucceeded(tx) - Require(t, err) - return safeProxyAddress -} From ca4b1d58f1876416e748cf967314a1b7b2056172 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Fri, 19 Jul 2024 21:11:34 +0530 Subject: [PATCH 19/24] Changes based on PR comments --- staker/fast_confirm.go | 46 +++++-------------------------- staker/staker.go | 34 ++++++++++++++++++----- system_tests/fast_confirm_test.go | 15 ++++------ 3 files changed, 39 insertions(+), 56 deletions(-) diff --git a/staker/fast_confirm.go b/staker/fast_confirm.go index 72945043f0..ef0a6fddb7 100644 --- a/staker/fast_confirm.go +++ b/staker/fast_confirm.go @@ -19,7 +19,6 @@ import ( ) type FastConfirmSafe struct { - rollupAddress common.Address safe *contractsgen.Safe owners []common.Address threshold uint64 @@ -28,26 +27,21 @@ type FastConfirmSafe struct { wallet ValidatorWalletInterface gasRefunder common.Address l1Reader *headerreader.HeaderReader - fastConfirmApprover common.Address } func NewFastConfirmSafe( callOpts bind.CallOpts, - rollupAddress common.Address, fastConfirmSafeAddress common.Address, builder *txbuilder.Builder, wallet ValidatorWalletInterface, gasRefunder common.Address, l1Reader *headerreader.HeaderReader, - fastConfirmApprover common.Address, ) (*FastConfirmSafe, error) { fastConfirmSafe := &FastConfirmSafe{ - builder: builder, - rollupAddress: rollupAddress, - wallet: wallet, - gasRefunder: gasRefunder, - l1Reader: l1Reader, - fastConfirmApprover: fastConfirmApprover, + builder: builder, + wallet: wallet, + gasRefunder: gasRefunder, + l1Reader: l1Reader, } safe, err := contractsgen.NewSafe(fastConfirmSafeAddress, builder) if err != nil { @@ -81,32 +75,6 @@ func NewFastConfirmSafe( return fastConfirmSafe, nil } -func (s *Staker) tryFastConfirmationNodeNumber(ctx context.Context, number uint64) error { - if !s.config.EnableFastConfirmation { - return nil - } - nodeInfo, err := s.rollup.LookupNode(ctx, number) - if err != nil { - return err - } - return s.tryFastConfirmation(ctx, nodeInfo.AfterState().GlobalState.BlockHash, nodeInfo.AfterState().GlobalState.SendRoot) -} - -func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error { - if !s.config.EnableFastConfirmation { - return nil - } - if s.fastConfirmSafe != nil { - return s.fastConfirmSafe.tryFastConfirmation(ctx, blockHash, sendRoot) - } - auth, err := s.builder.Auth(ctx) - if err != nil { - return err - } - _, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot) - return err -} - func (f *FastConfirmSafe) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error { fastConfirmCallData, err := f.createFastConfirmCalldata(blockHash, sendRoot) if err != nil { @@ -121,7 +89,7 @@ func (f *FastConfirmSafe) tryFastConfirmation(ctx context.Context, blockHash com // Hash of the safe transaction. safeTxHash, err := f.safe.GetTransactionHash( callOpts, - f.rollupAddress, + f.wallet.RollupAddress(), big.NewInt(0), fastConfirmCallData, 0, @@ -197,7 +165,7 @@ func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Contex var approved *big.Int // No need check if fastConfirmApprover has approved the hash, // since checkApprovedHashAndExecTransaction is called only after fastConfirmApprover has approved the hash. - if f.fastConfirmApprover == owner { + if *f.wallet.Address() == owner { approved = common.Big1 } else { var err error @@ -230,7 +198,7 @@ func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Contex } _, err = f.safe.ExecTransaction( auth, - f.rollupAddress, + f.wallet.RollupAddress(), big.NewInt(0), fastConfirmCallData, 0, diff --git a/staker/staker.go b/staker/staker.go index 5917559079..e3dd11dc07 100644 --- a/staker/staker.go +++ b/staker/staker.go @@ -92,7 +92,6 @@ type L1ValidatorConfig struct { ParentChainWallet genericconf.WalletConfig `koanf:"parent-chain-wallet"` EnableFastConfirmation bool `koanf:"enable-fast-confirmation"` FastConfirmSafeAddress string `koanf:"fast-confirm-safe-address"` - FastConfirmApprover string `koanf:"fast-confirm-approver"` LogQueryBatchSize uint64 `koanf:"log-query-batch-size" reload:"hot"` strategy StakerStrategy @@ -162,7 +161,6 @@ var DefaultL1ValidatorConfig = L1ValidatorConfig{ ParentChainWallet: DefaultValidatorL1WalletConfig, EnableFastConfirmation: false, FastConfirmSafeAddress: "", - FastConfirmApprover: "", LogQueryBatchSize: 0, } @@ -186,7 +184,6 @@ var TestL1ValidatorConfig = L1ValidatorConfig{ ParentChainWallet: DefaultValidatorL1WalletConfig, EnableFastConfirmation: false, FastConfirmSafeAddress: "", - FastConfirmApprover: "", LogQueryBatchSize: 0, } @@ -219,7 +216,6 @@ func L1ValidatorConfigAddOptions(prefix string, f *flag.FlagSet) { genericconf.WalletConfigAddOptions(prefix+".parent-chain-wallet", f, DefaultL1ValidatorConfig.ParentChainWallet.Pathname) f.Bool(prefix+".enable-fast-confirmation", DefaultL1ValidatorConfig.EnableFastConfirmation, "enable fast confirmation") f.String(prefix+".fast-confirm-safe-address", DefaultL1ValidatorConfig.FastConfirmSafeAddress, "safe address for fast confirmation") - f.String(prefix+".fast-confirm-approver", DefaultL1ValidatorConfig.FastConfirmApprover, "approver address for fast confirmation") } type DangerousConfig struct { @@ -320,13 +316,11 @@ func NewStaker( if config.EnableFastConfirmation && config.FastConfirmSafeAddress != "" { fastConfirmSafe, err = NewFastConfirmSafe( callOpts, - wallet.RollupAddress(), common.HexToAddress(config.FastConfirmSafeAddress), val.builder, - val.wallet, + wallet, config.gasRefunder, l1Reader, - common.HexToAddress(config.FastConfirmApprover), ) if err != nil { return nil, err @@ -377,6 +371,32 @@ func (s *Staker) Initialize(ctx context.Context) error { return nil } +func (s *Staker) tryFastConfirmationNodeNumber(ctx context.Context, number uint64) error { + if !s.config.EnableFastConfirmation { + return nil + } + nodeInfo, err := s.rollup.LookupNode(ctx, number) + if err != nil { + return err + } + return s.tryFastConfirmation(ctx, nodeInfo.AfterState().GlobalState.BlockHash, nodeInfo.AfterState().GlobalState.SendRoot) +} + +func (s *Staker) tryFastConfirmation(ctx context.Context, blockHash common.Hash, sendRoot common.Hash) error { + if !s.config.EnableFastConfirmation { + return nil + } + if s.fastConfirmSafe != nil { + return s.fastConfirmSafe.tryFastConfirmation(ctx, blockHash, sendRoot) + } + auth, err := s.builder.Auth(ctx) + if err != nil { + return err + } + _, err = s.rollup.FastConfirmNextNode(auth, blockHash, sendRoot) + return err +} + func (s *Staker) getLatestStakedState(ctx context.Context, staker common.Address) (uint64, arbutil.MessageIndex, *validator.GoGlobalState, error) { callOpts := s.getCallOpts(ctx) if s.l1Reader.UseFinalityData() { diff --git a/system_tests/fast_confirm_test.go b/system_tests/fast_confirm_test.go index 750175ba1b..c552aa51e0 100644 --- a/system_tests/fast_confirm_test.go +++ b/system_tests/fast_confirm_test.go @@ -319,7 +319,7 @@ func TestFastConfirmationWithSafe(t *testing.T) { valConfig := staker.TestL1ValidatorConfig valConfig.EnableFastConfirmation = true valConfig.FastConfirmSafeAddress = safeAddress.String() - valConfig.FastConfirmApprover = valWalletAddrA.String() + parentChainID, err := builder.L1.Client.ChainID(ctx) if err != nil { t.Fatalf("Failed to get parent chain id: %v", err) @@ -369,10 +369,8 @@ func TestFastConfirmationWithSafe(t *testing.T) { ) Require(t, err) err = stakerA.Initialize(ctx) - if stakerA.Strategy() != staker.WatchtowerStrategy { - err = valWalletA.Initialize(ctx) - Require(t, err) - } + Require(t, err) + err = valWalletA.Initialize(ctx) Require(t, err) cfg := arbnode.ConfigDefaultL1NonSequencerTest() signerCfg, err := externalSignerTestCfg(srv.Address, srv.URL()) @@ -394,7 +392,6 @@ func TestFastConfirmationWithSafe(t *testing.T) { valWalletB, err := validatorwallet.NewEOA(dpB, l2nodeB.DeployInfo.Rollup, l2nodeB.L1Reader.Client(), func() uint64 { return 0 }) Require(t, err) valConfig.Strategy = "watchtower" - valConfig.FastConfirmApprover = srv.Address.String() statelessB, err := staker.NewStatelessBlockValidator( l2nodeB.InboxReader, l2nodeB.InboxTracker, @@ -423,10 +420,8 @@ func TestFastConfirmationWithSafe(t *testing.T) { Require(t, err) err = stakerB.Initialize(ctx) Require(t, err) - if stakerB.Strategy() != staker.WatchtowerStrategy { - err = valWalletB.Initialize(ctx) - Require(t, err) - } + err = valWalletB.Initialize(ctx) + Require(t, err) builder.L2Info.GenerateAccount("BackgroundUser") tx = builder.L2Info.PrepareTx("Faucet", "BackgroundUser", builder.L2Info.TransferGas, balance, nil) From 2ccca3b4c7d2adda4ac5b40f115a110838cdeae6 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Fri, 19 Jul 2024 21:14:10 +0530 Subject: [PATCH 20/24] Changes based on PR comments --- staker/fast_confirm.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/staker/fast_confirm.go b/staker/fast_confirm.go index ef0a6fddb7..1f0e775d94 100644 --- a/staker/fast_confirm.go +++ b/staker/fast_confirm.go @@ -162,9 +162,12 @@ func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Contex var signatures []byte approvedHashCount := uint64(0) for _, owner := range f.owners { + if f.wallet.Address() == nil { + return errors.New("wallet address is nil") + } var approved *big.Int - // No need check if fastConfirmApprover has approved the hash, - // since checkApprovedHashAndExecTransaction is called only after fastConfirmApprover has approved the hash. + // No need check if wallet has approved the hash, + // since checkApprovedHashAndExecTransaction is called only after wallet has approved the hash. if *f.wallet.Address() == owner { approved = common.Big1 } else { From 478a2194ca040917af21018a0abdb3458667665f Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Fri, 19 Jul 2024 21:39:42 +0530 Subject: [PATCH 21/24] fix test --- system_tests/fast_confirm_test.go | 16 ++++++++++++++++ system_tests/staker_test.go | 16 ---------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/system_tests/fast_confirm_test.go b/system_tests/fast_confirm_test.go index c552aa51e0..e1ba80fbe0 100644 --- a/system_tests/fast_confirm_test.go +++ b/system_tests/fast_confirm_test.go @@ -504,3 +504,19 @@ func deploySafe(t *testing.T, l1 *TestClient, backend bind.ContractBackend, depl Require(t, err) return safeProxyAddress } + +func makeBackgroundTxs(ctx context.Context, builder *NodeBuilder) error { + for i := uint64(0); ctx.Err() == nil; i++ { + builder.L2Info.Accounts["BackgroundUser"].Nonce.Store(i) + tx := builder.L2Info.PrepareTx("BackgroundUser", "BackgroundUser", builder.L2Info.TransferGas, common.Big0, nil) + err := builder.L2.Client.SendTransaction(ctx, tx) + if err != nil { + return err + } + _, err = builder.L2.EnsureTxSucceeded(tx) + if err != nil { + return err + } + } + return nil +} diff --git a/system_tests/staker_test.go b/system_tests/staker_test.go index 52f16614f7..3eb5c2891c 100644 --- a/system_tests/staker_test.go +++ b/system_tests/staker_test.go @@ -40,22 +40,6 @@ import ( "github.com/offchainlabs/nitro/validator/valnode" ) -func makeBackgroundTxs(ctx context.Context, builder *NodeBuilder) error { - for i := uint64(0); ctx.Err() == nil; i++ { - builder.L2Info.Accounts["BackgroundUser"].Nonce.Store(i) - tx := builder.L2Info.PrepareTx("BackgroundUser", "BackgroundUser", builder.L2Info.TransferGas, common.Big0, nil) - err := builder.L2.Client.SendTransaction(ctx, tx) - if err != nil { - return err - } - _, err = builder.L2.EnsureTxSucceeded(tx) - if err != nil { - return err - } - } - return nil -} - func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) { t.Parallel() ctx, cancelCtx := context.WithCancel(context.Background()) From a13ddb6b48963fb76d9d1cf9d6e173d090955305 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Fri, 19 Jul 2024 22:04:52 +0530 Subject: [PATCH 22/24] skip race --- system_tests/fast_confirm_test.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/system_tests/fast_confirm_test.go b/system_tests/fast_confirm_test.go index e1ba80fbe0..3719d5422c 100644 --- a/system_tests/fast_confirm_test.go +++ b/system_tests/fast_confirm_test.go @@ -1,3 +1,10 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + +// race detection makes things slow and miss timeouts +//go:build !race +// +build !race + package arbtest import ( From 84c2fec9a6e08c7a7427b28cdab93298174a68cd Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Fri, 19 Jul 2024 22:07:17 +0530 Subject: [PATCH 23/24] fix --- staker/fast_confirm.go | 3 +++ system_tests/fast_confirm_test.go | 16 ---------------- system_tests/staker_test.go | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/staker/fast_confirm.go b/staker/fast_confirm.go index 1f0e775d94..22e4c84350 100644 --- a/staker/fast_confirm.go +++ b/staker/fast_confirm.go @@ -1,3 +1,6 @@ +// Copyright 2023-2024, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE + package staker import ( diff --git a/system_tests/fast_confirm_test.go b/system_tests/fast_confirm_test.go index 3719d5422c..d780f80414 100644 --- a/system_tests/fast_confirm_test.go +++ b/system_tests/fast_confirm_test.go @@ -511,19 +511,3 @@ func deploySafe(t *testing.T, l1 *TestClient, backend bind.ContractBackend, depl Require(t, err) return safeProxyAddress } - -func makeBackgroundTxs(ctx context.Context, builder *NodeBuilder) error { - for i := uint64(0); ctx.Err() == nil; i++ { - builder.L2Info.Accounts["BackgroundUser"].Nonce.Store(i) - tx := builder.L2Info.PrepareTx("BackgroundUser", "BackgroundUser", builder.L2Info.TransferGas, common.Big0, nil) - err := builder.L2.Client.SendTransaction(ctx, tx) - if err != nil { - return err - } - _, err = builder.L2.EnsureTxSucceeded(tx) - if err != nil { - return err - } - } - return nil -} diff --git a/system_tests/staker_test.go b/system_tests/staker_test.go index 3eb5c2891c..52f16614f7 100644 --- a/system_tests/staker_test.go +++ b/system_tests/staker_test.go @@ -40,6 +40,22 @@ import ( "github.com/offchainlabs/nitro/validator/valnode" ) +func makeBackgroundTxs(ctx context.Context, builder *NodeBuilder) error { + for i := uint64(0); ctx.Err() == nil; i++ { + builder.L2Info.Accounts["BackgroundUser"].Nonce.Store(i) + tx := builder.L2Info.PrepareTx("BackgroundUser", "BackgroundUser", builder.L2Info.TransferGas, common.Big0, nil) + err := builder.L2.Client.SendTransaction(ctx, tx) + if err != nil { + return err + } + _, err = builder.L2.EnsureTxSucceeded(tx) + if err != nil { + return err + } + } + return nil +} + func stakerTestImpl(t *testing.T, faultyStaker bool, honestStakerInactive bool) { t.Parallel() ctx, cancelCtx := context.WithCancel(context.Background()) From 9985c67d7637f8cdbd4830b70f5d9f9ff29254d0 Mon Sep 17 00:00:00 2001 From: Aman Sanghi Date: Sat, 20 Jul 2024 07:51:27 +0530 Subject: [PATCH 24/24] Changes based on PR comments --- staker/fast_confirm.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/staker/fast_confirm.go b/staker/fast_confirm.go index 22e4c84350..59a7443826 100644 --- a/staker/fast_confirm.go +++ b/staker/fast_confirm.go @@ -126,7 +126,22 @@ func (f *FastConfirmSafe) tryFastConfirmation(ctx context.Context, blockHash com return err } } - return f.checkApprovedHashAndExecTransaction(ctx, fastConfirmCallData, safeTxHash) + executedTx, err := f.checkApprovedHashAndExecTransaction(ctx, fastConfirmCallData, safeTxHash) + if err != nil { + return err + } + if executedTx { + return nil + } + // If the transaction was not executed, we need to flush the transactions (for approve hash) and try again. + // This is because the hash might have been approved by another wallet in the same block, + // which might have led to a race condition. + err = f.flushTransactions(ctx) + if err != nil { + return err + } + _, err = f.checkApprovedHashAndExecTransaction(ctx, fastConfirmCallData, safeTxHash) + return err } func (f *FastConfirmSafe) flushTransactions(ctx context.Context) error { @@ -161,12 +176,12 @@ func (f *FastConfirmSafe) createFastConfirmCalldata( return fullCalldata, nil } -func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Context, fastConfirmCallData []byte, safeTxHash [32]byte) error { +func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Context, fastConfirmCallData []byte, safeTxHash [32]byte) (bool, error) { var signatures []byte approvedHashCount := uint64(0) for _, owner := range f.owners { if f.wallet.Address() == nil { - return errors.New("wallet address is nil") + return false, errors.New("wallet address is nil") } var approved *big.Int // No need check if wallet has approved the hash, @@ -177,7 +192,7 @@ func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Contex var err error approved, err = f.safe.ApprovedHashes(&bind.CallOpts{Context: ctx}, owner, safeTxHash) if err != nil { - return err + return false, err } } @@ -200,7 +215,7 @@ func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Contex if approvedHashCount >= f.threshold { auth, err := f.builder.Auth(ctx) if err != nil { - return err + return false, err } _, err = f.safe.ExecTransaction( auth, @@ -216,8 +231,9 @@ func (f *FastConfirmSafe) checkApprovedHashAndExecTransaction(ctx context.Contex signatures, ) if err != nil { - return err + return false, err } + return true, nil } - return nil + return false, nil }