diff --git a/bold/challenge/chain/watcher.go b/bold/challenge/chain/watcher.go index 9843b05e931..8ecd2700762 100644 --- a/bold/challenge/chain/watcher.go +++ b/bold/challenge/chain/watcher.go @@ -20,12 +20,14 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + gethtypes "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" "github.com/offchainlabs/nitro/bold/api" "github.com/offchainlabs/nitro/bold/api/db" "github.com/offchainlabs/nitro/bold/challenge/tree" + "github.com/offchainlabs/nitro/bold/containers/events" "github.com/offchainlabs/nitro/bold/containers/option" "github.com/offchainlabs/nitro/bold/containers/threadsafe" "github.com/offchainlabs/nitro/bold/protocol" @@ -87,6 +89,7 @@ type Watcher struct { // Track all if empty / nil. trackChallengeParentAssertionHashes []protocol.AssertionHash maxGetLogBlocks uint64 + blockNotifier *events.Producer[*gethtypes.Header] } // New initializes a watcher service for frequently scanning the chain @@ -124,6 +127,12 @@ func (w *Watcher) SetEdgeManager(em EdgeManager) { w.edgeManager = em } +// SetBlockNotifier sets a block notifier that the watcher will subscribe to +// for reactive event polling instead of using a fixed timer interval. +func (w *Watcher) SetBlockNotifier(notifier *events.Producer[*gethtypes.Header]) { + w.blockNotifier = notifier +} + // AvgBlockTime returns the average time for block creation. func (w *Watcher) AvgBlockTime() time.Duration { return w.averageTimeForBlockCreation @@ -245,58 +254,75 @@ func (w *Watcher) Start(ctx context.Context) { } fromBlock = toBlock - ticker := time.NewTicker(w.pollEventsInterval) - defer ticker.Stop() - for { - select { - case <-ticker.C: - toBlock, err := w.chain.DesiredHeaderU64(ctx) - if err != nil { - log.Error("Could not get latest header", "err", err) - continue - } - // AssertionChain's rpcHeadBlockNumber is set to finalized and this might occur due to l1 backends of load balancer - // not being in consensus wrt finalized. In which case we ignore and continue - if fromBlock > toBlock { - continue - } - if fromBlock == toBlock { - w.initialSyncCompleted.Store(true) - continue - } - // Get a challenge manager instance and filterer. - challengeManager := w.chain.SpecChallengeManager() - filterer, err = retry.UntilSucceeds(ctx, func() (*challengeV2gen.EdgeChallengeManagerFilterer, error) { - return challengeV2gen.NewEdgeChallengeManagerFilterer(challengeManager.Address(), w.backend) - }) - if err != nil { - log.Error("Could not get challenge manager filterer", "err", err) + w.initialSyncCompleted.Store(true) + if w.blockNotifier != nil { + sub := w.blockNotifier.Subscribe() + for { + if _, done := sub.Next(ctx); done { return } - filterOpts := &bind.FilterOpts{ - Start: fromBlock, - End: &toBlock, - Context: ctx, - } - if err = w.checkForEdgeAdded(ctx, filterer, filterOpts); err != nil { - log.Error("Could not check for edge added", "err", err) - continue - } - if err = w.checkForEdgeConfirmedByOneStepProof(ctx, filterer, filterOpts); err != nil { - log.Error("Could not check for edge confirmed by osp", "err", err) - continue - } - if err = w.checkForEdgeConfirmedByTime(ctx, filterer, filterOpts); err != nil { - log.Error("Could not check for edge confirmed by time", "err", err) - continue + fromBlock = w.processNewBlocks(ctx, fromBlock) + } + } else { + ticker := time.NewTicker(w.pollEventsInterval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + fromBlock = w.processNewBlocks(ctx, fromBlock) + case <-ctx.Done(): + return } - fromBlock = toBlock - case <-ctx.Done(): - return } } } +// processNewBlocks polls for new edge events since fromBlock and returns the +// updated fromBlock value. +func (w *Watcher) processNewBlocks(ctx context.Context, fromBlock uint64) uint64 { + toBlock, err := w.chain.DesiredHeaderU64(ctx) + if err != nil { + log.Error("Could not get latest header", "err", err) + return fromBlock + } + // AssertionChain's rpcHeadBlockNumber is set to finalized and this might occur due to l1 backends of load balancer + // not being in consensus wrt finalized. In which case we ignore and continue + if fromBlock > toBlock { + return fromBlock + } + if fromBlock == toBlock { + w.initialSyncCompleted.Store(true) + return fromBlock + } + // Get a challenge manager instance and filterer. + challengeManager := w.chain.SpecChallengeManager() + filterer, err := retry.UntilSucceeds(ctx, func() (*challengeV2gen.EdgeChallengeManagerFilterer, error) { + return challengeV2gen.NewEdgeChallengeManagerFilterer(challengeManager.Address(), w.backend) + }) + if err != nil { + log.Error("Could not get challenge manager filterer", "err", err) + return fromBlock + } + filterOpts := &bind.FilterOpts{ + Start: fromBlock, + End: &toBlock, + Context: ctx, + } + if err = w.checkForEdgeAdded(ctx, filterer, filterOpts); err != nil { + log.Error("Could not check for edge added", "err", err) + return fromBlock + } + if err = w.checkForEdgeConfirmedByOneStepProof(ctx, filterer, filterOpts); err != nil { + log.Error("Could not check for edge confirmed by osp", "err", err) + return fromBlock + } + if err = w.checkForEdgeConfirmedByTime(ctx, filterer, filterOpts); err != nil { + log.Error("Could not check for edge confirmed by time", "err", err) + return fromBlock + } + return toBlock +} + // GetRoyalEdges returns all royal, tracked edges in the watcher by assertion // hash. func (w *Watcher) GetRoyalEdges(ctx context.Context) (map[protocol.AssertionHash][]*api.JsonTrackedRoyalEdge, error) { diff --git a/bold/challenge/manager.go b/bold/challenge/manager.go index 954228ab18a..55f7188282d 100644 --- a/bold/challenge/manager.go +++ b/bold/challenge/manager.go @@ -141,6 +141,7 @@ func New( o(m) } m.watcher.SetEdgeManager(m) + m.watcher.SetBlockNotifier(m.newBlockNotifier) m.assertionManager.SetRivalHandler(m) log.Info("Setting up challenge manager", "name", m.name, diff --git a/bold/challenge/tracker/challenge_confirmation.go b/bold/challenge/tracker/challenge_confirmation.go index a392bde0577..f987aeca6ec 100644 --- a/bold/challenge/tracker/challenge_confirmation.go +++ b/bold/challenge/tracker/challenge_confirmation.go @@ -13,7 +13,6 @@ import ( "github.com/ccoveille/go-safecast" "github.com/pkg/errors" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/metrics" @@ -339,7 +338,7 @@ func (cc *challengeConfirmer) waitForTxToBeSafe( // This is to handle the case where the transaction is mined in a block, but then the block is reorged. // In this case, we want to wait for the transaction to be mined again. - receiptLatest, err := bind.WaitMined(ctx, backend, tx) + receiptLatest, err := protocol.WaitMined(ctx, backend, tx) if err != nil { return err } diff --git a/bold/challenge/tracker/tracker.go b/bold/challenge/tracker/tracker.go index 0e5bdfe8a6c..659f478d6e8 100644 --- a/bold/challenge/tracker/tracker.go +++ b/bold/challenge/tracker/tracker.go @@ -215,6 +215,11 @@ func (et *Tracker) Spawn(ctx context.Context) { } if err := et.Act(ctx); err != nil { log.Error("Could not act with edge tracker", append(fields, "err", err)...) + select { + case <-time.After(5 * time.Second): + case <-ctx.Done(): + return + } } } } diff --git a/bold/containers/events/producer.go b/bold/containers/events/producer.go index 83615d6e868..42fcf5a8bd1 100644 --- a/bold/containers/events/producer.go +++ b/bold/containers/events/producer.go @@ -22,6 +22,7 @@ type Producer[T any] struct { doneListener chan subId // channel to listen for IDs of subscriptions to be remove. broadcastTimeout time.Duration // maximum duration to wait for an event to be sent. nextId subId // monotonically increasing id for stable subscription identification + stopped chan struct{} // closed when the producer shuts down } type ProducerOpt[T any] func(*Producer[T]) @@ -47,6 +48,7 @@ func NewProducer[T any](opts ...ProducerOpt[T]) *Producer[T] { subscriptionBufferSize: defaultSubscriptionBufferSize, doneListener: make(chan subId, 100), broadcastTimeout: defaultBroadcastTimeout, + stopped: make(chan struct{}), } for _, opt := range opts { opt(producer) @@ -73,6 +75,7 @@ func (ep *Producer[T]) Start(ctx context.Context) { } ep.Unlock() case <-ctx.Done(): + close(ep.stopped) close(ep.doneListener) ep.subs = nil return @@ -86,9 +89,10 @@ func (ep *Producer[T]) Subscribe() *Subscription[T] { ep.Lock() defer ep.Unlock() sub := &Subscription[T]{ - id: ep.nextId, // Assign a stable, monotonically increasing ID - events: make(chan T), - done: ep.doneListener, + id: ep.nextId, // Assign a stable, monotonically increasing ID + events: make(chan T), + done: ep.doneListener, + stopped: ep.stopped, } ep.nextId++ ep.subs = append(ep.subs, sub) @@ -106,6 +110,7 @@ func (ep *Producer[T]) Broadcast(ctx context.Context, event T) { go func(listener *Subscription[T]) { select { case listener.events <- event: + case <-listener.stopped: case <-time.After(ep.broadcastTimeout): case <-ctx.Done(): } @@ -118,9 +123,10 @@ type subId int // Subscription defines a generic handle to a subscription of // events from a producer. type Subscription[T any] struct { - id subId - events chan T - done chan subId + id subId + events chan T + done chan subId + stopped <-chan struct{} } // Next waits for the next event or context cancelation, returning the event or an error. @@ -130,9 +136,10 @@ func (es *Subscription[T]) Next(ctx context.Context) (T, bool) { select { case ev := <-es.events: return ev, false + case <-es.stopped: + return zeroVal, true case <-ctx.Done(): es.done <- es.id - close(es.events) return zeroVal, true } } diff --git a/bold/protocol/sol/transact.go b/bold/protocol/sol/transact.go index 4a93208b1da..f64dbaeff5d 100644 --- a/bold/protocol/sol/transact.go +++ b/bold/protocol/sol/transact.go @@ -101,7 +101,7 @@ func (a *AssertionChain) transact( } ctxWaitMined, cancelWaitMined := context.WithTimeout(ctx, time.Minute) defer cancelWaitMined() - receipt, err := bind.WaitMined(ctxWaitMined, backend, tx) + receipt, err := protocol.WaitMined(ctxWaitMined, backend, tx) if err != nil { return nil, err } @@ -178,7 +178,7 @@ func (a *AssertionChain) waitForTxToBeSafe( // This is to handle the case where the transaction is mined in a block, but then the block is reorged. // In this case, we want to wait for the transaction to be mined again. - receiptLatest, err := bind.WaitMined(ctx, backend, tx) + receiptLatest, err := protocol.WaitMined(ctx, backend, tx) if err != nil { return nil, err } diff --git a/bold/protocol/wait_mined.go b/bold/protocol/wait_mined.go new file mode 100644 index 00000000000..7fb21e6c255 --- /dev/null +++ b/bold/protocol/wait_mined.go @@ -0,0 +1,47 @@ +// Copyright 2023-2026, Offchain Labs, Inc. +// For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE.md + +package protocol + +import ( + "context" + "fmt" + + "github.com/pkg/errors" + + "github.com/ethereum/go-ethereum/accounts/abi/bind" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" +) + +// WaitMined waits for a transaction to be mined by subscribing to new head +// notifications from the backend. This is faster than bind.WaitMined's +// hardcoded 1s polling because ChainBackend always supports head +// subscriptions. Falls back to bind.WaitMined if the subscription fails. +func WaitMined(ctx context.Context, b ChainBackend, tx *types.Transaction) (*types.Receipt, error) { + txHash := tx.Hash() + heads := make(chan *types.Header, 1) + sub, err := b.SubscribeNewHead(ctx, heads) + if err != nil { + log.Warn("Could not subscribe to new heads for WaitMined, falling back to polling", "err", err) + return bind.WaitMined(ctx, b, tx) + } + defer sub.Unsubscribe() + + for { + receipt, err := b.TransactionReceipt(ctx, txHash) + if err == nil { + return receipt, nil + } + select { + case <-ctx.Done(): + return nil, ctx.Err() + case err := <-sub.Err(): + if err != nil { + return nil, fmt.Errorf("head subscription error while waiting for tx: %w", err) + } + return nil, errors.New("head subscription closed unexpectedly") + case <-heads: + } + } +} diff --git a/bold/testing/tx.go b/bold/testing/tx.go index 8e43b312031..1647281e757 100644 --- a/bold/testing/tx.go +++ b/bold/testing/tx.go @@ -11,6 +11,7 @@ import ( "github.com/pkg/errors" + "github.com/ethereum/go-ethereum" "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" @@ -50,7 +51,14 @@ type committer interface { Commit() common.Hash } -// WaitForTx to be mined. This method will trigger .Commit() on a simulated backend. +type headSubscriber interface { + SubscribeNewHead(ctx context.Context, ch chan<- *types.Header) (ethereum.Subscription, error) +} + +// WaitForTx waits for a transaction to be mined. It triggers .Commit() on +// simulated backends. If the backend supports head subscriptions, those are +// used for near-instant notification; otherwise it falls back to +// bind.WaitMined (1-second polling). func WaitForTx(ctx context.Context, be bind.DeployBackend, tx *types.Transaction) error { if simulated, ok := be.(committer); ok { simulated.Commit() @@ -59,7 +67,36 @@ func WaitForTx(ctx context.Context, be bind.DeployBackend, tx *types.Transaction ctx, cancel := context.WithTimeout(ctx, time.Minute) defer cancel() + if subscriber, ok := be.(headSubscriber); ok { + return waitMinedWithSubscription(ctx, be, subscriber, tx) + } _, err := bind.WaitMined(ctx, be, tx) - return err } + +func waitMinedWithSubscription(ctx context.Context, be bind.DeployBackend, subscriber headSubscriber, tx *types.Transaction) error { + heads := make(chan *types.Header, 8) + sub, err := subscriber.SubscribeNewHead(ctx, heads) + if err != nil { + _, err = bind.WaitMined(ctx, be, tx) + return err + } + defer sub.Unsubscribe() + + for { + receipt, err := be.TransactionReceipt(ctx, tx.Hash()) + if err == nil && receipt != nil { + return nil + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-sub.Err(): + // Subscription failed; fall back to polling. + _, err = bind.WaitMined(ctx, be, tx) + return err + case <-heads: + case <-time.After(time.Second): + } + } +} diff --git a/changelog/hkalodner-challenge-test-improvements.md b/changelog/hkalodner-challenge-test-improvements.md new file mode 100644 index 00000000000..2acabc5e288 --- /dev/null +++ b/changelog/hkalodner-challenge-test-improvements.md @@ -0,0 +1,7 @@ +### Fixed +- Fix race condition in event producer shutdown where receiver-side channel close could panic concurrent broadcast goroutines + +### Ignored +- Speed up BOLD challenge protocol tests via subscription-based watcher triggering, execution run caching, and subscription-based WaitMined +- Extract shared helpers for BOLD challenge test node creation to deduplicate ~700 lines across test files +- Cache execution runs in Redis validation consumer to avoid redundant machine creation diff --git a/changelog/hkalodner-waitfortx-subscriptions.md b/changelog/hkalodner-waitfortx-subscriptions.md new file mode 100644 index 00000000000..c689fb4c364 --- /dev/null +++ b/changelog/hkalodner-waitfortx-subscriptions.md @@ -0,0 +1,3 @@ +### Ignored +- Use head subscriptions in WaitForTx for faster test notification +- Fix flaky MaxEmptyBatchDelay test with dynamic sleep diff --git a/system_tests/batch_poster_test.go b/system_tests/batch_poster_test.go index 39988f8ea60..03b73f9a83d 100644 --- a/system_tests/batch_poster_test.go +++ b/system_tests/batch_poster_test.go @@ -910,6 +910,7 @@ func TestBatchPosterPostsReportOnlyBatchAfterMaxEmptyBatchDelay(t *testing.T) { builder := NewNodeBuilder(ctx). DefaultConfig(t, true). + WithPreBoldDeployment(). TakeOwnership() // Enable delayed sequencer and set fast finalization so reports appear quickly on L2 @@ -948,14 +949,18 @@ func TestBatchPosterPostsReportOnlyBatchAfterMaxEmptyBatchDelay(t *testing.T) { // Spin L1 to get batch poster report AdvanceL1(t, ctx, builder.L1.Client, builder.L1Info, 1) - // Wait for the delayed message's timestamp to become old enough to trigger MaxEmptyBatchDelay. - // The batch posting report comes back as a delayed message with an L1 block timestamp. - // L1 block timestamps can be up to ~12 seconds ahead of wall clock time (Ethereum PoS block interval). - // We need to wait for: - // 1. MaxEmptyBatchDelay (1 second) - the configured threshold that triggers batch posting - // 2. ~12-13 seconds - to ensure the L1 block timestamp is in the past relative to time.Now() - // 3. Extra buffer - for the delayed sequencer to process and make the report available - time.Sleep(builder.nodeConfig.BatchPoster.MaxEmptyBatchDelay + 15*time.Second) + // The batch posting report's timestamp comes from the L1 block that included it. + // In the simulated beacon, block timestamps can race far ahead of wall clock + // (each block gets lastBlockTime+1 when blocks are mined faster than 1/second). + // We need wall clock to pass the report's L1 timestamp + MaxEmptyBatchDelay + // before the batch poster will consider the report old enough to trigger posting. + latestHeader, err := builder.L1.Client.HeaderByNumber(ctx, nil) + require.NoError(t, err) + l1AheadBy := time.Until(time.Unix(int64(latestHeader.Time), 0)) //#nosec G115 + if l1AheadBy > 0 { + time.Sleep(l1AheadBy) + } + time.Sleep(builder.nodeConfig.BatchPoster.MaxEmptyBatchDelay) // Force second batch, posting should be triggered by MaxEmptyBatchDelay posted, err = builder.L2.ConsensusNode.BatchPoster.MaybePostSequencerBatch(ctx) diff --git a/system_tests/bold_challenge_protocol_test.go b/system_tests/bold_challenge_protocol_test.go index e9f2fb5e379..f5e0aaebbe9 100644 --- a/system_tests/bold_challenge_protocol_test.go +++ b/system_tests/bold_challenge_protocol_test.go @@ -18,41 +18,30 @@ import ( "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/ethclient" "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rlp" - "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbcompress" "github.com/offchainlabs/nitro/arbnode" - "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/arbos" "github.com/offchainlabs/nitro/arbos/l2pricing" "github.com/offchainlabs/nitro/arbstate" - "github.com/offchainlabs/nitro/bold/challenge" - modes "github.com/offchainlabs/nitro/bold/challenge/types" - "github.com/offchainlabs/nitro/bold/protocol/sol" "github.com/offchainlabs/nitro/bold/state" "github.com/offchainlabs/nitro/bold/testing/setup" "github.com/offchainlabs/nitro/cmd/chaininfo" - "github.com/offchainlabs/nitro/cmd/nitro/init" "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/execution_consensus" "github.com/offchainlabs/nitro/solgen/go/bridgegen" - "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" - "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker" "github.com/offchainlabs/nitro/staker/bold" "github.com/offchainlabs/nitro/statetransfer" "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/redisutil" - "github.com/offchainlabs/nitro/util/signature" - "github.com/offchainlabs/nitro/util/testhelpers" "github.com/offchainlabs/nitro/validator/client/redis" "github.com/offchainlabs/nitro/validator/server_arb" "github.com/offchainlabs/nitro/validator/server_common" @@ -132,7 +121,7 @@ func testChallengeProtocolBOLD(t *gotesting.T, useExternalSigner bool, useRedis go keepChainMoving(t, ctx, l1info, l1client) l2nodeConfig := arbnode.ConfigDefaultL1Test() - l2StackB, _, l2nodeB, l2execNodeB, _ := create2ndNodeWithConfigForBoldProtocol( + l2StackB, _, l2nodeB, l2execNodeB := create2ndNodeWithConfigForBoldProtocol( t, ctx, l2nodeA, @@ -140,7 +129,6 @@ func testChallengeProtocolBOLD(t *gotesting.T, useExternalSigner bool, useRedis l1info, &l2info.ArbInitData, l2nodeConfig, - nil, sconf, stakeTokenAddr, asserterOpts, @@ -188,7 +176,7 @@ func testChallengeProtocolBOLD(t *gotesting.T, useExternalSigner bool, useRedis valCfg := valnode.TestValidationConfig valCfg.UseJit = false _, valStackB := createTestValidationNode(t, ctx, &valCfg, spawnerOpts...) - + blockValidatorConfigB := staker.TestBlockValidatorConfig statelessB, err := staker.NewStatelessBlockValidator( l2nodeB.InboxReader, l2nodeB.InboxTracker, @@ -196,7 +184,7 @@ func testChallengeProtocolBOLD(t *gotesting.T, useExternalSigner bool, useRedis l2nodeB.ExecutionRecorder, l2nodeB.ConsensusDB, nil, - StaticFetcherFrom(t, &blockValidatorConfig), + StaticFetcherFrom(t, &blockValidatorConfigB), valStackB, locator.LatestWasmModuleRoot(), ) @@ -219,7 +207,7 @@ func testChallengeProtocolBOLD(t *gotesting.T, useExternalSigner bool, useRedis statelessB, l2nodeB.InboxTracker, l2nodeB.TxStreamer, - StaticFetcherFrom(t, &blockValidatorConfig), + StaticFetcherFrom(t, &blockValidatorConfigB), nil, ) Require(t, err) @@ -267,28 +255,7 @@ func testChallengeProtocolBOLD(t *gotesting.T, useExternalSigner bool, useRedis chalManagerAddr := assertionChain.SpecChallengeManager() evilOpts := l1info.GetDefaultTransactOpts("EvilAsserter", ctx) - l1ChainId, err := l1client.ChainID(ctx) - Require(t, err) - dp, err := arbnode.StakerDataposter( - ctx, - rawdb.NewTable(l2nodeB.ConsensusDB, storage.StakerPrefix), - l2nodeB.L1Reader, - &evilOpts, - NewCommonConfigFetcher(l2nodeConfig), - l2nodeB.SyncMonitor, - l1ChainId, - ) - Require(t, err) - chainB, err := sol.NewAssertionChain( - ctx, - assertionChain.RollupAddress(), - chalManagerAddr.Address(), - &evilOpts, - l1client, - bold.NewDataPosterTransactor(dp), - sol.WithRpcHeadBlockNumber(rpc.LatestBlockNumber), - ) - Require(t, err) + chainB := createEvilAssertionChain(t, ctx, assertionChain.RollupAddress(), chalManagerAddr.Address(), l1client, l2nodeB, &evilOpts, l2nodeConfig) l2info.GenerateAccount("Destination") sequencerTxOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) @@ -387,13 +354,12 @@ func testChallengeProtocolBOLD(t *gotesting.T, useExternalSigner bool, useRedis Require(t, err) totalBatches := totalBatchesBig.Uint64() - // Wait until the validators have validated the batches. for { lastInfo, err := blockValidatorA.ReadLastValidatedInfo() if lastInfo == nil || err != nil { continue } - t.Log(lastInfo.GlobalState.Batch, totalBatches-1) + t.Log("Validator A:", lastInfo.GlobalState.Batch, "/", totalBatches-1) if lastInfo.GlobalState.Batch >= totalBatches-1 { break } @@ -404,120 +370,19 @@ func testChallengeProtocolBOLD(t *gotesting.T, useExternalSigner bool, useRedis if lastInfo == nil || err != nil { continue } - t.Log(lastInfo.GlobalState.Batch, totalBatches-1) + t.Log("Validator B:", lastInfo.GlobalState.Batch, "/", totalBatches-1) if lastInfo.GlobalState.Batch >= totalBatches-1 { break } time.Sleep(time.Millisecond * 200) } - provider := state.NewHistoryCommitmentProvider( - stateManager, - stateManager, - stateManager, - []state.Height{ - state.Height(blockChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(smallStepChallengeLeafHeight), - }, - stateManager, - nil, // Api db - ) - - evilProvider := state.NewHistoryCommitmentProvider( - stateManagerB, - stateManagerB, - stateManagerB, - []state.Height{ - state.Height(blockChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(smallStepChallengeLeafHeight), - }, - stateManagerB, - nil, // Api db - ) - - stackOpts := []challenge.StackOpt{ - challenge.StackWithName("honest"), - challenge.StackWithMode(modes.MakeMode), - challenge.StackWithPostingInterval(time.Second * 3), - challenge.StackWithPollingInterval(time.Second), - challenge.StackWithMinimumGapToParentAssertion(0), - challenge.StackWithAverageBlockCreationTime(time.Second), - } - - manager, err := challenge.NewChallengeStack( - assertionChain, - provider, - stackOpts..., - ) - Require(t, err) - - evilStackOpts := append(stackOpts, challenge.StackWithName("evil")) - - managerB, err := challenge.NewChallengeStack( - chainB, - evilProvider, - evilStackOpts..., - ) - Require(t, err) - - manager.Start(ctx) - managerB.Start(ctx) - - chalManager := assertionChain.SpecChallengeManager() - filterer, err := challengeV2gen.NewEdgeChallengeManagerFilterer(chalManager.Address(), l1client) - Require(t, err) - - fromBlock := uint64(0) - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - for { - select { - case <-ticker.C: - latestBlock, err := l1client.HeaderByNumber(ctx, nil) - Require(t, err) - toBlock := latestBlock.Number.Uint64() - if fromBlock == toBlock { - continue - } - filterOpts := &bind.FilterOpts{ - Start: fromBlock, - End: &toBlock, - Context: ctx, - } - it, err := filterer.FilterEdgeConfirmedByOneStepProof(filterOpts, nil, nil) - Require(t, err) - for it.Next() { - if it.Error() != nil { - t.Fatalf("Error in filter iterator: %v", it.Error()) - } - t.Log("Received event of OSP confirmation!") - tx, _, err := l1client.TransactionByHash(ctx, it.Event.Raw.TxHash) - Require(t, err) - signer := types.NewCancunSigner(tx.ChainId()) - address, err := signer.Sender(tx) - Require(t, err) - if address == asserterOpts.From { - t.Log("Honest party won OSP, impossible for evil party to win if honest party continues") - Require(t, it.Close()) - return - } - } - fromBlock = toBlock - case <-ctx.Done(): - return - } - } + runFastChallengeAndAssertHonestWin(t, ctx, assertionChain, chainB, stateManager, stateManagerB, l2nodeA, l2nodeB, l1client, asserterOpts.From) } -// Every 3 seconds, send an L1 transaction to keep the chain moving. +// Send L1 transactions to keep the chain moving. func keepChainMoving(t *gotesting.T, ctx context.Context, l1Info *BlockchainTestInfo, client *ethclient.Client) { - delay := time.Second * 3 + delay := 100 * time.Millisecond for { select { case <-ctx.Done(): @@ -549,13 +414,11 @@ func create2ndNodeWithConfigForBoldProtocol( l1info *BlockchainTestInfo, l2InitData *statetransfer.ArbosInitializationInfo, nodeConfig *arbnode.Config, - stackConfig *node.Config, rollupStackConf setup.RollupStackConfig, stakeTokenAddr common.Address, asserterOpts *bind.TransactOpts, enableCustomDA bool, -) (*node.Node, *ethclient.Client, *arbnode.Node, *gethexec.ExecutionNode, *sol.AssertionChain) { - fatalErrChan := make(chan error, 10) +) (*node.Node, *ethclient.Client, *arbnode.Node, *gethexec.ExecutionNode) { l1rpcClient := l1stack.Attach() l1client := ethclient.NewClient(l1rpcClient) firstExec, ok := first.ExecutionClient.(*gethexec.ExecutionNode) @@ -571,77 +434,7 @@ func create2ndNodeWithConfigForBoldProtocol( l1info.SetContract("EvilRollup", addresses.Rollup) l1info.SetContract("EvilUpgradeExecutor", addresses.UpgradeExecutor) - if nodeConfig == nil { - nodeConfig = arbnode.ConfigDefaultL1NonSequencerTest() - } - nodeConfig.ParentChainReader.OldHeaderTimeout = 10 * time.Minute - nodeConfig.BatchPoster.DataPoster.MaxMempoolTransactions = 18 - if stackConfig == nil { - stackConfig = testhelpers.CreateStackConfigForTest(t.TempDir()) - stackConfig.DBEngine = rawdb.DBPebble - } - l2stack, err := node.New(stackConfig) - Require(t, err) - - l2executionDB, err := l2stack.OpenDatabase("chaindb", 0, 0, "", false) - Require(t, err) - l2consensusDB, err := l2stack.OpenDatabase("arbdb", 0, 0, "", false) - Require(t, err) - - AddValNodeIfNeeded(t, ctx, nodeConfig, true, "", "") - - dataSigner := signature.DataSignerFromPrivateKey(l1info.GetInfoWithPrivKey("Sequencer").PrivateKey) - txOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) - - initReader := statetransfer.NewMemoryInitDataReader(l2InitData) - initMessage, err := nitroinit.GetConsensusParsedInitMsg(ctx, true, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) - Require(t, err) - - execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) - Require(t, execConfig.Validate()) - coreCacheConfig := gethexec.DefaultCacheConfigFor(&execConfig.Caching) - l2blockchain, err := gethexec.WriteOrTestBlockChain(l2executionDB, coreCacheConfig, initReader, chainConfig, nil, nil, initMessage, &execConfig.TxIndexer, 0, execConfig.ExposeMultiGas) - Require(t, err) - - l1ChainId, err := l1client.ChainID(ctx) - Require(t, err) - execNode, err := gethexec.CreateExecutionNode(ctx, l2stack, l2executionDB, l2blockchain, l1client, NewCommonConfigFetcher(execConfig), l1ChainId, 0) - Require(t, err) - locator, err := server_common.NewMachineLocator("") - Require(t, err) - l2node, err := arbnode.CreateConsensusNode(ctx, l2stack, execNode, l2consensusDB, NewCommonConfigFetcher(nodeConfig), l2blockchain.Config(), l1client, addresses, &txOpts, &txOpts, dataSigner, fatalErrChan, l1ChainId, nil /* blob reader */, locator.LatestWasmModuleRoot()) - Require(t, err) - - l2client := ClientForStack(t, l2stack, clientForStackUseHTTP(stackConfig)) - - StartWatchChanErr(t, ctx, fatalErrChan, l2node) - - rollupUserLogic, err := rollupgen.NewRollupUserLogic(addresses.Rollup, l1client) - Require(t, err) - chalManagerAddr, err := rollupUserLogic.ChallengeManager(&bind.CallOpts{}) - Require(t, err) - evilOpts := l1info.GetDefaultTransactOpts("EvilAsserter", ctx) - dp, err := arbnode.StakerDataposter( - ctx, - rawdb.NewTable(l2consensusDB, storage.StakerPrefix), - l2node.L1Reader, - &evilOpts, - NewCommonConfigFetcher(nodeConfig), - l2node.SyncMonitor, - l1ChainId, - ) - Require(t, err) - assertionChain, err := sol.NewAssertionChain( - ctx, - addresses.Rollup, - chalManagerAddr, - &evilOpts, - l1client, - bold.NewDataPosterTransactor(dp), - ) - Require(t, err) - - return l2stack, l2client, l2node, execNode, assertionChain + return createSecondL2Node(t, ctx, first, l1info, l1client, l2InitData, addresses, nodeConfig, nil) } // createBoldBatchData creates the compressed batch data diff --git a/system_tests/bold_customda_challenge_test.go b/system_tests/bold_customda_challenge_test.go index 7d7e10d5ee5..1ebf443ae55 100644 --- a/system_tests/bold_customda_challenge_test.go +++ b/system_tests/bold_customda_challenge_test.go @@ -19,24 +19,16 @@ import ( "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/crypto" "github.com/ethereum/go-ethereum/ethclient" - "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbnode" - "github.com/offchainlabs/nitro/arbnode/dataposter/storage" "github.com/offchainlabs/nitro/arbos/l2pricing" - "github.com/offchainlabs/nitro/bold/challenge" - modes "github.com/offchainlabs/nitro/bold/challenge/types" - "github.com/offchainlabs/nitro/bold/protocol/sol" "github.com/offchainlabs/nitro/bold/state" "github.com/offchainlabs/nitro/bold/testing/setup" "github.com/offchainlabs/nitro/cmd/chaininfo" - "github.com/offchainlabs/nitro/cmd/nitro/init" "github.com/offchainlabs/nitro/daprovider" "github.com/offchainlabs/nitro/daprovider/daclient" "github.com/offchainlabs/nitro/daprovider/data_streaming" @@ -45,15 +37,12 @@ import ( "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/execution_consensus" "github.com/offchainlabs/nitro/solgen/go/bridgegen" - "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/staker" "github.com/offchainlabs/nitro/staker/bold" - "github.com/offchainlabs/nitro/statetransfer" "github.com/offchainlabs/nitro/util" "github.com/offchainlabs/nitro/util/containers" "github.com/offchainlabs/nitro/util/signature" - "github.com/offchainlabs/nitro/util/testhelpers" "github.com/offchainlabs/nitro/validator/proofenhancement" "github.com/offchainlabs/nitro/validator/server_arb" "github.com/offchainlabs/nitro/validator/server_common" @@ -154,82 +143,6 @@ func (w *assertingWriter) GetMaxMessageSize() containers.PromiseInterface[int] { panic("assertingWriter.GetMaxMessageSize should never be called - evil provider server should not be used for writing") } -// createNodeBWithSharedContracts creates a second node that uses the same contracts as the first node -func createNodeBWithSharedContracts( - t *testing.T, - ctx context.Context, - first *arbnode.Node, - l1stack *node.Node, - l1info *BlockchainTestInfo, - l2InitData *statetransfer.ArbosInitializationInfo, - nodeConfig *arbnode.Config, - stackConfig *node.Config, - rollupStackConf setup.RollupStackConfig, - stakeTokenAddr common.Address, - l1client *ethclient.Client, - assertionChain *sol.AssertionChain, -) (*ethclient.Client, *arbnode.Node, *gethexec.ExecutionNode, *node.Node) { - fatalErrChan := make(chan error, 10) - - firstExec, ok := first.ExecutionClient.(*gethexec.ExecutionNode) - if !ok { - Fatal(t, "not geth execution node") - } - chainConfig := firstExec.ArbInterface.BlockChain().Config() - - // Use the same addresses as the first node - addresses := first.DeployInfo - - if nodeConfig == nil { - nodeConfig = arbnode.ConfigDefaultL1NonSequencerTest() - } - nodeConfig.ParentChainReader.OldHeaderTimeout = 10 * time.Minute - nodeConfig.BatchPoster.DataPoster.MaxMempoolTransactions = 18 - if stackConfig == nil { - stackConfig = testhelpers.CreateStackConfigForTest(t.TempDir()) - stackConfig.DBEngine = rawdb.DBPebble - } - l2stack, err := node.New(stackConfig) - Require(t, err) - - l2executionDB, err := l2stack.OpenDatabase("chaindb", 0, 0, "", false) - Require(t, err) - l2consensusDB, err := l2stack.OpenDatabase("arbdb", 0, 0, "", false) - Require(t, err) - - AddValNodeIfNeeded(t, ctx, nodeConfig, true, "", "") - - dataSigner := signature.DataSignerFromPrivateKey(l1info.GetInfoWithPrivKey("Sequencer").PrivateKey) - txOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) - - initReader := statetransfer.NewMemoryInitDataReader(l2InitData) - initMessage, err := nitroinit.GetConsensusParsedInitMsg(ctx, true, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) - Require(t, err) - - execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) - Require(t, execConfig.Validate()) - coreCacheConfig := gethexec.DefaultCacheConfigFor(&execConfig.Caching) - l2blockchain, err := gethexec.WriteOrTestBlockChain(l2executionDB, coreCacheConfig, initReader, chainConfig, nil, nil, initMessage, &execConfig.TxIndexer, 0, execConfig.ExposeMultiGas) - Require(t, err) - - execNode, err := gethexec.CreateExecutionNode(ctx, l2stack, l2executionDB, l2blockchain, l1client, NewCommonConfigFetcher(execConfig), big.NewInt(1337), 0) - Require(t, err) - l1ChainId, err := l1client.ChainID(ctx) - Require(t, err) - locator, err := server_common.NewMachineLocator("") - Require(t, err) - - // Create node using the same addresses as the first node - l2node, err := arbnode.CreateConsensusNode(ctx, l2stack, execNode, l2consensusDB, NewCommonConfigFetcher(nodeConfig), l2blockchain.Config(), l1client, addresses, &txOpts, &txOpts, dataSigner, fatalErrChan, l1ChainId, nil /* blob reader */, locator.LatestWasmModuleRoot()) - Require(t, err) - - l2client := ClientForStack(t, l2stack, clientForStackUseHTTP(stackConfig)) - - StartWatchChanErr(t, ctx, fatalErrChan, l2node) - - return l2client, l2node, execNode, l2stack -} - func testChallengeProtocolBOLDCustomDA(t *testing.T, evilStrategy EvilStrategy, spawnerOpts ...server_arb.SpawnerOption) { goodDir, err := os.MkdirTemp("", "good_*") Require(t, err) @@ -274,7 +187,7 @@ func testChallengeProtocolBOLDCustomDA(t *testing.T, evilStrategy EvilStrategy, nodeConfigA.DA.ExternalProvider.Enable = true // Set up L1 first to get validator address - l1info, _, l1client, l1stack, addresses, stakeTokenAddr, asserterOpts, signerCfg := setupL1WithRollupAddresses( + l1info, _, l1client, l1stack, addresses, _, asserterOpts, signerCfg := setupL1WithRollupAddresses( t, ctx, sconf, false, nodeConfigA, l2chainConfig, true, // useExternalSigner=false, enableCustomDA=true ) defer requireClose(t, l1stack) @@ -341,19 +254,8 @@ func testChallengeProtocolBOLDCustomDA(t *testing.T, evilStrategy EvilStrategy, l2nodeConfig.DA.ExternalProvider.RPC.URL = providerURLNodeB // Create node B using the same contracts as node A - l2clientB, l2nodeB, l2execNodeB, l2stackB := createNodeBWithSharedContracts( - t, - ctx, - l2nodeA, - l1stack, - l1info, - &l2info.ArbInitData, - l2nodeConfig, - nil, - sconf, - stakeTokenAddr, - l1client, - assertionChain, + l2stackB, l2clientB, l2nodeB, l2execNodeB := createSecondL2Node( + t, ctx, l2nodeA, l1info, l1client, &l2info.ArbInitData, l2nodeA.DeployInfo, l2nodeConfig, nil, ) defer l2nodeB.StopAndWait() _ = l2clientB // suppress unused variable warning @@ -510,28 +412,7 @@ func testChallengeProtocolBOLDCustomDA(t *testing.T, evilStrategy EvilStrategy, chalManagerAddr := assertionChain.SpecChallengeManager() evilOpts := l1info.GetDefaultTransactOpts("EvilAsserter", ctx) - l1ChainId, err := l1client.ChainID(ctx) - Require(t, err) - dp, err := arbnode.StakerDataposter( - ctx, - rawdb.NewTable(l2nodeB.ConsensusDB, storage.StakerPrefix), - l2nodeB.L1Reader, - &evilOpts, - NewCommonConfigFetcher(l2nodeConfig), - l2nodeB.SyncMonitor, - l1ChainId, - ) - Require(t, err) - chainB, err := sol.NewAssertionChain( - ctx, - assertionChain.RollupAddress(), - chalManagerAddr.Address(), - &evilOpts, - l1client, - bold.NewDataPosterTransactor(dp), - sol.WithRpcHeadBlockNumber(rpc.LatestBlockNumber), - ) - Require(t, err) + chainB := createEvilAssertionChain(t, ctx, assertionChain.RollupAddress(), chalManagerAddr.Address(), l1client, l2nodeB, &evilOpts, l2nodeConfig) l2info.GenerateAccount("Destination") sequencerTxOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) @@ -850,107 +731,5 @@ func testChallengeProtocolBOLDCustomDA(t *testing.T, evilStrategy EvilStrategy, time.Sleep(time.Millisecond * 200) } - provider := state.NewHistoryCommitmentProvider( - stateManager, - stateManager, - stateManager, - []state.Height{ - state.Height(blockChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(smallStepChallengeLeafHeight), - }, - stateManager, - nil, // Api db - ) - - evilHistoryProvider := state.NewHistoryCommitmentProvider( - stateManagerB, - stateManagerB, - stateManagerB, - []state.Height{ - state.Height(blockChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(smallStepChallengeLeafHeight), - }, - stateManagerB, - nil, // Api db - ) - - stackOpts := []challenge.StackOpt{ - challenge.StackWithName("honest"), - challenge.StackWithMode(modes.MakeMode), - challenge.StackWithPostingInterval(time.Second * 3), - challenge.StackWithPollingInterval(time.Second), - challenge.StackWithMinimumGapToParentAssertion(0), - challenge.StackWithAverageBlockCreationTime(time.Second), - } - - manager, err := challenge.NewChallengeStack( - assertionChain, - provider, - stackOpts..., - ) - Require(t, err) - - evilStackOpts := append(stackOpts, challenge.StackWithName("evil")) - - managerB, err := challenge.NewChallengeStack( - chainB, - evilHistoryProvider, - evilStackOpts..., - ) - Require(t, err) - - manager.Start(ctx) - managerB.Start(ctx) - - chalManager := assertionChain.SpecChallengeManager() - filterer, err := challengeV2gen.NewEdgeChallengeManagerFilterer(chalManager.Address(), l1client) - Require(t, err) - - fromBlock := uint64(0) - ticker := time.NewTicker(time.Second) - defer ticker.Stop() - for { - select { - case <-ticker.C: - latestBlock, err := l1client.HeaderByNumber(ctx, nil) - Require(t, err) - toBlock := latestBlock.Number.Uint64() - if fromBlock == toBlock { - continue - } - filterOpts := &bind.FilterOpts{ - Start: fromBlock, - End: &toBlock, - Context: ctx, - } - it, err := filterer.FilterEdgeConfirmedByOneStepProof(filterOpts, nil, nil) - Require(t, err) - for it.Next() { - if it.Error() != nil { - t.Fatalf("FATAL: Error in filter iterator: %v", it.Error()) - } - t.Log("Received event of OSP confirmation!") - tx, _, err := l1client.TransactionByHash(ctx, it.Event.Raw.TxHash) - Require(t, err) - signer := types.NewCancunSigner(tx.ChainId()) - address, err := signer.Sender(tx) - Require(t, err) - if address == l1info.GetDefaultTransactOpts("Asserter", ctx).From { - t.Log("Honest party won OSP, impossible for evil party to win if honest party continues") - Require(t, it.Close()) - time.Sleep(5 * time.Second) - return - } - } - fromBlock = toBlock - case <-ctx.Done(): - return - } - } + runFastChallengeAndAssertHonestWin(t, ctx, assertionChain, chainB, stateManager, stateManagerB, l2nodeA, l2nodeB, l1client, l1info.GetDefaultTransactOpts("Asserter", ctx).From) } diff --git a/system_tests/bold_l3_support_test.go b/system_tests/bold_l3_support_test.go index 7c688cc7d19..aabae402ab2 100644 --- a/system_tests/bold_l3_support_test.go +++ b/system_tests/bold_l3_support_test.go @@ -14,21 +14,12 @@ import ( "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/ethclient" "github.com/ethereum/go-ethereum/params" - "github.com/ethereum/go-ethereum/rpc" "github.com/offchainlabs/nitro/arbnode" - "github.com/offchainlabs/nitro/arbnode/dataposter/storage" - "github.com/offchainlabs/nitro/bold/challenge" - modes "github.com/offchainlabs/nitro/bold/challenge/types" - "github.com/offchainlabs/nitro/bold/protocol/sol" - "github.com/offchainlabs/nitro/bold/state" - "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" "github.com/offchainlabs/nitro/solgen/go/localgen" "github.com/offchainlabs/nitro/solgen/go/rollupgen" - "github.com/offchainlabs/nitro/staker/bold" ) func TestL3ChallengeProtocolBOLD(t *testing.T) { @@ -81,12 +72,18 @@ func TestL3ChallengeProtocolBOLD(t *testing.T) { builder.L2Info.GenerateAccount("EvilAsserter") fundL3Staker(t, ctx, builder, builder.L2.Client, "EvilAsserter") - assertionChain, cleanupHonestChallengeManager := startL3BoldChallengeManager(t, ctx, builder, firstNodeTestClient, "HonestAsserter", nil) - defer cleanupHonestChallengeManager() + l3Params := boldChallengeManagerParams{ + rollupAddr: builder.l3Addresses.Rollup, + parentClient: builder.L2.Client, + parentInfo: builder.L2Info, + l1Reader: builder.L3.ConsensusNode.L1Reader, + nodeConfig: builder.nodeConfig, + } - _ = assertionChain + assertionChain, cleanupHonestChallengeManager := startBoldChallengeManager(t, ctx, l3Params, firstNodeTestClient, "HonestAsserter", nil) + defer cleanupHonestChallengeManager() - _, cleanupEvilChallengeManager := startL3BoldChallengeManager(t, ctx, builder, secondNodeTestClient, "EvilAsserter", func(stateManager BoldStateProviderInterface) BoldStateProviderInterface { + _, cleanupEvilChallengeManager := startBoldChallengeManager(t, ctx, l3Params, secondNodeTestClient, "EvilAsserter", func(stateManager BoldStateProviderInterface) BoldStateProviderInterface { return &incorrectBlockStateProvider{ honest: stateManager, chain: assertionChain, @@ -99,64 +96,7 @@ func TestL3ChallengeProtocolBOLD(t *testing.T) { TransferBalance(t, "Faucet", "Faucet", common.Big0, builder.L3Info, builder.L3.Client, ctx) // Everything's setup, now just wait for the challenge to complete and ensure the honest party won - rollupUserLogic, err := rollupgen.NewRollupUserLogic(builder.l3Addresses.Rollup, builder.L2.Client) - Require(t, err) - chalManagerAddr, err := rollupUserLogic.ChallengeManager(&bind.CallOpts{Context: ctx}) - Require(t, err) - filterer, err := challengeV2gen.NewEdgeChallengeManagerFilterer(chalManagerAddr, builder.L2.Client) - Require(t, err) - - fromBlock := uint64(0) - ticker := time.NewTicker(30 * time.Second) - defer ticker.Stop() - for { - select { - case <-ticker.C: - latestBlock, err := builder.L2.Client.HeaderByNumber(ctx, nil) - if err != nil { - t.Logf("Error getting latest block: %v", err) - continue - } - toBlock := latestBlock.Number.Uint64() - if fromBlock == toBlock { - continue - } - filterOpts := &bind.FilterOpts{ - Start: fromBlock, - End: &toBlock, - Context: ctx, - } - it, err := filterer.FilterEdgeConfirmedByOneStepProof(filterOpts, nil, nil) - if err != nil { - t.Logf("Error creating filter: %v", err) - continue - } - for it.Next() { - if it.Error() != nil { - t.Fatalf("Error in filter iterator: %v", it.Error()) - } - tx, _, err := builder.L2.Client.TransactionByHash(ctx, it.Event.Raw.TxHash) - if err != nil { - t.Logf("Error getting transaction: %v", err) - continue - } - signer := types.NewCancunSigner(tx.ChainId()) - address, err := signer.Sender(tx) - if err != nil { - t.Logf("Error getting sender address: %v", err) - continue - } - if address == builder.L2Info.GetAddress("Validator") { - t.Log("Honest party confirmed a challenge edge by one step proof") - Require(t, it.Close()) - return - } - } - fromBlock = toBlock - case <-ctx.Done(): - return - } - } + waitForHonestOSPWin(t, ctx, builder.L2.Client, assertionChain.SpecChallengeManager().Address(), builder.L2Info.GetAddress("Validator"), 30*time.Second) } func fundL3Staker(t *testing.T, ctx context.Context, builder *NodeBuilder, l2Client *ethclient.Client, name string) { @@ -192,96 +132,3 @@ func fundL3Staker(t *testing.T, ctx context.Context, builder *NodeBuilder, l2Cli _, err = builder.L2.EnsureTxSucceeded(tx) Require(t, err) } - -func startL3BoldChallengeManager(t *testing.T, ctx context.Context, builder *NodeBuilder, node *TestClient, addressName string, mockStateProvider func(BoldStateProviderInterface) BoldStateProviderInterface) (*sol.AssertionChain, func()) { - if !builder.deployBold { - t.Fatal("bold deployment not enabled") - } - - var stateManager BoldStateProviderInterface - var err error - cacheDir := t.TempDir() - stateManager, err = bold.NewBOLDStateProvider( - node.ConsensusNode.BlockValidator, - node.ConsensusNode.StatelessBlockValidator, - state.Height(blockChallengeLeafHeight), - &bold.StateProviderConfig{ - ValidatorName: addressName, - MachineLeavesCachePath: cacheDir, - CheckBatchFinality: false, - }, - cacheDir, - node.ConsensusNode.InboxTracker, - node.ConsensusNode.TxStreamer, - node.ConsensusNode.InboxReader, - nil, - ) - Require(t, err) - - if mockStateProvider != nil { - stateManager = mockStateProvider(stateManager) - } - - provider := state.NewHistoryCommitmentProvider( - stateManager, - stateManager, - stateManager, - []state.Height{ - state.Height(blockChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(smallStepChallengeLeafHeight), - }, - stateManager, - nil, // Api db - ) - - rollupUserLogic, err := rollupgen.NewRollupUserLogic(builder.l3Addresses.Rollup, builder.L2.Client) - Require(t, err) - chalManagerAddr, err := rollupUserLogic.ChallengeManager(&bind.CallOpts{}) - Require(t, err) - - txOpts := builder.L2Info.GetDefaultTransactOpts(addressName, ctx) - - dp, err := arbnode.StakerDataposter( - ctx, - rawdb.NewTable(node.ConsensusNode.ConsensusDB, storage.StakerPrefix), - builder.L3.ConsensusNode.L1Reader, - &txOpts, - NewCommonConfigFetcher(builder.nodeConfig), - node.ConsensusNode.SyncMonitor, - builder.L2Info.Signer.ChainID(), - ) - Require(t, err) - - assertionChain, err := sol.NewAssertionChain( - ctx, - builder.l3Addresses.Rollup, - chalManagerAddr, - &txOpts, - builder.L2.Client, - bold.NewDataPosterTransactor(dp), - sol.WithRpcHeadBlockNumber(rpc.LatestBlockNumber), - ) - Require(t, err) - - stackOpts := []challenge.StackOpt{ - challenge.StackWithName(addressName), - challenge.StackWithMode(modes.MakeMode), - challenge.StackWithPostingInterval(time.Second * 3), - challenge.StackWithPollingInterval(time.Second), - challenge.StackWithAverageBlockCreationTime(time.Second), - challenge.StackWithMinimumGapToParentAssertion(0), - } - - challengeManager, err := challenge.NewChallengeStack( - assertionChain, - provider, - stackOpts..., - ) - Require(t, err) - - challengeManager.Start(ctx) - return assertionChain, challengeManager.StopAndWait -} diff --git a/system_tests/bold_new_challenge_test.go b/system_tests/bold_new_challenge_test.go index 0bb2d6a5739..2aeb6898342 100644 --- a/system_tests/bold_new_challenge_test.go +++ b/system_tests/bold_new_challenge_test.go @@ -16,6 +16,8 @@ import ( "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/ethclient" + "github.com/ethereum/go-ethereum/node" "github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/rpc" @@ -28,10 +30,18 @@ import ( "github.com/offchainlabs/nitro/bold/protocol" "github.com/offchainlabs/nitro/bold/protocol/sol" "github.com/offchainlabs/nitro/bold/state" + "github.com/offchainlabs/nitro/cmd/chaininfo" + "github.com/offchainlabs/nitro/cmd/nitro/init" + "github.com/offchainlabs/nitro/execution/gethexec" "github.com/offchainlabs/nitro/solgen/go/challengeV2gen" "github.com/offchainlabs/nitro/solgen/go/mocksgen" "github.com/offchainlabs/nitro/solgen/go/rollupgen" "github.com/offchainlabs/nitro/staker/bold" + "github.com/offchainlabs/nitro/statetransfer" + "github.com/offchainlabs/nitro/util/headerreader" + "github.com/offchainlabs/nitro/util/signature" + "github.com/offchainlabs/nitro/util/testhelpers" + "github.com/offchainlabs/nitro/validator/server_common" ) type incorrectBlockStateProvider struct { @@ -157,10 +167,18 @@ func testChallengeProtocolBOLDVirtualBlocks(t *testing.T, wrongAtFirstVirtual bo builder.L1Info.GenerateAccount("EvilAsserter") fundBoldStaker(t, ctx, builder, "EvilAsserter") - assertionChain, cleanupHonestChallengeManager := startBoldChallengeManager(t, ctx, builder, builder.L2, "HonestAsserter", nil) + l2Params := boldChallengeManagerParams{ + rollupAddr: builder.addresses.Rollup, + parentClient: builder.L1.Client, + parentInfo: builder.L1Info, + l1Reader: builder.L2.ConsensusNode.L1Reader, + nodeConfig: builder.nodeConfig, + } + + assertionChain, cleanupHonestChallengeManager := startBoldChallengeManager(t, ctx, l2Params, builder.L2, "HonestAsserter", nil) defer cleanupHonestChallengeManager() - _, cleanupEvilChallengeManager := startBoldChallengeManager(t, ctx, builder, evilNode, "EvilAsserter", func(stateManager BoldStateProviderInterface) BoldStateProviderInterface { + _, cleanupEvilChallengeManager := startBoldChallengeManager(t, ctx, l2Params, evilNode, "EvilAsserter", func(stateManager BoldStateProviderInterface) BoldStateProviderInterface { p := &incorrectBlockStateProvider{ honest: stateManager, chain: assertionChain, @@ -176,18 +194,77 @@ func testChallengeProtocolBOLDVirtualBlocks(t *testing.T, wrongAtFirstVirtual bo TransferBalance(t, "Faucet", "Faucet", common.Big0, builder.L2Info, builder.L2.Client, ctx) // Everything's setup, now just wait for the challenge to complete and ensure the honest party won + waitForHonestOSPWin(t, ctx, builder.L1.Client, assertionChain.SpecChallengeManager().Address(), builder.L1Info.GetAddress("HonestAsserter"), time.Second) +} + +func fundBoldStaker(t *testing.T, ctx context.Context, builder *NodeBuilder, name string) { + balance := big.NewInt(params.Ether) + balance.Mul(balance, big.NewInt(100)) + TransferBalance(t, "Faucet", name, balance, builder.L1Info, builder.L1.Client, ctx) + + rollupUserLogic, err := rollupgen.NewRollupUserLogic(builder.addresses.Rollup, builder.L1.Client) + Require(t, err) + stakeToken, err := rollupUserLogic.StakeToken(&bind.CallOpts{Context: ctx}) + Require(t, err) + stakeTokenWeth, err := mocksgen.NewTestWETH9(stakeToken, builder.L1.Client) + Require(t, err) + + txOpts := builder.L1Info.GetDefaultTransactOpts(name, ctx) + + txOpts.Value = big.NewInt(params.Ether) + tx, err := stakeTokenWeth.Deposit(&txOpts) + Require(t, err) + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) + txOpts.Value = nil + + tx, err = stakeTokenWeth.Approve(&txOpts, builder.addresses.Rollup, balance) + Require(t, err) + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) - chalManager := assertionChain.SpecChallengeManager() - filterer, err := challengeV2gen.NewEdgeChallengeManagerFilterer(chalManager.Address(), builder.L1.Client) + challengeManager, err := rollupUserLogic.ChallengeManager(&bind.CallOpts{Context: ctx}) + Require(t, err) + tx, err = stakeTokenWeth.Approve(&txOpts, challengeManager, balance) + Require(t, err) + _, err = builder.L1.EnsureTxSucceeded(tx) + Require(t, err) +} + +func newTestHistoryProvider(sm BoldStateProviderInterface) *state.HistoryCommitmentProvider { + return state.NewHistoryCommitmentProvider( + sm, sm, sm, + []state.Height{ + state.Height(blockChallengeLeafHeight), + state.Height(bigStepChallengeLeafHeight), + state.Height(bigStepChallengeLeafHeight), + state.Height(bigStepChallengeLeafHeight), + state.Height(smallStepChallengeLeafHeight), + }, + sm, + nil, + ) +} + +func waitForHonestOSPWin( + t *testing.T, + ctx context.Context, + client *ethclient.Client, + chalManagerAddr common.Address, + honestAddr common.Address, + pollInterval time.Duration, +) { + t.Helper() + filterer, err := challengeV2gen.NewEdgeChallengeManagerFilterer(chalManagerAddr, client) Require(t, err) fromBlock := uint64(0) - ticker := time.NewTicker(time.Second) + ticker := time.NewTicker(pollInterval) defer ticker.Stop() for { select { case <-ticker.C: - latestBlock, err := builder.L1.Client.HeaderByNumber(ctx, nil) + latestBlock, err := client.HeaderByNumber(ctx, nil) Require(t, err) toBlock := latestBlock.Number.Uint64() if fromBlock == toBlock { @@ -205,12 +282,12 @@ func testChallengeProtocolBOLDVirtualBlocks(t *testing.T, wrongAtFirstVirtual bo t.Fatalf("Error in filter iterator: %v", it.Error()) } t.Log("Received event of OSP confirmation!") - tx, _, err := builder.L1.Client.TransactionByHash(ctx, it.Event.Raw.TxHash) + tx, _, err := client.TransactionByHash(ctx, it.Event.Raw.TxHash) Require(t, err) signer := types.NewCancunSigner(tx.ChainId()) address, err := signer.Sender(tx) Require(t, err) - if address == builder.L1Info.GetAddress("HonestAsserter") { + if address == honestAddr { t.Log("Honest party won OSP, impossible for evil party to win if honest party continues") Require(t, it.Close()) return @@ -223,38 +300,157 @@ func testChallengeProtocolBOLDVirtualBlocks(t *testing.T, wrongAtFirstVirtual bo } } -func fundBoldStaker(t *testing.T, ctx context.Context, builder *NodeBuilder, name string) { - balance := big.NewInt(params.Ether) - balance.Mul(balance, big.NewInt(100)) - TransferBalance(t, "Faucet", name, balance, builder.L1Info, builder.L1.Client, ctx) +func fastChallengeStackOpts(name string, headerProvider challenge.HeaderProvider) []challenge.StackOpt { + return []challenge.StackOpt{ + challenge.StackWithName(name), + challenge.StackWithMode(modes.MakeMode), + challenge.StackWithPostingInterval(200 * time.Millisecond), + challenge.StackWithPollingInterval(100 * time.Millisecond), + challenge.StackWithConfirmationInterval(200 * time.Millisecond), + challenge.StackWithMinimumGapToParentAssertion(0), + challenge.StackWithAverageBlockCreationTime(100 * time.Millisecond), + challenge.StackWithHeaderProvider(headerProvider), + } +} - rollupUserLogic, err := rollupgen.NewRollupUserLogic(builder.addresses.Rollup, builder.L1.Client) +func createEvilAssertionChain( + t *testing.T, + ctx context.Context, + rollupAddr common.Address, + chalManagerAddr common.Address, + l1client *ethclient.Client, + evilNode *arbnode.Node, + evilOpts *bind.TransactOpts, + nodeConfig *arbnode.Config, +) *sol.AssertionChain { + t.Helper() + l1ChainId, err := l1client.ChainID(ctx) Require(t, err) - stakeToken, err := rollupUserLogic.StakeToken(&bind.CallOpts{Context: ctx}) + dp, err := arbnode.StakerDataposter( + ctx, + rawdb.NewTable(evilNode.ConsensusDB, storage.StakerPrefix), + evilNode.L1Reader, + evilOpts, + NewCommonConfigFetcher(nodeConfig), + evilNode.SyncMonitor, + l1ChainId, + ) Require(t, err) - stakeTokenWeth, err := mocksgen.NewTestWETH9(stakeToken, builder.L1.Client) + chain, err := sol.NewAssertionChain( + ctx, + rollupAddr, + chalManagerAddr, + evilOpts, + l1client, + bold.NewDataPosterTransactor(dp), + sol.WithRpcHeadBlockNumber(rpc.LatestBlockNumber), + sol.WithParentChainBlockCreationTime(10*time.Millisecond), + ) Require(t, err) + return chain +} - txOpts := builder.L1Info.GetDefaultTransactOpts(name, ctx) +// createSecondL2Node creates a second L2 node that shares an L1 chain with the first node. +// The addresses parameter determines which rollup contracts the new node connects to. +func createSecondL2Node( + t *testing.T, + ctx context.Context, + first *arbnode.Node, + l1info *BlockchainTestInfo, + l1client *ethclient.Client, + l2InitData *statetransfer.ArbosInitializationInfo, + addresses *chaininfo.RollupAddresses, + nodeConfig *arbnode.Config, + stackConfig *node.Config, +) (*node.Node, *ethclient.Client, *arbnode.Node, *gethexec.ExecutionNode) { + t.Helper() + fatalErrChan := make(chan error, 10) + + firstExec, ok := first.ExecutionClient.(*gethexec.ExecutionNode) + if !ok { + Fatal(t, "not geth execution node") + } + chainConfig := firstExec.ArbInterface.BlockChain().Config() - txOpts.Value = big.NewInt(params.Ether) - tx, err := stakeTokenWeth.Deposit(&txOpts) + if nodeConfig == nil { + nodeConfig = arbnode.ConfigDefaultL1NonSequencerTest() + } + nodeConfig.ParentChainReader.OldHeaderTimeout = 10 * time.Minute + nodeConfig.BatchPoster.DataPoster.MaxMempoolTransactions = 18 + if stackConfig == nil { + stackConfig = testhelpers.CreateStackConfigForTest(t.TempDir()) + stackConfig.DBEngine = rawdb.DBPebble + } + l2stack, err := node.New(stackConfig) Require(t, err) - _, err = builder.L1.EnsureTxSucceeded(tx) + + l2executionDB, err := l2stack.OpenDatabase("chaindb", 0, 0, "", false) + Require(t, err) + l2consensusDB, err := l2stack.OpenDatabase("arbdb", 0, 0, "", false) Require(t, err) - txOpts.Value = nil - tx, err = stakeTokenWeth.Approve(&txOpts, builder.addresses.Rollup, balance) + AddValNodeIfNeeded(t, ctx, nodeConfig, true, "", "") + + dataSigner := signature.DataSignerFromPrivateKey(l1info.GetInfoWithPrivKey("Sequencer").PrivateKey) + txOpts := l1info.GetDefaultTransactOpts("Sequencer", ctx) + + initReader := statetransfer.NewMemoryInitDataReader(l2InitData) + initMessage, err := nitroinit.GetConsensusParsedInitMsg(ctx, true, chainConfig.ChainID, l1client, first.DeployInfo, chainConfig) Require(t, err) - _, err = builder.L1.EnsureTxSucceeded(tx) + + execConfig := ExecConfigDefaultNonSequencerTest(t, rawdb.HashScheme) + Require(t, execConfig.Validate()) + coreCacheConfig := gethexec.DefaultCacheConfigFor(&execConfig.Caching) + l2blockchain, err := gethexec.WriteOrTestBlockChain(l2executionDB, coreCacheConfig, initReader, chainConfig, nil, nil, initMessage, &execConfig.TxIndexer, 0, execConfig.ExposeMultiGas) Require(t, err) - challengeManager, err := rollupUserLogic.ChallengeManager(&bind.CallOpts{Context: ctx}) + l1ChainId, err := l1client.ChainID(ctx) Require(t, err) - tx, err = stakeTokenWeth.Approve(&txOpts, challengeManager, balance) + execNode, err := gethexec.CreateExecutionNode(ctx, l2stack, l2executionDB, l2blockchain, l1client, NewCommonConfigFetcher(execConfig), l1ChainId, 0) Require(t, err) - _, err = builder.L1.EnsureTxSucceeded(tx) + locator, err := server_common.NewMachineLocator("") + Require(t, err) + l2node, err := arbnode.CreateConsensusNode(ctx, l2stack, execNode, l2consensusDB, NewCommonConfigFetcher(nodeConfig), l2blockchain.Config(), l1client, addresses, &txOpts, &txOpts, dataSigner, fatalErrChan, l1ChainId, nil /* blob reader */, locator.LatestWasmModuleRoot()) + Require(t, err) + + l2client := ClientForStack(t, l2stack, clientForStackUseHTTP(stackConfig)) + + StartWatchChanErr(t, ctx, fatalErrChan, l2node) + + return l2stack, l2client, l2node, execNode +} + +func runFastChallengeAndAssertHonestWin( + t *testing.T, + ctx context.Context, + honestChain, evilChain *sol.AssertionChain, + honestSM, evilSM BoldStateProviderInterface, + honestNode, evilNode *arbnode.Node, + parentClient *ethclient.Client, + honestAddr common.Address, +) { + t.Helper() + provider := newTestHistoryProvider(honestSM) + evilProvider := newTestHistoryProvider(evilSM) + + manager, err := challenge.NewChallengeStack( + honestChain, + provider, + fastChallengeStackOpts("honest", honestNode.L1Reader)..., + ) + Require(t, err) + + managerB, err := challenge.NewChallengeStack( + evilChain, + evilProvider, + fastChallengeStackOpts("evil", evilNode.L1Reader)..., + ) Require(t, err) + + manager.Start(ctx) + managerB.Start(ctx) + + waitForHonestOSPWin(t, ctx, parentClient, honestChain.SpecChallengeManager().Address(), honestAddr, 50*time.Millisecond) } func TestChallengeProtocolBOLDNearLastVirtualBlock(t *testing.T) { @@ -274,11 +470,15 @@ type BoldStateProviderInterface interface { state.ExecutionProvider } -func startBoldChallengeManager(t *testing.T, ctx context.Context, builder *NodeBuilder, node *TestClient, addressName string, mockStateProvider func(BoldStateProviderInterface) BoldStateProviderInterface) (*sol.AssertionChain, func()) { - if !builder.deployBold { - t.Fatal("bold deployment not enabled") - } +type boldChallengeManagerParams struct { + rollupAddr common.Address + parentClient *ethclient.Client + parentInfo *BlockchainTestInfo + l1Reader *headerreader.HeaderReader + nodeConfig *arbnode.Config +} +func startBoldChallengeManager(t *testing.T, ctx context.Context, params boldChallengeManagerParams, node *TestClient, addressName string, mockStateProvider func(BoldStateProviderInterface) BoldStateProviderInterface) (*sol.AssertionChain, func()) { var stateManager BoldStateProviderInterface var err error cacheDir := t.TempDir() @@ -303,45 +503,32 @@ func startBoldChallengeManager(t *testing.T, ctx context.Context, builder *NodeB stateManager = mockStateProvider(stateManager) } - provider := state.NewHistoryCommitmentProvider( - stateManager, - stateManager, - stateManager, - []state.Height{ - state.Height(blockChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(bigStepChallengeLeafHeight), - state.Height(smallStepChallengeLeafHeight), - }, - stateManager, - nil, // Api db - ) + provider := newTestHistoryProvider(stateManager) - rollupUserLogic, err := rollupgen.NewRollupUserLogic(builder.addresses.Rollup, builder.L1.Client) + rollupUserLogic, err := rollupgen.NewRollupUserLogic(params.rollupAddr, params.parentClient) Require(t, err) chalManagerAddr, err := rollupUserLogic.ChallengeManager(&bind.CallOpts{}) Require(t, err) - txOpts := builder.L1Info.GetDefaultTransactOpts(addressName, ctx) + txOpts := params.parentInfo.GetDefaultTransactOpts(addressName, ctx) dp, err := arbnode.StakerDataposter( ctx, rawdb.NewTable(node.ConsensusNode.ConsensusDB, storage.StakerPrefix), - node.ConsensusNode.L1Reader, + params.l1Reader, &txOpts, - NewCommonConfigFetcher(builder.nodeConfig), + NewCommonConfigFetcher(params.nodeConfig), node.ConsensusNode.SyncMonitor, - builder.L1Info.Signer.ChainID(), + params.parentInfo.Signer.ChainID(), ) Require(t, err) assertionChain, err := sol.NewAssertionChain( ctx, - builder.addresses.Rollup, + params.rollupAddr, chalManagerAddr, &txOpts, - builder.L1.Client, + params.parentClient, bold.NewDataPosterTransactor(dp), sol.WithRpcHeadBlockNumber(rpc.LatestBlockNumber), ) diff --git a/system_tests/genesis_assertion_test.go b/system_tests/genesis_assertion_test.go index 75ee65e5472..78cc66cc4f9 100644 --- a/system_tests/genesis_assertion_test.go +++ b/system_tests/genesis_assertion_test.go @@ -360,6 +360,7 @@ func createL2NodeWithRollupAddresses( l1client, bold.NewDataPosterTransactor(dp), sol.WithRpcHeadBlockNumber(rpc.LatestBlockNumber), + sol.WithParentChainBlockCreationTime(10*time.Millisecond), ) Require(t, err) assertionChain = assertionChainBindings @@ -408,8 +409,8 @@ func deployContractsOnly( BigStepChallengeHeight: protocol.Height(bigStepChallengeLeafHeight), SmallStepChallengeHeight: protocol.Height(smallStepChallengeLeafHeight), }), - challenge_testing.WithNumBigStepLevels(uint8(3)), // TODO: Hardcoded. - challenge_testing.WithConfirmPeriodBlocks(uint64(120)), // TODO: Hardcoded. + challenge_testing.WithNumBigStepLevels(uint8(3)), // TODO: Hardcoded. + challenge_testing.WithConfirmPeriodBlocks(uint64(10000)), ) config, err := json.Marshal(chaininfo.ArbitrumDevTestChainConfig()) Require(t, err)