From 9121b5eeb13a2fda1dc6c01b799de0c7e66c28c5 Mon Sep 17 00:00:00 2001 From: Ganesh Vanahalli Date: Thu, 30 May 2024 17:01:39 -0500 Subject: [PATCH 01/18] Use persisted basefee in txProcessor hooks and ArbGasInfo precompile when basefee is lowered to 0 --- arbos/tx_processor.go | 32 +++++++++++++++----------------- go-ethereum | 2 +- precompiles/ArbGasInfo.go | 28 ++++++++++++++++++++++++---- 3 files changed, 40 insertions(+), 22 deletions(-) diff --git a/arbos/tx_processor.go b/arbos/tx_processor.go index 65762fd2d1..ed2a37f87d 100644 --- a/arbos/tx_processor.go +++ b/arbos/tx_processor.go @@ -283,15 +283,10 @@ func (p *TxProcessor) StartTxHook() (endTxNow bool, gasUsed uint64, err error, r } balance := statedb.GetBalance(tx.From) + // evm.Context.BaseFee is already lowered to 0 when vm runs with NoBaseFee flag and 0 gas price effectiveBaseFee := evm.Context.BaseFee usergas := p.msg.GasLimit - if p.msg.TxRunMode != core.MessageCommitMode && p.msg.GasFeeCap.BitLen() == 0 { - // In gas estimation or eth_call mode, we permit a zero gas fee cap. - // This matches behavior with normal tx gas estimation and eth_call. - effectiveBaseFee = common.Big0 - } - maxGasCost := arbmath.BigMulByUint(tx.GasFeeCap, usergas) maxFeePerGasTooLow := arbmath.BigLessThan(tx.GasFeeCap, effectiveBaseFee) if arbmath.BigLessThan(balance.ToBig(), maxGasCost) || usergas < params.TxGas || maxFeePerGasTooLow { @@ -433,7 +428,12 @@ func (p *TxProcessor) GasChargingHook(gasRemaining *uint64) (common.Address, err var gasNeededToStartEVM uint64 tipReceipient, _ := p.state.NetworkFeeAccount() - basefee := p.evm.Context.BaseFee + var basefee *big.Int + if p.evm.Context.BaseFeeCopy != nil { + basefee = p.evm.Context.BaseFeeCopy + } else { + basefee = p.evm.Context.BaseFee + } var poster common.Address if p.msg.TxRunMode != core.MessageCommitMode { @@ -594,7 +594,12 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { return } - basefee := p.evm.Context.BaseFee + var basefee *big.Int + if p.evm.Context.BaseFeeCopy != nil { + basefee = p.evm.Context.BaseFeeCopy + } else { + basefee = p.evm.Context.BaseFee + } totalCost := arbmath.BigMul(basefee, arbmath.UintToBig(gasUsed)) // total cost = price of gas * gas burnt computeCost := arbmath.BigSub(totalCost, p.PosterFee) // total cost = network's compute + poster's L1 costs if computeCost.Sign() < 0 { @@ -656,15 +661,10 @@ func (p *TxProcessor) EndTxHook(gasLeft uint64, success bool) { func (p *TxProcessor) ScheduledTxes() types.Transactions { scheduled := types.Transactions{} time := p.evm.Context.Time + // p.evm.Context.BaseFee is already lowered to 0 when vm runs with NoBaseFee flag and 0 gas price effectiveBaseFee := p.evm.Context.BaseFee chainID := p.evm.ChainConfig().ChainID - if p.msg.TxRunMode != core.MessageCommitMode && p.msg.GasFeeCap.BitLen() == 0 { - // In gas estimation or eth_call mode, we permit a zero gas fee cap. - // This matches behavior with normal tx gas estimation and eth_call. - effectiveBaseFee = common.Big0 - } - logs := p.evm.StateDB.GetCurrentTxLogs() for _, log := range logs { if log.Address != ArbRetryableTxAddress || log.Topics[0] != RedeemScheduledEventID { @@ -738,10 +738,8 @@ func (p *TxProcessor) GetPaidGasPrice() *big.Int { gasPrice := p.evm.GasPrice version := p.state.ArbOSVersion() if version != 9 { + // p.evm.Context.BaseFee is already lowered to 0 when vm runs with NoBaseFee flag and 0 gas price gasPrice = p.evm.Context.BaseFee - if p.msg.TxRunMode != core.MessageCommitMode && p.msg.GasFeeCap.Sign() == 0 { - gasPrice = common.Big0 - } } return gasPrice } diff --git a/go-ethereum b/go-ethereum index f45f6d7560..13c31a6b24 160000 --- a/go-ethereum +++ b/go-ethereum @@ -1 +1 @@ -Subproject commit f45f6d75601626daf108aa62ea6cb1549d91c528 +Subproject commit 13c31a6b245a9a79bc11e1cbf8ec18de9657d719 diff --git a/precompiles/ArbGasInfo.go b/precompiles/ArbGasInfo.go index 25801109c7..324d935991 100644 --- a/precompiles/ArbGasInfo.go +++ b/precompiles/ArbGasInfo.go @@ -36,7 +36,12 @@ func (con ArbGasInfo) GetPricesInWeiWithAggregator( if err != nil { return nil, nil, nil, nil, nil, nil, err } - l2GasPrice := evm.Context.BaseFee + var l2GasPrice *big.Int + if evm.Context.BaseFeeCopy != nil { + l2GasPrice = evm.Context.BaseFeeCopy + } else { + l2GasPrice = evm.Context.BaseFee + } // aggregators compress calldata, so we must estimate accordingly weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) @@ -69,7 +74,12 @@ func (con ArbGasInfo) _preVersion4_GetPricesInWeiWithAggregator( if err != nil { return nil, nil, nil, nil, nil, nil, err } - l2GasPrice := evm.Context.BaseFee + var l2GasPrice *big.Int + if evm.Context.BaseFeeCopy != nil { + l2GasPrice = evm.Context.BaseFeeCopy + } else { + l2GasPrice = evm.Context.BaseFee + } // aggregators compress calldata, so we must estimate accordingly weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) @@ -101,7 +111,12 @@ func (con ArbGasInfo) GetPricesInArbGasWithAggregator(c ctx, evm mech, aggregato if err != nil { return nil, nil, nil, err } - l2GasPrice := evm.Context.BaseFee + var l2GasPrice *big.Int + if evm.Context.BaseFeeCopy != nil { + l2GasPrice = evm.Context.BaseFeeCopy + } else { + l2GasPrice = evm.Context.BaseFee + } // aggregators compress calldata, so we must estimate accordingly weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) @@ -121,7 +136,12 @@ func (con ArbGasInfo) _preVersion4_GetPricesInArbGasWithAggregator(c ctx, evm me if err != nil { return nil, nil, nil, err } - l2GasPrice := evm.Context.BaseFee + var l2GasPrice *big.Int + if evm.Context.BaseFeeCopy != nil { + l2GasPrice = evm.Context.BaseFeeCopy + } else { + l2GasPrice = evm.Context.BaseFee + } // aggregators compress calldata, so we must estimate accordingly weiForL1Calldata := arbmath.BigMulByUint(l1GasPrice, params.TxDataNonZeroGasEIP2028) From 99a7c658fbf557a453e5f4eb0a2810229565281c Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Tue, 4 Jun 2024 11:13:22 -0700 Subject: [PATCH 02/18] Add batchPoster backlog metric --- arbnode/batch_poster.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 058db160c8..2a0f3a1ec8 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -66,6 +66,8 @@ var ( blobGasLimitGauge = metrics.NewRegisteredGauge("arb/batchposter/blobgas/limit", nil) suggestedTipCapGauge = metrics.NewRegisteredGauge("arb/batchposter/suggestedtipcap", nil) + batchPosterBacklogGauge = metrics.NewRegisteredGauge("arb/batchposter/backlog", nil) + usableBytesInBlob = big.NewInt(int64(len(kzg4844.Blob{}) * 31 / 32)) blobTxBlobGasPerBlob = big.NewInt(params.BlobTxBlobGasPerBlob) ) @@ -1347,6 +1349,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) messagesPerBatch = 1 } backlog := uint64(unpostedMessages) / messagesPerBatch + batchPosterBacklogGauge.Update(int64(backlog)) if backlog > 10 { logLevel := log.Warn if recentlyHitL1Bounds { From 390523713d86a6195768dae0b8e83270191bb364 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Tue, 4 Jun 2024 11:19:58 -0700 Subject: [PATCH 03/18] Add metrics around DA that can be reused for various DA plugins (celestia, eigenda, anytrust, etc) --- arbnode/batch_poster.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 2a0f3a1ec8..df5c145f0c 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -68,6 +68,10 @@ var ( batchPosterBacklogGauge = metrics.NewRegisteredGauge("arb/batchposter/backlog", nil) + batchPosterDALastSuccessfulActionGauge = metrics.NewRegisteredGauge("arb/batchPoster/action/da_last_success", nil) + batchPosterDASuccessCounter = metrics.NewRegisteredCounter("arb/batchPoster/action/da_success", nil) + batchPosterDAFailureCounter = metrics.NewRegisteredCounter("arb/batchPoster/action/da_failure", nil) + usableBytesInBlob = big.NewInt(int64(len(kzg4844.Blob{}) * 31 / 32)) blobTxBlobGasPerBlob = big.NewInt(params.BlobTxBlobGasPerBlob) ) @@ -1256,15 +1260,21 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) gotNonce, gotMeta, err := b.dataPoster.GetNextNonceAndMeta(ctx) if err != nil { + batchPosterDAFailureCounter.Inc(1) return false, err } if nonce != gotNonce || !bytes.Equal(batchPositionBytes, gotMeta) { + batchPosterDAFailureCounter.Inc(1) return false, fmt.Errorf("%w: nonce changed from %d to %d while creating batch", storage.ErrStorageRace, nonce, gotNonce) } sequencerMsg, err = b.dapWriter.Store(ctx, sequencerMsg, uint64(time.Now().Add(config.DASRetentionPeriod).Unix()), []byte{}, config.DisableDapFallbackStoreDataOnChain) if err != nil { + batchPosterDAFailureCounter.Inc(1) return false, err } + + batchPosterDASuccessCounter.Inc(1) + batchPosterDALastSuccessfulActionGauge.Update(time.Now().Unix()) } data, kzgBlobs, err := b.encodeAddBatch(new(big.Int).SetUint64(batchPosition.NextSeqNum), batchPosition.MessageCount, b.building.msgCount, sequencerMsg, b.building.segments.delayedMsg, b.building.use4844) From 95d6ee7ebf40d69bee97e1c3505cc85ad4d85838 Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Tue, 4 Jun 2024 17:05:38 -0700 Subject: [PATCH 04/18] Add metric around batch poster failures --- arbnode/batch_poster.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index df5c145f0c..bb4e03bf05 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -72,6 +72,8 @@ var ( batchPosterDASuccessCounter = metrics.NewRegisteredCounter("arb/batchPoster/action/da_success", nil) batchPosterDAFailureCounter = metrics.NewRegisteredCounter("arb/batchPoster/action/da_failure", nil) + batchPosterFailureCounter = metrics.NewRegisteredCounter("arb/batchPoster/action/failure", nil) + usableBytesInBlob = big.NewInt(int64(len(kzg4844.Blob{}) * 31 / 32)) blobTxBlobGasPerBlob = big.NewInt(params.BlobTxBlobGasPerBlob) ) @@ -1043,7 +1045,7 @@ const ethPosBlockTime = 12 * time.Second var errAttemptLockFailed = errors.New("failed to acquire lock; either another batch poster posted a batch or this node fell behind") -func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) { +func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (ret bool, err error) { if b.batchReverted.Load() { return false, fmt.Errorf("batch was reverted, not posting any more batches") } @@ -1243,6 +1245,13 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) // don't post anything for now return false, nil } + + defer func() { + if err != nil { + batchPosterFailureCounter.Inc(1) + } + }() + sequencerMsg, err := b.building.segments.CloseAndGetBytes() if err != nil { return false, err From b2b53eb6644a1f8668a32039f2257a07f80a71bf Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Wed, 5 Jun 2024 08:38:02 -0700 Subject: [PATCH 05/18] Add failure at the top --- arbnode/batch_poster.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index bb4e03bf05..6af1c5471c 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -1046,6 +1046,12 @@ const ethPosBlockTime = 12 * time.Second var errAttemptLockFailed = errors.New("failed to acquire lock; either another batch poster posted a batch or this node fell behind") func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (ret bool, err error) { + defer func() { + if err != nil { + batchPosterFailureCounter.Inc(1) + } + }() + if b.batchReverted.Load() { return false, fmt.Errorf("batch was reverted, not posting any more batches") } @@ -1246,12 +1252,6 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (ret bool, er return false, nil } - defer func() { - if err != nil { - batchPosterFailureCounter.Inc(1) - } - }() - sequencerMsg, err := b.building.segments.CloseAndGetBytes() if err != nil { return false, err From f5962139e907a4f16bd8e7807341752b06ec80cc Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 13 Jun 2024 15:50:07 -0500 Subject: [PATCH 06/18] add bold deps --- staker/challenge-cache/cache.go | 272 +++++++++++++++++++++++ staker/challenge-cache/cache_test.go | 318 +++++++++++++++++++++++++++ 2 files changed, 590 insertions(+) create mode 100644 staker/challenge-cache/cache.go create mode 100644 staker/challenge-cache/cache_test.go diff --git a/staker/challenge-cache/cache.go b/staker/challenge-cache/cache.go new file mode 100644 index 0000000000..cc8e56faee --- /dev/null +++ b/staker/challenge-cache/cache.go @@ -0,0 +1,272 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE +/* +* Package challengecache stores hashes required for making history commitments in Arbitrum BOLD. +When a challenge begins, validators need to post Merkle commitments to a series of block hashes to +narrow down their disagreement to a single block. Once a disagreement is reached, another BOLD challenge begins +to narrow down within the execution of a block. This requires using the Arbitrator emulator to compute +the intermediate hashes of executing the block as WASM opcodes. These hashes are expensive to compute, so we +store them in a filesystem cache to avoid recomputing them and for hierarchical access. +Each file contains a list of 32 byte hashes, concatenated together as bytes. +Using this structure, we can namespace hashes by message number and by challenge level. + +Once a validator receives a full list of computed machine hashes for the first time from a validatio node, +it will write the roots to this filesystem hierarchy for fast access next time these roots are needed. + +Example: +- Compute all the hashes for the execution of message num 70 with the required step size for the big step challenge level. +- Compute all the hashes for the execution of individual steps for a small step challenge level from big step 100 to 101 + + wavm-module-root-0xab/ + message-num-70/ + hashes.bin + subchallenge-level-1-big-step-100/ + hashes.bin + +We namespace top-level block challenges by wavm module root. Then, we can retrieve +the state roots for any data within a challenge or associated subchallenge based on the hierarchy above. +*/ + +package challengecache + +import ( + "bufio" + "errors" + "fmt" + "io" + "os" + "path/filepath" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/log" +) + +var ( + ErrNotFoundInCache = errors.New("no found in challenge cache") + ErrFileAlreadyExists = errors.New("file already exists") + ErrNoStateRoots = errors.New("no state roots being written") + stateRootsFileName = "hashes.bin" + wavmModuleRootPrefix = "wavm-module-root" + messageNumberPrefix = "message-num" + bigStepPrefix = "big-step" + challengeLevelPrefix = "subchallenge-level" +) + +// HistoryCommitmentCacher can retrieve history commitment state roots given lookup keys. +type HistoryCommitmentCacher interface { + Get(lookup *Key, numToRead uint64) ([]common.Hash, error) + Put(lookup *Key, stateRoots []common.Hash) error +} + +// Cache for history commitments on disk. +type Cache struct { + baseDir string +} + +// New cache from a base directory path. +func New(baseDir string) *Cache { + return &Cache{ + baseDir: baseDir, + } +} + +// Key for cache lookups includes the wavm module root of a challenge, as well +// as the heights for messages and big steps as needed. +type Key struct { + WavmModuleRoot common.Hash + MessageHeight uint64 + StepHeights []uint64 +} + +// Get a list of state roots from the cache up to a certain index. State roots are saved as files in the directory +// hierarchy for the cache. If a file is not present, ErrNotFoundInCache +// is returned. +func (c *Cache) Get( + lookup *Key, + numToRead uint64, +) ([]common.Hash, error) { + fName, err := determineFilePath(c.baseDir, lookup) + if err != nil { + return nil, err + } + if _, err := os.Stat(fName); err != nil { + log.Warn("Cache miss", "fileName", fName) + return nil, ErrNotFoundInCache + } + log.Debug("Cache hit", "fileName", fName) + f, err := os.Open(fName) + if err != nil { + return nil, err + } + defer func() { + if err := f.Close(); err != nil { + log.Error("Could not close file after reading", "err", err, "file", fName) + } + }() + return readStateRoots(f, numToRead) +} + +// Put a list of state roots into the cache. +// State roots are saved as files in a directory hierarchy for the cache. +// This function first creates a temporary file, writes the state roots to it, and then renames the file +// to the final directory to ensure atomic writes. +func (c *Cache) Put(lookup *Key, stateRoots []common.Hash) error { + // We should error if trying to put 0 state roots to disk. + if len(stateRoots) == 0 { + return ErrNoStateRoots + } + fName, err := determineFilePath(c.baseDir, lookup) + if err != nil { + return err + } + // We create a tmp file to write our state roots to first. If writing fails, + // we don't want to leave a half-written file in our cache directory. + // Once writing succeeds, we rename in an atomic operation to the correct file name + // in the cache directory hierarchy. + tmp := os.TempDir() + tmpFName := filepath.Join(tmp, fName) + dir := filepath.Dir(tmpFName) + if err := os.MkdirAll(dir, os.ModePerm); err != nil { + return fmt.Errorf("could not make tmp directory %s: %w", dir, err) + } + f, err := os.Create(tmpFName) + if err != nil { + return err + } + defer func() { + if err := f.Close(); err != nil { + log.Error("Could not close file after writing", "err", err, "file", fName) + } + }() + if err := writeStateRoots(f, stateRoots); err != nil { + return err + } + if err := os.MkdirAll(filepath.Dir(fName), os.ModePerm); err != nil { + return fmt.Errorf("could not make file directory %s: %w", fName, err) + } + // If the file writing was successful, we rename the file from the tmp directory + // into our cache directory. This is an atomic operation. + // For more information on this atomic write pattern, see: + // https://stackoverflow.com/questions/2333872/how-to-make-file-creation-an-atomic-operation + return Move(tmpFName /* old */, fName /* new */) +} + +// Reads 32 bytes at a time from a reader up to a specified height. If none, then read all. +func readStateRoots(r io.Reader, numToRead uint64) ([]common.Hash, error) { + br := bufio.NewReader(r) + stateRoots := make([]common.Hash, 0) + buf := make([]byte, 0, 32) + for totalRead := uint64(0); totalRead < numToRead; totalRead++ { + n, err := br.Read(buf[:cap(buf)]) + if err != nil { + // If we try to read but reach EOF, we break out of the loop. + if err == io.EOF { + break + } + return nil, err + } + buf = buf[:n] + if n != 32 { + return nil, fmt.Errorf("expected to read 32 bytes, got %d bytes", n) + } + stateRoots = append(stateRoots, common.BytesToHash(buf)) + } + if numToRead > uint64(len(stateRoots)) { + return nil, fmt.Errorf( + "wanted to read %d roots, but only read %d state roots", + numToRead, + len(stateRoots), + ) + } + return stateRoots, nil +} + +func writeStateRoots(w io.Writer, stateRoots []common.Hash) error { + for i, rt := range stateRoots { + n, err := w.Write(rt[:]) + if err != nil { + return err + } + if n != len(rt) { + return fmt.Errorf( + "for state root %d, wrote %d bytes, expected to write %d bytes", + i, + n, + len(rt), + ) + } + } + return nil +} + +/* +* +When provided with a cache lookup struct, this function determines the file path +for the data requested within the cache directory hierarchy. The folder structure +for a given filesystem challenge cache will look as follows: + + wavm-module-root-0xab/ + message-num-70/ + hashes.bin + subchallenge-level-1-big-step-100/ + hashes.bin +*/ +func determineFilePath(baseDir string, lookup *Key) (string, error) { + key := make([]string, 0) + key = append(key, fmt.Sprintf("%s-%s", wavmModuleRootPrefix, lookup.WavmModuleRoot.Hex())) + key = append(key, fmt.Sprintf("%s-%d", messageNumberPrefix, lookup.MessageHeight)) + for challengeLevel, height := range lookup.StepHeights { + key = append(key, fmt.Sprintf( + "%s-%d-%s-%d", + challengeLevelPrefix, + challengeLevel+1, // subchallenges start at 1, as level 0 is the block challenge level. + bigStepPrefix, + height, + ), + ) + + } + key = append(key, stateRootsFileName) + return filepath.Join(baseDir, filepath.Join(key...)), nil +} + +// Move function that is robust against cross-device link errors. Credits to: +// https://gist.github.com/var23rav/23ae5d0d4d830aff886c3c970b8f6c6b +func Move(source, destination string) error { + err := os.Rename(source, destination) + if err != nil && strings.Contains(err.Error(), "cross-device link") { + return moveCrossDevice(source, destination) + } + return err +} + +func moveCrossDevice(source, destination string) error { + src, err := os.Open(source) + if err != nil { + return err + } + dst, err := os.Create(destination) + if err != nil { + src.Close() + return err + } + _, err = io.Copy(dst, src) + src.Close() + dst.Close() + if err != nil { + return err + } + fi, err := os.Stat(source) + if err != nil { + os.Remove(destination) + return err + } + err = os.Chmod(destination, fi.Mode()) + if err != nil { + os.Remove(destination) + return err + } + os.Remove(source) + return nil +} diff --git a/staker/challenge-cache/cache_test.go b/staker/challenge-cache/cache_test.go new file mode 100644 index 0000000000..5d670fbad2 --- /dev/null +++ b/staker/challenge-cache/cache_test.go @@ -0,0 +1,318 @@ +// Copyright 2023, Offchain Labs, Inc. +// For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE +package challengecache + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "strings" + "testing" + + l2stateprovider "github.com/OffchainLabs/bold/layer2-state-provider" + "github.com/ethereum/go-ethereum/common" +) + +var _ HistoryCommitmentCacher = (*Cache)(nil) + +func TestCache(t *testing.T) { + basePath := t.TempDir() + if err := os.MkdirAll(basePath, os.ModePerm); err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + if err := os.RemoveAll(basePath); err != nil { + t.Fatal(err) + } + }) + cache := New(basePath) + key := &Key{ + WavmModuleRoot: common.BytesToHash([]byte("foo")), + MessageHeight: 0, + StepHeights: []l2stateprovider.Height{l2stateprovider.Height(0)}, + } + t.Run("Not found", func(t *testing.T) { + _, err := cache.Get(key, 0) + if !errors.Is(err, ErrNotFoundInCache) { + t.Fatal(err) + } + }) + t.Run("Putting empty root fails", func(t *testing.T) { + if err := cache.Put(key, []common.Hash{}); !errors.Is(err, ErrNoStateRoots) { + t.Fatalf("Unexpected error: %v", err) + } + }) + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + err := cache.Put(key, want) + if err != nil { + t.Fatal(err) + } + got, err := cache.Get(key, 3) + if err != nil { + t.Fatal(err) + } + if len(got) != len(want) { + t.Fatalf("Wrong number of roots. Expected %d, got %d", len(want), len(got)) + } + for i, rt := range got { + if rt != want[i] { + t.Fatalf("Wrong root. Expected %#x, got %#x", want[i], rt) + } + } +} + +func TestReadWriteStateRoots(t *testing.T) { + t.Run("read up to, but had empty reader", func(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + _, err := readStateRoots(b, 100) + if err == nil { + t.Fatal("Wanted error") + } + if !strings.Contains(err.Error(), "only read 0 state roots") { + t.Fatal("Unexpected error") + } + }) + t.Run("read single root", func(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + want := common.BytesToHash([]byte("foo")) + b.Write(want.Bytes()) + roots, err := readStateRoots(b, 1) + if err != nil { + t.Fatal(err) + } + if len(roots) == 0 { + t.Fatal("Got no roots") + } + if roots[0] != want { + t.Fatalf("Wrong root. Expected %#x, got %#x", want, roots[0]) + } + }) + t.Run("Three roots exist, want to read only two", func(t *testing.T) { + b := bytes.NewBuffer([]byte{}) + foo := common.BytesToHash([]byte("foo")) + bar := common.BytesToHash([]byte("bar")) + baz := common.BytesToHash([]byte("baz")) + b.Write(foo.Bytes()) + b.Write(bar.Bytes()) + b.Write(baz.Bytes()) + roots, err := readStateRoots(b, 2) + if err != nil { + t.Fatal(err) + } + if len(roots) != 2 { + t.Fatalf("Expected two roots, got %d", len(roots)) + } + if roots[0] != foo { + t.Fatalf("Wrong root. Expected %#x, got %#x", foo, roots[0]) + } + if roots[1] != bar { + t.Fatalf("Wrong root. Expected %#x, got %#x", bar, roots[1]) + } + }) + t.Run("Fails to write enough data to writer", func(t *testing.T) { + m := &mockWriter{wantErr: true} + err := writeStateRoots(m, []common.Hash{common.BytesToHash([]byte("foo"))}) + if err == nil { + t.Fatal("Wanted error") + } + m = &mockWriter{wantErr: false, numWritten: 16} + err = writeStateRoots(m, []common.Hash{common.BytesToHash([]byte("foo"))}) + if err == nil { + t.Fatal("Wanted error") + } + if !strings.Contains(err.Error(), "expected to write 32 bytes") { + t.Fatalf("Got wrong error kind: %v", err) + } + }) +} + +type mockWriter struct { + wantErr bool + numWritten int +} + +func (m *mockWriter) Write(_ []byte) (n int, err error) { + if m.wantErr { + return 0, errors.New("something went wrong") + } + return m.numWritten, nil +} + +type mockReader struct { + wantErr bool + err error + roots []common.Hash + readIdx int + bytesRead int +} + +func (m *mockReader) Read(out []byte) (n int, err error) { + if m.wantErr { + return 0, m.err + } + if m.readIdx == len(m.roots) { + return 0, io.EOF + } + copy(out, m.roots[m.readIdx].Bytes()) + m.readIdx++ + return m.bytesRead, nil +} + +func Test_readStateRoots(t *testing.T) { + t.Run("Unexpected error", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: true, roots: want, err: errors.New("foo")} + _, err := readStateRoots(m, 1) + if err == nil { + t.Fatal(err) + } + if !strings.Contains(err.Error(), "foo") { + t.Fatalf("Unexpected error: %v", err) + } + }) + t.Run("EOF, but did not read as much as was expected", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: true, roots: want, err: io.EOF} + _, err := readStateRoots(m, 100) + if err == nil { + t.Fatal(err) + } + if !strings.Contains(err.Error(), "wanted to read 100") { + t.Fatalf("Unexpected error: %v", err) + } + }) + t.Run("Reads wrong number of bytes", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: false, roots: want, bytesRead: 16} + _, err := readStateRoots(m, 2) + if err == nil { + t.Fatal(err) + } + if !strings.Contains(err.Error(), "expected to read 32 bytes, got 16") { + t.Fatalf("Unexpected error: %v", err) + } + }) + t.Run("Reads all until EOF", func(t *testing.T) { + want := []common.Hash{ + common.BytesToHash([]byte("foo")), + common.BytesToHash([]byte("bar")), + common.BytesToHash([]byte("baz")), + } + m := &mockReader{wantErr: false, roots: want, bytesRead: 32} + got, err := readStateRoots(m, 3) + if err != nil { + t.Fatal(err) + } + if len(want) != len(got) { + t.Fatal("Wrong number of roots") + } + for i, rt := range got { + if rt != want[i] { + t.Fatal("Wrong root") + } + } + }) +} + +func Test_determineFilePath(t *testing.T) { + type args struct { + baseDir string + key *Key + } + tests := []struct { + name string + args args + want string + wantErr bool + errContains string + }{ + { + name: "OK", + args: args{ + baseDir: "", + key: &Key{ + MessageHeight: 100, + StepHeights: []l2stateprovider.Height{l2stateprovider.Height(50)}, + }, + }, + want: "wavm-module-root-0x0000000000000000000000000000000000000000000000000000000000000000/message-num-100/subchallenge-level-1-big-step-50/hashes.bin", + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := determineFilePath(tt.args.baseDir, tt.args.key) + if (err != nil) != tt.wantErr { + t.Logf("got: %v, and key %+v, got %s", err, tt.args.key, got) + if !strings.Contains(err.Error(), tt.errContains) { + t.Fatalf("Expected %s, got %s", tt.errContains, err.Error()) + } + t.Errorf("determineFilePath() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf( + "determineFilePath() = %v, want %v", + got, + tt.want, + ) + } + }) + } +} + +func BenchmarkCache_Read_32Mb(b *testing.B) { + b.StopTimer() + basePath := os.TempDir() + if err := os.MkdirAll(basePath, os.ModePerm); err != nil { + b.Fatal(err) + } + b.Cleanup(func() { + if err := os.RemoveAll(basePath); err != nil { + b.Fatal(err) + } + }) + cache := New(basePath) + key := &Key{ + WavmModuleRoot: common.BytesToHash([]byte("foo")), + MessageHeight: 0, + StepHeights: []l2stateprovider.Height{l2stateprovider.Height(0)}, + } + numRoots := 1 << 20 + roots := make([]common.Hash, numRoots) + for i := range roots { + roots[i] = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) + } + if err := cache.Put(key, roots); err != nil { + b.Fatal(err) + } + b.StartTimer() + for i := 0; i < b.N; i++ { + readUpTo := uint64(1 << 20) + roots, err := cache.Get(key, readUpTo) + if err != nil { + b.Fatal(err) + } + if len(roots) != numRoots { + b.Fatalf("Wrong number of roots. Expected %d, got %d", numRoots, len(roots)) + } + } +} From e755c2346f4c891331d7db73a31108fdb4363a3a Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 13 Jun 2024 15:50:23 -0500 Subject: [PATCH 07/18] cache key --- staker/challenge-cache/cache_test.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/staker/challenge-cache/cache_test.go b/staker/challenge-cache/cache_test.go index 5d670fbad2..7a56a7bcf0 100644 --- a/staker/challenge-cache/cache_test.go +++ b/staker/challenge-cache/cache_test.go @@ -11,7 +11,6 @@ import ( "strings" "testing" - l2stateprovider "github.com/OffchainLabs/bold/layer2-state-provider" "github.com/ethereum/go-ethereum/common" ) @@ -31,7 +30,7 @@ func TestCache(t *testing.T) { key := &Key{ WavmModuleRoot: common.BytesToHash([]byte("foo")), MessageHeight: 0, - StepHeights: []l2stateprovider.Height{l2stateprovider.Height(0)}, + StepHeights: []uint64{0}, } t.Run("Not found", func(t *testing.T) { _, err := cache.Get(key, 0) @@ -250,7 +249,7 @@ func Test_determineFilePath(t *testing.T) { baseDir: "", key: &Key{ MessageHeight: 100, - StepHeights: []l2stateprovider.Height{l2stateprovider.Height(50)}, + StepHeights: []uint64{50}, }, }, want: "wavm-module-root-0x0000000000000000000000000000000000000000000000000000000000000000/message-num-100/subchallenge-level-1-big-step-50/hashes.bin", @@ -294,7 +293,7 @@ func BenchmarkCache_Read_32Mb(b *testing.B) { key := &Key{ WavmModuleRoot: common.BytesToHash([]byte("foo")), MessageHeight: 0, - StepHeights: []l2stateprovider.Height{l2stateprovider.Height(0)}, + StepHeights: []uint64{0}, } numRoots := 1 << 20 roots := make([]common.Hash, numRoots) From 4ef651dbed95b2787328fcb3f37131d439e7d857 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 13 Jun 2024 15:51:26 -0500 Subject: [PATCH 08/18] comments --- staker/challenge-cache/cache.go | 6 +++--- staker/challenge-cache/cache_test.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/staker/challenge-cache/cache.go b/staker/challenge-cache/cache.go index cc8e56faee..10797c25c3 100644 --- a/staker/challenge-cache/cache.go +++ b/staker/challenge-cache/cache.go @@ -1,4 +1,4 @@ -// Copyright 2023, Offchain Labs, Inc. +// Copyright 2023-2024, Offchain Labs, Inc. // For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE /* * Package challengecache stores hashes required for making history commitments in Arbitrum BOLD. @@ -18,7 +18,7 @@ Example: - Compute all the hashes for the execution of individual steps for a small step challenge level from big step 100 to 101 wavm-module-root-0xab/ - message-num-70/ + rollup-block-hash-0x12...-message-num-70/ hashes.bin subchallenge-level-1-big-step-100/ hashes.bin @@ -207,7 +207,7 @@ for the data requested within the cache directory hierarchy. The folder structur for a given filesystem challenge cache will look as follows: wavm-module-root-0xab/ - message-num-70/ + rollup-block-hash-0x12...-message-num-70/ hashes.bin subchallenge-level-1-big-step-100/ hashes.bin diff --git a/staker/challenge-cache/cache_test.go b/staker/challenge-cache/cache_test.go index 7a56a7bcf0..e59efbfccd 100644 --- a/staker/challenge-cache/cache_test.go +++ b/staker/challenge-cache/cache_test.go @@ -1,4 +1,4 @@ -// Copyright 2023, Offchain Labs, Inc. +// Copyright 2023-2024, Offchain Labs, Inc. // For license information, see https://github.com/offchainlabs/bold/blob/main/LICENSE package challengecache From 6937358bf986069cb6010e40549ad4b06997ecc1 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 13 Jun 2024 16:01:53 -0500 Subject: [PATCH 09/18] hash addr --- staker/challenge-cache/cache.go | 32 +++++++++++++++------------- staker/challenge-cache/cache_test.go | 2 +- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/staker/challenge-cache/cache.go b/staker/challenge-cache/cache.go index 10797c25c3..fc7012917a 100644 --- a/staker/challenge-cache/cache.go +++ b/staker/challenge-cache/cache.go @@ -13,9 +13,9 @@ Using this structure, we can namespace hashes by message number and by challenge Once a validator receives a full list of computed machine hashes for the first time from a validatio node, it will write the roots to this filesystem hierarchy for fast access next time these roots are needed. -Example: -- Compute all the hashes for the execution of message num 70 with the required step size for the big step challenge level. -- Compute all the hashes for the execution of individual steps for a small step challenge level from big step 100 to 101 +Example uses: +- Obtain all the hashes for the execution of message num 70 to 71 for a given wavm module root. +- Obtain all the hashes from step 100 to 101 at subchallenge level 1 for the execution of message num 70. wavm-module-root-0xab/ rollup-block-hash-0x12...-message-num-70/ @@ -43,14 +43,15 @@ import ( ) var ( - ErrNotFoundInCache = errors.New("no found in challenge cache") - ErrFileAlreadyExists = errors.New("file already exists") - ErrNoStateRoots = errors.New("no state roots being written") - stateRootsFileName = "hashes.bin" - wavmModuleRootPrefix = "wavm-module-root" - messageNumberPrefix = "message-num" - bigStepPrefix = "big-step" - challengeLevelPrefix = "subchallenge-level" + ErrNotFoundInCache = errors.New("no found in challenge cache") + ErrFileAlreadyExists = errors.New("file already exists") + ErrNoStateRoots = errors.New("no state roots being written") + stateRootsFileName = "hashes.bin" + wavmModuleRootPrefix = "wavm-module-root" + rollupBlockHashPrefix = "rollup-block-hash" + messageNumberPrefix = "message-num" + bigStepPrefix = "big-step" + challengeLevelPrefix = "subchallenge-level" ) // HistoryCommitmentCacher can retrieve history commitment state roots given lookup keys. @@ -74,9 +75,10 @@ func New(baseDir string) *Cache { // Key for cache lookups includes the wavm module root of a challenge, as well // as the heights for messages and big steps as needed. type Key struct { - WavmModuleRoot common.Hash - MessageHeight uint64 - StepHeights []uint64 + RollupBlockHash common.Hash + WavmModuleRoot common.Hash + MessageHeight uint64 + StepHeights []uint64 } // Get a list of state roots from the cache up to a certain index. State roots are saved as files in the directory @@ -215,7 +217,7 @@ for a given filesystem challenge cache will look as follows: func determineFilePath(baseDir string, lookup *Key) (string, error) { key := make([]string, 0) key = append(key, fmt.Sprintf("%s-%s", wavmModuleRootPrefix, lookup.WavmModuleRoot.Hex())) - key = append(key, fmt.Sprintf("%s-%d", messageNumberPrefix, lookup.MessageHeight)) + key = append(key, fmt.Sprintf("%s-%s-%s-%d", rollupBlockHashPrefix, lookup.RollupBlockHash.Hex(), messageNumberPrefix, lookup.MessageHeight)) for challengeLevel, height := range lookup.StepHeights { key = append(key, fmt.Sprintf( "%s-%d-%s-%d", diff --git a/staker/challenge-cache/cache_test.go b/staker/challenge-cache/cache_test.go index e59efbfccd..09fcc46811 100644 --- a/staker/challenge-cache/cache_test.go +++ b/staker/challenge-cache/cache_test.go @@ -252,7 +252,7 @@ func Test_determineFilePath(t *testing.T) { StepHeights: []uint64{50}, }, }, - want: "wavm-module-root-0x0000000000000000000000000000000000000000000000000000000000000000/message-num-100/subchallenge-level-1-big-step-50/hashes.bin", + want: "wavm-module-root-0x0000000000000000000000000000000000000000000000000000000000000000/rollup-block-hash-0x0000000000000000000000000000000000000000000000000000000000000000-message-num-100/subchallenge-level-1-big-step-50/hashes.bin", wantErr: false, }, } From 94b807c38a8b575969350831cac9e7d8a50495f6 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 13 Jun 2024 16:09:45 -0500 Subject: [PATCH 10/18] rename --- staker/challenge-cache/cache.go | 56 +++++++++---------- staker/challenge-cache/cache_test.go | 84 ++++++++++++++-------------- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/staker/challenge-cache/cache.go b/staker/challenge-cache/cache.go index fc7012917a..9086b1aad1 100644 --- a/staker/challenge-cache/cache.go +++ b/staker/challenge-cache/cache.go @@ -11,7 +11,7 @@ Each file contains a list of 32 byte hashes, concatenated together as bytes. Using this structure, we can namespace hashes by message number and by challenge level. Once a validator receives a full list of computed machine hashes for the first time from a validatio node, -it will write the roots to this filesystem hierarchy for fast access next time these roots are needed. +it will write the hashes to this filesystem hierarchy for fast access next time these hashes are needed. Example uses: - Obtain all the hashes for the execution of message num 70 to 71 for a given wavm module root. @@ -24,7 +24,7 @@ Example uses: hashes.bin We namespace top-level block challenges by wavm module root. Then, we can retrieve -the state roots for any data within a challenge or associated subchallenge based on the hierarchy above. +the hashes for any data within a challenge or associated subchallenge based on the hierarchy above. */ package challengecache @@ -45,8 +45,8 @@ import ( var ( ErrNotFoundInCache = errors.New("no found in challenge cache") ErrFileAlreadyExists = errors.New("file already exists") - ErrNoStateRoots = errors.New("no state roots being written") - stateRootsFileName = "hashes.bin" + ErrNoHashes = errors.New("no hashes being written") + hashesFileName = "hashes.bin" wavmModuleRootPrefix = "wavm-module-root" rollupBlockHashPrefix = "rollup-block-hash" messageNumberPrefix = "message-num" @@ -54,10 +54,10 @@ var ( challengeLevelPrefix = "subchallenge-level" ) -// HistoryCommitmentCacher can retrieve history commitment state roots given lookup keys. +// HistoryCommitmentCacher can retrieve history commitment hashes given lookup keys. type HistoryCommitmentCacher interface { Get(lookup *Key, numToRead uint64) ([]common.Hash, error) - Put(lookup *Key, stateRoots []common.Hash) error + Put(lookup *Key, hashes []common.Hash) error } // Cache for history commitments on disk. @@ -81,7 +81,7 @@ type Key struct { StepHeights []uint64 } -// Get a list of state roots from the cache up to a certain index. State roots are saved as files in the directory +// Get a list of hashes from the cache from index 0 up to a certain index. Hashes are saved as files in the directory // hierarchy for the cache. If a file is not present, ErrNotFoundInCache // is returned. func (c *Cache) Get( @@ -106,23 +106,23 @@ func (c *Cache) Get( log.Error("Could not close file after reading", "err", err, "file", fName) } }() - return readStateRoots(f, numToRead) + return readHashes(f, numToRead) } -// Put a list of state roots into the cache. -// State roots are saved as files in a directory hierarchy for the cache. -// This function first creates a temporary file, writes the state roots to it, and then renames the file +// Put a list of hashes into the cache. +// Hashes are saved as files in a directory hierarchy for the cache. +// This function first creates a temporary file, writes the hashes to it, and then renames the file // to the final directory to ensure atomic writes. -func (c *Cache) Put(lookup *Key, stateRoots []common.Hash) error { - // We should error if trying to put 0 state roots to disk. - if len(stateRoots) == 0 { - return ErrNoStateRoots +func (c *Cache) Put(lookup *Key, hashes []common.Hash) error { + // We should error if trying to put 0 hashes to disk. + if len(hashes) == 0 { + return ErrNoHashes } fName, err := determineFilePath(c.baseDir, lookup) if err != nil { return err } - // We create a tmp file to write our state roots to first. If writing fails, + // We create a tmp file to write our hashes to first. If writing fails, // we don't want to leave a half-written file in our cache directory. // Once writing succeeds, we rename in an atomic operation to the correct file name // in the cache directory hierarchy. @@ -141,7 +141,7 @@ func (c *Cache) Put(lookup *Key, stateRoots []common.Hash) error { log.Error("Could not close file after writing", "err", err, "file", fName) } }() - if err := writeStateRoots(f, stateRoots); err != nil { + if err := writeHashes(f, hashes); err != nil { return err } if err := os.MkdirAll(filepath.Dir(fName), os.ModePerm); err != nil { @@ -155,9 +155,9 @@ func (c *Cache) Put(lookup *Key, stateRoots []common.Hash) error { } // Reads 32 bytes at a time from a reader up to a specified height. If none, then read all. -func readStateRoots(r io.Reader, numToRead uint64) ([]common.Hash, error) { +func readHashes(r io.Reader, numToRead uint64) ([]common.Hash, error) { br := bufio.NewReader(r) - stateRoots := make([]common.Hash, 0) + hashes := make([]common.Hash, 0) buf := make([]byte, 0, 32) for totalRead := uint64(0); totalRead < numToRead; totalRead++ { n, err := br.Read(buf[:cap(buf)]) @@ -172,27 +172,27 @@ func readStateRoots(r io.Reader, numToRead uint64) ([]common.Hash, error) { if n != 32 { return nil, fmt.Errorf("expected to read 32 bytes, got %d bytes", n) } - stateRoots = append(stateRoots, common.BytesToHash(buf)) + hashes = append(hashes, common.BytesToHash(buf)) } - if numToRead > uint64(len(stateRoots)) { + if numToRead > uint64(len(hashes)) { return nil, fmt.Errorf( - "wanted to read %d roots, but only read %d state roots", + "wanted to read %d hashes, but only read %d hashes", numToRead, - len(stateRoots), + len(hashes), ) } - return stateRoots, nil + return hashes, nil } -func writeStateRoots(w io.Writer, stateRoots []common.Hash) error { - for i, rt := range stateRoots { +func writeHashes(w io.Writer, hashes []common.Hash) error { + for i, rt := range hashes { n, err := w.Write(rt[:]) if err != nil { return err } if n != len(rt) { return fmt.Errorf( - "for state root %d, wrote %d bytes, expected to write %d bytes", + "for hash %d, wrote %d bytes, expected to write %d bytes", i, n, len(rt), @@ -229,7 +229,7 @@ func determineFilePath(baseDir string, lookup *Key) (string, error) { ) } - key = append(key, stateRootsFileName) + key = append(key, hashesFileName) return filepath.Join(baseDir, filepath.Join(key...)), nil } diff --git a/staker/challenge-cache/cache_test.go b/staker/challenge-cache/cache_test.go index 09fcc46811..0d36f3d260 100644 --- a/staker/challenge-cache/cache_test.go +++ b/staker/challenge-cache/cache_test.go @@ -38,8 +38,8 @@ func TestCache(t *testing.T) { t.Fatal(err) } }) - t.Run("Putting empty root fails", func(t *testing.T) { - if err := cache.Put(key, []common.Hash{}); !errors.Is(err, ErrNoStateRoots) { + t.Run("Putting empty hash fails", func(t *testing.T) { + if err := cache.Put(key, []common.Hash{}); !errors.Is(err, ErrNoHashes) { t.Fatalf("Unexpected error: %v", err) } }) @@ -57,7 +57,7 @@ func TestCache(t *testing.T) { t.Fatal(err) } if len(got) != len(want) { - t.Fatalf("Wrong number of roots. Expected %d, got %d", len(want), len(got)) + t.Fatalf("Wrong number of hashes. Expected %d, got %d", len(want), len(got)) } for i, rt := range got { if rt != want[i] { @@ -66,14 +66,14 @@ func TestCache(t *testing.T) { } } -func TestReadWriteStateRoots(t *testing.T) { +func TestReadWriteStatehashes(t *testing.T) { t.Run("read up to, but had empty reader", func(t *testing.T) { b := bytes.NewBuffer([]byte{}) - _, err := readStateRoots(b, 100) + _, err := readHashes(b, 100) if err == nil { t.Fatal("Wanted error") } - if !strings.Contains(err.Error(), "only read 0 state roots") { + if !strings.Contains(err.Error(), "only read 0 state hashes") { t.Fatal("Unexpected error") } }) @@ -81,18 +81,18 @@ func TestReadWriteStateRoots(t *testing.T) { b := bytes.NewBuffer([]byte{}) want := common.BytesToHash([]byte("foo")) b.Write(want.Bytes()) - roots, err := readStateRoots(b, 1) + hashes, err := readHashes(b, 1) if err != nil { t.Fatal(err) } - if len(roots) == 0 { - t.Fatal("Got no roots") + if len(hashes) == 0 { + t.Fatal("Got no hashes") } - if roots[0] != want { - t.Fatalf("Wrong root. Expected %#x, got %#x", want, roots[0]) + if hashes[0] != want { + t.Fatalf("Wrong root. Expected %#x, got %#x", want, hashes[0]) } }) - t.Run("Three roots exist, want to read only two", func(t *testing.T) { + t.Run("Three hashes exist, want to read only two", func(t *testing.T) { b := bytes.NewBuffer([]byte{}) foo := common.BytesToHash([]byte("foo")) bar := common.BytesToHash([]byte("bar")) @@ -100,28 +100,28 @@ func TestReadWriteStateRoots(t *testing.T) { b.Write(foo.Bytes()) b.Write(bar.Bytes()) b.Write(baz.Bytes()) - roots, err := readStateRoots(b, 2) + hashes, err := readHashes(b, 2) if err != nil { t.Fatal(err) } - if len(roots) != 2 { - t.Fatalf("Expected two roots, got %d", len(roots)) + if len(hashes) != 2 { + t.Fatalf("Expected two hashes, got %d", len(hashes)) } - if roots[0] != foo { - t.Fatalf("Wrong root. Expected %#x, got %#x", foo, roots[0]) + if hashes[0] != foo { + t.Fatalf("Wrong root. Expected %#x, got %#x", foo, hashes[0]) } - if roots[1] != bar { - t.Fatalf("Wrong root. Expected %#x, got %#x", bar, roots[1]) + if hashes[1] != bar { + t.Fatalf("Wrong root. Expected %#x, got %#x", bar, hashes[1]) } }) t.Run("Fails to write enough data to writer", func(t *testing.T) { m := &mockWriter{wantErr: true} - err := writeStateRoots(m, []common.Hash{common.BytesToHash([]byte("foo"))}) + err := writeHashes(m, []common.Hash{common.BytesToHash([]byte("foo"))}) if err == nil { t.Fatal("Wanted error") } m = &mockWriter{wantErr: false, numWritten: 16} - err = writeStateRoots(m, []common.Hash{common.BytesToHash([]byte("foo"))}) + err = writeHashes(m, []common.Hash{common.BytesToHash([]byte("foo"))}) if err == nil { t.Fatal("Wanted error") } @@ -146,7 +146,7 @@ func (m *mockWriter) Write(_ []byte) (n int, err error) { type mockReader struct { wantErr bool err error - roots []common.Hash + hashes []common.Hash readIdx int bytesRead int } @@ -155,23 +155,23 @@ func (m *mockReader) Read(out []byte) (n int, err error) { if m.wantErr { return 0, m.err } - if m.readIdx == len(m.roots) { + if m.readIdx == len(m.hashes) { return 0, io.EOF } - copy(out, m.roots[m.readIdx].Bytes()) + copy(out, m.hashes[m.readIdx].Bytes()) m.readIdx++ return m.bytesRead, nil } -func Test_readStateRoots(t *testing.T) { +func Test_readHashes(t *testing.T) { t.Run("Unexpected error", func(t *testing.T) { want := []common.Hash{ common.BytesToHash([]byte("foo")), common.BytesToHash([]byte("bar")), common.BytesToHash([]byte("baz")), } - m := &mockReader{wantErr: true, roots: want, err: errors.New("foo")} - _, err := readStateRoots(m, 1) + m := &mockReader{wantErr: true, hashes: want, err: errors.New("foo")} + _, err := readHashes(m, 1) if err == nil { t.Fatal(err) } @@ -185,8 +185,8 @@ func Test_readStateRoots(t *testing.T) { common.BytesToHash([]byte("bar")), common.BytesToHash([]byte("baz")), } - m := &mockReader{wantErr: true, roots: want, err: io.EOF} - _, err := readStateRoots(m, 100) + m := &mockReader{wantErr: true, hashes: want, err: io.EOF} + _, err := readHashes(m, 100) if err == nil { t.Fatal(err) } @@ -200,8 +200,8 @@ func Test_readStateRoots(t *testing.T) { common.BytesToHash([]byte("bar")), common.BytesToHash([]byte("baz")), } - m := &mockReader{wantErr: false, roots: want, bytesRead: 16} - _, err := readStateRoots(m, 2) + m := &mockReader{wantErr: false, hashes: want, bytesRead: 16} + _, err := readHashes(m, 2) if err == nil { t.Fatal(err) } @@ -215,13 +215,13 @@ func Test_readStateRoots(t *testing.T) { common.BytesToHash([]byte("bar")), common.BytesToHash([]byte("baz")), } - m := &mockReader{wantErr: false, roots: want, bytesRead: 32} - got, err := readStateRoots(m, 3) + m := &mockReader{wantErr: false, hashes: want, bytesRead: 32} + got, err := readHashes(m, 3) if err != nil { t.Fatal(err) } if len(want) != len(got) { - t.Fatal("Wrong number of roots") + t.Fatal("Wrong number of hashes") } for i, rt := range got { if rt != want[i] { @@ -295,23 +295,23 @@ func BenchmarkCache_Read_32Mb(b *testing.B) { MessageHeight: 0, StepHeights: []uint64{0}, } - numRoots := 1 << 20 - roots := make([]common.Hash, numRoots) - for i := range roots { - roots[i] = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) + numHashes := 1 << 20 + hashes := make([]common.Hash, numHashes) + for i := range hashes { + hashes[i] = common.BytesToHash([]byte(fmt.Sprintf("%d", i))) } - if err := cache.Put(key, roots); err != nil { + if err := cache.Put(key, hashes); err != nil { b.Fatal(err) } b.StartTimer() for i := 0; i < b.N; i++ { readUpTo := uint64(1 << 20) - roots, err := cache.Get(key, readUpTo) + hashes, err := cache.Get(key, readUpTo) if err != nil { b.Fatal(err) } - if len(roots) != numRoots { - b.Fatalf("Wrong number of roots. Expected %d, got %d", numRoots, len(roots)) + if len(hashes) != numHashes { + b.Fatalf("Wrong number of hashes. Expected %d, got %d", hashes, len(hashes)) } } } From b4f7cc3c4990e787748ee35642e736243df69b05 Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Thu, 13 Jun 2024 16:57:15 -0500 Subject: [PATCH 11/18] ci --- staker/challenge-cache/cache_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/staker/challenge-cache/cache_test.go b/staker/challenge-cache/cache_test.go index 0d36f3d260..ff895649f2 100644 --- a/staker/challenge-cache/cache_test.go +++ b/staker/challenge-cache/cache_test.go @@ -73,7 +73,7 @@ func TestReadWriteStatehashes(t *testing.T) { if err == nil { t.Fatal("Wanted error") } - if !strings.Contains(err.Error(), "only read 0 state hashes") { + if !strings.Contains(err.Error(), "only read 0 hashes") { t.Fatal("Unexpected error") } }) From e47cc29a1b702a7bfbe1563e45bbc319ced1f8fc Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Mon, 17 Jun 2024 11:47:00 -0500 Subject: [PATCH 12/18] comments --- staker/challenge-cache/cache.go | 59 ++++++---------------------- staker/challenge-cache/cache_test.go | 2 +- 2 files changed, 12 insertions(+), 49 deletions(-) diff --git a/staker/challenge-cache/cache.go b/staker/challenge-cache/cache.go index 9086b1aad1..c4e5876985 100644 --- a/staker/challenge-cache/cache.go +++ b/staker/challenge-cache/cache.go @@ -36,7 +36,6 @@ import ( "io" "os" "path/filepath" - "strings" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" @@ -126,7 +125,10 @@ func (c *Cache) Put(lookup *Key, hashes []common.Hash) error { // we don't want to leave a half-written file in our cache directory. // Once writing succeeds, we rename in an atomic operation to the correct file name // in the cache directory hierarchy. - tmp := os.TempDir() + tmp, err := os.MkdirTemp(c.baseDir, "tmpdir") + if err != nil { + return err + } tmpFName := filepath.Join(tmp, fName) dir := filepath.Dir(tmpFName) if err := os.MkdirAll(dir, os.ModePerm); err != nil { @@ -151,14 +153,14 @@ func (c *Cache) Put(lookup *Key, hashes []common.Hash) error { // into our cache directory. This is an atomic operation. // For more information on this atomic write pattern, see: // https://stackoverflow.com/questions/2333872/how-to-make-file-creation-an-atomic-operation - return Move(tmpFName /* old */, fName /* new */) + return os.Rename(tmpFName /*old */, fName /* new */) } // Reads 32 bytes at a time from a reader up to a specified height. If none, then read all. func readHashes(r io.Reader, numToRead uint64) ([]common.Hash, error) { br := bufio.NewReader(r) hashes := make([]common.Hash, 0) - buf := make([]byte, 0, 32) + buf := make([]byte, 0, common.HashLength) for totalRead := uint64(0); totalRead < numToRead; totalRead++ { n, err := br.Read(buf[:cap(buf)]) if err != nil { @@ -169,8 +171,8 @@ func readHashes(r io.Reader, numToRead uint64) ([]common.Hash, error) { return nil, err } buf = buf[:n] - if n != 32 { - return nil, fmt.Errorf("expected to read 32 bytes, got %d bytes", n) + if n != common.HashLength { + return nil, fmt.Errorf("expected to read %d bytes, got %d bytes", common.HashLength, n) } hashes = append(hashes, common.BytesToHash(buf)) } @@ -185,8 +187,9 @@ func readHashes(r io.Reader, numToRead uint64) ([]common.Hash, error) { } func writeHashes(w io.Writer, hashes []common.Hash) error { + bw := bufio.NewWriter(w) for i, rt := range hashes { - n, err := w.Write(rt[:]) + n, err := bw.Write(rt[:]) if err != nil { return err } @@ -199,7 +202,7 @@ func writeHashes(w io.Writer, hashes []common.Hash) error { ) } } - return nil + return bw.Flush() } /* @@ -232,43 +235,3 @@ func determineFilePath(baseDir string, lookup *Key) (string, error) { key = append(key, hashesFileName) return filepath.Join(baseDir, filepath.Join(key...)), nil } - -// Move function that is robust against cross-device link errors. Credits to: -// https://gist.github.com/var23rav/23ae5d0d4d830aff886c3c970b8f6c6b -func Move(source, destination string) error { - err := os.Rename(source, destination) - if err != nil && strings.Contains(err.Error(), "cross-device link") { - return moveCrossDevice(source, destination) - } - return err -} - -func moveCrossDevice(source, destination string) error { - src, err := os.Open(source) - if err != nil { - return err - } - dst, err := os.Create(destination) - if err != nil { - src.Close() - return err - } - _, err = io.Copy(dst, src) - src.Close() - dst.Close() - if err != nil { - return err - } - fi, err := os.Stat(source) - if err != nil { - os.Remove(destination) - return err - } - err = os.Chmod(destination, fi.Mode()) - if err != nil { - os.Remove(destination) - return err - } - os.Remove(source) - return nil -} diff --git a/staker/challenge-cache/cache_test.go b/staker/challenge-cache/cache_test.go index ff895649f2..bcb2dc396d 100644 --- a/staker/challenge-cache/cache_test.go +++ b/staker/challenge-cache/cache_test.go @@ -125,7 +125,7 @@ func TestReadWriteStatehashes(t *testing.T) { if err == nil { t.Fatal("Wanted error") } - if !strings.Contains(err.Error(), "expected to write 32 bytes") { + if !strings.Contains(err.Error(), "short write") { t.Fatalf("Got wrong error kind: %v", err) } }) From 9896decbc3d36cbf8ab4d3b9c3bc4be3b6f0f578 Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Mon, 17 Jun 2024 14:35:24 -0700 Subject: [PATCH 13/18] Split envvars with commas in them into a slice This makes command line options that accept multiple string values separated by commas also work correctly when used as an environment variable. eg ARB_NODE_VALIDATION_WASM_ALLOWED__WASM__MODULE__ROOTS=/a,/b now works as expected, with separate entries "/a" and "/b" --- cmd/util/confighelpers/configuration.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/cmd/util/confighelpers/configuration.go b/cmd/util/confighelpers/configuration.go index 3ff27d65ce..5b41627697 100644 --- a/cmd/util/confighelpers/configuration.go +++ b/cmd/util/confighelpers/configuration.go @@ -95,11 +95,18 @@ func applyOverrideOverrides(f *flag.FlagSet, k *koanf.Koanf) error { func loadEnvironmentVariables(k *koanf.Koanf) error { envPrefix := k.String("conf.env-prefix") if len(envPrefix) != 0 { - return k.Load(env.Provider(envPrefix+"_", ".", func(s string) string { + return k.Load(env.ProviderWithValue(envPrefix+"_", ".", func(key string, v string) (string, interface{}) { // FOO__BAR -> foo-bar to handle dash in config names - s = strings.ReplaceAll(strings.ToLower( - strings.TrimPrefix(s, envPrefix+"_")), "__", "-") - return strings.ReplaceAll(s, "_", ".") + key = strings.ReplaceAll(strings.ToLower( + strings.TrimPrefix(key, envPrefix+"_")), "__", "-") + key = strings.ReplaceAll(key, "_", ".") + + // If there is a space in the value, split the value into a slice by the space. + if strings.Contains(v, ",") { + return key, strings.Split(v, ",") + } + + return key, v }), nil) } From 89912ae7146e1e0d319f114c1017d49ca91ffc8f Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Mon, 17 Jun 2024 14:56:34 -0700 Subject: [PATCH 14/18] Split only specific envvars that are StringSlices --- cmd/util/confighelpers/configuration.go | 29 ++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/cmd/util/confighelpers/configuration.go b/cmd/util/confighelpers/configuration.go index 5b41627697..9b5abdecc9 100644 --- a/cmd/util/confighelpers/configuration.go +++ b/cmd/util/confighelpers/configuration.go @@ -92,6 +92,22 @@ func applyOverrideOverrides(f *flag.FlagSet, k *koanf.Koanf) error { return nil } +var envvarsToSplitOnComma map[string]any = map[string]any{ + "allowed-wasm-module-roots": struct{}{}, + "module-roots": struct{}{}, + "url": struct{}{}, + "secondary-url": struct{}{}, + "file": struct{}{}, + "api": struct{}{}, + "corsdomain": struct{}{}, + "vhosts": struct{}{}, + "origins": struct{}{}, + "bootnodes": struct{}{}, + "bootnodes-v5": struct{}{}, + "secondary-forwarding-target": struct{}{}, + "allowed-addresses": struct{}{}, +} + func loadEnvironmentVariables(k *koanf.Koanf) error { envPrefix := k.String("conf.env-prefix") if len(envPrefix) != 0 { @@ -101,9 +117,16 @@ func loadEnvironmentVariables(k *koanf.Koanf) error { strings.TrimPrefix(key, envPrefix+"_")), "__", "-") key = strings.ReplaceAll(key, "_", ".") - // If there is a space in the value, split the value into a slice by the space. - if strings.Contains(v, ",") { - return key, strings.Split(v, ",") + keyParts := strings.Split(key, ".") + if len(keyParts) > 0 { + if _, found := envvarsToSplitOnComma[keyParts[len(keyParts)-1]]; found { + // If there are commas in the value, split the value into a slice. + if strings.Contains(v, ",") { + return key, strings.Split(v, ",") + + } + } + } return key, v From 9426628e8e3be36535f2db553fa03e6176d6d52f Mon Sep 17 00:00:00 2001 From: Tristan Wilson Date: Mon, 17 Jun 2024 16:29:13 -0700 Subject: [PATCH 15/18] Whitelist specific envvars for comma splitting --- cmd/util/confighelpers/configuration.go | 47 ++++++++++++++----------- 1 file changed, 26 insertions(+), 21 deletions(-) diff --git a/cmd/util/confighelpers/configuration.go b/cmd/util/confighelpers/configuration.go index 9b5abdecc9..ff33da6732 100644 --- a/cmd/util/confighelpers/configuration.go +++ b/cmd/util/confighelpers/configuration.go @@ -93,19 +93,28 @@ func applyOverrideOverrides(f *flag.FlagSet, k *koanf.Koanf) error { } var envvarsToSplitOnComma map[string]any = map[string]any{ - "allowed-wasm-module-roots": struct{}{}, - "module-roots": struct{}{}, - "url": struct{}{}, - "secondary-url": struct{}{}, - "file": struct{}{}, - "api": struct{}{}, - "corsdomain": struct{}{}, - "vhosts": struct{}{}, - "origins": struct{}{}, - "bootnodes": struct{}{}, - "bootnodes-v5": struct{}{}, - "secondary-forwarding-target": struct{}{}, - "allowed-addresses": struct{}{}, + "auth.api": struct{}{}, + "auth.origins": struct{}{}, + "chain.info-files": struct{}{}, + "conf.file": struct{}{}, + "execution.secondary-forwarding-target": struct{}{}, + "graphql.corsdomain": struct{}{}, + "graphql.vhosts": struct{}{}, + "http.api": struct{}{}, + "http.corsdomain": struct{}{}, + "http.vhosts": struct{}{}, + "node.data-availability.rest-aggregator.urls": struct{}{}, + "node.feed.input.secondary-url": struct{}{}, + "node.feed.input.url": struct{}{}, + "node.feed.input.verify.allowed-addresses": struct{}{}, + "node.seq-coordinator.signer.ecdsa.allowed-addresses": struct{}{}, + "p2p.bootnodes": struct{}{}, + "p2p.bootnodes-v5": struct{}{}, + "validation.api-auth": struct{}{}, + "validation.arbitrator.redis-validation-server-config.module-roots": struct{}{}, + "validation.wasm.allowed-wasm-module-roots": struct{}{}, + "ws.api": struct{}{}, + "ws.origins": struct{}{}, } func loadEnvironmentVariables(k *koanf.Koanf) error { @@ -117,16 +126,12 @@ func loadEnvironmentVariables(k *koanf.Koanf) error { strings.TrimPrefix(key, envPrefix+"_")), "__", "-") key = strings.ReplaceAll(key, "_", ".") - keyParts := strings.Split(key, ".") - if len(keyParts) > 0 { - if _, found := envvarsToSplitOnComma[keyParts[len(keyParts)-1]]; found { - // If there are commas in the value, split the value into a slice. - if strings.Contains(v, ",") { - return key, strings.Split(v, ",") + if _, found := envvarsToSplitOnComma[key]; found { + // If there are commas in the value, split the value into a slice. + if strings.Contains(v, ",") { + return key, strings.Split(v, ",") - } } - } return key, v From 20c5e1f6bb309d65a759630fcdee0976e49fe65a Mon Sep 17 00:00:00 2001 From: Raul Jordan Date: Tue, 18 Jun 2024 09:57:58 -0500 Subject: [PATCH 16/18] edits --- staker/challenge-cache/cache.go | 9 +++++++-- staker/challenge-cache/cache_test.go | 12 +++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/staker/challenge-cache/cache.go b/staker/challenge-cache/cache.go index c4e5876985..8cca4bb835 100644 --- a/staker/challenge-cache/cache.go +++ b/staker/challenge-cache/cache.go @@ -65,10 +65,15 @@ type Cache struct { } // New cache from a base directory path. -func New(baseDir string) *Cache { +func New(baseDir string) (*Cache, error) { + if _, err := os.Stat(baseDir); err != nil { + if err := os.MkdirAll(baseDir, os.ModePerm); err != nil { + return nil, fmt.Errorf("could not make base cache directory %s: %w", baseDir, err) + } + } return &Cache{ baseDir: baseDir, - } + }, nil } // Key for cache lookups includes the wavm module root of a challenge, as well diff --git a/staker/challenge-cache/cache_test.go b/staker/challenge-cache/cache_test.go index bcb2dc396d..6b15d62af7 100644 --- a/staker/challenge-cache/cache_test.go +++ b/staker/challenge-cache/cache_test.go @@ -26,7 +26,10 @@ func TestCache(t *testing.T) { t.Fatal(err) } }) - cache := New(basePath) + cache, err := New(basePath) + if err != nil { + t.Fatal(err) + } key := &Key{ WavmModuleRoot: common.BytesToHash([]byte("foo")), MessageHeight: 0, @@ -48,7 +51,7 @@ func TestCache(t *testing.T) { common.BytesToHash([]byte("bar")), common.BytesToHash([]byte("baz")), } - err := cache.Put(key, want) + err = cache.Put(key, want) if err != nil { t.Fatal(err) } @@ -289,7 +292,10 @@ func BenchmarkCache_Read_32Mb(b *testing.B) { b.Fatal(err) } }) - cache := New(basePath) + cache, err := New(basePath) + if err != nil { + b.Fatal(err) + } key := &Key{ WavmModuleRoot: common.BytesToHash([]byte("foo")), MessageHeight: 0, From 89b5611665301fd60f76c4daf1ea6f77aa8c7f2d Mon Sep 17 00:00:00 2001 From: Andrew Huang Date: Fri, 21 Jun 2024 09:00:08 -0700 Subject: [PATCH 17/18] Address comments --- arbnode/batch_poster.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/arbnode/batch_poster.go b/arbnode/batch_poster.go index 6af1c5471c..478bbd395e 100644 --- a/arbnode/batch_poster.go +++ b/arbnode/batch_poster.go @@ -66,7 +66,7 @@ var ( blobGasLimitGauge = metrics.NewRegisteredGauge("arb/batchposter/blobgas/limit", nil) suggestedTipCapGauge = metrics.NewRegisteredGauge("arb/batchposter/suggestedtipcap", nil) - batchPosterBacklogGauge = metrics.NewRegisteredGauge("arb/batchposter/backlog", nil) + batchPosterEstimatedBatchBacklogGauge = metrics.NewRegisteredGauge("arb/batchposter/estimated_batch_backlog", nil) batchPosterDALastSuccessfulActionGauge = metrics.NewRegisteredGauge("arb/batchPoster/action/da_last_success", nil) batchPosterDASuccessCounter = metrics.NewRegisteredCounter("arb/batchPoster/action/da_success", nil) @@ -1045,13 +1045,7 @@ const ethPosBlockTime = 12 * time.Second var errAttemptLockFailed = errors.New("failed to acquire lock; either another batch poster posted a batch or this node fell behind") -func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (ret bool, err error) { - defer func() { - if err != nil { - batchPosterFailureCounter.Inc(1) - } - }() - +func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (bool, error) { if b.batchReverted.Load() { return false, fmt.Errorf("batch was reverted, not posting any more batches") } @@ -1368,7 +1362,7 @@ func (b *BatchPoster) maybePostSequencerBatch(ctx context.Context) (ret bool, er messagesPerBatch = 1 } backlog := uint64(unpostedMessages) / messagesPerBatch - batchPosterBacklogGauge.Update(int64(backlog)) + batchPosterEstimatedBatchBacklogGauge.Update(int64(backlog)) if backlog > 10 { logLevel := log.Warn if recentlyHitL1Bounds { @@ -1481,6 +1475,7 @@ func (b *BatchPoster) Start(ctxIn context.Context) { logLevel = normalGasEstimationFailedEphemeralErrorHandler.LogLevel(err, logLevel) logLevel = accumulatorNotFoundEphemeralErrorHandler.LogLevel(err, logLevel) logLevel("error posting batch", "err", err) + batchPosterFailureCounter.Inc(1) return b.config().ErrorDelay } else if posted { return 0 From 43d6517cfc81fb203f0c6333c47a10beb3ad3715 Mon Sep 17 00:00:00 2001 From: Lee Bousfield Date: Tue, 25 Jun 2024 10:25:55 -0600 Subject: [PATCH 18/18] Work around duplicate response from CL --- util/headerreader/blob_client.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/util/headerreader/blob_client.go b/util/headerreader/blob_client.go index 73849d0d3a..2b47a940c3 100644 --- a/util/headerreader/blob_client.go +++ b/util/headerreader/blob_client.go @@ -229,10 +229,11 @@ func (b *BlobClient) blobSidecars(ctx context.Context, slot uint64, versionedHas var found bool for outputIdx = range versionedHashes { if versionedHashes[outputIdx] == versionedHash { - found = true if outputsFound[outputIdx] { - return nil, fmt.Errorf("found blob with versioned hash %v twice", versionedHash) + // Duplicate, skip this one + break } + found = true outputsFound[outputIdx] = true break }