From 04e02e959e556b81737a82772a68dea7edbe1ce9 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Thu, 11 Sep 2025 11:28:35 +0100 Subject: [PATCH 01/31] feat: `blocks` package --- .github/workflows/lint.yml | 2 +- .golangci.yml | 5 + blocks/block.go | 124 ++++++++++ blocks/block_test.go | 99 ++++++++ blocks/cmpopt.go | 50 +++++ blocks/db.go | 125 +++++++++++ blocks/execution.canoto.go | 281 +++++++++++++++++++++++ blocks/execution.go | 158 +++++++++++++ blocks/execution_test.go | 163 ++++++++++++++ blocks/invariants.go | 72 ++++++ blocks/settlement.go | 165 ++++++++++++++ blocks/settlement_test.go | 383 +++++++++++++++++++++++++++++++ blocks/snow.go | 53 +++++ cmputils/cmputils.go | 23 ++ go.mod | 45 +++- go.sum | 448 +++++++++++++++++++++++++++++++++++++ saetest/logging.go | 62 +++++ saetest/saetest.go | 29 +++ 18 files changed, 2282 insertions(+), 5 deletions(-) create mode 100644 blocks/block.go create mode 100644 blocks/block_test.go create mode 100644 blocks/cmpopt.go create mode 100644 blocks/db.go create mode 100644 blocks/execution.canoto.go create mode 100644 blocks/execution.go create mode 100644 blocks/execution_test.go create mode 100644 blocks/invariants.go create mode 100644 blocks/settlement.go create mode 100644 blocks/settlement_test.go create mode 100644 blocks/snow.go create mode 100644 saetest/logging.go create mode 100644 saetest/saetest.go diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 473c709..f0b5a69 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,7 +36,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v6 with: - version: v1.63.3 + version: v1.64.8 yamllint: runs-on: ubuntu-latest diff --git a/.golangci.yml b/.golangci.yml index 8694165..e221766 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -82,6 +82,11 @@ linters-settings: severity: warning disabled: false + testifylint: + enable-all: true + disable: + - require-error # Blanket usage of require over assert is an anti-pattern + issues: include: # Many of the default exclusions are because, verbatim "Annoying issue", diff --git a/blocks/block.go b/blocks/block.go new file mode 100644 index 0000000..5f453e4 --- /dev/null +++ b/blocks/block.go @@ -0,0 +1,124 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package blocks defines [Streaming Asynchronous Execution] (SAE) blocks. +// +// [Streaming Asynchronous Execution]: https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/194-streaming-asynchronous-execution +package blocks + +import ( + "errors" + "fmt" + "math" + "runtime" + "sync/atomic" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/types" + "go.uber.org/zap" +) + +// A Block extends a [types.Block] to track SAE-defined concepts of async +// execution and settlement. It MUST be constructed with [New]. +type Block struct { + *types.Block + // Invariant: ancestry is non-nil and contains non-nil pointers i.f.f. the + // block hasn't itself been settled. A synchronous block (e.g. SAE genesis + // or the last pre-SAE block) is always considered settled. + // + // Rationale: the ancestral pointers form a linked list that would prevent + // garbage collection if not severed. Once a block is settled there is no + // need to inspect its history so we sacrifice the ancestors to the GC + // Overlord as a sign of our unwavering fealty. See [InMemoryBlockCount] for + // observability. + ancestry atomic.Pointer[ancestry] + // Non-nil i.f.f. [Block.MarkExecuted] or [Block.ResotrePostExecutionState] + // have returned without error. + execution atomic.Pointer[executionResults] + + // See [Block.SetInterimExecutionTime for setting and [LastToSettleAt] for + // usage. The pointer MAY be nil if execution is yet to commence. + executionExceededSecond atomic.Pointer[uint64] + + executed chan struct{} // closed after `execution` is set + settled chan struct{} // closed after `ancestry` is cleared + + log logging.Logger +} + +var inMemoryBlockCount atomic.Uint64 + +// InMemoryBlockCount returns the number of blocks created with [New] that are +// yet to have their GC finalizers run. +func InMemoryBlockCount() uint64 { + return inMemoryBlockCount.Load() +} + +// New constructs a new Block. +func New(eth *types.Block, parent, lastSettled *Block, log logging.Logger) (*Block, error) { + b := &Block{ + Block: eth, + executed: make(chan struct{}), + settled: make(chan struct{}), + } + + inMemoryBlockCount.Add(1) + runtime.AddCleanup(b, func(struct{}) { + inMemoryBlockCount.Add(math.MaxUint64) // -1 + }, struct{}{}) + + if err := b.setAncestors(parent, lastSettled); err != nil { + return nil, err + } + b.log = log.With( + zap.Uint64("height", b.Height()), + zap.Stringer("hash", b.Hash()), + ) + return b, nil +} + +var ( + errParentHashMismatch = errors.New("block-parent hash mismatch") + errHashMismatch = errors.New("block hash mismatch") +) + +func (b *Block) setAncestors(parent, lastSettled *Block) error { + if parent != nil { + if got, want := parent.Hash(), b.ParentHash(); got != want { + return fmt.Errorf("%w: constructing Block with parent hash %v; expecting %v", errParentHashMismatch, got, want) + } + } + b.ancestry.Store(&ancestry{ + parent: parent, + lastSettled: lastSettled, + }) + return nil +} + +// CopyAncestorsFrom populates the [Block.ParentBlock] and [Block.LastSettled] +// values, typically only required during database recovery. The source block +// MUST have the same hash as b. +// +// Although the individual ancestral blocks are shallow copied, calling +// [Block.MarkSettled] on either the source or destination will NOT clear the +// pointers of the other. +func (b *Block) CopyAncestorsFrom(c *Block) error { + if from, to := c.Hash(), b.Hash(); from != to { + return fmt.Errorf("%w: copying internals from block %#x to %#x", errHashMismatch, from, to) + } + a := c.ancestry.Load() + return b.setAncestors(a.parent, a.lastSettled) +} + +// Root is a noop that shadows the equivalent method on [types.Block], which is +// embedded in the [Block] and is ambiguous under SAE. Use +// [Block.PostExecutionStateRoot] or [Block.SettledStateRoot] instead. +func (b *Block) Root() {} + +// SettledStateRoot returns the state root after execution of the last block +// settled by b. It is a convenience wrapper for calling [types.Block.Root] on +// the embedded [types.Block]. +func (b *Block) SettledStateRoot() common.Hash { + return b.Block.Root() +} diff --git a/blocks/block_test.go b/blocks/block_test.go new file mode 100644 index 0000000..6fb057e --- /dev/null +++ b/blocks/block_test.go @@ -0,0 +1,99 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blocks + +import ( + "math/big" + "testing" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/libevm/core/types" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func newEthBlock(num, time uint64, parent *types.Block) *types.Block { + hdr := &types.Header{ + Number: new(big.Int).SetUint64(num), + Time: time, + } + if parent != nil { + hdr.ParentHash = parent.Hash() + } + return types.NewBlockWithHeader(hdr) +} + +func newBlock(tb testing.TB, eth *types.Block, parent, lastSettled *Block) *Block { + tb.Helper() + b, err := New(eth, parent, lastSettled, logging.NoLog{}) + require.NoError(tb, err, "New()") + return b +} + +func newChain(tb testing.TB, startHeight, total uint64, lastSettledAtHeight map[uint64]uint64) []*Block { + tb.Helper() + + var ( + ethParent *types.Block + parent *Block + blocks []*Block + ) + byNum := make(map[uint64]*Block) + + if lastSettledAtHeight == nil { + lastSettledAtHeight = make(map[uint64]uint64) + } + + for i := range total { + n := startHeight + i + + var settle *Block + if s, ok := lastSettledAtHeight[n]; ok { + settle = byNum[s] + } + + byNum[n] = newBlock(tb, newEthBlock(n, n /*time*/, ethParent), parent, settle) + blocks = append(blocks, byNum[n]) + + parent = byNum[n] + ethParent = parent.Block + } + + return blocks +} + +func TestSetAncestors(t *testing.T) { + parent := newBlock(t, newEthBlock(5, 5, nil), nil, nil) + lastSettled := newBlock(t, newEthBlock(3, 0, nil), nil, nil) + child := newEthBlock(6, 6, parent.Block) + + t.Run("incorrect_parent", func(t *testing.T) { + // Note that the arguments to [New] are inverted. + _, err := New(child, lastSettled, parent, logging.NoLog{}) + require.ErrorIs(t, err, errParentHashMismatch, "New() with inverted parent and last-settled blocks") + }) + + source := newBlock(t, child, parent, lastSettled) + dest := newBlock(t, child, nil, nil) + + t.Run("destination_before_copy", func(t *testing.T) { + assert.Nilf(t, dest.ParentBlock(), "%T.ParentBlock()", dest) + assert.Nilf(t, dest.LastSettled(), "%T.LastSettled()", dest) + }) + if t.Failed() { + t.FailNow() + } + + require.NoError(t, dest.CopyAncestorsFrom(source), "CopyAncestorsFrom()") + if diff := cmp.Diff(source, dest, CmpOpt()); diff != "" { + t.Errorf("After %T.CopyAncestorsFrom(); diff (-want +got):\n%s", dest, diff) + } + + t.Run("incompatible_destination_block", func(t *testing.T) { + ethB := newEthBlock(dest.Height()+1 /*mismatch*/, dest.Time(), parent.Block) + dest := newBlock(t, ethB, nil, nil) + require.ErrorIs(t, dest.CopyAncestorsFrom(source), errHashMismatch) + }) +} diff --git a/blocks/cmpopt.go b/blocks/cmpopt.go new file mode 100644 index 0000000..1ab9faf --- /dev/null +++ b/blocks/cmpopt.go @@ -0,0 +1,50 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +//go:build !prod && !nocmpopts + +package blocks + +import ( + "github.com/google/go-cmp/cmp" + + "github.com/ava-labs/strevm/cmputils" + "github.com/ava-labs/strevm/saetest" +) + +// CmpOpt returns a configuration for [cmp.Diff] to compare [Block] instances in +// tests. +func CmpOpt() cmp.Option { + return cmp.Comparer((*Block).equalForTests) +} + +func (b *Block) equalForTests(c *Block) bool { + fn := cmputils.WithNilCheck(func(b, c *Block) bool { + return true && + b.Hash() == c.Hash() && + b.ancestry.Load().equalForTests(c.ancestry.Load()) && + b.execution.Load().equalForTests(c.execution.Load()) + }) + return fn(b, c) +} + +func (a *ancestry) equalForTests(b *ancestry) bool { + fn := cmputils.WithNilCheck(func(a, b *ancestry) bool { + return true && + a.parent.equalForTests(b.parent) && + a.lastSettled.equalForTests(b.lastSettled) + }) + return fn(a, b) +} + +func (e *executionResults) equalForTests(f *executionResults) bool { + fn := cmputils.WithNilCheck(func(e, f *executionResults) bool { + return true && + e.byGas.Rate() == f.byGas.Rate() && + e.byGas.Compare(f.byGas.Time) == 0 && // N.B. Compare is only valid if rates are equal + e.receiptRoot == f.receiptRoot && + saetest.MerkleRootsEqual(e.receipts, f.receipts) && + e.stateRootPost == f.stateRootPost + }) + return fn(e, f) +} diff --git a/blocks/db.go b/blocks/db.go new file mode 100644 index 0000000..3baa7eb --- /dev/null +++ b/blocks/db.go @@ -0,0 +1,125 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blocks + +import ( + "encoding/binary" + "fmt" + "math/big" + "slices" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/rawdb" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/params" + "github.com/ava-labs/libevm/trie" +) + +/* ===== Common =====*/ + +func blockNumDBKey(prefix string, blockNum uint64) []byte { + return binary.BigEndian.AppendUint64([]byte(prefix), blockNum) +} + +func (b *Block) writeToKVStore(w ethdb.KeyValueWriter, key func(uint64) []byte, val []byte) error { + return w.Put(key(b.NumberU64()), val) +} + +/* ===== Post-execution state =====*/ + +func execResultsDBKey(blockNum uint64) []byte { + return blockNumDBKey("sae-post-exec-", blockNum) +} + +func (b *Block) writePostExecutionState(w ethdb.KeyValueWriter, e *executionResults) error { + return b.writeToKVStore(w, execResultsDBKey, e.MarshalCanoto()) +} + +// RestorePostExecutionState restores b to the same post-execution state as when +// [Block.MarkExecuted] was called on it—this is only expected to be used after +// a restart. The receipts MUST match those originally passed to +// [Block.MarkExecuted] as they will be checked against the persisted Merkle +// root. +// +// This method is considered equivalent to a call to [Block.MarkExecuted] for +// the purposes of post-execution events (e.g. unblocking +// [Block.WaitUntilExecuted]) and artefacts (e.g. [Block.ExecutedByGasTime]). +// Similarly, it MUST NOT be called more than once, and usage of this and +// [Block.MarkExecuted] is mutually exclusive. +func (b *Block) RestorePostExecutionState(db ethdb.Database, receipts types.Receipts) error { + e, err := readExecResults(db, b.NumberU64()) + if err != nil { + return err + } + if argRoot := types.DeriveSha(receipts, trie.NewStackTrie(nil)); argRoot != e.receiptRoot { + return fmt.Errorf( + "restoring execution state of block %d: receipt-root mismatch (db = %v; arg = %v)", + b.Height(), e.receiptRoot, argRoot, + ) + } + e.receipts = slices.Clone(receipts) + return b.markExecuted(e) +} + +// RestorePostExecutionStateAndReceipts is a convenience wrapper for calling +// [rawdb.ReadReceipts], the results of which are propagated to +// [Block.RestorePostExecutionState]. +func (b *Block) RestorePostExecutionStateAndReceipts(db ethdb.Database, config *params.ChainConfig) error { + rs := rawdb.ReadReceipts(db, b.Hash(), b.NumberU64(), b.Time(), config) + return b.RestorePostExecutionState(db, rs) +} + +// StateRootPostExecution returns the state root passed to [Block.MarkExecuted], +// as persisted in the database. The [Block.PostExecutionStateRoot] method is +// the in-memory equivalent of this function. +func StateRootPostExecution(db ethdb.Database, blockNum uint64) (common.Hash, error) { + e, err := readExecResults(db, blockNum) + if err != nil { + return common.Hash{}, err + } + return e.stateRootPost, nil +} + +func readExecResults(db ethdb.Database, num uint64) (*executionResults, error) { + buf, err := db.Get(execResultsDBKey(num)) + if err != nil { + return nil, err + } + e := new(executionResults) + if err := e.UnmarshalCanoto(buf); err != nil { + return nil, err + } + return e, nil +} + +/* ===== Last-settled block at chain height ===== */ + +func lastSettledDBKey(blockNum uint64) []byte { + return blockNumDBKey("sae-last-settled-", blockNum) +} + +// WriteLastSettledNumber writes, to w, the block height of the last-settled +// block of b (i.e. of [Block.LastSettled]). +func (b *Block) WriteLastSettledNumber(w ethdb.KeyValueWriter) error { + return b.writeToKVStore(w, lastSettledDBKey, b.LastSettled().Number().Bytes()) +} + +// ReadLastSettledNumber is the counterpart of [Block.WriteLastSettledNumber], +// returning the height of the last-settled block of the block with the +// specified height. +func ReadLastSettledNumber(db ethdb.Database, blockNum uint64) (uint64, error) { + buf, err := db.Get(lastSettledDBKey(blockNum)) + if err != nil { + return 0, err + } + settled := new(big.Int).SetBytes(buf) + if !settled.IsUint64() { + return 0, fmt.Errorf("read non-uint64 last-settled block of block %d", blockNum) + } + if settled.Uint64() >= blockNum { // only a sense check + return 0, fmt.Errorf("read last-settled block num %d of block %d", settled.Uint64(), blockNum) + } + return settled.Uint64(), nil +} diff --git a/blocks/execution.canoto.go b/blocks/execution.canoto.go new file mode 100644 index 0000000..de70a45 --- /dev/null +++ b/blocks/execution.canoto.go @@ -0,0 +1,281 @@ +// Code generated by canoto. DO NOT EDIT. +// versions: +// canoto v0.17.1 +// source: execution.go + +package blocks + +import ( + "io" + "reflect" + "sync/atomic" + + "github.com/StephenButtolph/canoto" +) + +// Ensure that unused imports do not error +var ( + _ atomic.Uint64 + + _ = io.ErrUnexpectedEOF +) + +const ( + canoto__executionResults__byGas__tag = "\x0a" // canoto.Tag(1, canoto.Len) + canoto__executionResults__receiptRoot__tag = "\x12" // canoto.Tag(2, canoto.Len) + canoto__executionResults__stateRootPost__tag = "\x1a" // canoto.Tag(3, canoto.Len) +) + +type canotoData_executionResults struct { + size uint64 +} + +// CanotoSpec returns the specification of this canoto message. +func (*executionResults) CanotoSpec(types ...reflect.Type) *canoto.Spec { + types = append(types, reflect.TypeOf(executionResults{})) + var zero executionResults + s := &canoto.Spec{ + Name: "executionResults", + Fields: []canoto.FieldType{ + canoto.FieldTypeFromField( + /*type inference:*/ (&zero.byGas), + /*FieldNumber: */ 1, + /*Name: */ "byGas", + /*FixedLength: */ 0, + /*Repeated: */ false, + /*OneOf: */ "", + /*types: */ types, + ), + { + FieldNumber: 2, + Name: "receiptRoot", + OneOf: "", + TypeFixedBytes: uint64(len(zero.receiptRoot)), + }, + { + FieldNumber: 3, + Name: "stateRootPost", + OneOf: "", + TypeFixedBytes: uint64(len(zero.stateRootPost)), + }, + }, + } + s.CalculateCanotoCache() + return s +} + +// MakeCanoto creates a new empty value. +func (*executionResults) MakeCanoto() *executionResults { + return new(executionResults) +} + +// UnmarshalCanoto unmarshals a Canoto-encoded byte slice into the struct. +// +// During parsing, the canoto cache is saved. +func (c *executionResults) UnmarshalCanoto(bytes []byte) error { + r := canoto.Reader{ + B: bytes, + } + return c.UnmarshalCanotoFrom(r) +} + +// UnmarshalCanotoFrom populates the struct from a [canoto.Reader]. Most users +// should just use UnmarshalCanoto. +// +// During parsing, the canoto cache is saved. +// +// This function enables configuration of reader options. +func (c *executionResults) UnmarshalCanotoFrom(r canoto.Reader) error { + // Zero the struct before unmarshaling. + *c = executionResults{} + atomic.StoreUint64(&c.canotoData.size, uint64(len(r.B))) + + var minField uint32 + for canoto.HasNext(&r) { + field, wireType, err := canoto.ReadTag(&r) + if err != nil { + return err + } + if field < minField { + return canoto.ErrInvalidFieldOrder + } + + switch field { + case 1: + if wireType != canoto.Len { + return canoto.ErrUnexpectedWireType + } + + // Read the bytes for the field. + originalUnsafe := r.Unsafe + r.Unsafe = true + var msgBytes []byte + if err := canoto.ReadBytes(&r, &msgBytes); err != nil { + return err + } + if len(msgBytes) == 0 { + return canoto.ErrZeroValue + } + r.Unsafe = originalUnsafe + + // Unmarshal the field from the bytes. + remainingBytes := r.B + r.B = msgBytes + if err := (&c.byGas).UnmarshalCanotoFrom(r); err != nil { + return err + } + r.B = remainingBytes + case 2: + if wireType != canoto.Len { + return canoto.ErrUnexpectedWireType + } + + const ( + expectedLength = len(c.receiptRoot) + expectedLengthUint64 = uint64(expectedLength) + ) + var length uint64 + if err := canoto.ReadUint(&r, &length); err != nil { + return err + } + if length != expectedLengthUint64 { + return canoto.ErrInvalidLength + } + if expectedLength > len(r.B) { + return io.ErrUnexpectedEOF + } + + copy((&c.receiptRoot)[:], r.B) + if canoto.IsZero(c.receiptRoot) { + return canoto.ErrZeroValue + } + r.B = r.B[expectedLength:] + case 3: + if wireType != canoto.Len { + return canoto.ErrUnexpectedWireType + } + + const ( + expectedLength = len(c.stateRootPost) + expectedLengthUint64 = uint64(expectedLength) + ) + var length uint64 + if err := canoto.ReadUint(&r, &length); err != nil { + return err + } + if length != expectedLengthUint64 { + return canoto.ErrInvalidLength + } + if expectedLength > len(r.B) { + return io.ErrUnexpectedEOF + } + + copy((&c.stateRootPost)[:], r.B) + if canoto.IsZero(c.stateRootPost) { + return canoto.ErrZeroValue + } + r.B = r.B[expectedLength:] + default: + return canoto.ErrUnknownField + } + + minField = field + 1 + } + return nil +} + +// ValidCanoto validates that the struct can be correctly marshaled into the +// Canoto format. +// +// Specifically, ValidCanoto ensures: +// 1. All OneOfs are specified at most once. +// 2. All strings are valid utf-8. +// 3. All custom fields are ValidCanoto. +func (c *executionResults) ValidCanoto() bool { + if c == nil { + return true + } + if !(&c.byGas).ValidCanoto() { + return false + } + return true +} + +// CalculateCanotoCache populates size and OneOf caches based on the current +// values in the struct. +// +// It is not safe to copy this struct concurrently. +func (c *executionResults) CalculateCanotoCache() { + if c == nil { + return + } + var size uint64 + (&c.byGas).CalculateCanotoCache() + if fieldSize := (&c.byGas).CachedCanotoSize(); fieldSize != 0 { + size += uint64(len(canoto__executionResults__byGas__tag)) + canoto.SizeUint(fieldSize) + fieldSize + } + if !canoto.IsZero(c.receiptRoot) { + size += uint64(len(canoto__executionResults__receiptRoot__tag)) + canoto.SizeBytes((&c.receiptRoot)[:]) + } + if !canoto.IsZero(c.stateRootPost) { + size += uint64(len(canoto__executionResults__stateRootPost__tag)) + canoto.SizeBytes((&c.stateRootPost)[:]) + } + atomic.StoreUint64(&c.canotoData.size, size) +} + +// CachedCanotoSize returns the previously calculated size of the Canoto +// representation from CalculateCanotoCache. +// +// If CalculateCanotoCache has not yet been called, it will return 0. +// +// If the struct has been modified since the last call to CalculateCanotoCache, +// the returned size may be incorrect. +func (c *executionResults) CachedCanotoSize() uint64 { + if c == nil { + return 0 + } + return atomic.LoadUint64(&c.canotoData.size) +} + +// MarshalCanoto returns the Canoto representation of this struct. +// +// It is assumed that this struct is ValidCanoto. +// +// It is not safe to copy this struct concurrently. +func (c *executionResults) MarshalCanoto() []byte { + c.CalculateCanotoCache() + w := canoto.Writer{ + B: make([]byte, 0, c.CachedCanotoSize()), + } + w = c.MarshalCanotoInto(w) + return w.B +} + +// MarshalCanotoInto writes the struct into a [canoto.Writer] and returns the +// resulting [canoto.Writer]. Most users should just use MarshalCanoto. +// +// It is assumed that CalculateCanotoCache has been called since the last +// modification to this struct. +// +// It is assumed that this struct is ValidCanoto. +// +// It is not safe to copy this struct concurrently. +func (c *executionResults) MarshalCanotoInto(w canoto.Writer) canoto.Writer { + if c == nil { + return w + } + if fieldSize := (&c.byGas).CachedCanotoSize(); fieldSize != 0 { + canoto.Append(&w, canoto__executionResults__byGas__tag) + canoto.AppendUint(&w, fieldSize) + w = (&c.byGas).MarshalCanotoInto(w) + } + if !canoto.IsZero(c.receiptRoot) { + canoto.Append(&w, canoto__executionResults__receiptRoot__tag) + canoto.AppendBytes(&w, (&c.receiptRoot)[:]) + } + if !canoto.IsZero(c.stateRootPost) { + canoto.Append(&w, canoto__executionResults__stateRootPost__tag) + canoto.AppendBytes(&w, (&c.stateRootPost)[:]) + } + return w +} diff --git a/blocks/execution.go b/blocks/execution.go new file mode 100644 index 0000000..df1f7fb --- /dev/null +++ b/blocks/execution.go @@ -0,0 +1,158 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blocks + +import ( + "context" + "errors" + "fmt" + "slices" + "time" + + "github.com/ava-labs/avalanchego/vms/components/gas" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/rawdb" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/trie" + + "github.com/ava-labs/strevm/gastime" + "github.com/ava-labs/strevm/proxytime" +) + +//go:generate go run github.com/StephenButtolph/canoto/canoto $GOFILE + +// SetInterimExecutionTime is expected to be called during execution of b's +// transactions, with the highest-known gas time. This MAY be at any resolution +// but MUST be monotonic. +func (b *Block) SetInterimExecutionTime(t *proxytime.Time[gas.Gas]) { + sec := t.Unix() + if t.Fraction().Numerator == 0 { + sec-- + } + b.executionExceededSecond.Store(&sec) +} + +type executionResults struct { + byGas gastime.Time `canoto:"value,1"` + byWall time.Time // For metrics only; allowed to be incorrect. + + // Receipts are deliberately not stored by the canoto representation as they + // are already in the database. All methods that read the stored canoto + // either accept a [types.Receipts] for comparison against the + // `receiptRoot`, or don't care about receipts at all. + receipts types.Receipts + receiptRoot common.Hash `canoto:"fixed bytes,2"` + stateRootPost common.Hash `canoto:"fixed bytes,3"` + + canotoData canotoData_executionResults +} + +// MarkExecuted marks the block as having been executed at the specified time(s) +// and with the specified results. It also sets the chain's head block to b. +// +// MarkExecuted guarantees that state is persisted to the database before +// in-memory indicators of execution are updated. [Block.Executed] returning +// true and [Block.WaitUntilExecuted] returning cleanly are both therefore +// indicative of a successful database write by MarkExecuted. +// +// This method MUST NOT be called more than once and its usage is mutually +// exclusive of [Block.RestorePostExecutionState]. The wall-clock [time.Time] is +// for metrics only. +func (b *Block) MarkExecuted(db ethdb.Database, byGas *gastime.Time, byWall time.Time, receipts types.Receipts, stateRootPost common.Hash) error { + e := &executionResults{ + byGas: *byGas.Clone(), + byWall: byWall, + receipts: slices.Clone(receipts), + receiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)), + stateRootPost: stateRootPost, + } + + batch := db.NewBatch() + hash := b.Hash() + rawdb.WriteHeadBlockHash(batch, hash) + rawdb.WriteHeadHeaderHash(batch, hash) + rawdb.WriteReceipts(batch, hash, b.NumberU64(), receipts) + if err := b.writePostExecutionState(batch, e); err != nil { + return err + } + if err := batch.Write(); err != nil { + return err + } + + return b.markExecuted(e) +} + +var errMarkBlockExecutedAgain = errors.New("block re-marked as executed") + +func (b *Block) markExecuted(e *executionResults) error { + if !b.execution.CompareAndSwap(nil, e) { + // This is fatal because we corrupted the database's head block if we + // got here by [Block.MarkExecuted] being called twice (an invalid use + // of the API). + b.log.Fatal("Block re-marked as executed") + return fmt.Errorf("%w: height %d", errMarkBlockExecutedAgain, b.Height()) + } + close(b.executed) + return nil +} + +// WaitUntilExecuted blocks until [Block.MarkExecuted] is called or the +// [context.Context] is cancelled. +func (b *Block) WaitUntilExecuted(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + case <-b.executed: + return nil + } +} + +// Executed reports whether [Block.MarkExecuted] has been called without +// resulting in an error. +func (b *Block) Executed() bool { + return b.execution.Load() != nil +} + +func executionArtefact[T any](b *Block, desc string, get func(*executionResults) T) T { + e := b.execution.Load() + if e == nil { + b.log.Error(fmt.Sprintf("Get block %s before execution", desc)) + var zero T + return zero + } + return get(e) +} + +// ExecutedByGasTime returns a clone of the gas time passed to +// [Block.MarkExecuted] or nil if no such successful call has been made. +func (b *Block) ExecutedByGasTime() *gastime.Time { + return executionArtefact(b, "execution (gas) time", func(e *executionResults) *gastime.Time { + return e.byGas.Clone() + }) +} + +// ExecutedByWallTime returns the wall time passed to [Block.MarkExecuted] or +// the zero time if no such successful call has been made. +func (b *Block) ExecutedByWallTime() time.Time { + return executionArtefact(b, "execution (wall) time", func(e *executionResults) time.Time { + return e.byWall + }) +} + +// Receipts returns the receipts passed to [Block.MarkExecuted] or nil if no +// such successful call has been made. +func (b *Block) Receipts() types.Receipts { + return executionArtefact(b, "receipts", func(e *executionResults) types.Receipts { + return slices.Clone(e.receipts) + }) +} + +// PostExecutionStateRoot returns the state root passed to [Block.MarkExecuted] +// or the zero hash if no such successful call has been made. +func (b *Block) PostExecutionStateRoot() common.Hash { + return executionArtefact(b, "state root", func(e *executionResults) common.Hash { + return e.stateRootPost + }) +} diff --git a/blocks/execution_test.go b/blocks/execution_test.go new file mode 100644 index 0000000..0173ddc --- /dev/null +++ b/blocks/execution_test.go @@ -0,0 +1,163 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blocks + +import ( + "context" + "fmt" + "math/big" + "testing" + "time" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/rawdb" + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/ethdb" + "github.com/ava-labs/libevm/params" + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/strevm/gastime" + "github.com/ava-labs/strevm/saetest" +) + +// markExecutedForTests calls [Block.MarkExecuted] with zero-value +// post-execution artefacts (other than the gas time). +func (b *Block) markExecutedForTests(tb testing.TB, db ethdb.Database, tm *gastime.Time) { + tb.Helper() + require.NoError(tb, b.MarkExecuted(db, tm, time.Time{}, nil, common.Hash{}), "MarkExecuted()") +} + +func TestMarkExecuted(t *testing.T) { + txs := make(types.Transactions, 10) + for i := range txs { + txs[i] = types.NewTx(&types.LegacyTx{ + Nonce: uint64(i), //nolint:gosec // Won't overflow + }) + } + + ethB := types.NewBlock( + &types.Header{ + Number: big.NewInt(1), + Time: 42, + }, + txs, + nil, nil, // uncles, receipts + saetest.TrieHasher(), + ) + db := rawdb.NewMemoryDatabase() + rawdb.WriteBlock(db, ethB) + + settles := newBlock(t, newEthBlock(0, 0, nil), nil, nil) + settles.markExecutedForTests(t, db, gastime.New(0, 1, 0)) + b := newBlock(t, ethB, nil, settles) + + t.Run("before_MarkExecuted", func(t *testing.T) { + require.False(t, b.Executed(), "Executed()") + require.NoError(t, b.CheckInvariants(NotExecuted), "CheckInvariants(NotExecuted)") + + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + require.ErrorIs(t, context.DeadlineExceeded, b.WaitUntilExecuted(ctx), "WaitUntilExecuted()") + + var rec saetest.LogRecorder + b.log = &rec + assertNumErrorLogs := func(t *testing.T, want int) { + t.Helper() + assert.Len(t, rec.At(logging.Error), want, "Number of ERROR logs") + } + + tests := []struct { + method string + call func() any + }{ + {"ExecutedByGasTime()", func() any { return b.ExecutedByGasTime() }}, + {"Receipts()", func() any { return b.Receipts() }}, + {"PostExecutionStateRoot()", func() any { return b.PostExecutionStateRoot() }}, + } + for i, tt := range tests { + assert.Zero(t, tt.call(), tt.method) + assertNumErrorLogs(t, i+1) + } + }) + + gasTime := gastime.New(42, 1e6, 42) + wallTime := time.Unix(42, 100) + stateRoot := common.Hash{'s', 't', 'a', 't', 'e'} + var receipts types.Receipts + for _, tx := range txs { + receipts = append(receipts, &types.Receipt{ + TxHash: tx.Hash(), + }) + } + require.NoError(t, b.MarkExecuted(db, gasTime, wallTime, receipts, stateRoot), "MarkExecuted()") + + assertPostExecutionVals := func(t *testing.T, b *Block) { + t.Helper() + require.True(t, b.Executed(), "Executed()") + assert.NoError(t, b.CheckInvariants(Executed), "CheckInvariants(Executed)") + + require.NoError(t, b.WaitUntilExecuted(context.Background()), "WaitUntilExecuted()") + + assert.Zero(t, b.ExecutedByGasTime().Compare(gasTime.Time), "ExecutedByGasTime().Compare([original input])") + assert.Empty(t, cmp.Diff(receipts, b.Receipts(), saetest.CmpByMerkleRoots[types.Receipts]()), "Receipts()") + + assert.Equal(t, stateRoot, b.PostExecutionStateRoot(), "PostExecutionStateRoot()") // i.e. this block + // Although not directly relevant to MarkExecuted, demonstrate that the + // two notion's of a state root are in fact different. + assert.Equal(t, settles.Block.Root(), b.SettledStateRoot(), "SettledStateRoot()") // i.e. the block this block settles + assert.NotEqual(t, b.SettledStateRoot(), b.PostExecutionStateRoot(), "PostExecutionStateRoot() != SettledStateRoot()") + + t.Run("MarkExecuted_again", func(t *testing.T) { + var rec saetest.LogRecorder + b.log = &rec + assert.ErrorIs(t, b.MarkExecuted(db, gasTime, wallTime, receipts, stateRoot), errMarkBlockExecutedAgain) + // The database's head block might have been corrupted so this MUST + // be a fatal action. + assert.Len(t, rec.At(logging.Fatal), 1, "FATAL logs") + }) + } + t.Run("after_MarkExecuted", func(t *testing.T) { + assertPostExecutionVals(t, b) + }) + + t.Run("database", func(t *testing.T) { + t.Run("RestorePostExecutionStateAndReceipts", func(t *testing.T) { + clone := newBlock(t, b.Block, nil, settles) + err := clone.RestorePostExecutionStateAndReceipts( + db, + params.TestChainConfig, // arbitrary + ) + require.NoError(t, err) + assertPostExecutionVals(t, clone) + }) + + t.Run("StateRootPostExecution", func(t *testing.T) { + got, err := StateRootPostExecution(db, b.NumberU64()) + require.NoError(t, err) + assert.Equal(t, stateRoot, got) + }) + + t.Run("head_block", func(t *testing.T) { + for fn, got := range map[string]interface{ Hash() common.Hash }{ + "ReadHeadBlockHash": selfAsHasher(rawdb.ReadHeadBlockHash(db)), + "ReadHeadHeaderHash": selfAsHasher(rawdb.ReadHeadHeaderHash(db)), + "ReadHeadBlock": rawdb.ReadHeadBlock(db), + "ReadHeadHeader": rawdb.ReadHeadHeader(db), + } { + t.Run(fmt.Sprintf("rawdb.%s", fn), func(t *testing.T) { + require.NotNil(t, got) + assert.Equalf(t, b.Hash(), got.Hash(), "rawdb.%s()", fn) + }) + } + }) + }) +} + +// selfAsHasher adds a Hash() method to a common.Hash, returning itself. +type selfAsHasher common.Hash + +func (h selfAsHasher) Hash() common.Hash { return common.Hash(h) } diff --git a/blocks/invariants.go b/blocks/invariants.go new file mode 100644 index 0000000..19532b5 --- /dev/null +++ b/blocks/invariants.go @@ -0,0 +1,72 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blocks + +import ( + "fmt" + + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/trie" +) + +type brokenInvariant struct { + b *Block + msg string +} + +func (err brokenInvariant) Error() string { + return fmt.Sprintf("block %d: %s", err.b.Height(), err.msg) +} + +func (b *Block) brokenInvariantErr(msg string) error { + return brokenInvariant{b: b, msg: msg} +} + +// A LifeCycleStage defines the progression of a block from acceptance through +// to settlement. +type LifeCycleStage int + +// Valid [LifeCycleStage] values. Blocks proceed in increasing stage numbers, +// but specific values MUST NOT be relied upon to be stable. +const ( + NotExecuted LifeCycleStage = iota + Executed + Settled + + Accepted = NotExecuted +) + +// CheckInvariants checks internal invariants against expected stage, typically +// only used during database recovery. +func (b *Block) CheckInvariants(expect LifeCycleStage) error { + switch e := b.execution.Load(); e { + case nil: // not executed + if expect >= Executed { + return b.brokenInvariantErr("expected to be executed") + } + default: // executed + if expect < Executed { + return b.brokenInvariantErr("unexpectedly executed") + } + if e.receiptRoot != types.DeriveSha(e.receipts, trie.NewStackTrie(nil)) { + return b.brokenInvariantErr("receipts don't match root") + } + } + + switch a := b.ancestry.Load(); a { + case nil: // settled + if expect < Settled { + return b.brokenInvariantErr("unexpectedly settled") + } + default: // not settled + if expect >= Settled { + return b.brokenInvariantErr("expected to be settled") + } + if b.SettledStateRoot() != b.LastSettled().PostExecutionStateRoot() { + return b.brokenInvariantErr("state root does not match last-settled post execution") + } + } + + return nil +} diff --git a/blocks/settlement.go b/blocks/settlement.go new file mode 100644 index 0000000..7208681 --- /dev/null +++ b/blocks/settlement.go @@ -0,0 +1,165 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blocks + +import ( + "context" + "errors" + "fmt" + "slices" +) + +type ancestry struct { + parent, lastSettled *Block +} + +var errBlockResettled = errors.New("block re-settled") + +// MarkSettled marks the block as having been settled. This function MUST NOT +// be called more than once. +// +// After a call to MarkSettled, future calls to [Block.ParentBlock] and +// [Block.LastSettled] will return nil. +func (b *Block) MarkSettled() error { + a := b.ancestry.Load() + if a == nil { + b.log.Error(errBlockResettled.Error()) + return fmt.Errorf("%w: block height %d", errBlockResettled, b.Height()) + } + if !b.ancestry.CompareAndSwap(a, nil) { // almost certainly means concurrent calls to this method + b.log.Fatal("Block ancestry changed during settlement") + // We have to return something to keen the compiler happy, even though we + // expect the Fatal to be, well, fatal. + return errors.New("block ancestry changed during settlement") + } + close(b.settled) + return nil +} + +// WaitUntilSettled blocks until either [Block.MarkSettled] is called or the +// [context.Context] is cancelled. +func (b *Block) WaitUntilSettled(ctx context.Context) error { + select { + case <-ctx.Done(): + return ctx.Err() + case <-b.settled: + return nil + } +} + +func (b *Block) ancestor(ifSettledErrMsg string, get func(*ancestry) *Block) *Block { + a := b.ancestry.Load() + if a == nil { + b.log.Error(ifSettledErrMsg) + return nil + } + return get(a) +} + +const ( + getParentOfSettledErrMsg = "Get parent of settled block" + getSettledOfSettledErrMsg = "Get last-settled of settled block" +) + +// ParentBlock returns the block's parent unless [Block.MarkSettled] has been +// called, in which case it returns nil. +func (b *Block) ParentBlock() *Block { + return b.ancestor(getParentOfSettledErrMsg, func(a *ancestry) *Block { + return a.parent + }) +} + +// LastSettled returns the last-settled block at the time of b's acceptance, +// unless [Block.MarkSettled] has been called, in which case it returns nil. +// Note that this value might not be distinct between contiguous blocks. +func (b *Block) LastSettled() *Block { + return b.ancestor(getSettledOfSettledErrMsg, func(a *ancestry) *Block { + return a.lastSettled + }) +} + +// Settles returns the executed blocks that b settles if it is accepted by +// consensus. If `x` is the block height of the `b.ParentBlock().LastSettled()` +// and `y` is the height of the `b.LastSettled()`, then Settles returns the +// contiguous, half-open range (x,y] or an empty slice i.f.f. x==y. Every block +// therefore returns a disjoint (and possibly empty) set of historical blocks. +// +// It is not valid to call Settles after a call to [Block.MarkSettled] on either +// b or its parent. +func (b *Block) Settles() []*Block { + return settling(b.ParentBlock().LastSettled(), b.LastSettled()) +} + +// IfChildSettles is similar to [Block.Settles] but with different definitions +// of `x` and `y` (as described in [Block.Settles]). It is intended for use +// during block building and defines `x` as the block height of +// `b.LastSettled()` while `y` as the height of the argument passed to this +// method. +// +// The argument is typically the return value of [LastToSettleAt], where that +// function receives b as the parent. See the Example. +func (b *Block) IfChildSettles(lastSettledOfChild *Block) []*Block { + return settling(b.LastSettled(), lastSettledOfChild) +} + +// settling returns all the blocks after `lastOfParent` up to and including +// `lastOfCurr`, each of which are expected to be the block last-settled by a +// respective block-and-parent pair. It returns an empty slice if the two +// arguments have the same block hash. +func settling(lastOfParent, lastOfCurr *Block) []*Block { + var settling []*Block + // TODO(arr4n) abstract this to combine functionality with iterators + // introduced by @StephenButtolph. + for s := lastOfCurr; s.ParentBlock() != nil && s.Hash() != lastOfParent.Hash(); s = s.ParentBlock() { + settling = append(settling, s) + } + slices.Reverse(settling) + return settling +} + +// LastToSettleAt returns (a) the last block to be settled at time `settleAt` if +// building on the specified parent block, and (b) a boolean to indicate if +// settlement is currently possible. If the returned boolean is false, the +// execution stream is lagging and LastToSettleAt can be called again after some +// indeterminate delay. +// +// See the Example for [Block.IfChildSettles] for one usage of the returned +// block. +func LastToSettleAt(settleAt uint64, parent *Block) (*Block, bool) { + // These variables are only abstracted for clarity; they are not needed + // beyond the scope of the `for` loop. + var block, child *Block + block = parent // therefore `child` remains nil because it's what we're building + + // The only way [Block.ParentBlock] can be nil is if `block` was already + // settled (see invariant in [Block]). If a block was already settled then + // only that or a later (i.e. unsettled) block can be returned by this loop, + // therefore we have a guarantee that the loop update will never result in + // `block==nil`. + for ; ; block, child = block.ParentBlock(), block { + if startsNoEarlierThan := block.Time(); startsNoEarlierThan > settleAt { + continue + } + + if t := block.executionExceededSecond.Load(); t != nil && *t >= settleAt { + continue + } + if e := block.execution.Load(); e != nil { + if e.byGas.CompareUnix(settleAt) > 0 { + // There may have been a race between this check and the + // execution-exceeded one above, so we have to check again. + continue + } + return block, true + } + + // TODO(arr4n) more fine-grained checks are possible for scenarios where + // (a) `block` could never execute before `settleAt` so we would + // `continue`; and (b) `block` will definitely execute in time and + // `child` could never, in which case return `nil, false`. + _ = child + + return nil, false + } +} diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go new file mode 100644 index 0000000..9d43559 --- /dev/null +++ b/blocks/settlement_test.go @@ -0,0 +1,383 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blocks + +import ( + "context" + "fmt" + "math/rand/v2" + "testing" + "time" + + "github.com/ava-labs/avalanchego/utils/logging" + "github.com/ava-labs/avalanchego/vms/components/gas" + "github.com/ava-labs/libevm/core/rawdb" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/ava-labs/strevm/gastime" + "github.com/ava-labs/strevm/proxytime" + "github.com/ava-labs/strevm/saetest" +) + +//nolint:testableexamples // Output is meaningless +func ExampleBlock_IfChildSettles() { + parent := blockBuildingPreference() + settle, ok := LastToSettleAt(uint64(time.Now().Unix()), parent) //nolint:gosec // Time won't overflow for quite a while + if !ok { + return // execution is lagging; please come back soon + } + + // Returns the (possibly empty) slice of blocks that would be settled by the + // block being built. + _ = parent.IfChildSettles(settle) +} + +// blockBuildingPreference exists only to allow examples to build. +func blockBuildingPreference() *Block { return nil } + +func TestSettlementInvariants(t *testing.T) { + parent := newBlock(t, newEthBlock(5, 5, nil), nil, nil) + lastSettled := newBlock(t, newEthBlock(3, 3, nil), nil, nil) + + b := newBlock(t, newEthBlock(6, 10, parent.Block), parent, lastSettled) + + db := rawdb.NewMemoryDatabase() + for _, b := range []*Block{b, parent, lastSettled} { + b.markExecutedForTests(t, db, gastime.New(b.Time(), 1, 0)) + } + + t.Run("before_MarkSettled", func(t *testing.T) { + ctx, cancel := context.WithTimeout(context.Background(), 100*time.Millisecond) + defer cancel() + assert.ErrorIs(t, b.WaitUntilSettled(ctx), context.DeadlineExceeded, "WaitUntilSettled()") + + assert.True(t, b.ParentBlock().equalForTests(parent), "ParentBlock().equalForTests([constructor arg])") + assert.True(t, b.LastSettled().equalForTests(lastSettled), "LastSettled().equalForTests([constructor arg])") + assert.NoError(t, b.CheckInvariants(Executed), "CheckInvariants(Executed)") + }) + if t.Failed() { + t.FailNow() + } + + require.NoError(t, b.MarkSettled(), "first call to MarkSettled()") + + t.Run("after_MarkSettled", func(t *testing.T) { + assert.NoError(t, b.WaitUntilSettled(context.Background()), "WaitUntilSettled()") + assert.NoError(t, b.CheckInvariants(Settled), "CheckInvariants(Settled)") + + var rec saetest.LogRecorder + b.log = &rec + assertNumErrorLogs := func(t *testing.T, want int) { + t.Helper() + assert.Len(t, rec.At(logging.Error), want, "Number of ERROR") + } + + assert.Nil(t, b.ParentBlock(), "ParentBlock()") + assertNumErrorLogs(t, 1) + assert.Nil(t, b.LastSettled(), "LastSettled()") + assertNumErrorLogs(t, 2) + assert.ErrorIs(t, b.MarkSettled(), errBlockResettled, "second call to MarkSettled()") + assertNumErrorLogs(t, 3) + if t.Failed() { + t.FailNow() + } + + want := []*saetest.LogRecord{ + { + Level: logging.Error, + Msg: getParentOfSettledErrMsg, + }, + { + Level: logging.Error, + Msg: getSettledOfSettledErrMsg, + }, + { + Level: logging.Error, + Msg: errBlockResettled.Error(), + }, + } + if diff := cmp.Diff(want, rec.AtLeast(logging.Error)); diff != "" { + t.Errorf("ERROR + FATAL logs diff (-want +got):\n%s", diff) + } + }) +} + +func TestPersistLastSettledNumber(t *testing.T) { + rng := rand.New(rand.NewPCG(0, 0)) + for range 10 { + settledHeight := rng.Uint64() + t.Run(fmt.Sprintf("settled_height_%d", settledHeight), func(t *testing.T) { + settles := newBlock(t, newEthBlock(settledHeight, 0, nil), nil, nil) + b := newBlock(t, newEthBlock(settledHeight+1 /*arbitrary*/, 0, nil), nil, settles) + + db := rawdb.NewMemoryDatabase() + require.NoError(t, b.WriteLastSettledNumber(db), "WriteLastSettledNumber()") + + t.Run("ReadLastSettledNumber", func(t *testing.T) { + got, err := ReadLastSettledNumber(db, b.NumberU64()) + require.NoError(t, err) + require.Equal(t, settles.NumberU64(), got) + }) + }) + } +} + +func TestSettles(t *testing.T) { + lastSettledAtHeight := map[uint64]uint64{ + 0: 0, // genesis block is self-settling by definition + 1: 0, + 2: 0, + 3: 0, + 4: 1, + 5: 1, + 6: 3, + 7: 3, + 8: 3, + 9: 7, + } + wantSettles := map[uint64][]uint64{ + // It is not valid to call Settles() on the genesis block + 1: nil, + 2: nil, + 3: nil, + 4: {1}, + 5: nil, + 6: {2, 3}, + 7: nil, + 8: nil, + 9: {4, 5, 6, 7}, + } + blocks := newChain(t, 0, 10, lastSettledAtHeight) + + numsToBlocks := func(nums ...uint64) []*Block { + bs := make([]*Block, len(nums)) + for i, n := range nums { + bs[i] = blocks[n] + } + return bs + } + + type testCase struct { + name string + got, want []*Block + } + var tests []testCase + + for num, wantNums := range wantSettles { + tests = append(tests, testCase{ + name: fmt.Sprintf("Block(%d).Settles()", num), + got: blocks[num].Settles(), + want: numsToBlocks(wantNums...), + }) + } + + for _, b := range blocks[1:] { + tests = append(tests, testCase{ + name: fmt.Sprintf("Block(%d).IfChildSettles([same as parent])", b.Height()), + got: b.IfChildSettles(b.LastSettled()), + want: nil, + }) + } + + tests = append(tests, []testCase{ + { + got: blocks[7].IfChildSettles(blocks[3]), + want: nil, + }, + { + got: blocks[7].IfChildSettles(blocks[4]), + want: numsToBlocks(4), + }, + { + got: blocks[7].IfChildSettles(blocks[5]), + want: numsToBlocks(4, 5), + }, + { + got: blocks[7].IfChildSettles(blocks[6]), + want: numsToBlocks(4, 5, 6), + }, + }...) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if diff := cmp.Diff(tt.want, tt.got, cmpopts.EquateEmpty(), CmpOpt()); diff != "" { + t.Errorf("Settles() diff (-want +got):\n%s", diff) + } + }) + } +} + +func TestLastToSettleAt(t *testing.T) { + blocks := newChain(t, 0, 30, nil) + t.Run("helper_invariants", func(t *testing.T) { + for i, b := range blocks { + require.Equal(t, uint64(i), b.Height()) //nolint:gosec // Slice index won't overflow + require.Equal(t, b.Time(), b.Height()) + } + }) + + db := rawdb.NewMemoryDatabase() + tm := gastime.New(0, 5 /*target*/, 0) + require.Equal(t, gas.Gas(10), tm.Rate()) + + requireTime := func(t *testing.T, sec uint64, numerator gas.Gas) { + t.Helper() + assert.Equalf(t, sec, tm.Unix(), "%T.Unix()", tm) + wantFrac := proxytime.FractionalSecond[gas.Gas]{ + Numerator: numerator, + Denominator: tm.Rate(), + } + assert.Equalf(t, wantFrac, tm.Fraction(), "%T.Fraction()", tm) + if t.Failed() { + t.FailNow() + } + } + + requireTime(t, 0, 0) + blocks[0].markExecutedForTests(t, db, tm) + + tm.Tick(13) + requireTime(t, 1, 3) + blocks[1].markExecutedForTests(t, db, tm) + + tm.Tick(20) + requireTime(t, 3, 3) + blocks[2].markExecutedForTests(t, db, tm) + + tm.Tick(5) + requireTime(t, 3, 8) + blocks[3].markExecutedForTests(t, db, tm) + + tm.Tick(23) + requireTime(t, 6, 1) + blocks[4].markExecutedForTests(t, db, tm) + + tm.Tick(9) + requireTime(t, 7, 0) + blocks[5].markExecutedForTests(t, db, tm) + + tm.Tick(10) + requireTime(t, 8, 0) + blocks[6].markExecutedForTests(t, db, tm) + + tm.Tick(1) + requireTime(t, 8, 1) + blocks[7].markExecutedForTests(t, db, tm) + + tm.Tick(50) + requireTime(t, 13, 1) + blocks[8].markExecutedForTests(t, db, tm) + + for i, b := range blocks { + // Setting interim execution time isn't required for the algorithm to + // work as it just allows [LastToSettleAt] to return definitive results + // earlier in execution. It does, however, risk an edge-case error for + // blocks that complete execution on an exact second boundary so needs + // to be tested; see the [Block.SetInterimExecutionTime] implementation + // for details. + if i%2 == 0 || !b.Executed() { + continue + } + b.SetInterimExecutionTime(b.ExecutedByGasTime().Time) + } + + type testCase struct { + name string + settleAt uint64 + parent *Block + wantOK bool + want *Block + } + + tests := []testCase{ + { + settleAt: 3, + parent: blocks[5], + wantOK: true, + want: blocks[1], + }, + { + settleAt: 4, + parent: blocks[9], + wantOK: true, + want: blocks[3], + }, + { + settleAt: 4, + parent: blocks[8], + wantOK: true, + want: blocks[3], + }, + { + settleAt: 7, + parent: blocks[10], + wantOK: true, + want: blocks[5], + }, + { + settleAt: 9, + parent: blocks[8], + wantOK: true, + want: blocks[7], + }, + { + settleAt: 9, + parent: blocks[9], + // The current implementation is very coarse-grained and MAY return + // false negatives that would simply require a retry after some + // indeterminate period of time. Even though the execution time of + // `blocks[8]` guarantees that `blocks[9]` MUST finish execution + // after the settlement time, our current implementation doesn't + // check this. It is expected that this specific test case will one + // day fail, at which point it MUST be updated to want `blocks[7]`. + wantOK: false, + }, + { + settleAt: 15, + parent: blocks[18], + wantOK: false, + }, + } + + { + // Scenario: + // * Mark block 24 as executed at time 25.1 + // * Mark block 25 as partially executed by time 27.1 + // * Settle at time 26 (between them) with 25 as parent + // + // If block 25 wasn't marked as partially executed then it could + // feasibly execute by settlement time (26) so [LastToSettleAt] would + // return false. As the partial execution time makes it impossible for + // block 25 to execute in time, we loop to its parent, which is already + // executed in time and is therefore the expected return value. + tm.Tick(120) + require.Equal(t, uint64(25), tm.Unix()) + require.Equal(t, proxytime.FractionalSecond[gas.Gas]{Numerator: 1, Denominator: 10}, tm.Fraction()) + blocks[24].markExecutedForTests(t, db, tm) + + partiallyExecutedAt := proxytime.New[gas.Gas](27, 100) + partiallyExecutedAt.Tick(1) + blocks[25].SetInterimExecutionTime(partiallyExecutedAt) + + tests = append(tests, testCase{ + settleAt: 26, + parent: blocks[25], + wantOK: true, + want: blocks[24], + }) + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, gotOK := LastToSettleAt(tt.settleAt, tt.parent) + require.Equalf(t, tt.wantOK, gotOK, "LastToSettleAt(%d, [parent height %d])", tt.settleAt, tt.parent.Height()) + if tt.wantOK { + require.Equal(t, tt.want.Height(), got.Height(), "LastToSettleAt(%d, [parent height %d])", tt.settleAt, tt.parent.Height()) + } + }) + } +} diff --git a/blocks/snow.go b/blocks/snow.go new file mode 100644 index 0000000..00dec74 --- /dev/null +++ b/blocks/snow.go @@ -0,0 +1,53 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blocks + +import ( + "time" + + "github.com/ava-labs/avalanchego/ids" + "github.com/ava-labs/libevm/rlp" + "go.uber.org/zap" + + "github.com/ava-labs/strevm/adaptor" + + // Imported to allow IDE resolution of comments like [types.Block]. The + // package is imported in other files so this is a no-op beyond devex. + _ "github.com/ava-labs/libevm/core/types" +) + +var _ adaptor.BlockProperties = (*Block)(nil) + +// ID returns [types.Block.Hash] from the embedded [types.Block]. +func (b *Block) ID() ids.ID { + return ids.ID(b.Hash()) +} + +// Parent returns [types.Block.ParentHash] from the embedded [types.Block]. +func (b *Block) Parent() ids.ID { + return ids.ID(b.ParentHash()) +} + +// Bytes returns the RLP encoding of the embedded [types.Block]. If encoding +// returns an error, it is logged at the WARNING level and a nil slice is +// returned. +func (b *Block) Bytes() []byte { + buf, err := rlp.EncodeToBytes(b) + if err != nil { + b.log.Warn("RLP encoding error", zap.Error(err)) + return nil + } + return buf +} + +// Height returns [types.Block.NumberU64] from the embedded [types.Block]. +func (b *Block) Height() uint64 { + return b.NumberU64() +} + +// Timestamp returns the timestamp of the embedded [types.Block], at +// [time.Second] resolution. +func (b *Block) Timestamp() time.Time { + return time.Unix(int64(b.Time()), 0) //nolint:gosec // Won't be a problem for a few millennia +} diff --git a/cmputils/cmputils.go b/cmputils/cmputils.go index 6ecedc5..1b3f35a 100644 --- a/cmputils/cmputils.go +++ b/cmputils/cmputils.go @@ -28,3 +28,26 @@ func pathIncludes[T any](p cmp.Path) bool { } return false } + +// WithNilCheck returns a function that returns: +// +// true if both a and b are nil +// false if exactly one of a or b is nil +// fn(a,b) if neither a nor b are nil +func WithNilCheck[T any](fn func(*T, *T) bool) func(*T, *T) bool { + return func(a, b *T) bool { + switch an, bn := a == nil, b == nil; { + case an && bn: + return true + case an || bn: + return false + } + return fn(a, b) + } +} + +// ComparerWithNilCheck is a convenience wrapper, returning a [cmp.Comparer] +// after wrapping `fn` in [WithNilCheck]. +func ComparerWithNilCheck[T any](fn func(*T, *T) bool) cmp.Option { + return cmp.Comparer(WithNilCheck(fn)) +} diff --git a/go.mod b/go.mod index 64ce228..d5550ed 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,54 @@ module github.com/ava-labs/strevm -go 1.23.9 - -toolchain go1.23.10 +go 1.24.7 require ( github.com/ava-labs/avalanchego v1.13.2 + github.com/ava-labs/libevm v1.13.14-0.3.0.rc.1 github.com/google/go-cmp v0.6.0 github.com/holiman/uint256 v1.2.4 github.com/stretchr/testify v1.10.0 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b ) +require ( + github.com/bits-and-blooms/bitset v1.10.0 // indirect + github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect + github.com/cockroachdb/errors v1.9.1 // indirect + github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b // indirect + github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 // indirect + github.com/cockroachdb/redact v1.1.3 // indirect + github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 // indirect + github.com/consensys/bavard v0.1.13 // indirect + github.com/consensys/gnark-crypto v0.12.1 // indirect + github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect + github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect + github.com/ethereum/c-kzg-4844 v0.4.0 // indirect + github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect + github.com/getsentry/sentry-go v0.18.0 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/gofrs/flock v0.8.1 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/klauspost/compress v1.15.15 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/kr/text v0.2.0 // indirect + github.com/mattn/go-runewidth v0.0.13 // indirect + github.com/mmcloughlin/addchain v0.4.0 // indirect + github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a // indirect + github.com/tklauser/go-sysconf v0.3.12 // indirect + github.com/tklauser/numcpus v0.6.1 // indirect + github.com/yusufpapurcu/wmi v1.2.2 // indirect + golang.org/x/sync v0.15.0 // indirect + rsc.io/tmplfunc v0.0.3 // indirect +) + require ( github.com/BurntSushi/toml v1.5.0 // indirect github.com/DataDog/zstd v1.5.2 // indirect @@ -47,7 +84,7 @@ require ( go.opentelemetry.io/otel/trace v1.22.0 // indirect go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.26.0 // indirect + go.uber.org/zap v1.26.0 golang.org/x/crypto v0.36.0 // indirect golang.org/x/net v0.38.0 // indirect golang.org/x/sys v0.31.0 // indirect diff --git a/go.sum b/go.sum index 4eb8c3a..eccb9f3 100644 --- a/go.sum +++ b/go.sum @@ -1,78 +1,370 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9/go.mod h1:bOvUY6CB00SOBii9/FifXqc0awNKxLFCL/+pkDPuyl8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= +github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo= github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8= github.com/DataDog/zstd v1.5.2/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/Shopify/goreferrer v0.0.0-20181106222321-ec9c9a553398/go.mod h1:a1uqRtAwp2Xwc6WNPJEufxJ7fx3npB4UV/JOLmbu5I0= github.com/StephenButtolph/canoto v0.17.1 h1:WnN5czIHHALq7pwc+Z2F1sCsKJCDhxlq0zL0YK1etHc= github.com/StephenButtolph/canoto v0.17.1/go.mod h1:IcnAHC6nJUfQFVR9y60ko2ecUqqHHSB6UwI9NnBFZnE= +github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= +github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= +github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanchego v1.13.2 h1:Kx/T2a8vqLlgHde3DWT5zMF5yBIh1rqEd6nJQMMzV/Y= github.com/ava-labs/avalanchego v1.13.2/go.mod h1:s7W/kim5L6hiD2PB1v/Ozy1ZZyoLQ4H6mxVO0aMnxng= +github.com/ava-labs/libevm v1.13.14-0.3.0.rc.1 h1:vBMYo+Iazw0rGTr+cwjkBdh5eadLPlv4ywI4lKye3CA= +github.com/ava-labs/libevm v1.13.14-0.3.0.rc.1/go.mod h1:+Iol+sVQ1KyoBsHf3veyrBmHCXr3xXRWq6ZXkgVfNLU= +github.com/aymerick/raymond v2.0.3-0.20180322193309-b565731e1464+incompatible/go.mod h1:osfaiScAUVup+UC9Nfq76eWqDhXlp+4UYaA8uhTBO6g= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bits-and-blooms/bitset v1.10.0 h1:ePXTeiPEazB5+opbv5fr8umg2R/1NlzgDsyepwsSr88= +github.com/bits-and-blooms/bitset v1.10.0/go.mod h1:7hO7Gc7Pp1vODcmWvKMRA9BNmbv6a/7QIWpPxHddWR8= +github.com/btcsuite/btcd/btcec/v2 v2.3.2 h1:5n0X6hX0Zk+6omWcihdYvdAlGf2DfasC0GMf7DClJ3U= +github.com/btcsuite/btcd/btcec/v2 v2.3.2/go.mod h1:zYzJ8etWJQIv1Ogk7OzpWjowwOdXY1W/17j2MW85J04= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1 h1:q0rUy8C/TYNBQS1+CGKw68tLOFYSNEs0TFnxxnS9+4U= +github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtydOrQY2mwhPclbT2snogU7SQQc= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/datadriven v1.0.2/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f h1:otljaYPt5hWxV3MUfO5dFPFiOXg9CyG5/kCfayTqsJ4= +github.com/cockroachdb/datadriven v1.0.3-0.20230413201302-be42291fc80f/go.mod h1:a9RdTaap04u637JoCzcUoIcDmvwSUtcUFtT/C3kJlTU= +github.com/cockroachdb/errors v1.9.1 h1:yFVvsI0VxmRShfawbt/laCIDy/mtTqqnvoNgiy5bEV8= +github.com/cockroachdb/errors v1.9.1/go.mod h1:2sxOtL2WIc096WSZqZ5h8fa17rdDq9HZOZLBCor4mBk= +github.com/cockroachdb/logtags v0.0.0-20211118104740-dabe8e521a4f/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b h1:r6VH0faHjZeQy818SGhaone5OnYfxFR/+AzdY3sf5aE= +github.com/cockroachdb/logtags v0.0.0-20230118201751-21c54148d20b/go.mod h1:Vz9DsVWQQhf3vs21MhPMZpMGSht7O/2vFW2xusFUVOs= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593 h1:aPEJyR4rPBvDmeyi+l/FS/VtA00IWvjeFvjen1m1l1A= +github.com/cockroachdb/pebble v0.0.0-20230928194634-aa077af62593/go.mod h1:6hk1eMY/u5t+Cf18q5lFMUA1Rc+Sm5I6Ra1QuPyxXCo= +github.com/cockroachdb/redact v1.1.3 h1:AKZds10rFSIj7qADf0g46UixK8NNLwWTNdCIGS5wfSQ= +github.com/cockroachdb/redact v1.1.3/go.mod h1:BVNblN9mBWFyMyqK1k3AAiSxhvhfK2oOZZ2lK+dpvRg= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06 h1:zuQyyAKVxetITBuuhv3BI9cMrmStnpT18zmgmTxunpo= +github.com/cockroachdb/tokenbucket v0.0.0-20230807174530-cc333fc44b06/go.mod h1:7nc4anLGjupUW/PeY5qiNYsdNXj7zopG+eqsS7To5IQ= +github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0/go.mod h1:4Zcjuz89kmFXt9morQgcfYZAYZ5n8WHjt81YYWIwtTM= +github.com/consensys/bavard v0.1.13 h1:oLhMLOFGTLdlda/kma4VOJazblc7IM5y5QPd2A/YjhQ= +github.com/consensys/bavard v0.1.13/go.mod h1:9ItSMtA/dXMAiL7BG6bqW2m3NdSEObYWoH223nGHukI= +github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJF7HpyG8M= +github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY= +github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ= +github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs= +github.com/crate-crypto/go-kzg-4844 v0.7.0 h1:C0vgZRk4q4EZ/JgPfzuSoxdCq3C3mOZMBShovmncxvA= +github.com/crate-crypto/go-kzg-4844 v0.7.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deckarep/golang-set/v2 v2.1.0 h1:g47V4Or+DUdzbs8FxCCmgb6VYd+ptPAngjM6dtGktsI= +github.com/deckarep/golang-set/v2 v2.1.0/go.mod h1:VAky9rY/yGXJOLEDv3OMci+7wtDpOF4IN+y82NBOac4= +github.com/decred/dcrd/crypto/blake256 v1.0.0 h1:/8DMNYp9SGi5f0w7uCm6d6M4OU2rGFK09Y2A4Xv7EE0= +github.com/decred/dcrd/crypto/blake256 v1.0.0/go.mod h1:sQl2p6Y26YV+ZOcSTP6thNdn47hh8kt6rqSlvmrXFAc= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 h1:HbphB4TFFXpv7MNrT52FGrrgVXF1owhMVTHFZIlnvd4= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0/go.mod h1:DZGJHZMqrU4JJqFAWUS2UO1+lbSKsdiOoYi9Zzey7Fc= +github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/ethereum/c-kzg-4844 v0.4.0 h1:3MS1s4JtA868KpJxroZoepdV0ZKBp3u/O5HcZ7R3nlY= +github.com/ethereum/c-kzg-4844 v0.4.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0= +github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8= +github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= +github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= +github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= +github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0= +github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ= +github.com/gin-contrib/sse v0.0.0-20190301062529-5545eab6dad3/go.mod h1:VJ0WA2NBN22VlZ2dKZQPAPnyWw5XTlK1KymzLKsr59s= +github.com/gin-gonic/gin v1.4.0/go.mod h1:OW2EZn3DO8Ln9oIKOvM++LBO+5UPHJJDH72/q/3rZdM= +github.com/go-check/check v0.0.0-20180628173108-788fd7840127/go.mod h1:9ES+weclKsC9YodN5RgxqK/VD9HM9JsCSh7rNhMZE98= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ= github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-martini/martini v0.0.0-20170121215854-22fa46961aab/go.mod h1:/P9AEU963A2AYjv4d1V5eVL1CQbEJq6aCNHDDjibzu8= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= +github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= +github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= +github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gogo/googleapis v0.0.0-20180223154316-0cd9801be74a/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/googleapis v1.4.1/go.mod h1:2lpHqI5OcWCtVElxXnPt+s8oJvMpySlOyM6xDCrzib4= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/gogo/status v1.1.0/go.mod h1:BFv9nrluPLmrS0EmGVvLaPNmRosr9KapBYd5/hpY1WM= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/glog v1.2.1 h1:OptwRhECazUx5ix5TTWC3EZhsZEHWcYWY4FQHTIubm4= github.com/golang/glog v1.2.1/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb h1:PBC98N2aIaM3XXiurYmW7fx4GZkL8feAMVq7nEjURHk= +github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qAhxg= github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= +github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= +github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= +github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0 h1:YBftPWNWd4WwGqtY2yeZL2ef8rHAxPBD8KFhJpmcqms= github.com/grpc-ecosystem/grpc-gateway/v2 v2.16.0/go.mod h1:YN5jB8ie0yfIUg6VvR9Kz84aCaG7AsGZnLjhHbUqwPg= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao= +github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA= github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/iris-contrib/blackfriday v2.0.0+incompatible/go.mod h1:UzZ2bDEoaSGPbkg6SAB4att1aAwTmVIx/5gCVqeyUdI= +github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= +github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= +github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= +github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/k0kubun/colorstring v0.0.0-20150214042306-9440f1994b88/go.mod h1:3w7q1U84EfirKl04SVQ/s7nPm1ZPhiXd34z40TNz36k= +github.com/kataras/golog v0.0.10/go.mod h1:yJ8YKCmyL+nWjERB90Qwn+bdyBZsaQwU3bTVFgkFIp8= +github.com/kataras/iris/v12 v12.1.8/go.mod h1:LMYy4VlP67TQ3Zgriz8RE2h2kMZV2SgMYbq3UhfoFmE= +github.com/kataras/neffos v0.0.14/go.mod h1:8lqADm8PnbeFfL7CLXh1WHw53dG27MC3pgi2R1rmoTE= +github.com/kataras/pio v0.0.2/go.mod h1:hAoW0t9UmXi4R5Oyq5Z4irTbaTsOemSrDGUtaTl7Dro= +github.com/kataras/sitemap v0.0.5/go.mod h1:KY2eugMKiPwsJgx7+U103YZehfvNGOXURubcGyk0Bz8= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.15.15 h1:EF27CXIuDsYJ6mmvtBRlEuB2UVOqHG1tAXgZ7yIO+lw= +github.com/klauspost/compress v1.15.15/go.mod h1:ZcK2JAFqKOpnBlxcLsJzYfrS9X1akm9fHZNnD9+Vo/4= +github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y= +github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k= +github.com/leanovate/gopter v0.2.9 h1:fQjYxZaynp97ozCzfOyOuAGOU4aU/z37zf/tOujFk7c= +github.com/leanovate/gopter v0.2.9/go.mod h1:U2L/78B+KVFIx2VmW6onHJQzXtFb+p5y3y2Sh+Jxxv8= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= +github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/goveralls v0.0.2/go.mod h1:8d1ZMHsd7fW6IRPKQh46F2WRpyib5/X4FOpevwGNQEw= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= +github.com/mediocregopher/radix/v3 v3.4.2/go.mod h1:8FL3F6UQRXHXIBSPUs5h0RybMF8i4n7wVopoX3x7Bv8= +github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mmcloughlin/addchain v0.4.0 h1:SobOdjm2xLj1KkXN5/n0xTIWyZA2+s99UCY1iPfkHRY= +github.com/mmcloughlin/addchain v0.4.0/go.mod h1:A86O+tHqZLMNO4w6ZZ4FlVQEadcoqkyU72HC5wJ4RlU= +github.com/mmcloughlin/profile v0.1.1/go.mod h1:IhHD7q1ooxgwTgjxQYkACGA77oFTDdFVejUS1/tS/qU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o= github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= +github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0 h1:4ieX6qQjPP/BfC3mpsAtIGGlxTWPeA3Inl/7DtXw1tw= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= +github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.16.0 h1:yk/hx9hDbrGHovbci4BY+pRMfSuuat626eFsHb7tmT8= github.com/prometheus/client_golang v1.16.0/go.mod h1:Zsulrv/L9oM40tJ7T815tM89lFEugiJ9HzIqaAx4LKc= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4= github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w= github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM= github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc= github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg= github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sanity-io/litter v1.5.1 h1:dwnrSypP6q56o3lFxTU+t2fwQ9A+U5qrXVO4Qg9KwVU= github.com/sanity-io/litter v1.5.1/go.mod h1:5Z71SvaYy5kcGtyglXOC9rrUi3c1E8CamFWjQsazTh0= +github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= +github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= +github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/supranational/blst v0.3.14 h1:xNMoHRJOTwMn63ip6qoWJ2Ymgvj7E2b9jY2FAwY+qRo= github.com/supranational/blst v0.3.14/go.mod h1:jZJtfjgudtNl4en1tzwPIV3KjUnQUvG3/j+w+fVonLw= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a h1:1ur3QoCqvE5fl+nylMaIr9PVV1w343YRDtsy+Rwu7XI= +github.com/syndtr/goleveldb v1.0.1-0.20220614013038-64ee5596c38a/go.mod h1:RRCYJbIwD5jmqPI9XoAFR0OcDxqUctll6zUj/+B4S48= github.com/thepudds/fzgen v0.4.3 h1:srUP/34BulQaEwPP/uHZkdjUcUjIzL7Jkf4CBVryiP8= github.com/thepudds/fzgen v0.4.3/go.mod h1:BhhwtRhzgvLWAjjcHDJ9pEiLD2Z9hrVIFjBCHJ//zJ4= +github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU= +github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= +github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= +github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= +github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= +github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= +github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg= +github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yusufpapurcu/wmi v1.2.2 h1:KBNDSne4vP5mbSWnJbO+51IMOXJB67QiYCSBrubbPRg= +github.com/yusufpapurcu/wmi v1.2.2/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/otel v1.22.0 h1:xS7Ku+7yTFvDfDraDIJVpw7XPyuHlB9MCiqqX5mcJ6Y= go.opentelemetry.io/otel v1.22.0/go.mod h1:eoV4iAi3Ea8LkAEI9+GFT44O6T/D0GWAVFyZVCC6pMI= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 h1:9M3+rhx7kZCIQQhQRYaZCdNu1V73tm4TvXs2ntl98C4= @@ -97,37 +389,193 @@ go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= +golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.15.0 h1:KWH3jNZsfyT6xfAfKiz6MRNmd46ByHDYaZ7KSkCtdW8= golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190626221950-04f50cda93cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190327201419-c70d86f8b7cf/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= +golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= gonum.org/v1/gonum v0.11.0 h1:f1IJhK4Km5tBJmaiJXtk/PkL4cdVX6J+tGiM187uT5E= gonum.org/v1/gonum v0.11.0/go.mod h1:fSG4YDCxxUZQJ7rKsQrj0gMOg00Il0Z96/qMA4bVQhA= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180518175338-11a468237815/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 h1:+rdxYoE3E5htTEWIe15GlN6IfvbURM//Jt0mmkmm6ZU= google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117/go.mod h1:OimBR/bc1wPO9iV4NC2bpyjy3VnAwZh5EBPQdtaE5oo= google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed h1:J6izYgfBXAI3xTKLgxzTmUltdYaLsuBxFCgDHWJ/eXg= google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/grpc v1.12.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.66.0 h1:DibZuoBznOxbDQxRINckZcUvnCEvrW9pcWIE2yF9r1c= google.golang.org/grpc v1.66.0/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191120175047-4206685974f2/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +rsc.io/tmplfunc v0.0.3 h1:53XFQh69AfOa8Tw0Jm7t+GV7KZhOi6jzsCzTtKbMvzU= +rsc.io/tmplfunc v0.0.3/go.mod h1:AG3sTPzElb1Io3Yg4voV9AGZJuleGAwaVRxL9M49PhA= diff --git a/saetest/logging.go b/saetest/logging.go new file mode 100644 index 0000000..cf9e788 --- /dev/null +++ b/saetest/logging.go @@ -0,0 +1,62 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package saetest + +import ( + "github.com/ava-labs/avalanchego/utils/logging" + "go.uber.org/zap" +) + +// A LogRecorder is a [logging.Logger] that stores all logs as [LogRecord] +// entries for inspection. +type LogRecorder struct { + logging.NoLog + records []*LogRecord +} + +// A LogRecord is a single entry in a [LogRecorder]. +type LogRecord struct { + Level logging.Level + Msg string + Fields []zap.Field +} + +// Filter returns the recorded logs for which `fn` returns true. +func (l *LogRecorder) Filter(fn func(*LogRecord) bool) []*LogRecord { + var out []*LogRecord + for _, r := range l.records { + if fn(r) { + out = append(out, r) + } + } + return out +} + +// At returns all recorded logs at the specified [logging.Level]. +func (l *LogRecorder) At(lvl logging.Level) []*LogRecord { + return l.Filter(func(r *LogRecord) bool { return r.Level == lvl }) +} + +// AtLeast returns all recorded logs at or above the specified [logging.Level]. +func (l *LogRecorder) AtLeast(lvl logging.Level) []*LogRecord { + return l.Filter(func(r *LogRecord) bool { return r.Level >= lvl }) +} + +func (l *LogRecorder) logAt(lvl logging.Level, msg string, fields ...zap.Field) { + l.records = append(l.records, &LogRecord{ + Level: lvl, + Msg: msg, + Fields: fields, + }) +} + +// Error records a [LogRecord] at [logging.Error]. +func (l *LogRecorder) Error(msg string, fields ...zap.Field) { + l.logAt(logging.Error, msg, fields...) +} + +// Fatal records a [LogRecord] at [logging.Fatal]. +func (l *LogRecorder) Fatal(msg string, fields ...zap.Field) { + l.logAt(logging.Fatal, msg, fields...) +} diff --git a/saetest/saetest.go b/saetest/saetest.go new file mode 100644 index 0000000..b0cc7d6 --- /dev/null +++ b/saetest/saetest.go @@ -0,0 +1,29 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +// Package saetest provides testing helpers for [Streaming Asynchronous +// Execution] (SAE). +// +// [Streaming Asynchronous Execution]: https://github.com/avalanche-foundation/ACPs/tree/main/ACPs/194-streaming-asynchronous-execution +package saetest + +import ( + "github.com/ava-labs/libevm/core/types" + "github.com/ava-labs/libevm/trie" + "github.com/google/go-cmp/cmp" +) + +// TrieHasher returns an arbitrary trie hasher. +func TrieHasher() types.TrieHasher { + return trie.NewStackTrie(nil) +} + +// MerkleRootsEqual returns whether the two arguments have the same Merkle root. +func MerkleRootsEqual[T types.DerivableList](a, b T) bool { + return types.DeriveSha(a, TrieHasher()) == types.DeriveSha(b, TrieHasher()) +} + +// CmpByMerkleRoots returns a [cmp.Comparer] using [MerkleRootsEqual]. +func CmpByMerkleRoots[T types.DerivableList]() cmp.Option { + return cmp.Comparer(MerkleRootsEqual[T]) +} From 741f88d08b51e9508fc0e2e771668f8eaa8fd6e3 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 12 Sep 2025 15:39:36 +0100 Subject: [PATCH 02/31] chore: placate the linter --- blocks/settlement_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go index 9d43559..a8f1eee 100644 --- a/blocks/settlement_test.go +++ b/blocks/settlement_test.go @@ -107,7 +107,7 @@ func TestSettlementInvariants(t *testing.T) { } func TestPersistLastSettledNumber(t *testing.T) { - rng := rand.New(rand.NewPCG(0, 0)) + rng := rand.New(rand.NewPCG(0, 0)) //nolint:gosec // Reproducibility is useful for tests for range 10 { settledHeight := rng.Uint64() t.Run(fmt.Sprintf("settled_height_%d", settledHeight), func(t *testing.T) { From 3ddbe1ee4582e0e0d3373778c24efacc7346a0fb Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 15 Sep 2025 05:50:36 +0100 Subject: [PATCH 03/31] chore: include `.envrc` --- .envrc | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .envrc diff --git a/.envrc b/.envrc new file mode 100644 index 0000000..00cf304 --- /dev/null +++ b/.envrc @@ -0,0 +1,2 @@ +export GOROOT="$(go1.24.7 env GOROOT)" +PATH_add "$(go1.24.7 env GOROOT)/bin" \ No newline at end of file From 6b07f43852bdec4814b52e8266383d00a9467acd Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 09:27:52 +0100 Subject: [PATCH 04/31] refactor: stop embedding `types.Block` due to ambiguous methods --- blocks/block.go | 17 ++--------------- blocks/block_test.go | 6 +++--- blocks/execution_test.go | 4 ++-- blocks/export.go | 38 ++++++++++++++++++++++++++++++++++++++ blocks/settlement_test.go | 2 +- blocks/snow.go | 10 +++++----- 6 files changed, 51 insertions(+), 26 deletions(-) create mode 100644 blocks/export.go diff --git a/blocks/block.go b/blocks/block.go index 5f453e4..9e9a23a 100644 --- a/blocks/block.go +++ b/blocks/block.go @@ -14,7 +14,6 @@ import ( "sync/atomic" "github.com/ava-labs/avalanchego/utils/logging" - "github.com/ava-labs/libevm/common" "github.com/ava-labs/libevm/core/types" "go.uber.org/zap" ) @@ -22,7 +21,7 @@ import ( // A Block extends a [types.Block] to track SAE-defined concepts of async // execution and settlement. It MUST be constructed with [New]. type Block struct { - *types.Block + b *types.Block // Invariant: ancestry is non-nil and contains non-nil pointers i.f.f. the // block hasn't itself been settled. A synchronous block (e.g. SAE genesis // or the last pre-SAE block) is always considered settled. @@ -58,7 +57,7 @@ func InMemoryBlockCount() uint64 { // New constructs a new Block. func New(eth *types.Block, parent, lastSettled *Block, log logging.Logger) (*Block, error) { b := &Block{ - Block: eth, + b: eth, executed: make(chan struct{}), settled: make(chan struct{}), } @@ -110,15 +109,3 @@ func (b *Block) CopyAncestorsFrom(c *Block) error { a := c.ancestry.Load() return b.setAncestors(a.parent, a.lastSettled) } - -// Root is a noop that shadows the equivalent method on [types.Block], which is -// embedded in the [Block] and is ambiguous under SAE. Use -// [Block.PostExecutionStateRoot] or [Block.SettledStateRoot] instead. -func (b *Block) Root() {} - -// SettledStateRoot returns the state root after execution of the last block -// settled by b. It is a convenience wrapper for calling [types.Block.Root] on -// the embedded [types.Block]. -func (b *Block) SettledStateRoot() common.Hash { - return b.Block.Root() -} diff --git a/blocks/block_test.go b/blocks/block_test.go index 6fb057e..3cb4f32 100644 --- a/blocks/block_test.go +++ b/blocks/block_test.go @@ -58,7 +58,7 @@ func newChain(tb testing.TB, startHeight, total uint64, lastSettledAtHeight map[ blocks = append(blocks, byNum[n]) parent = byNum[n] - ethParent = parent.Block + ethParent = parent.EthBlock() } return blocks @@ -67,7 +67,7 @@ func newChain(tb testing.TB, startHeight, total uint64, lastSettledAtHeight map[ func TestSetAncestors(t *testing.T) { parent := newBlock(t, newEthBlock(5, 5, nil), nil, nil) lastSettled := newBlock(t, newEthBlock(3, 0, nil), nil, nil) - child := newEthBlock(6, 6, parent.Block) + child := newEthBlock(6, 6, parent.EthBlock()) t.Run("incorrect_parent", func(t *testing.T) { // Note that the arguments to [New] are inverted. @@ -92,7 +92,7 @@ func TestSetAncestors(t *testing.T) { } t.Run("incompatible_destination_block", func(t *testing.T) { - ethB := newEthBlock(dest.Height()+1 /*mismatch*/, dest.Time(), parent.Block) + ethB := newEthBlock(dest.Height()+1 /*mismatch*/, dest.Time(), parent.EthBlock()) dest := newBlock(t, ethB, nil, nil) require.ErrorIs(t, dest.CopyAncestorsFrom(source), errHashMismatch) }) diff --git a/blocks/execution_test.go b/blocks/execution_test.go index 0173ddc..1c6fadb 100644 --- a/blocks/execution_test.go +++ b/blocks/execution_test.go @@ -108,7 +108,7 @@ func TestMarkExecuted(t *testing.T) { assert.Equal(t, stateRoot, b.PostExecutionStateRoot(), "PostExecutionStateRoot()") // i.e. this block // Although not directly relevant to MarkExecuted, demonstrate that the // two notion's of a state root are in fact different. - assert.Equal(t, settles.Block.Root(), b.SettledStateRoot(), "SettledStateRoot()") // i.e. the block this block settles + assert.Equal(t, settles.EthBlock().Root(), b.SettledStateRoot(), "SettledStateRoot()") // i.e. the block this block settles assert.NotEqual(t, b.SettledStateRoot(), b.PostExecutionStateRoot(), "PostExecutionStateRoot() != SettledStateRoot()") t.Run("MarkExecuted_again", func(t *testing.T) { @@ -126,7 +126,7 @@ func TestMarkExecuted(t *testing.T) { t.Run("database", func(t *testing.T) { t.Run("RestorePostExecutionStateAndReceipts", func(t *testing.T) { - clone := newBlock(t, b.Block, nil, settles) + clone := newBlock(t, b.EthBlock(), nil, settles) err := clone.RestorePostExecutionStateAndReceipts( db, params.TestChainConfig, // arbitrary diff --git a/blocks/export.go b/blocks/export.go new file mode 100644 index 0000000..3e137d8 --- /dev/null +++ b/blocks/export.go @@ -0,0 +1,38 @@ +// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. +// See the file LICENSE for licensing terms. + +package blocks + +import ( + "math/big" + + "github.com/ava-labs/libevm/common" + "github.com/ava-labs/libevm/core/types" +) + +// EthBlock returns the raw EVM block wrapped by b. Prefer accessing its +// properties via the methods aliased on [Block] as some (e.g. +// [types.Block.Root]) have ambiguous interpretation under SAE. +func (b *Block) EthBlock() *types.Block { return b.b } + +// SettledStateRoot returns the state root after execution of the last block +// settled by b. It is a convenience wrapper for calling [types.Block.Root] on +// the embedded [types.Block]. +func (b *Block) SettledStateRoot() common.Hash { + return b.b.Root() +} + +// Hash returns [types.Block.Hash] from the wrapped [types.Block]. +func (b *Block) Hash() common.Hash { return b.b.Hash() } + +// ParentHash returns [types.Block.ParentHash] from the wrapped [types.Block]. +func (b *Block) ParentHash() common.Hash { return b.b.ParentHash() } + +// NumberU64 returns [types.Block.NumberU64] from the wrapped [types.Block]. +func (b *Block) NumberU64() uint64 { return b.b.NumberU64() } + +// Time returns [types.Block.Time] from the wrapped [types.Block]. +func (b *Block) Time() uint64 { return b.b.Time() } + +// Number returns [types.Block.Number] from the wrapped [types.Block]. +func (b *Block) Number() *big.Int { return b.b.Number() } diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go index a8f1eee..5dd1240 100644 --- a/blocks/settlement_test.go +++ b/blocks/settlement_test.go @@ -43,7 +43,7 @@ func TestSettlementInvariants(t *testing.T) { parent := newBlock(t, newEthBlock(5, 5, nil), nil, nil) lastSettled := newBlock(t, newEthBlock(3, 3, nil), nil, nil) - b := newBlock(t, newEthBlock(6, 10, parent.Block), parent, lastSettled) + b := newBlock(t, newEthBlock(6, 10, parent.EthBlock()), parent, lastSettled) db := rawdb.NewMemoryDatabase() for _, b := range []*Block{b, parent, lastSettled} { diff --git a/blocks/snow.go b/blocks/snow.go index 00dec74..e540a45 100644 --- a/blocks/snow.go +++ b/blocks/snow.go @@ -19,17 +19,17 @@ import ( var _ adaptor.BlockProperties = (*Block)(nil) -// ID returns [types.Block.Hash] from the embedded [types.Block]. +// ID returns [types.Block.Hash] from the wrapped [types.Block]. func (b *Block) ID() ids.ID { return ids.ID(b.Hash()) } -// Parent returns [types.Block.ParentHash] from the embedded [types.Block]. +// Parent returns [types.Block.ParentHash] from the wrapped [types.Block]. func (b *Block) Parent() ids.ID { return ids.ID(b.ParentHash()) } -// Bytes returns the RLP encoding of the embedded [types.Block]. If encoding +// Bytes returns the RLP encoding of the wrapped [types.Block]. If encoding // returns an error, it is logged at the WARNING level and a nil slice is // returned. func (b *Block) Bytes() []byte { @@ -41,12 +41,12 @@ func (b *Block) Bytes() []byte { return buf } -// Height returns [types.Block.NumberU64] from the embedded [types.Block]. +// Height returns [types.Block.NumberU64] from the wrapped [types.Block]. func (b *Block) Height() uint64 { return b.NumberU64() } -// Timestamp returns the timestamp of the embedded [types.Block], at +// Timestamp returns the timestamp of the wrapped [types.Block], at // [time.Second] resolution. func (b *Block) Timestamp() time.Time { return time.Unix(int64(b.Time()), 0) //nolint:gosec // Won't be a problem for a few millennia From f0e8bde99e3b3e95259c1cdb8900dd05e2875f24 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 09:40:53 +0100 Subject: [PATCH 05/31] refactor: change `Block.Time()` to `Block.BuildTime()` to disambiguate w/ execution --- blocks/block_test.go | 2 +- blocks/db.go | 2 +- blocks/export.go | 17 +++++++++++++---- blocks/settlement.go | 2 +- blocks/settlement_test.go | 4 ++-- blocks/snow.go | 2 +- 6 files changed, 19 insertions(+), 10 deletions(-) diff --git a/blocks/block_test.go b/blocks/block_test.go index 3cb4f32..79be3e0 100644 --- a/blocks/block_test.go +++ b/blocks/block_test.go @@ -92,7 +92,7 @@ func TestSetAncestors(t *testing.T) { } t.Run("incompatible_destination_block", func(t *testing.T) { - ethB := newEthBlock(dest.Height()+1 /*mismatch*/, dest.Time(), parent.EthBlock()) + ethB := newEthBlock(dest.Height()+1 /*mismatch*/, dest.BuildTime(), parent.EthBlock()) dest := newBlock(t, ethB, nil, nil) require.ErrorIs(t, dest.CopyAncestorsFrom(source), errHashMismatch) }) diff --git a/blocks/db.go b/blocks/db.go index 3baa7eb..f416ea8 100644 --- a/blocks/db.go +++ b/blocks/db.go @@ -67,7 +67,7 @@ func (b *Block) RestorePostExecutionState(db ethdb.Database, receipts types.Rece // [rawdb.ReadReceipts], the results of which are propagated to // [Block.RestorePostExecutionState]. func (b *Block) RestorePostExecutionStateAndReceipts(db ethdb.Database, config *params.ChainConfig) error { - rs := rawdb.ReadReceipts(db, b.Hash(), b.NumberU64(), b.Time(), config) + rs := rawdb.ReadReceipts(db, b.Hash(), b.NumberU64(), b.BuildTime(), config) return b.RestorePostExecutionState(db, rs) } diff --git a/blocks/export.go b/blocks/export.go index 3e137d8..80541e7 100644 --- a/blocks/export.go +++ b/blocks/export.go @@ -10,6 +10,12 @@ import ( "github.com/ava-labs/libevm/core/types" ) +// While an argument can be made for embedding the [types.Block] in [Block], +// instead of aliasing methods, that risks incorrect usage of subtle differences +// under SAE. These methods are direct aliases i.f.f. the interpretation is +// unambiguous, otherwise their names are clarified (e.g. +// [Block.SettledStateRoot]). + // EthBlock returns the raw EVM block wrapped by b. Prefer accessing its // properties via the methods aliased on [Block] as some (e.g. // [types.Block.Root]) have ambiguous interpretation under SAE. @@ -17,11 +23,17 @@ func (b *Block) EthBlock() *types.Block { return b.b } // SettledStateRoot returns the state root after execution of the last block // settled by b. It is a convenience wrapper for calling [types.Block.Root] on -// the embedded [types.Block]. +// the wrapped [types.Block]. func (b *Block) SettledStateRoot() common.Hash { return b.b.Root() } +// BuildTime returns the Unix timestamp of the block, which is the canonical +// inclusion time of its transactions; see [Block.ExecutedByGasTime] for their +// execution timestamp. BuildTime is a convenience wrapper for calling +// [types.Block.Time] on the wrapped [types.Block]. +func (b *Block) BuildTime() uint64 { return b.b.Time() } + // Hash returns [types.Block.Hash] from the wrapped [types.Block]. func (b *Block) Hash() common.Hash { return b.b.Hash() } @@ -31,8 +43,5 @@ func (b *Block) ParentHash() common.Hash { return b.b.ParentHash() } // NumberU64 returns [types.Block.NumberU64] from the wrapped [types.Block]. func (b *Block) NumberU64() uint64 { return b.b.NumberU64() } -// Time returns [types.Block.Time] from the wrapped [types.Block]. -func (b *Block) Time() uint64 { return b.b.Time() } - // Number returns [types.Block.Number] from the wrapped [types.Block]. func (b *Block) Number() *big.Int { return b.b.Number() } diff --git a/blocks/settlement.go b/blocks/settlement.go index 7208681..4ef4fe5 100644 --- a/blocks/settlement.go +++ b/blocks/settlement.go @@ -138,7 +138,7 @@ func LastToSettleAt(settleAt uint64, parent *Block) (*Block, bool) { // therefore we have a guarantee that the loop update will never result in // `block==nil`. for ; ; block, child = block.ParentBlock(), block { - if startsNoEarlierThan := block.Time(); startsNoEarlierThan > settleAt { + if startsNoEarlierThan := block.BuildTime(); startsNoEarlierThan > settleAt { continue } diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go index 5dd1240..da35f61 100644 --- a/blocks/settlement_test.go +++ b/blocks/settlement_test.go @@ -47,7 +47,7 @@ func TestSettlementInvariants(t *testing.T) { db := rawdb.NewMemoryDatabase() for _, b := range []*Block{b, parent, lastSettled} { - b.markExecutedForTests(t, db, gastime.New(b.Time(), 1, 0)) + b.markExecutedForTests(t, db, gastime.New(b.BuildTime(), 1, 0)) } t.Run("before_MarkSettled", func(t *testing.T) { @@ -216,7 +216,7 @@ func TestLastToSettleAt(t *testing.T) { t.Run("helper_invariants", func(t *testing.T) { for i, b := range blocks { require.Equal(t, uint64(i), b.Height()) //nolint:gosec // Slice index won't overflow - require.Equal(t, b.Time(), b.Height()) + require.Equal(t, b.BuildTime(), b.Height()) } }) diff --git a/blocks/snow.go b/blocks/snow.go index e540a45..58fcd63 100644 --- a/blocks/snow.go +++ b/blocks/snow.go @@ -49,5 +49,5 @@ func (b *Block) Height() uint64 { // Timestamp returns the timestamp of the wrapped [types.Block], at // [time.Second] resolution. func (b *Block) Timestamp() time.Time { - return time.Unix(int64(b.Time()), 0) //nolint:gosec // Won't be a problem for a few millennia + return time.Unix(int64(b.BuildTime()), 0) //nolint:gosec // Won't be a problem for a few millennia } From 8585c429c019c0f021067e289af88f278fed8a49 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 14:24:18 +0100 Subject: [PATCH 06/31] refactor: use `int64` for in-memory block counter --- blocks/block.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/blocks/block.go b/blocks/block.go index 9e9a23a..653fb10 100644 --- a/blocks/block.go +++ b/blocks/block.go @@ -9,7 +9,6 @@ package blocks import ( "errors" "fmt" - "math" "runtime" "sync/atomic" @@ -46,11 +45,11 @@ type Block struct { log logging.Logger } -var inMemoryBlockCount atomic.Uint64 +var inMemoryBlockCount atomic.Int64 // InMemoryBlockCount returns the number of blocks created with [New] that are // yet to have their GC finalizers run. -func InMemoryBlockCount() uint64 { +func InMemoryBlockCount() int64 { return inMemoryBlockCount.Load() } @@ -64,7 +63,7 @@ func New(eth *types.Block, parent, lastSettled *Block, log logging.Logger) (*Blo inMemoryBlockCount.Add(1) runtime.AddCleanup(b, func(struct{}) { - inMemoryBlockCount.Add(math.MaxUint64) // -1 + inMemoryBlockCount.Add(-1) }, struct{}{}) if err := b.setAncestors(parent, lastSettled); err != nil { From a844571c3d07f5d1c1b726ef7af2af68daf79325 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Tue, 16 Sep 2025 14:30:46 +0100 Subject: [PATCH 07/31] refactor: avoid `fmt.Sprintf` in logging Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- blocks/execution.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/blocks/execution.go b/blocks/execution.go index df1f7fb..a5fe589 100644 --- a/blocks/execution.go +++ b/blocks/execution.go @@ -118,7 +118,9 @@ func (b *Block) Executed() bool { func executionArtefact[T any](b *Block, desc string, get func(*executionResults) T) T { e := b.execution.Load() if e == nil { - b.log.Error(fmt.Sprintf("Get block %s before execution", desc)) + b.log.Error("execution artefact requested before execution", + zap.String("artefact", desc), + ) var zero T return zero } From 4612b9c54b46604d7d807e02bd98c26ac2992f83 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 14:35:54 +0100 Subject: [PATCH 08/31] refactor: avoid extra malloc for db keys --- blocks/db.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/blocks/db.go b/blocks/db.go index f416ea8..7bffae2 100644 --- a/blocks/db.go +++ b/blocks/db.go @@ -20,7 +20,10 @@ import ( /* ===== Common =====*/ func blockNumDBKey(prefix string, blockNum uint64) []byte { - return binary.BigEndian.AppendUint64([]byte(prefix), blockNum) + n := len(prefix) + key := make([]byte, n, n+8) + copy(key, prefix) + return binary.BigEndian.AppendUint64(key, blockNum) } func (b *Block) writeToKVStore(w ethdb.KeyValueWriter, key func(uint64) []byte, val []byte) error { From c93f3405567e6506153c694559347b4062edd957 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 14:46:06 +0100 Subject: [PATCH 09/31] doc: `Block.executionExceededSecond` field --- blocks/block.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/blocks/block.go b/blocks/block.go index 653fb10..4abfbe2 100644 --- a/blocks/block.go +++ b/blocks/block.go @@ -35,8 +35,11 @@ type Block struct { // have returned without error. execution atomic.Pointer[executionResults] - // See [Block.SetInterimExecutionTime for setting and [LastToSettleAt] for - // usage. The pointer MAY be nil if execution is yet to commence. + // Allows this block to be ruled out as able to be settled at a particular + // time (i.e. if this field is >= said time). The pointer MAY be nil if + // execution is yet to commence. For more details, see + // [Block.SetInterimExecutionTime for setting and [LastToSettleAt] for + // usage. executionExceededSecond atomic.Pointer[uint64] executed chan struct{} // closed after `execution` is set From c12250034ac13dd2c63abb8b6c134fdb11ea9746 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 14:49:32 +0100 Subject: [PATCH 10/31] fix: log `Block.Bytes()` errors at `ERROR` --- blocks/snow.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blocks/snow.go b/blocks/snow.go index 58fcd63..1c7bc55 100644 --- a/blocks/snow.go +++ b/blocks/snow.go @@ -30,12 +30,12 @@ func (b *Block) Parent() ids.ID { } // Bytes returns the RLP encoding of the wrapped [types.Block]. If encoding -// returns an error, it is logged at the WARNING level and a nil slice is +// returns an error, it is logged at the ERROR level and a nil slice is // returned. func (b *Block) Bytes() []byte { buf, err := rlp.EncodeToBytes(b) if err != nil { - b.log.Warn("RLP encoding error", zap.Error(err)) + b.log.Error("RLP encoding error", zap.Error(err)) return nil } return buf From 70e6459825c696d159f6e2148b52c3fc4665ef88 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 14:51:42 +0100 Subject: [PATCH 11/31] refactor: sentinel error if ancestry changes during settlement --- blocks/settlement.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/blocks/settlement.go b/blocks/settlement.go index 4ef4fe5..a42f77e 100644 --- a/blocks/settlement.go +++ b/blocks/settlement.go @@ -14,7 +14,10 @@ type ancestry struct { parent, lastSettled *Block } -var errBlockResettled = errors.New("block re-settled") +var ( + errBlockResettled = errors.New("block re-settled") + errBlockAncestryChanged = errors.New("block ancestry changed during settlement") +) // MarkSettled marks the block as having been settled. This function MUST NOT // be called more than once. @@ -31,7 +34,7 @@ func (b *Block) MarkSettled() error { b.log.Fatal("Block ancestry changed during settlement") // We have to return something to keen the compiler happy, even though we // expect the Fatal to be, well, fatal. - return errors.New("block ancestry changed during settlement") + return errBlockAncestryChanged } close(b.settled) return nil From 23e2f65853efe6df31ed1147efac45df3edb6200 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 14:53:38 +0100 Subject: [PATCH 12/31] fix: import needed by accepted suggestion --- blocks/execution.go | 1 + 1 file changed, 1 insertion(+) diff --git a/blocks/execution.go b/blocks/execution.go index a5fe589..71db6c1 100644 --- a/blocks/execution.go +++ b/blocks/execution.go @@ -16,6 +16,7 @@ import ( "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/ethdb" "github.com/ava-labs/libevm/trie" + "go.uber.org/zap" "github.com/ava-labs/strevm/gastime" "github.com/ava-labs/strevm/proxytime" From 6f79b420f76af885df8380931ac1fe4323ddba33 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 15:13:34 +0100 Subject: [PATCH 13/31] refactor: rename to `Block.ChildSettles()` and improve comment --- blocks/settlement.go | 26 ++++++++++++++++++-------- blocks/settlement_test.go | 14 +++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/blocks/settlement.go b/blocks/settlement.go index a42f77e..34d95ba 100644 --- a/blocks/settlement.go +++ b/blocks/settlement.go @@ -94,15 +94,25 @@ func (b *Block) Settles() []*Block { return settling(b.ParentBlock().LastSettled(), b.LastSettled()) } -// IfChildSettles is similar to [Block.Settles] but with different definitions -// of `x` and `y` (as described in [Block.Settles]). It is intended for use -// during block building and defines `x` as the block height of -// `b.LastSettled()` while `y` as the height of the argument passed to this -// method. +// ChildSettles returns the blocks that would be settled by a child of `b`, +// given the last-settled block at the child's block time. Note that the +// last-settled block at the child's time MAY be equal to the last-settled of +// `b` (its parent), in which case ChildSettles returns an empty slice. // // The argument is typically the return value of [LastToSettleAt], where that -// function receives b as the parent. See the Example. -func (b *Block) IfChildSettles(lastSettledOfChild *Block) []*Block { +// function receives `b` as the parent. See the Example. +// +// ChildSettles MUST only be called before the call to [Block.MarkSettled] on +// `b`. The intention is that this method is called on the VM's preferred block, +// which always meets this criterion. This is by definition of settlement, which +// requires that at least one descendant block has already been accepted, which +// the preference never has. +// +// ChildSettles is similar to [Block.Settles] but with different definitions of +// `x` and `y` (as described in [Block.Settles]). It is intended for use during +// block building and defines `x` as the block height of `b.LastSettled()` while +// `y` as the height of the argument passed to this method. +func (b *Block) ChildSettles(lastSettledOfChild *Block) []*Block { return settling(b.LastSettled(), lastSettledOfChild) } @@ -127,7 +137,7 @@ func settling(lastOfParent, lastOfCurr *Block) []*Block { // execution stream is lagging and LastToSettleAt can be called again after some // indeterminate delay. // -// See the Example for [Block.IfChildSettles] for one usage of the returned +// See the Example for [Block.ChildSettles] for one usage of the returned // block. func LastToSettleAt(settleAt uint64, parent *Block) (*Block, bool) { // These variables are only abstracted for clarity; they are not needed diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go index da35f61..614b022 100644 --- a/blocks/settlement_test.go +++ b/blocks/settlement_test.go @@ -24,7 +24,7 @@ import ( ) //nolint:testableexamples // Output is meaningless -func ExampleBlock_IfChildSettles() { +func ExampleBlock_ChildSettles() { parent := blockBuildingPreference() settle, ok := LastToSettleAt(uint64(time.Now().Unix()), parent) //nolint:gosec // Time won't overflow for quite a while if !ok { @@ -33,7 +33,7 @@ func ExampleBlock_IfChildSettles() { // Returns the (possibly empty) slice of blocks that would be settled by the // block being built. - _ = parent.IfChildSettles(settle) + _ = parent.ChildSettles(settle) } // blockBuildingPreference exists only to allow examples to build. @@ -178,26 +178,26 @@ func TestSettles(t *testing.T) { for _, b := range blocks[1:] { tests = append(tests, testCase{ name: fmt.Sprintf("Block(%d).IfChildSettles([same as parent])", b.Height()), - got: b.IfChildSettles(b.LastSettled()), + got: b.ChildSettles(b.LastSettled()), want: nil, }) } tests = append(tests, []testCase{ { - got: blocks[7].IfChildSettles(blocks[3]), + got: blocks[7].ChildSettles(blocks[3]), want: nil, }, { - got: blocks[7].IfChildSettles(blocks[4]), + got: blocks[7].ChildSettles(blocks[4]), want: numsToBlocks(4), }, { - got: blocks[7].IfChildSettles(blocks[5]), + got: blocks[7].ChildSettles(blocks[5]), want: numsToBlocks(4, 5), }, { - got: blocks[7].IfChildSettles(blocks[6]), + got: blocks[7].ChildSettles(blocks[6]), want: numsToBlocks(4, 5, 6), }, }...) From 7153bf74c242989e2db5b1bf7ebbf2cdb2948e1d Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 18:18:06 +0100 Subject: [PATCH 14/31] test: propagate `Block` logs to `testing.TB` --- blocks/block_test.go | 4 +- blocks/execution_test.go | 8 +-- blocks/settlement_test.go | 6 +-- go.mod | 18 +++++-- go.sum | 36 +++++++++---- saetest/logging.go | 109 ++++++++++++++++++++++++++++++++------ 6 files changed, 143 insertions(+), 38 deletions(-) diff --git a/blocks/block_test.go b/blocks/block_test.go index 79be3e0..8ab62ef 100644 --- a/blocks/block_test.go +++ b/blocks/block_test.go @@ -12,6 +12,8 @@ import ( "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "github.com/ava-labs/strevm/saetest" ) func newEthBlock(num, time uint64, parent *types.Block) *types.Block { @@ -27,7 +29,7 @@ func newEthBlock(num, time uint64, parent *types.Block) *types.Block { func newBlock(tb testing.TB, eth *types.Block, parent, lastSettled *Block) *Block { tb.Helper() - b, err := New(eth, parent, lastSettled, logging.NoLog{}) + b, err := New(eth, parent, lastSettled, saetest.NewTBLogger(tb, logging.Warn)) require.NoError(tb, err, "New()") return b } diff --git a/blocks/execution_test.go b/blocks/execution_test.go index 1c6fadb..e2f426c 100644 --- a/blocks/execution_test.go +++ b/blocks/execution_test.go @@ -63,8 +63,8 @@ func TestMarkExecuted(t *testing.T) { defer cancel() require.ErrorIs(t, context.DeadlineExceeded, b.WaitUntilExecuted(ctx), "WaitUntilExecuted()") - var rec saetest.LogRecorder - b.log = &rec + rec := saetest.NewLogRecorder(logging.Warn) + b.log = rec assertNumErrorLogs := func(t *testing.T, want int) { t.Helper() assert.Len(t, rec.At(logging.Error), want, "Number of ERROR logs") @@ -112,8 +112,8 @@ func TestMarkExecuted(t *testing.T) { assert.NotEqual(t, b.SettledStateRoot(), b.PostExecutionStateRoot(), "PostExecutionStateRoot() != SettledStateRoot()") t.Run("MarkExecuted_again", func(t *testing.T) { - var rec saetest.LogRecorder - b.log = &rec + rec := saetest.NewLogRecorder(logging.Warn) + b.log = rec assert.ErrorIs(t, b.MarkExecuted(db, gasTime, wallTime, receipts, stateRoot), errMarkBlockExecutedAgain) // The database's head block might have been corrupted so this MUST // be a fatal action. diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go index 614b022..8bc86c7 100644 --- a/blocks/settlement_test.go +++ b/blocks/settlement_test.go @@ -69,8 +69,8 @@ func TestSettlementInvariants(t *testing.T) { assert.NoError(t, b.WaitUntilSettled(context.Background()), "WaitUntilSettled()") assert.NoError(t, b.CheckInvariants(Settled), "CheckInvariants(Settled)") - var rec saetest.LogRecorder - b.log = &rec + rec := saetest.NewLogRecorder(logging.Warn) + b.log = rec assertNumErrorLogs := func(t *testing.T, want int) { t.Helper() assert.Len(t, rec.At(logging.Error), want, "Number of ERROR") @@ -177,7 +177,7 @@ func TestSettles(t *testing.T) { for _, b := range blocks[1:] { tests = append(tests, testCase{ - name: fmt.Sprintf("Block(%d).IfChildSettles([same as parent])", b.Height()), + name: fmt.Sprintf("Block(%d).ChildSettles([same as parent])", b.Height()), got: b.ChildSettles(b.LastSettled()), want: nil, }) diff --git a/go.mod b/go.mod index d5550ed..d6ca477 100644 --- a/go.mod +++ b/go.mod @@ -8,10 +8,13 @@ require ( github.com/google/go-cmp v0.6.0 github.com/holiman/uint256 v1.2.4 github.com/stretchr/testify v1.10.0 + go.uber.org/goleak v1.3.0 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b ) require ( + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/cockroachdb/errors v1.9.1 // indirect @@ -23,6 +26,7 @@ require ( github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect + github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect @@ -31,6 +35,8 @@ require ( github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect + github.com/gorilla/websocket v1.5.0 // indirect + github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -45,7 +51,9 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect + golang.org/x/mod v0.25.0 // indirect golang.org/x/sync v0.15.0 // indirect + golang.org/x/tools v0.34.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) @@ -85,11 +93,11 @@ require ( go.opentelemetry.io/proto/otlp v1.0.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.26.0 - golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.38.0 // indirect - golang.org/x/sys v0.31.0 // indirect - golang.org/x/term v0.30.0 // indirect - golang.org/x/text v0.23.0 // indirect + golang.org/x/crypto v0.39.0 // indirect + golang.org/x/net v0.41.0 // indirect + golang.org/x/sys v0.33.0 // indirect + golang.org/x/term v0.32.0 // indirect + golang.org/x/text v0.26.0 // indirect gonum.org/v1/gonum v0.11.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240604185151-ef581f913117 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240827150818-7e3bb234dfed // indirect diff --git a/go.sum b/go.sum index eccb9f3..67129fb 100644 --- a/go.sum +++ b/go.sum @@ -16,6 +16,8 @@ github.com/StephenButtolph/canoto v0.17.1/go.mod h1:IcnAHC6nJUfQFVR9y60ko2ecUqqH github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= +github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanchego v1.13.2 h1:Kx/T2a8vqLlgHde3DWT5zMF5yBIh1rqEd6nJQMMzV/Y= github.com/ava-labs/avalanchego v1.13.2/go.mod h1:s7W/kim5L6hiD2PB1v/Ozy1ZZyoLQ4H6mxVO0aMnxng= @@ -33,6 +35,7 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -99,6 +102,8 @@ github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmV github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= +github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= +github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= @@ -170,6 +175,8 @@ github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qA github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= @@ -185,6 +192,8 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= +github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= @@ -196,6 +205,8 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= +github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= +github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -323,6 +334,8 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= +github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= +github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -342,6 +355,8 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= +github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= +github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -397,8 +412,8 @@ golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= -golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM= +golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= @@ -433,8 +448,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20211008194852-3b03d305991f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8= -golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= +golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -478,22 +493,23 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= -golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= +golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181221001348-537d06c36207/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= diff --git a/saetest/logging.go b/saetest/logging.go index cf9e788..ca57e8b 100644 --- a/saetest/logging.go +++ b/saetest/logging.go @@ -4,15 +4,68 @@ package saetest import ( + "runtime" + "slices" + "testing" + "github.com/ava-labs/avalanchego/utils/logging" "go.uber.org/zap" + "go.uber.org/zap/zapcore" ) +// logger is the common wrapper around [LogRecorder] and [tbLogger] handlers, +// plumbing all levels into the handler. +type logger struct { + level logging.Level + handler interface { + log(logging.Level, string, ...zap.Field) + } + with []zap.Field + // Some methods will panic, in which case they need to be implemented. This + // is better than embedding a [logging.NoLog], which could silently drop + // important entries. + logging.Logger +} + +var _ logging.Logger = (*logger)(nil) + +func (l *logger) With(fields ...zap.Field) logging.Logger { + return &logger{ + level: l.level, + handler: l.handler, + with: slices.Concat(l.with, fields), + } +} + +func (l *logger) log(lvl logging.Level, msg string, fields ...zap.Field) { + if lvl < l.level { + return + } + l.handler.log(lvl, msg, slices.Concat(l.with, fields)...) +} + +func (l *logger) Debug(msg string, fs ...zap.Field) { l.log(logging.Debug, msg, fs...) } +func (l *logger) Trace(msg string, fs ...zap.Field) { l.log(logging.Trace, msg, fs...) } +func (l *logger) Info(msg string, fs ...zap.Field) { l.log(logging.Info, msg, fs...) } +func (l *logger) Warn(msg string, fs ...zap.Field) { l.log(logging.Warn, msg, fs...) } +func (l *logger) Error(msg string, fs ...zap.Field) { l.log(logging.Error, msg, fs...) } +func (l *logger) Fatal(msg string, fs ...zap.Field) { l.log(logging.Fatal, msg, fs...) } + +// NewLogRecorder constructs a new [LogRecorder] at the specified level. +func NewLogRecorder(level logging.Level) *LogRecorder { + r := new(LogRecorder) + r.logger = &logger{ + handler: r, // yes, the recursion is gross, but that's composition for you ¯\_(ツ)_/¯ + level: level, + } + return r +} + // A LogRecorder is a [logging.Logger] that stores all logs as [LogRecord] // entries for inspection. type LogRecorder struct { - logging.NoLog - records []*LogRecord + *logger + Records []*LogRecord } // A LogRecord is a single entry in a [LogRecorder]. @@ -22,10 +75,18 @@ type LogRecord struct { Fields []zap.Field } +func (l *LogRecorder) log(lvl logging.Level, msg string, fields ...zap.Field) { + l.Records = append(l.Records, &LogRecord{ + Level: lvl, + Msg: msg, + Fields: fields, + }) +} + // Filter returns the recorded logs for which `fn` returns true. func (l *LogRecorder) Filter(fn func(*LogRecord) bool) []*LogRecord { var out []*LogRecord - for _, r := range l.records { + for _, r := range l.Records { if fn(r) { out = append(out, r) } @@ -43,20 +104,38 @@ func (l *LogRecorder) AtLeast(lvl logging.Level) []*LogRecord { return l.Filter(func(r *LogRecord) bool { return r.Level >= lvl }) } -func (l *LogRecorder) logAt(lvl logging.Level, msg string, fields ...zap.Field) { - l.records = append(l.records, &LogRecord{ - Level: lvl, - Msg: msg, - Fields: fields, - }) +// NewTBLogger constructs a logger that propagates logs to the [testing.TB]. +// WARNING and ERROR logs are sent to [testing.TB.Errorf] while FATAL is sent to +// [testing.TB.Fatalf]. All other logs are sent to [testing.TB.Logf]. Although +// the level can be configured, it is silently capped at [logging.Warn]. +// +//nolint:thelper // The outputs include the logging site while the TB site is most useful if here +func NewTBLogger(tb testing.TB, level logging.Level) logging.Logger { + return &logger{ + level: min(level, logging.Warn), + handler: &tbLogger{tb: tb}, + } } -// Error records a [LogRecord] at [logging.Error]. -func (l *LogRecorder) Error(msg string, fields ...zap.Field) { - l.logAt(logging.Error, msg, fields...) +type tbLogger struct { + tb testing.TB } -// Fatal records a [LogRecord] at [logging.Fatal]. -func (l *LogRecorder) Fatal(msg string, fields ...zap.Field) { - l.logAt(logging.Fatal, msg, fields...) +func (l *tbLogger) log(lvl logging.Level, msg string, fields ...zap.Field) { + var to func(string, ...any) + switch { + case lvl == logging.Warn || lvl == logging.Error: // because @ARR4N says warnings in tests are errors + to = l.tb.Errorf + case lvl >= logging.Fatal: + to = l.tb.Fatalf + default: + to = l.tb.Logf + } + + enc := zapcore.NewMapObjectEncoder() + for _, f := range fields { + f.AddTo(enc) + } + _, file, line, _ := runtime.Caller(3) + to("[Log@%s] %s %v - %s:%d", lvl, msg, enc.Fields, file, line) } From 75720f9e75831c992ad98c279261dfc88f81b236 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 18:23:14 +0100 Subject: [PATCH 15/31] chore: new line at end of `.envrc` --- .envrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.envrc b/.envrc index 00cf304..6083ec6 100644 --- a/.envrc +++ b/.envrc @@ -1,2 +1,2 @@ export GOROOT="$(go1.24.7 env GOROOT)" -PATH_add "$(go1.24.7 env GOROOT)/bin" \ No newline at end of file +PATH_add "$(go1.24.7 env GOROOT)/bin" From 5bb58d3b949268decd3cc9b9ee0e09bc1f3a7997 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 18:24:39 +0100 Subject: [PATCH 16/31] chore: `go mod tidy` --- go.mod | 8 -------- go.sum | 16 ---------------- 2 files changed, 24 deletions(-) diff --git a/go.mod b/go.mod index d6ca477..e85eefc 100644 --- a/go.mod +++ b/go.mod @@ -8,13 +8,10 @@ require ( github.com/google/go-cmp v0.6.0 github.com/holiman/uint256 v1.2.4 github.com/stretchr/testify v1.10.0 - go.uber.org/goleak v1.3.0 golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b ) require ( - github.com/Microsoft/go-winio v0.6.1 // indirect - github.com/VictoriaMetrics/fastcache v1.12.1 // indirect github.com/bits-and-blooms/bitset v1.10.0 // indirect github.com/btcsuite/btcd/btcec/v2 v2.3.2 // indirect github.com/cockroachdb/errors v1.9.1 // indirect @@ -26,7 +23,6 @@ require ( github.com/consensys/gnark-crypto v0.12.1 // indirect github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 // indirect github.com/crate-crypto/go-kzg-4844 v0.7.0 // indirect - github.com/deckarep/golang-set/v2 v2.1.0 // indirect github.com/decred/dcrd/dcrec/secp256k1/v4 v4.1.0 // indirect github.com/ethereum/c-kzg-4844 v0.4.0 // indirect github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 // indirect @@ -35,8 +31,6 @@ require ( github.com/gofrs/flock v0.8.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect - github.com/gorilla/websocket v1.5.0 // indirect - github.com/holiman/bloomfilter/v2 v2.0.3 // indirect github.com/klauspost/compress v1.15.15 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -51,9 +45,7 @@ require ( github.com/tklauser/go-sysconf v0.3.12 // indirect github.com/tklauser/numcpus v0.6.1 // indirect github.com/yusufpapurcu/wmi v1.2.2 // indirect - golang.org/x/mod v0.25.0 // indirect golang.org/x/sync v0.15.0 // indirect - golang.org/x/tools v0.34.0 // indirect rsc.io/tmplfunc v0.0.3 // indirect ) diff --git a/go.sum b/go.sum index 67129fb..6d4d354 100644 --- a/go.sum +++ b/go.sum @@ -16,8 +16,6 @@ github.com/StephenButtolph/canoto v0.17.1/go.mod h1:IcnAHC6nJUfQFVR9y60ko2ecUqqH github.com/VictoriaMetrics/fastcache v1.12.1 h1:i0mICQuojGDL3KblA7wUNlY5lOK6a4bwt3uRKnkZU40= github.com/VictoriaMetrics/fastcache v1.12.1/go.mod h1:tX04vaqcNoQeGLD+ra5pU5sWkuxnzWhEzLwhP9w653o= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156 h1:eMwmnE/GDgah4HI848JfFxHt+iPb26b4zyfspmqY0/8= -github.com/allegro/bigcache v1.2.1-0.20190218064605-e24eb225f156/go.mod h1:Cb/ax3seSYIx7SuZdm2G2xzfwmv3TPSk2ucNfQESPXM= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/ava-labs/avalanchego v1.13.2 h1:Kx/T2a8vqLlgHde3DWT5zMF5yBIh1rqEd6nJQMMzV/Y= github.com/ava-labs/avalanchego v1.13.2/go.mod h1:s7W/kim5L6hiD2PB1v/Ozy1ZZyoLQ4H6mxVO0aMnxng= @@ -35,7 +33,6 @@ github.com/btcsuite/btcd/chaincfg/chainhash v1.0.1/go.mod h1:7SFka0XMvUgj3hfZtyd github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= @@ -102,8 +99,6 @@ github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmV github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= -github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08 h1:f6D9Hr8xV8uYKlyuj8XIruxlh9WjVjdh1gIicAS7ays= -github.com/gballet/go-libpcsclite v0.0.0-20191108122812-4678299bea08/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE= github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc= github.com/getsentry/sentry-go v0.12.0/go.mod h1:NSap0JBYWzHND8oMbyi0+XZhUalc1TBdRL1M71JZW2c= @@ -175,8 +170,6 @@ github.com/google/renameio/v2 v2.0.0 h1:UifI23ZTGY8Tt29JbYFiuyIU3eX+RNFtUwefq9qA github.com/google/renameio/v2 v2.0.0/go.mod h1:BtmJXm5YlszgC+TD4HOEEUFgkJP3nLxehU6hfe7jRt4= github.com/google/subcommands v1.2.0/go.mod h1:ZjhPrFU+Olkh9WazFPsl27BQ4UPiG37m3yTrtFlrHVk= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/rpc v1.2.0 h1:WvvdC2lNeT1SP32zrIce5l0ECBfbAlmrmSBsuc57wfk= github.com/gorilla/rpc v1.2.0/go.mod h1:V4h9r+4sF5HnzqbwIez0fKSpANP0zlYd3qR7p36jkTQ= @@ -192,8 +185,6 @@ github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iU github.com/holiman/uint256 v1.2.4 h1:jUc4Nk8fm9jZabQuqr2JzednajVmBpC+oiTiXZJEApU= github.com/holiman/uint256 v1.2.4/go.mod h1:EOMSn4q6Nyt9P6efbI3bueV4e1b3dGlUCXeiRV4ng7E= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huin/goupnp v1.3.0 h1:UvLUlWDNpoUdYzb2TCn+MuTWtcjXKSza2n6CBdQ0xXc= -github.com/huin/goupnp v1.3.0/go.mod h1:gnGPsThkYa7bFi/KWmEysQRf48l2dvR5bxr2OFckNX8= github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91/go.mod h1:qEIFzExnS6016fRpRfxrExeVn2gbClQA99gQhnIcdhE= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= @@ -205,8 +196,6 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/ github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk= github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g= github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw= -github.com/jackpal/go-nat-pmp v1.0.2 h1:KzKSgb7qkJvOUTqYl9/Hg/me3pWgBmERKrTGD7BdWus= -github.com/jackpal/go-nat-pmp v1.0.2/go.mod h1:QPH045xvCAeXUZOxsnwmrtiCoxIr9eob+4orBN1SBKc= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= @@ -334,8 +323,6 @@ github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/status-im/keycard-go v0.2.0 h1:QDLFswOQu1r5jsycloeQh3bVU8n/NatHHaZobtDnDzA= -github.com/status-im/keycard-go v0.2.0/go.mod h1:wlp8ZLbsmrF6g6WjugPAx+IzoLrkdf9+mHxBEeo3Hbg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= @@ -355,8 +342,6 @@ github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFA github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI= github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= -github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8= -github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U= github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= @@ -493,7 +478,6 @@ golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= From e324ce8f154460db73dd0e8ddc97a55532cc08ca Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Tue, 16 Sep 2025 20:12:40 +0100 Subject: [PATCH 17/31] fix: `Settles()` of synchronous (genesis) block --- blocks/block.go | 3 +++ blocks/block_test.go | 21 +++++++++++++++++---- blocks/settlement.go | 17 ++++++++++++++++- blocks/settlement_test.go | 2 +- 4 files changed, 37 insertions(+), 6 deletions(-) diff --git a/blocks/block.go b/blocks/block.go index 4abfbe2..d4b2760 100644 --- a/blocks/block.go +++ b/blocks/block.go @@ -31,6 +31,9 @@ type Block struct { // Overlord as a sign of our unwavering fealty. See [InMemoryBlockCount] for // observability. ancestry atomic.Pointer[ancestry] + // Only the genesis block or the last pre-SAE block is synchronous. These + // are self-settling by definition so their `ancestry` MUST be nil. + synchronous bool // Non-nil i.f.f. [Block.MarkExecuted] or [Block.ResotrePostExecutionState] // have returned without error. execution atomic.Pointer[executionResults] diff --git a/blocks/block_test.go b/blocks/block_test.go index 8ab62ef..a60447e 100644 --- a/blocks/block_test.go +++ b/blocks/block_test.go @@ -51,13 +51,26 @@ func newChain(tb testing.TB, startHeight, total uint64, lastSettledAtHeight map[ for i := range total { n := startHeight + i - var settle *Block + var ( + settle *Block + synchronous bool + ) if s, ok := lastSettledAtHeight[n]; ok { - settle = byNum[s] + if s == n { + require.Equal(tb, uint64(0), s, "Only genesis block is self-settling") + synchronous = true + } else { + require.Less(tb, s, n, "Last-settled height MUST be <= current height") + settle = byNum[s] + } } - byNum[n] = newBlock(tb, newEthBlock(n, n /*time*/, ethParent), parent, settle) - blocks = append(blocks, byNum[n]) + b := newBlock(tb, newEthBlock(n, n /*time*/, ethParent), parent, settle) + byNum[n] = b + blocks = append(blocks, b) + if synchronous { + require.NoError(tb, b.MarkSynchronous(), "MarkSynchronous()") + } parent = byNum[n] ethParent = parent.EthBlock() diff --git a/blocks/settlement.go b/blocks/settlement.go index 34d95ba..99a2e3f 100644 --- a/blocks/settlement.go +++ b/blocks/settlement.go @@ -40,6 +40,15 @@ func (b *Block) MarkSettled() error { return nil } +// MarkSynchronous is a special case of [Block.MarkSettled], reserved for the +// last pre-SAE block, which MAY be the genesis block. These are, by definition, +// self-settling so require special treatment as such behaviour is impossible +// under SAE rules. +func (b *Block) MarkSynchronous() error { + b.synchronous = true + return b.MarkSettled() +} + // WaitUntilSettled blocks until either [Block.MarkSettled] is called or the // [context.Context] is cancelled. func (b *Block) WaitUntilSettled(ctx context.Context) error { @@ -77,6 +86,9 @@ func (b *Block) ParentBlock() *Block { // unless [Block.MarkSettled] has been called, in which case it returns nil. // Note that this value might not be distinct between contiguous blocks. func (b *Block) LastSettled() *Block { + if b.synchronous { + return b + } return b.ancestor(getSettledOfSettledErrMsg, func(a *ancestry) *Block { return a.lastSettled }) @@ -91,6 +103,9 @@ func (b *Block) LastSettled() *Block { // It is not valid to call Settles after a call to [Block.MarkSettled] on either // b or its parent. func (b *Block) Settles() []*Block { + if b.synchronous { + return []*Block{b} + } return settling(b.ParentBlock().LastSettled(), b.LastSettled()) } @@ -124,7 +139,7 @@ func settling(lastOfParent, lastOfCurr *Block) []*Block { var settling []*Block // TODO(arr4n) abstract this to combine functionality with iterators // introduced by @StephenButtolph. - for s := lastOfCurr; s.ParentBlock() != nil && s.Hash() != lastOfParent.Hash(); s = s.ParentBlock() { + for s := lastOfCurr; s.Hash() != lastOfParent.Hash(); s = s.ParentBlock() { settling = append(settling, s) } slices.Reverse(settling) diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go index 8bc86c7..61b0037 100644 --- a/blocks/settlement_test.go +++ b/blocks/settlement_test.go @@ -140,7 +140,7 @@ func TestSettles(t *testing.T) { 9: 7, } wantSettles := map[uint64][]uint64{ - // It is not valid to call Settles() on the genesis block + 0: {0}, 1: nil, 2: nil, 3: nil, From 4810c9cf43aa3c1a32610612e1466967bc5c2e95 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Tue, 16 Sep 2025 20:15:09 +0100 Subject: [PATCH 18/31] refactor: remove override of `nil` map as read-only Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- blocks/block_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/blocks/block_test.go b/blocks/block_test.go index a60447e..cbcd834 100644 --- a/blocks/block_test.go +++ b/blocks/block_test.go @@ -44,10 +44,6 @@ func newChain(tb testing.TB, startHeight, total uint64, lastSettledAtHeight map[ ) byNum := make(map[uint64]*Block) - if lastSettledAtHeight == nil { - lastSettledAtHeight = make(map[uint64]uint64) - } - for i := range total { n := startHeight + i From d3f5b0438b86d879eacca4962efb29ee4aa737c2 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 17 Sep 2025 06:19:26 +0100 Subject: [PATCH 19/31] refactor: rename (again) to `Block.WhenChildSettles()` --- blocks/settlement.go | 29 +++++++++++++++-------------- blocks/settlement_test.go | 16 ++++++++-------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/blocks/settlement.go b/blocks/settlement.go index 99a2e3f..c5e2f8e 100644 --- a/blocks/settlement.go +++ b/blocks/settlement.go @@ -109,25 +109,26 @@ func (b *Block) Settles() []*Block { return settling(b.ParentBlock().LastSettled(), b.LastSettled()) } -// ChildSettles returns the blocks that would be settled by a child of `b`, -// given the last-settled block at the child's block time. Note that the +// WhenChildSettles returns the blocks that would be settled by a child of `b`, +// given the last-settled block at that child's block time. Note that the // last-settled block at the child's time MAY be equal to the last-settled of -// `b` (its parent), in which case ChildSettles returns an empty slice. +// `b` (its parent), in which case WhenChildSettles returns an empty slice. // // The argument is typically the return value of [LastToSettleAt], where that // function receives `b` as the parent. See the Example. // -// ChildSettles MUST only be called before the call to [Block.MarkSettled] on -// `b`. The intention is that this method is called on the VM's preferred block, -// which always meets this criterion. This is by definition of settlement, which -// requires that at least one descendant block has already been accepted, which -// the preference never has. +// WhenChildSettles MUST only be called before the call to [Block.MarkSettled] +// on `b`. The intention is that this method is called on the VM's preferred +// block, which always meets this criterion. This is by definition of +// settlement, which requires that at least one descendant block has already +// been accepted, which the preference never has. // -// ChildSettles is similar to [Block.Settles] but with different definitions of -// `x` and `y` (as described in [Block.Settles]). It is intended for use during -// block building and defines `x` as the block height of `b.LastSettled()` while -// `y` as the height of the argument passed to this method. -func (b *Block) ChildSettles(lastSettledOfChild *Block) []*Block { +// WhenChildSettles is similar to [Block.Settles] but with different definitions +// of `x` and `y` (as described in [Block.Settles]). It is intended for use +// during block building and defines `x` as the block height of +// `b.LastSettled()` while `y` as the height of the argument passed to this +// method. +func (b *Block) WhenChildSettles(lastSettledOfChild *Block) []*Block { return settling(b.LastSettled(), lastSettledOfChild) } @@ -152,7 +153,7 @@ func settling(lastOfParent, lastOfCurr *Block) []*Block { // execution stream is lagging and LastToSettleAt can be called again after some // indeterminate delay. // -// See the Example for [Block.ChildSettles] for one usage of the returned +// See the Example for [Block.WhenChildSettles] for one usage of the returned // block. func LastToSettleAt(settleAt uint64, parent *Block) (*Block, bool) { // These variables are only abstracted for clarity; they are not needed diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go index 61b0037..997744b 100644 --- a/blocks/settlement_test.go +++ b/blocks/settlement_test.go @@ -24,7 +24,7 @@ import ( ) //nolint:testableexamples // Output is meaningless -func ExampleBlock_ChildSettles() { +func ExampleBlock_WhenChildSettles() { parent := blockBuildingPreference() settle, ok := LastToSettleAt(uint64(time.Now().Unix()), parent) //nolint:gosec // Time won't overflow for quite a while if !ok { @@ -33,7 +33,7 @@ func ExampleBlock_ChildSettles() { // Returns the (possibly empty) slice of blocks that would be settled by the // block being built. - _ = parent.ChildSettles(settle) + _ = parent.WhenChildSettles(settle) } // blockBuildingPreference exists only to allow examples to build. @@ -177,27 +177,27 @@ func TestSettles(t *testing.T) { for _, b := range blocks[1:] { tests = append(tests, testCase{ - name: fmt.Sprintf("Block(%d).ChildSettles([same as parent])", b.Height()), - got: b.ChildSettles(b.LastSettled()), + name: fmt.Sprintf("Block(%d).WhenChildSettles([same as parent])", b.Height()), + got: b.WhenChildSettles(b.LastSettled()), want: nil, }) } tests = append(tests, []testCase{ { - got: blocks[7].ChildSettles(blocks[3]), + got: blocks[7].WhenChildSettles(blocks[3]), want: nil, }, { - got: blocks[7].ChildSettles(blocks[4]), + got: blocks[7].WhenChildSettles(blocks[4]), want: numsToBlocks(4), }, { - got: blocks[7].ChildSettles(blocks[5]), + got: blocks[7].WhenChildSettles(blocks[5]), want: numsToBlocks(4, 5), }, { - got: blocks[7].ChildSettles(blocks[6]), + got: blocks[7].WhenChildSettles(blocks[6]), want: numsToBlocks(4, 5, 6), }, }...) From abfc7f3032abdc3f423da433d30755a97c7192f3 Mon Sep 17 00:00:00 2001 From: Stephen Buttolph Date: Wed, 17 Sep 2025 03:00:44 -0400 Subject: [PATCH 20/31] feat: `LastToSettleAt()` supports unknown grandchild execution time (#19) --------- Co-authored-by: Arran Schlosberg --- blocks/settlement.go | 37 ++++++++++++++++++++++++------------- blocks/settlement_test.go | 15 +++++++-------- 2 files changed, 31 insertions(+), 21 deletions(-) diff --git a/blocks/settlement.go b/blocks/settlement.go index c5e2f8e..dfcaca7 100644 --- a/blocks/settlement.go +++ b/blocks/settlement.go @@ -156,39 +156,50 @@ func settling(lastOfParent, lastOfCurr *Block) []*Block { // See the Example for [Block.WhenChildSettles] for one usage of the returned // block. func LastToSettleAt(settleAt uint64, parent *Block) (*Block, bool) { - // These variables are only abstracted for clarity; they are not needed - // beyond the scope of the `for` loop. - var block, child *Block - block = parent // therefore `child` remains nil because it's what we're building + // A block can be the last to settle at some time i.f.f. two criteria are + // met: + // + // 1. The block has finished execution by said time and; + // + // 2. The block's child is known to have *not* finished execution or be + // unable to finish by that time. + // + // The block currently being built can never finish in time, so we start + // with criterion (2) being met. + known := true // The only way [Block.ParentBlock] can be nil is if `block` was already // settled (see invariant in [Block]). If a block was already settled then // only that or a later (i.e. unsettled) block can be returned by this loop, // therefore we have a guarantee that the loop update will never result in // `block==nil`. - for ; ; block, child = block.ParentBlock(), block { + for block := parent; ; block = block.ParentBlock() { if startsNoEarlierThan := block.BuildTime(); startsNoEarlierThan > settleAt { + known = true continue } + // TODO(arr4n) more fine-grained checks are possible by computing the + // minimum possible gas consumption of blocks. For example, + // `block.BuildTime()+block.intrinsicGasSum()` can be compared against + // `settleAt`, as can the sum of a chain of blocks. if t := block.executionExceededSecond.Load(); t != nil && *t >= settleAt { + known = true continue } if e := block.execution.Load(); e != nil { if e.byGas.CompareUnix(settleAt) > 0 { // There may have been a race between this check and the // execution-exceeded one above, so we have to check again. + known = true continue } - return block, true + return block, known } - // TODO(arr4n) more fine-grained checks are possible for scenarios where - // (a) `block` could never execute before `settleAt` so we would - // `continue`; and (b) `block` will definitely execute in time and - // `child` could never, in which case return `nil, false`. - _ = child - - return nil, false + // Note that a grandchild block having unknown execution completion time + // does not rule out knowing a child's completion time, so this could be + // set to true in a future loop iteration. + known = false } } diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go index 997744b..2343e2f 100644 --- a/blocks/settlement_test.go +++ b/blocks/settlement_test.go @@ -272,6 +272,11 @@ func TestLastToSettleAt(t *testing.T) { requireTime(t, 13, 1) blocks[8].markExecutedForTests(t, db, tm) + require.False( + t, blocks[9].Executed(), + "Block 9 MUST remain unexecuted", // exercises lagging-execution logic when building on 9 + ) + for i, b := range blocks { // Setting interim execution time isn't required for the algorithm to // work as it just allows [LastToSettleAt] to return definitive results @@ -327,14 +332,8 @@ func TestLastToSettleAt(t *testing.T) { { settleAt: 9, parent: blocks[9], - // The current implementation is very coarse-grained and MAY return - // false negatives that would simply require a retry after some - // indeterminate period of time. Even though the execution time of - // `blocks[8]` guarantees that `blocks[9]` MUST finish execution - // after the settlement time, our current implementation doesn't - // check this. It is expected that this specific test case will one - // day fail, at which point it MUST be updated to want `blocks[7]`. - wantOK: false, + wantOK: true, + want: blocks[7], }, { settleAt: 15, From 92168842098d8072b93542fe6528298491993c23 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Wed, 17 Sep 2025 08:09:55 +0100 Subject: [PATCH 21/31] refactor: remove unnecessary `type brokenInvariant` Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- blocks/invariants.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/blocks/invariants.go b/blocks/invariants.go index 19532b5..ac08e9f 100644 --- a/blocks/invariants.go +++ b/blocks/invariants.go @@ -10,17 +10,8 @@ import ( "github.com/ava-labs/libevm/trie" ) -type brokenInvariant struct { - b *Block - msg string -} - -func (err brokenInvariant) Error() string { - return fmt.Sprintf("block %d: %s", err.b.Height(), err.msg) -} - func (b *Block) brokenInvariantErr(msg string) error { - return brokenInvariant{b: b, msg: msg} + return fmt.Errorf("block %d: %s", b.Height(), msg) } // A LifeCycleStage defines the progression of a block from acceptance through From 6200fddb6a04ab0fa38830f8a635e3aaa0798001 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 17 Sep 2025 08:11:06 +0100 Subject: [PATCH 22/31] refactor: move `Block.brokenInvariantErr()` implementation --- blocks/invariants.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/blocks/invariants.go b/blocks/invariants.go index ac08e9f..29494ea 100644 --- a/blocks/invariants.go +++ b/blocks/invariants.go @@ -10,10 +10,6 @@ import ( "github.com/ava-labs/libevm/trie" ) -func (b *Block) brokenInvariantErr(msg string) error { - return fmt.Errorf("block %d: %s", b.Height(), msg) -} - // A LifeCycleStage defines the progression of a block from acceptance through // to settlement. type LifeCycleStage int @@ -28,6 +24,10 @@ const ( Accepted = NotExecuted ) +func (b *Block) brokenInvariantErr(msg string) error { + return fmt.Errorf("block %d: %s", b.Height(), msg) +} + // CheckInvariants checks internal invariants against expected stage, typically // only used during database recovery. func (b *Block) CheckInvariants(expect LifeCycleStage) error { From 9c1481446da1af8b96854083c87e1c1068c2fc61 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 17 Sep 2025 09:37:18 +0100 Subject: [PATCH 23/31] chore!: delete `db.go` to defer implementation --- blocks/execution.go | 4 +--- blocks/execution_test.go | 17 ----------------- blocks/settlement_test.go | 21 --------------------- 3 files changed, 1 insertion(+), 41 deletions(-) diff --git a/blocks/execution.go b/blocks/execution.go index 71db6c1..7345fa3 100644 --- a/blocks/execution.go +++ b/blocks/execution.go @@ -75,9 +75,7 @@ func (b *Block) MarkExecuted(db ethdb.Database, byGas *gastime.Time, byWall time rawdb.WriteHeadBlockHash(batch, hash) rawdb.WriteHeadHeaderHash(batch, hash) rawdb.WriteReceipts(batch, hash, b.NumberU64(), receipts) - if err := b.writePostExecutionState(batch, e); err != nil { - return err - } + // TODO(arr4n) persist the [executionResults] if err := batch.Write(); err != nil { return err } diff --git a/blocks/execution_test.go b/blocks/execution_test.go index e2f426c..6784f2d 100644 --- a/blocks/execution_test.go +++ b/blocks/execution_test.go @@ -15,7 +15,6 @@ import ( "github.com/ava-labs/libevm/core/rawdb" "github.com/ava-labs/libevm/core/types" "github.com/ava-labs/libevm/ethdb" - "github.com/ava-labs/libevm/params" "github.com/google/go-cmp/cmp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -125,22 +124,6 @@ func TestMarkExecuted(t *testing.T) { }) t.Run("database", func(t *testing.T) { - t.Run("RestorePostExecutionStateAndReceipts", func(t *testing.T) { - clone := newBlock(t, b.EthBlock(), nil, settles) - err := clone.RestorePostExecutionStateAndReceipts( - db, - params.TestChainConfig, // arbitrary - ) - require.NoError(t, err) - assertPostExecutionVals(t, clone) - }) - - t.Run("StateRootPostExecution", func(t *testing.T) { - got, err := StateRootPostExecution(db, b.NumberU64()) - require.NoError(t, err) - assert.Equal(t, stateRoot, got) - }) - t.Run("head_block", func(t *testing.T) { for fn, got := range map[string]interface{ Hash() common.Hash }{ "ReadHeadBlockHash": selfAsHasher(rawdb.ReadHeadBlockHash(db)), diff --git a/blocks/settlement_test.go b/blocks/settlement_test.go index 2343e2f..3232a59 100644 --- a/blocks/settlement_test.go +++ b/blocks/settlement_test.go @@ -6,7 +6,6 @@ package blocks import ( "context" "fmt" - "math/rand/v2" "testing" "time" @@ -106,26 +105,6 @@ func TestSettlementInvariants(t *testing.T) { }) } -func TestPersistLastSettledNumber(t *testing.T) { - rng := rand.New(rand.NewPCG(0, 0)) //nolint:gosec // Reproducibility is useful for tests - for range 10 { - settledHeight := rng.Uint64() - t.Run(fmt.Sprintf("settled_height_%d", settledHeight), func(t *testing.T) { - settles := newBlock(t, newEthBlock(settledHeight, 0, nil), nil, nil) - b := newBlock(t, newEthBlock(settledHeight+1 /*arbitrary*/, 0, nil), nil, settles) - - db := rawdb.NewMemoryDatabase() - require.NoError(t, b.WriteLastSettledNumber(db), "WriteLastSettledNumber()") - - t.Run("ReadLastSettledNumber", func(t *testing.T) { - got, err := ReadLastSettledNumber(db, b.NumberU64()) - require.NoError(t, err) - require.Equal(t, settles.NumberU64(), got) - }) - }) - } -} - func TestSettles(t *testing.T) { lastSettledAtHeight := map[uint64]uint64{ 0: 0, // genesis block is self-settling by definition From a7fa4a97f6e5233f323d9082f4ab7a3966640621 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 17 Sep 2025 09:41:16 +0100 Subject: [PATCH 24/31] chore!: actually delete `db.go`, not just call sites --- blocks/db.go | 128 --------------------------------------------------- 1 file changed, 128 deletions(-) delete mode 100644 blocks/db.go diff --git a/blocks/db.go b/blocks/db.go deleted file mode 100644 index 7bffae2..0000000 --- a/blocks/db.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (C) 2025, Ava Labs, Inc. All rights reserved. -// See the file LICENSE for licensing terms. - -package blocks - -import ( - "encoding/binary" - "fmt" - "math/big" - "slices" - - "github.com/ava-labs/libevm/common" - "github.com/ava-labs/libevm/core/rawdb" - "github.com/ava-labs/libevm/core/types" - "github.com/ava-labs/libevm/ethdb" - "github.com/ava-labs/libevm/params" - "github.com/ava-labs/libevm/trie" -) - -/* ===== Common =====*/ - -func blockNumDBKey(prefix string, blockNum uint64) []byte { - n := len(prefix) - key := make([]byte, n, n+8) - copy(key, prefix) - return binary.BigEndian.AppendUint64(key, blockNum) -} - -func (b *Block) writeToKVStore(w ethdb.KeyValueWriter, key func(uint64) []byte, val []byte) error { - return w.Put(key(b.NumberU64()), val) -} - -/* ===== Post-execution state =====*/ - -func execResultsDBKey(blockNum uint64) []byte { - return blockNumDBKey("sae-post-exec-", blockNum) -} - -func (b *Block) writePostExecutionState(w ethdb.KeyValueWriter, e *executionResults) error { - return b.writeToKVStore(w, execResultsDBKey, e.MarshalCanoto()) -} - -// RestorePostExecutionState restores b to the same post-execution state as when -// [Block.MarkExecuted] was called on it—this is only expected to be used after -// a restart. The receipts MUST match those originally passed to -// [Block.MarkExecuted] as they will be checked against the persisted Merkle -// root. -// -// This method is considered equivalent to a call to [Block.MarkExecuted] for -// the purposes of post-execution events (e.g. unblocking -// [Block.WaitUntilExecuted]) and artefacts (e.g. [Block.ExecutedByGasTime]). -// Similarly, it MUST NOT be called more than once, and usage of this and -// [Block.MarkExecuted] is mutually exclusive. -func (b *Block) RestorePostExecutionState(db ethdb.Database, receipts types.Receipts) error { - e, err := readExecResults(db, b.NumberU64()) - if err != nil { - return err - } - if argRoot := types.DeriveSha(receipts, trie.NewStackTrie(nil)); argRoot != e.receiptRoot { - return fmt.Errorf( - "restoring execution state of block %d: receipt-root mismatch (db = %v; arg = %v)", - b.Height(), e.receiptRoot, argRoot, - ) - } - e.receipts = slices.Clone(receipts) - return b.markExecuted(e) -} - -// RestorePostExecutionStateAndReceipts is a convenience wrapper for calling -// [rawdb.ReadReceipts], the results of which are propagated to -// [Block.RestorePostExecutionState]. -func (b *Block) RestorePostExecutionStateAndReceipts(db ethdb.Database, config *params.ChainConfig) error { - rs := rawdb.ReadReceipts(db, b.Hash(), b.NumberU64(), b.BuildTime(), config) - return b.RestorePostExecutionState(db, rs) -} - -// StateRootPostExecution returns the state root passed to [Block.MarkExecuted], -// as persisted in the database. The [Block.PostExecutionStateRoot] method is -// the in-memory equivalent of this function. -func StateRootPostExecution(db ethdb.Database, blockNum uint64) (common.Hash, error) { - e, err := readExecResults(db, blockNum) - if err != nil { - return common.Hash{}, err - } - return e.stateRootPost, nil -} - -func readExecResults(db ethdb.Database, num uint64) (*executionResults, error) { - buf, err := db.Get(execResultsDBKey(num)) - if err != nil { - return nil, err - } - e := new(executionResults) - if err := e.UnmarshalCanoto(buf); err != nil { - return nil, err - } - return e, nil -} - -/* ===== Last-settled block at chain height ===== */ - -func lastSettledDBKey(blockNum uint64) []byte { - return blockNumDBKey("sae-last-settled-", blockNum) -} - -// WriteLastSettledNumber writes, to w, the block height of the last-settled -// block of b (i.e. of [Block.LastSettled]). -func (b *Block) WriteLastSettledNumber(w ethdb.KeyValueWriter) error { - return b.writeToKVStore(w, lastSettledDBKey, b.LastSettled().Number().Bytes()) -} - -// ReadLastSettledNumber is the counterpart of [Block.WriteLastSettledNumber], -// returning the height of the last-settled block of the block with the -// specified height. -func ReadLastSettledNumber(db ethdb.Database, blockNum uint64) (uint64, error) { - buf, err := db.Get(lastSettledDBKey(blockNum)) - if err != nil { - return 0, err - } - settled := new(big.Int).SetBytes(buf) - if !settled.IsUint64() { - return 0, fmt.Errorf("read non-uint64 last-settled block of block %d", blockNum) - } - if settled.Uint64() >= blockNum { // only a sense check - return 0, fmt.Errorf("read last-settled block num %d of block %d", settled.Uint64(), blockNum) - } - return settled.Uint64(), nil -} From cdbb8cafc52041c8dad4010e3b06945df7c88c47 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Wed, 17 Sep 2025 14:29:04 +0100 Subject: [PATCH 25/31] fix: guarantee that `LastToSettleAt()` loop exits --- blocks/settlement.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/blocks/settlement.go b/blocks/settlement.go index dfcaca7..087e122 100644 --- a/blocks/settlement.go +++ b/blocks/settlement.go @@ -155,7 +155,14 @@ func settling(lastOfParent, lastOfCurr *Block) []*Block { // // See the Example for [Block.WhenChildSettles] for one usage of the returned // block. -func LastToSettleAt(settleAt uint64, parent *Block) (*Block, bool) { +func LastToSettleAt(settleAt uint64, parent *Block) (b *Block, ok bool) { + defer func() { + // Avoids having to perform this check at every return. + if !ok { + b = nil + } + }() + // A block can be the last to settle at some time i.f.f. two criteria are // met: // @@ -174,6 +181,12 @@ func LastToSettleAt(settleAt uint64, parent *Block) (*Block, bool) { // therefore we have a guarantee that the loop update will never result in // `block==nil`. for block := parent; ; block = block.ParentBlock() { + // Guarantees that the loop will always exit as the last pre-SAE block + // (perhaps the genesis) is always settled, by definition. + if settled := block.ancestry.Load() == nil; settled { + return block, known + } + if startsNoEarlierThan := block.BuildTime(); startsNoEarlierThan > settleAt { known = true continue From 0753526c4a0afca418921a179b9b85018e11a859 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 19 Sep 2025 12:54:40 +0100 Subject: [PATCH 26/31] feat!: `MarkExecuted()` accepts gas base fee --- blocks/execution.go | 13 ++++++++++++- blocks/execution_test.go | 15 +++++++-------- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/blocks/execution.go b/blocks/execution.go index 7345fa3..ea84471 100644 --- a/blocks/execution.go +++ b/blocks/execution.go @@ -7,6 +7,7 @@ import ( "context" "errors" "fmt" + "math/big" "slices" "time" @@ -39,6 +40,7 @@ type executionResults struct { byGas gastime.Time `canoto:"value,1"` byWall time.Time // For metrics only; allowed to be incorrect. + baseFee *big.Int // Receipts are deliberately not stored by the canoto representation as they // are already in the database. All methods that read the stored canoto // either accept a [types.Receipts] for comparison against the @@ -61,10 +63,11 @@ type executionResults struct { // This method MUST NOT be called more than once and its usage is mutually // exclusive of [Block.RestorePostExecutionState]. The wall-clock [time.Time] is // for metrics only. -func (b *Block) MarkExecuted(db ethdb.Database, byGas *gastime.Time, byWall time.Time, receipts types.Receipts, stateRootPost common.Hash) error { +func (b *Block) MarkExecuted(db ethdb.Database, byGas *gastime.Time, byWall time.Time, baseFee *big.Int, receipts types.Receipts, stateRootPost common.Hash) error { e := &executionResults{ byGas: *byGas.Clone(), byWall: byWall, + baseFee: new(big.Int).Set(baseFee), receipts: slices.Clone(receipts), receiptRoot: types.DeriveSha(receipts, trie.NewStackTrie(nil)), stateRootPost: stateRootPost, @@ -142,6 +145,14 @@ func (b *Block) ExecutedByWallTime() time.Time { }) } +// BaseFee returns the base gas price passed to [Block.MarkExecuted] or nil if +// no such successful call has been made. +func (b *Block) BaseFee() *big.Int { + return executionArtefact(b, "receipts", func(e *executionResults) *big.Int { + return new(big.Int).Set(e.baseFee) + }) +} + // Receipts returns the receipts passed to [Block.MarkExecuted] or nil if no // such successful call has been made. func (b *Block) Receipts() types.Receipts { diff --git a/blocks/execution_test.go b/blocks/execution_test.go index 6784f2d..7f6107e 100644 --- a/blocks/execution_test.go +++ b/blocks/execution_test.go @@ -27,7 +27,7 @@ import ( // post-execution artefacts (other than the gas time). func (b *Block) markExecutedForTests(tb testing.TB, db ethdb.Database, tm *gastime.Time) { tb.Helper() - require.NoError(tb, b.MarkExecuted(db, tm, time.Time{}, nil, common.Hash{}), "MarkExecuted()") + require.NoError(tb, b.MarkExecuted(db, tm, time.Time{}, new(big.Int), nil, common.Hash{}), "MarkExecuted()") } func TestMarkExecuted(t *testing.T) { @@ -74,6 +74,7 @@ func TestMarkExecuted(t *testing.T) { call func() any }{ {"ExecutedByGasTime()", func() any { return b.ExecutedByGasTime() }}, + {"BaseFee()", func() any { return b.BaseFee() }}, {"Receipts()", func() any { return b.Receipts() }}, {"PostExecutionStateRoot()", func() any { return b.PostExecutionStateRoot() }}, } @@ -86,22 +87,23 @@ func TestMarkExecuted(t *testing.T) { gasTime := gastime.New(42, 1e6, 42) wallTime := time.Unix(42, 100) stateRoot := common.Hash{'s', 't', 'a', 't', 'e'} + baseFee := big.NewInt(314159) var receipts types.Receipts for _, tx := range txs { receipts = append(receipts, &types.Receipt{ TxHash: tx.Hash(), }) } - require.NoError(t, b.MarkExecuted(db, gasTime, wallTime, receipts, stateRoot), "MarkExecuted()") + require.NoError(t, b.MarkExecuted(db, gasTime, wallTime, baseFee, receipts, stateRoot), "MarkExecuted()") - assertPostExecutionVals := func(t *testing.T, b *Block) { - t.Helper() + t.Run("after_MarkExecuted", func(t *testing.T) { require.True(t, b.Executed(), "Executed()") assert.NoError(t, b.CheckInvariants(Executed), "CheckInvariants(Executed)") require.NoError(t, b.WaitUntilExecuted(context.Background()), "WaitUntilExecuted()") assert.Zero(t, b.ExecutedByGasTime().Compare(gasTime.Time), "ExecutedByGasTime().Compare([original input])") + assert.Zero(t, b.BaseFee().Cmp(baseFee), "BaseFee().Cmp([original input])") assert.Empty(t, cmp.Diff(receipts, b.Receipts(), saetest.CmpByMerkleRoots[types.Receipts]()), "Receipts()") assert.Equal(t, stateRoot, b.PostExecutionStateRoot(), "PostExecutionStateRoot()") // i.e. this block @@ -113,14 +115,11 @@ func TestMarkExecuted(t *testing.T) { t.Run("MarkExecuted_again", func(t *testing.T) { rec := saetest.NewLogRecorder(logging.Warn) b.log = rec - assert.ErrorIs(t, b.MarkExecuted(db, gasTime, wallTime, receipts, stateRoot), errMarkBlockExecutedAgain) + assert.ErrorIs(t, b.MarkExecuted(db, gasTime, wallTime, baseFee, receipts, stateRoot), errMarkBlockExecutedAgain) // The database's head block might have been corrupted so this MUST // be a fatal action. assert.Len(t, rec.At(logging.Fatal), 1, "FATAL logs") }) - } - t.Run("after_MarkExecuted", func(t *testing.T) { - assertPostExecutionVals(t, b) }) t.Run("database", func(t *testing.T) { From e45f69669988a282bf999b3d2aa4bad162cf9cb7 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 19 Sep 2025 12:55:50 +0100 Subject: [PATCH 27/31] chore: remove unused `canoto` functionality --- blocks/execution.canoto.go | 281 ------------------------------------- 1 file changed, 281 deletions(-) delete mode 100644 blocks/execution.canoto.go diff --git a/blocks/execution.canoto.go b/blocks/execution.canoto.go deleted file mode 100644 index de70a45..0000000 --- a/blocks/execution.canoto.go +++ /dev/null @@ -1,281 +0,0 @@ -// Code generated by canoto. DO NOT EDIT. -// versions: -// canoto v0.17.1 -// source: execution.go - -package blocks - -import ( - "io" - "reflect" - "sync/atomic" - - "github.com/StephenButtolph/canoto" -) - -// Ensure that unused imports do not error -var ( - _ atomic.Uint64 - - _ = io.ErrUnexpectedEOF -) - -const ( - canoto__executionResults__byGas__tag = "\x0a" // canoto.Tag(1, canoto.Len) - canoto__executionResults__receiptRoot__tag = "\x12" // canoto.Tag(2, canoto.Len) - canoto__executionResults__stateRootPost__tag = "\x1a" // canoto.Tag(3, canoto.Len) -) - -type canotoData_executionResults struct { - size uint64 -} - -// CanotoSpec returns the specification of this canoto message. -func (*executionResults) CanotoSpec(types ...reflect.Type) *canoto.Spec { - types = append(types, reflect.TypeOf(executionResults{})) - var zero executionResults - s := &canoto.Spec{ - Name: "executionResults", - Fields: []canoto.FieldType{ - canoto.FieldTypeFromField( - /*type inference:*/ (&zero.byGas), - /*FieldNumber: */ 1, - /*Name: */ "byGas", - /*FixedLength: */ 0, - /*Repeated: */ false, - /*OneOf: */ "", - /*types: */ types, - ), - { - FieldNumber: 2, - Name: "receiptRoot", - OneOf: "", - TypeFixedBytes: uint64(len(zero.receiptRoot)), - }, - { - FieldNumber: 3, - Name: "stateRootPost", - OneOf: "", - TypeFixedBytes: uint64(len(zero.stateRootPost)), - }, - }, - } - s.CalculateCanotoCache() - return s -} - -// MakeCanoto creates a new empty value. -func (*executionResults) MakeCanoto() *executionResults { - return new(executionResults) -} - -// UnmarshalCanoto unmarshals a Canoto-encoded byte slice into the struct. -// -// During parsing, the canoto cache is saved. -func (c *executionResults) UnmarshalCanoto(bytes []byte) error { - r := canoto.Reader{ - B: bytes, - } - return c.UnmarshalCanotoFrom(r) -} - -// UnmarshalCanotoFrom populates the struct from a [canoto.Reader]. Most users -// should just use UnmarshalCanoto. -// -// During parsing, the canoto cache is saved. -// -// This function enables configuration of reader options. -func (c *executionResults) UnmarshalCanotoFrom(r canoto.Reader) error { - // Zero the struct before unmarshaling. - *c = executionResults{} - atomic.StoreUint64(&c.canotoData.size, uint64(len(r.B))) - - var minField uint32 - for canoto.HasNext(&r) { - field, wireType, err := canoto.ReadTag(&r) - if err != nil { - return err - } - if field < minField { - return canoto.ErrInvalidFieldOrder - } - - switch field { - case 1: - if wireType != canoto.Len { - return canoto.ErrUnexpectedWireType - } - - // Read the bytes for the field. - originalUnsafe := r.Unsafe - r.Unsafe = true - var msgBytes []byte - if err := canoto.ReadBytes(&r, &msgBytes); err != nil { - return err - } - if len(msgBytes) == 0 { - return canoto.ErrZeroValue - } - r.Unsafe = originalUnsafe - - // Unmarshal the field from the bytes. - remainingBytes := r.B - r.B = msgBytes - if err := (&c.byGas).UnmarshalCanotoFrom(r); err != nil { - return err - } - r.B = remainingBytes - case 2: - if wireType != canoto.Len { - return canoto.ErrUnexpectedWireType - } - - const ( - expectedLength = len(c.receiptRoot) - expectedLengthUint64 = uint64(expectedLength) - ) - var length uint64 - if err := canoto.ReadUint(&r, &length); err != nil { - return err - } - if length != expectedLengthUint64 { - return canoto.ErrInvalidLength - } - if expectedLength > len(r.B) { - return io.ErrUnexpectedEOF - } - - copy((&c.receiptRoot)[:], r.B) - if canoto.IsZero(c.receiptRoot) { - return canoto.ErrZeroValue - } - r.B = r.B[expectedLength:] - case 3: - if wireType != canoto.Len { - return canoto.ErrUnexpectedWireType - } - - const ( - expectedLength = len(c.stateRootPost) - expectedLengthUint64 = uint64(expectedLength) - ) - var length uint64 - if err := canoto.ReadUint(&r, &length); err != nil { - return err - } - if length != expectedLengthUint64 { - return canoto.ErrInvalidLength - } - if expectedLength > len(r.B) { - return io.ErrUnexpectedEOF - } - - copy((&c.stateRootPost)[:], r.B) - if canoto.IsZero(c.stateRootPost) { - return canoto.ErrZeroValue - } - r.B = r.B[expectedLength:] - default: - return canoto.ErrUnknownField - } - - minField = field + 1 - } - return nil -} - -// ValidCanoto validates that the struct can be correctly marshaled into the -// Canoto format. -// -// Specifically, ValidCanoto ensures: -// 1. All OneOfs are specified at most once. -// 2. All strings are valid utf-8. -// 3. All custom fields are ValidCanoto. -func (c *executionResults) ValidCanoto() bool { - if c == nil { - return true - } - if !(&c.byGas).ValidCanoto() { - return false - } - return true -} - -// CalculateCanotoCache populates size and OneOf caches based on the current -// values in the struct. -// -// It is not safe to copy this struct concurrently. -func (c *executionResults) CalculateCanotoCache() { - if c == nil { - return - } - var size uint64 - (&c.byGas).CalculateCanotoCache() - if fieldSize := (&c.byGas).CachedCanotoSize(); fieldSize != 0 { - size += uint64(len(canoto__executionResults__byGas__tag)) + canoto.SizeUint(fieldSize) + fieldSize - } - if !canoto.IsZero(c.receiptRoot) { - size += uint64(len(canoto__executionResults__receiptRoot__tag)) + canoto.SizeBytes((&c.receiptRoot)[:]) - } - if !canoto.IsZero(c.stateRootPost) { - size += uint64(len(canoto__executionResults__stateRootPost__tag)) + canoto.SizeBytes((&c.stateRootPost)[:]) - } - atomic.StoreUint64(&c.canotoData.size, size) -} - -// CachedCanotoSize returns the previously calculated size of the Canoto -// representation from CalculateCanotoCache. -// -// If CalculateCanotoCache has not yet been called, it will return 0. -// -// If the struct has been modified since the last call to CalculateCanotoCache, -// the returned size may be incorrect. -func (c *executionResults) CachedCanotoSize() uint64 { - if c == nil { - return 0 - } - return atomic.LoadUint64(&c.canotoData.size) -} - -// MarshalCanoto returns the Canoto representation of this struct. -// -// It is assumed that this struct is ValidCanoto. -// -// It is not safe to copy this struct concurrently. -func (c *executionResults) MarshalCanoto() []byte { - c.CalculateCanotoCache() - w := canoto.Writer{ - B: make([]byte, 0, c.CachedCanotoSize()), - } - w = c.MarshalCanotoInto(w) - return w.B -} - -// MarshalCanotoInto writes the struct into a [canoto.Writer] and returns the -// resulting [canoto.Writer]. Most users should just use MarshalCanoto. -// -// It is assumed that CalculateCanotoCache has been called since the last -// modification to this struct. -// -// It is assumed that this struct is ValidCanoto. -// -// It is not safe to copy this struct concurrently. -func (c *executionResults) MarshalCanotoInto(w canoto.Writer) canoto.Writer { - if c == nil { - return w - } - if fieldSize := (&c.byGas).CachedCanotoSize(); fieldSize != 0 { - canoto.Append(&w, canoto__executionResults__byGas__tag) - canoto.AppendUint(&w, fieldSize) - w = (&c.byGas).MarshalCanotoInto(w) - } - if !canoto.IsZero(c.receiptRoot) { - canoto.Append(&w, canoto__executionResults__receiptRoot__tag) - canoto.AppendBytes(&w, (&c.receiptRoot)[:]) - } - if !canoto.IsZero(c.stateRootPost) { - canoto.Append(&w, canoto__executionResults__stateRootPost__tag) - canoto.AppendBytes(&w, (&c.stateRootPost)[:]) - } - return w -} From 2d8b93cc0dd482787789ce79b639dad4c260c489 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 19 Sep 2025 12:56:50 +0100 Subject: [PATCH 28/31] chore: remove unused `canoto` tags --- blocks/execution.go | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/blocks/execution.go b/blocks/execution.go index ea84471..0fc9fd5 100644 --- a/blocks/execution.go +++ b/blocks/execution.go @@ -37,8 +37,8 @@ func (b *Block) SetInterimExecutionTime(t *proxytime.Time[gas.Gas]) { } type executionResults struct { - byGas gastime.Time `canoto:"value,1"` - byWall time.Time // For metrics only; allowed to be incorrect. + byGas gastime.Time + byWall time.Time // For metrics only; allowed to be incorrect. baseFee *big.Int // Receipts are deliberately not stored by the canoto representation as they @@ -46,10 +46,8 @@ type executionResults struct { // either accept a [types.Receipts] for comparison against the // `receiptRoot`, or don't care about receipts at all. receipts types.Receipts - receiptRoot common.Hash `canoto:"fixed bytes,2"` - stateRootPost common.Hash `canoto:"fixed bytes,3"` - - canotoData canotoData_executionResults + receiptRoot common.Hash + stateRootPost common.Hash } // MarkExecuted marks the block as having been executed at the specified time(s) From ec708b0a6a224f4ea36aef87d8784e37d1aef0ea Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Mon, 22 Sep 2025 13:44:22 +0100 Subject: [PATCH 29/31] doc: differences between `MarkSettled()` and `MarkSynchronous()` methods --- blocks/settlement.go | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/blocks/settlement.go b/blocks/settlement.go index 087e122..2b77bb2 100644 --- a/blocks/settlement.go +++ b/blocks/settlement.go @@ -44,6 +44,10 @@ func (b *Block) MarkSettled() error { // last pre-SAE block, which MAY be the genesis block. These are, by definition, // self-settling so require special treatment as such behaviour is impossible // under SAE rules. +// +// Wherever MarkSynchronous results in different behaviour to +// [Block.MarkSettled], the respective methods are documented as such. They can +// otherwise be considered identical. func (b *Block) MarkSynchronous() error { b.synchronous = true return b.MarkSettled() @@ -83,8 +87,9 @@ func (b *Block) ParentBlock() *Block { } // LastSettled returns the last-settled block at the time of b's acceptance, -// unless [Block.MarkSettled] has been called, in which case it returns nil. -// Note that this value might not be distinct between contiguous blocks. +// unless [Block.MarkSettled] has been called, in which case it returns nil. If +// [Block.MarkSynchronous] was called instead, LastSettled always returns `b` +// itself. Note that this value might not be distinct between contiguous blocks. func (b *Block) LastSettled() *Block { if b.synchronous { return b @@ -101,7 +106,8 @@ func (b *Block) LastSettled() *Block { // therefore returns a disjoint (and possibly empty) set of historical blocks. // // It is not valid to call Settles after a call to [Block.MarkSettled] on either -// b or its parent. +// b or its parent. If [Block.MarkSynchronous] was called instead, Settles +// always returns a single-element slice of `b` itself. func (b *Block) Settles() []*Block { if b.synchronous { return []*Block{b} From 12890b70cc2a8b0227357d698429fd8fab96ae38 Mon Sep 17 00:00:00 2001 From: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> Date: Fri, 26 Sep 2025 08:24:34 +0100 Subject: [PATCH 30/31] refactor: use `require.Zero` Co-authored-by: Stephen Buttolph Signed-off-by: Arran Schlosberg <519948+ARR4N@users.noreply.github.com> --- blocks/block_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blocks/block_test.go b/blocks/block_test.go index cbcd834..7053882 100644 --- a/blocks/block_test.go +++ b/blocks/block_test.go @@ -53,7 +53,7 @@ func newChain(tb testing.TB, startHeight, total uint64, lastSettledAtHeight map[ ) if s, ok := lastSettledAtHeight[n]; ok { if s == n { - require.Equal(tb, uint64(0), s, "Only genesis block is self-settling") + require.Zero(tb, s, "Only genesis block is self-settling") synchronous = true } else { require.Less(tb, s, n, "Last-settled height MUST be <= current height") From 321e284982653d2c86742df726f8e35a7d7752ce Mon Sep 17 00:00:00 2001 From: Arran Schlosberg Date: Fri, 26 Sep 2025 08:28:00 +0100 Subject: [PATCH 31/31] doc: `LastSttled()` and `ParentBlock()` error logging --- blocks/settlement.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/blocks/settlement.go b/blocks/settlement.go index 2b77bb2..b825cdf 100644 --- a/blocks/settlement.go +++ b/blocks/settlement.go @@ -79,7 +79,7 @@ const ( ) // ParentBlock returns the block's parent unless [Block.MarkSettled] has been -// called, in which case it returns nil. +// called, in which case it returns nil and logs an error. func (b *Block) ParentBlock() *Block { return b.ancestor(getParentOfSettledErrMsg, func(a *ancestry) *Block { return a.parent @@ -87,9 +87,10 @@ func (b *Block) ParentBlock() *Block { } // LastSettled returns the last-settled block at the time of b's acceptance, -// unless [Block.MarkSettled] has been called, in which case it returns nil. If -// [Block.MarkSynchronous] was called instead, LastSettled always returns `b` -// itself. Note that this value might not be distinct between contiguous blocks. +// unless [Block.MarkSettled] has been called, in which case it returns nil and +// logs an error. If [Block.MarkSynchronous] was called instead, LastSettled +// always returns `b` itself, without logging. Note that this value might not be +// distinct between contiguous blocks. func (b *Block) LastSettled() *Block { if b.synchronous { return b