diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6c5f5f57b9..adf90f464f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -145,13 +145,13 @@ jobs: if: matrix.test-mode == 'defaults' run: | packages=`go list ./...` - gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 -- -timeout 25m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -skip TestEspressoE2E + gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 -- -timeout 25m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -skip 'TestEspressoE2E|TestEspressoSwitch' - name: run tests with race detection if: matrix.test-mode == 'race' run: | packages=`go list ./...` - gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 -- --timeout 30m -race -skip TestEspressoE2E + gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 -- --timeout 30m -race -skip 'TestEspressoE2E|TestEspressoSwitch' - name: run redis tests if: matrix.test-mode == 'defaults' diff --git a/.github/workflows/espresso-e2e.yml b/.github/workflows/espresso-e2e.yml index 45c1c530fc..24b37075b5 100644 --- a/.github/workflows/espresso-e2e.yml +++ b/.github/workflows/espresso-e2e.yml @@ -133,4 +133,4 @@ jobs: - name: Run test run: | packages=`go list ./... | grep system_tests` - gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 -- -v -timeout 25m ./... -run TestEspressoE2E + gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 -- -v -timeout 35m ./... -run 'TestEspressoE2E|TestEspressoSwitch' diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index eaa8e23082..52cf890ed3 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -33,6 +33,7 @@ import ( "github.com/ethereum/go-ethereum/rpc" hotshotClient "github.com/EspressoSystems/espresso-sequencer-go/client" + lightclient "github.com/EspressoSystems/espresso-sequencer-go/light-client" "github.com/offchainlabs/nitro/arbnode/dataposter" "github.com/offchainlabs/nitro/arbnode/dataposter/storage" @@ -75,10 +76,6 @@ type batchPosterPosition struct { NextSeqNum uint64 } -type LightClientReaderInterface interface { - ValidatedHeight() (validatedHeight uint64, l1Height uint64, err error) -} - type BatchPoster struct { stopwaiter.StopWaiter l1Reader *headerreader.HeaderReader @@ -111,7 +108,7 @@ type BatchPoster struct { accessList func(SequencerInboxAccs, AfterDelayedMessagesRead int) types.AccessList // Espresso readers - lightClientReader LightClientReaderInterface + lightClientReader lightclient.LightClientReaderInterface hotshotClient *hotshotClient.Client } @@ -314,17 +311,17 @@ func NewBatchPoster(ctx context.Context, opts *BatchPosterOpts) (*BatchPoster, e return nil, err } var hotShotClient *hotshotClient.Client - var lightClientReader LightClientReaderInterface + var lightClientReader lightclient.LightClientReaderInterface hotShotUrl := opts.Config().HotShotUrl lightClientAddr := opts.Config().LightClientAddress if hotShotUrl != "" { - hotShotClient = hotshotClient.NewClient(log.New(), hotShotUrl) + hotShotClient = hotshotClient.NewClient(hotShotUrl) } if lightClientAddr != "" { - lightClientReader, err = NewMockLightClientReader(common.HexToAddress(lightClientAddr), opts.L1Reader.Client()) + lightClientReader, err = arbos.NewMockLightClientReader(common.HexToAddress(lightClientAddr), opts.L1Reader.Client()) if err != nil { return nil, err } diff --git a/arbnode/mock_light_client_reader.go b/arbos/mock_light_client_reader.go similarity index 64% rename from arbnode/mock_light_client_reader.go rename to arbos/mock_light_client_reader.go index aa9b46c33a..7074ef52f9 100644 --- a/arbnode/mock_light_client_reader.go +++ b/arbos/mock_light_client_reader.go @@ -1,6 +1,9 @@ -package arbnode +package arbos import ( + "time" + + "github.com/EspressoSystems/espresso-sequencer-go/types" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" ) @@ -17,3 +20,11 @@ func NewMockLightClientReader(lightClientAddr common.Address, l1client bind.Cont func (l *MockLightClientReader) ValidatedHeight() (validatedHeight uint64, l1Height uint64, err error) { return 18446744073709551615, 18446744073709551615, nil } + +func (l *MockLightClientReader) IsHotShotAvaliable(t time.Duration) bool { + return true +} + +func (l *MockLightClientReader) FetchMerkleRootAtL1Block(l1BlockHeight uint64) (types.BlockMerkleRoot, error) { + return types.BlockMerkleRoot{}, nil +} diff --git a/execution/gethexec/arb_interface.go b/execution/gethexec/arb_interface.go index 50d7dfb891..6381ee08b0 100644 --- a/execution/gethexec/arb_interface.go +++ b/execution/gethexec/arb_interface.go @@ -18,6 +18,10 @@ type TransactionPublisher interface { Start(context.Context) error StopAndWait() Started() bool + + // This is only for testing the switch sequencer. Will be removed if the espresso light client + // contract is ready and we will use another way to trigger the mode switching. + SetMode(ctx context.Context, espresso bool) error } type ArbInterface struct { diff --git a/execution/gethexec/espresso_sequencer.go b/execution/gethexec/espresso_sequencer.go index 05d9b56023..014d21e3b2 100644 --- a/execution/gethexec/espresso_sequencer.go +++ b/execution/gethexec/espresso_sequencer.go @@ -29,7 +29,7 @@ type HotShotState struct { func NewHotShotState(log log.Logger, url string, startBlock uint64) *HotShotState { return &HotShotState{ - client: *espressoClient.NewClient(log, url), + client: *espressoClient.NewClient(url), nextSeqBlockNum: startBlock, } } @@ -112,14 +112,13 @@ func (s *EspressoSequencer) createBlock(ctx context.Context) (returnValue bool) func (s *EspressoSequencer) Start(ctxIn context.Context) error { s.StopWaiter.Start(ctxIn, s) s.CallIteratively(func(ctx context.Context) time.Duration { - retryBlockTime := time.Now().Add(retryTime) madeBlock := s.createBlock(ctx) if madeBlock { // Allow the sequencer to catch up to HotShot return 0 } // If we didn't make a block, try again in a bit - return time.Until(retryBlockTime) + return retryTime }) return nil diff --git a/execution/gethexec/node.go b/execution/gethexec/node.go index 18b9b31f0a..2457a241b7 100644 --- a/execution/gethexec/node.go +++ b/execution/gethexec/node.go @@ -171,21 +171,27 @@ func CreateExecutionNode( } } - if config.Sequencer.Enable && !config.Sequencer.Espresso { + if config.Sequencer.Enable { seqConfigFetcher := func() *SequencerConfig { return &configFetcher().Sequencer } sequencer, err = NewSequencer(execEngine, parentChainReader, seqConfigFetcher) if err != nil { return nil, err } - txPublisher = sequencer - } else if config.Sequencer.Enable && config.Sequencer.Espresso { - seqConfigFetcher := func() *SequencerConfig { return &configFetcher().Sequencer } - espressoSequencer, err := NewEspressoSequencer(execEngine, seqConfigFetcher) - if err != nil { - return nil, err - } - txPublisher = espressoSequencer + if config.Sequencer.Espresso { + seqConfigFetcher := func() *SequencerConfig { return &configFetcher().Sequencer } + espressoSequencer, err := NewEspressoSequencer(execEngine, seqConfigFetcher) + if err != nil { + return nil, err + } + switchSequencer, err := NewSwitchSequencer(sequencer, espressoSequencer, parentChainReader.Client(), seqConfigFetcher) + if err != nil { + return nil, err + } + txPublisher = switchSequencer + } else { + txPublisher = sequencer + } } else { if config.Forwarder.RedisUrl != "" { txPublisher = NewRedisTxForwarder(config.forwardingTarget, &config.Forwarder) diff --git a/execution/gethexec/sequencer.go b/execution/gethexec/sequencer.go index 232fd6bb6d..8a4268c757 100644 --- a/execution/gethexec/sequencer.go +++ b/execution/gethexec/sequencer.go @@ -68,10 +68,13 @@ type SequencerConfig struct { NonceFailureCacheExpiry time.Duration `koanf:"nonce-failure-cache-expiry" reload:"hot"` // Espresso specific flags - Espresso bool `koanf:"espresso"` - HotShotUrl string `koanf:"hotshot-url"` - EspressoNamespace uint64 `koanf:"espresso-namespace"` - StartHotShotBlock uint64 `koanf:"start-hotshot-block"` + Espresso bool `koanf:"espresso"` + HotShotUrl string `koanf:"hotshot-url"` + LightClientAddress string `koanf:"light-client-address"` + EspressoNamespace uint64 `koanf:"espresso-namespace"` + StartHotShotBlock uint64 `koanf:"start-hotshot-block"` + MaxHotShotDriftTime time.Duration `koanf:"max-hotshot-drift-time"` + SwitchPollInterval time.Duration `koanf:"switch-poll-interval"` } func (c *SequencerConfig) Validate() error { @@ -84,6 +87,9 @@ func (c *SequencerConfig) Validate() error { return fmt.Errorf("sequencer sender whitelist entry \"%v\" is not a valid address", address) } } + if c.LightClientAddress == "" && c.Espresso { + log.Warn("LightClientAddress is empty, running the espresso test mode") + } return nil } @@ -138,6 +144,8 @@ func SequencerConfigAddOptions(prefix string, f *flag.FlagSet) { f.String(prefix+".hotshot-url", DefaultSequencerConfig.HotShotUrl, "") f.Uint64(prefix+".espresso-namespace", DefaultSequencerConfig.EspressoNamespace, "espresso namespace that corresponds the L2 chain") f.Uint64(prefix+".start-hotshot-block", DefaultSequencerConfig.StartHotShotBlock, "the starting block number of hotshot") + f.Duration(prefix+".max-hotshot-drift-time", DefaultSequencerConfig.MaxHotShotDriftTime, "maximum drift time of hotshot") + f.Duration(prefix+".switch-poll-interval", DefaultSequencerConfig.SwitchPollInterval, "the poll interval of checking the sequencer should be switched or not") } type txQueueItem struct { diff --git a/execution/gethexec/switch_sequencer.go b/execution/gethexec/switch_sequencer.go new file mode 100644 index 0000000000..25c35230d9 --- /dev/null +++ b/execution/gethexec/switch_sequencer.go @@ -0,0 +1,160 @@ +package gethexec + +import ( + "context" + "time" + + lightClient "github.com/EspressoSystems/espresso-sequencer-go/light-client" + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/arbitrum_types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbos" + "github.com/offchainlabs/nitro/util/stopwaiter" +) + +const ( + SequencingMode_Espresso = 0 + SequencingMode_Centralized = 1 +) + +type SwitchSequencer struct { + stopwaiter.StopWaiter + + centralized *Sequencer + espresso *EspressoSequencer + + maxHotShotDriftTime time.Duration + switchPollInterval time.Duration + lightClient lightClient.LightClientReaderInterface + + mode int +} + +func NewSwitchSequencer(centralized *Sequencer, espresso *EspressoSequencer, l1client bind.ContractBackend, configFetcher SequencerConfigFetcher) (*SwitchSequencer, error) { + config := configFetcher() + err := config.Validate() + if err != nil { + return nil, err + } + + var lightClient lightClient.LightClientReaderInterface + if config.LightClientAddress != "" { + lightClient, err = arbos.NewMockLightClientReader(common.HexToAddress(config.LightClientAddress), l1client) + if err != nil { + return nil, err + } + } + + return &SwitchSequencer{ + centralized: centralized, + espresso: espresso, + lightClient: lightClient, + mode: SequencingMode_Espresso, + maxHotShotDriftTime: config.MaxHotShotDriftTime, + switchPollInterval: config.SwitchPollInterval, + }, nil +} + +func (s *SwitchSequencer) IsRunningEspressoMode() bool { + return s.mode == SequencingMode_Espresso +} + +func (s *SwitchSequencer) SwitchToEspresso(ctx context.Context) error { + if s.IsRunningEspressoMode() { + return nil + } + log.Info("Switching to espresso sequencer") + + s.mode = SequencingMode_Espresso + + s.centralized.StopAndWait() + return s.espresso.Start(ctx) +} + +func (s *SwitchSequencer) SwitchToCentralized(ctx context.Context) error { + if !s.IsRunningEspressoMode() { + return nil + } + s.mode = SequencingMode_Centralized + log.Info("Switching to centrialized sequencer") + + s.espresso.StopAndWait() + return s.centralized.Start(ctx) +} + +func (s *SwitchSequencer) getRunningSequencer() TransactionPublisher { + if s.IsRunningEspressoMode() { + return s.espresso + } + return s.centralized +} + +func (s *SwitchSequencer) PublishTransaction(ctx context.Context, tx *types.Transaction, options *arbitrum_types.ConditionalOptions) error { + return s.getRunningSequencer().PublishTransaction(ctx, tx, options) +} + +func (s *SwitchSequencer) CheckHealth(ctx context.Context) error { + return s.getRunningSequencer().CheckHealth(ctx) +} + +func (s *SwitchSequencer) Initialize(ctx context.Context) error { + err := s.centralized.Initialize(ctx) + if err != nil { + return err + } + + return s.espresso.Initialize(ctx) +} + +func (s *SwitchSequencer) Start(ctx context.Context) error { + s.StopWaiter.Start(ctx, s) + err := s.getRunningSequencer().Start(ctx) + if err != nil { + return err + } + + if s.lightClient != nil { + s.CallIteratively(func(ctx context.Context) time.Duration { + espresso := s.lightClient.IsHotShotAvaliable(s.maxHotShotDriftTime) + + var err error + if s.IsRunningEspressoMode() && !espresso { + err = s.SwitchToCentralized(ctx) + } else if !s.IsRunningEspressoMode() && espresso { + err = s.SwitchToEspresso(ctx) + } + + if err != nil { + return 0 + } + return s.switchPollInterval + }) + } + + return nil +} + +func (s *SwitchSequencer) StopAndWait() { + s.getRunningSequencer().StopAndWait() + s.StopWaiter.StopAndWait() +} + +func (s *SwitchSequencer) Started() bool { + return s.getRunningSequencer().Started() +} + +func (s *SwitchSequencer) SetMode(ctx context.Context, m bool) error { + if m { + return s.SwitchToEspresso(ctx) + } else { + return s.SwitchToCentralized(ctx) + } +} + +func (s *Sequencer) SetMode(ctx context.Context, espresso bool) error { return nil } +func (s *EspressoSequencer) SetMode(ctx context.Context, m bool) error { return nil } +func (s *RedisTxForwarder) SetMode(ctx context.Context, m bool) error { return nil } +func (s *TxDropper) SetMode(ctx context.Context, m bool) error { return nil } +func (s *TxForwarder) SetMode(ctx context.Context, m bool) error { return nil } diff --git a/go.mod b/go.mod index c461ed6c77..73e6ff0c8a 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ replace github.com/VictoriaMetrics/fastcache => ./fastcache replace github.com/ethereum/go-ethereum => ./go-ethereum require ( - github.com/EspressoSystems/espresso-sequencer-go v0.0.10 + github.com/EspressoSystems/espresso-sequencer-go v0.0.11 github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible github.com/Shopify/toxiproxy v2.1.4+incompatible github.com/alicebob/miniredis/v2 v2.21.0 @@ -71,7 +71,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.11.4 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.16.4 // indirect github.com/aws/smithy-go v1.11.2 // indirect - github.com/benbjohnson/clock v1.3.0 // indirect + github.com/benbjohnson/clock v1.3.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bits-and-blooms/bitset v1.7.0 // indirect github.com/blang/semver/v4 v4.0.0 // indirect diff --git a/go.sum b/go.sum index c6df12125d..da2856dc5b 100644 --- a/go.sum +++ b/go.sum @@ -51,8 +51,8 @@ github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3 github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= -github.com/EspressoSystems/espresso-sequencer-go v0.0.10 h1:INykV9fatyUZXzgV4wyZAwBtqXCPGHgOcwaUsHsVJA4= -github.com/EspressoSystems/espresso-sequencer-go v0.0.10/go.mod h1:9dSL1bj0l+jpgaMRmi55YeRBd3AhOZz8/HXQcQ42mRQ= +github.com/EspressoSystems/espresso-sequencer-go v0.0.11 h1:PWBzbf8/WX10dYizIco0bAmIVE7/lwR4bNM/t+AT+HU= +github.com/EspressoSystems/espresso-sequencer-go v0.0.11/go.mod h1:BbU8N23RGl45QXSf/bYc8OQ8TG/vlMaPC1GU1acqKmc= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= @@ -154,8 +154,9 @@ github.com/aws/smithy-go v1.11.2 h1:eG/N+CcUMAvsdffgMvjMKwfyDzIkjM6pfxMJ8Mzc6mE= github.com/aws/smithy-go v1.11.2/go.mod h1:3xHYmszWVx2c0kIwQeEVf9uSm4fYZt67FBJnwub1bgM= github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= -github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/benbjohnson/clock v1.3.5 h1:VvXlSJBzZpA/zum6Sj74hxwYI2DIxRWuNIoXAzHZz5o= +github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= diff --git a/system_tests/espresso_e2e_test.go b/system_tests/espresso_e2e_test.go index 562b8b030d..47ffdc19a7 100644 --- a/system_tests/espresso_e2e_test.go +++ b/system_tests/espresso_e2e_test.go @@ -307,16 +307,12 @@ func waitForWith( } } -func TestEspressoE2E(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() +// We run one L1 node, two L2 nodes and the espresso containers in this function. +func runNodes(ctx context.Context, t *testing.T) (*NodeBuilder, *TestClient, *BlockchainTestInfo, func()) { cleanValNode := createValidationNode(ctx, t, false) - defer cleanValNode() builder, cleanup := createL1ValidatorPosterNode(ctx, t, hotShotUrl) - defer cleanup() - node := builder.L2 err := waitFor(t, ctx, func() bool { if e := exec.Command( @@ -336,10 +332,8 @@ func TestEspressoE2E(t *testing.T) { Require(t, err) cleanEspresso := runEspresso(t, ctx) - defer cleanEspresso() l2Node, l2Info, cleanL2Node := createL2Node(ctx, t, hotShotUrl, builder) - defer cleanL2Node() // wait for the commitment task err = waitForWith(t, ctx, 60*time.Second, 1*time.Second, func() bool { @@ -352,9 +346,25 @@ func TestEspressoE2E(t *testing.T) { }) Require(t, err) + return builder, l2Node, l2Info, func() { + cleanL2Node() + cleanEspresso() + cleanup() + cleanValNode() + } +} + +func TestEspressoE2E(t *testing.T) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder, l2Node, l2Info, cleanup := runNodes(ctx, t) + defer cleanup() + node := builder.L2 + // Wait for the initial message expected := arbutil.MessageIndex(1) - err = waitFor(t, ctx, func() bool { + err := waitFor(t, ctx, func() bool { msgCnt, err := l2Node.ConsensusNode.TxStreamer.GetMessageCount() if err != nil { panic(err) diff --git a/system_tests/espresso_switch_test.go b/system_tests/espresso_switch_test.go new file mode 100644 index 0000000000..dafb2759b1 --- /dev/null +++ b/system_tests/espresso_switch_test.go @@ -0,0 +1,97 @@ +package arbtest + +import ( + "context" + "math/big" + "testing" + "time" + + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbutil" +) + +func TestEspressoSwitch(t *testing.T) { + + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + builder, l2Node, l2Info, cleanup := runNodes(ctx, t) + defer cleanup() + node := builder.L2 + // Inacitivating the delayed sequencer for convenient sake + node.ConsensusNode.DelayedSequencer.StopAndWait() + l2Node.ConsensusNode.DelayedSequencer.StopAndWait() + + seq := l2Node.ExecNode.TxPublisher + err := seq.SetMode(ctx, false) + Require(t, err) + + currMsg := arbutil.MessageIndex(0) + // Wait for the switch to be totally finished + err = waitForWith(t, ctx, 3*time.Minute, 30*time.Second, func() bool { + msg, err := node.ConsensusNode.TxStreamer.GetMessageCount() + if err != nil { + return false + } + if currMsg == msg { + return true + } + + currMsg = msg + return false + }) + Require(t, err) + + // Make sure it is a totally new account + newAccount := "User10" + l2Info.GenerateAccount(newAccount) + addr := l2Info.GetAddress(newAccount) + balance := l2Node.GetBalance(t, addr) + if balance.Cmp(big.NewInt(0)) > 0 { + Fatal(t, "empty account") + } + + // Check if the tx is executed correctly + transferAmount := big.NewInt(1e16) + tx := l2Info.PrepareTx("Faucet", newAccount, 3e7, transferAmount, nil) + err = l2Node.Client.SendTransaction(ctx, tx) + Require(t, err) + + err = waitFor(t, ctx, func() bool { + balance := l2Node.GetBalance(t, addr) + log.Info("waiting for balance", "addr", addr, "balance", balance) + return balance.Cmp(transferAmount) >= 0 + }) + Require(t, err) + + msg, err := node.ConsensusNode.TxStreamer.GetMessageCount() + Require(t, err) + + if msg != currMsg+1 { + t.Fatal("") + } + + err = waitForWith(t, ctx, 60*time.Second, 5*time.Second, func() bool { + validatedCnt := node.ConsensusNode.BlockValidator.Validated(t) + return validatedCnt >= msg + }) + Require(t, err) + + err = seq.SetMode(ctx, true) + Require(t, err) + + expectedMsg := msg + 10 + err = waitForWith(t, ctx, 120*time.Second, 5*time.Second, func() bool { + msg, err := node.ConsensusNode.TxStreamer.GetMessageCount() + if err != nil { + return false + } + return msg >= expectedMsg + }) + Require(t, err) + err = waitForWith(t, ctx, 60*time.Second, 5*time.Second, func() bool { + validatedCnt := node.ConsensusNode.BlockValidator.Validated(t) + return validatedCnt >= expectedMsg + }) + Require(t, err) +} diff --git a/util/stopwaiter/stopwaiter.go b/util/stopwaiter/stopwaiter.go index 1e70e328eb..38b6299542 100644 --- a/util/stopwaiter/stopwaiter.go +++ b/util/stopwaiter/stopwaiter.go @@ -81,16 +81,14 @@ func getParentName(parent any) string { func (s *StopWaiterSafe) Start(ctx context.Context, parent any) error { s.mutex.Lock() defer s.mutex.Unlock() - if s.started { + if s.started && !s.stopped { return errors.New("start after start") } s.started = true + s.stopped = false s.name = getParentName(parent) s.parentCtx = ctx s.ctx, s.stopFunc = context.WithCancel(s.parentCtx) - if s.stopped { - s.stopFunc() - } return nil }