diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 1282b281d63..542e16247d3 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -18,6 +18,6 @@ jobs:
go-version: 1.21.4
- name: Run tests
run: | # Upstream flakes are race conditions exacerbated by concurrent tests
- FLAKY_REGEX='go-ethereum/(eth|accounts/keystore|eth/downloader|miner|ethclient|ethclient/gethclient|eth/catalyst)$';
+ FLAKY_REGEX='go-ethereum/(eth|eth/tracers/logger|accounts/keystore|eth/downloader|miner|ethclient|ethclient/gethclient|eth/catalyst)$';
go list ./... | grep -P "${FLAKY_REGEX}" | xargs -n 1 go test -short;
go test -short $(go list ./... | grep -Pv "${FLAKY_REGEX}");
diff --git a/libevm/ethtest/rand.go b/libevm/ethtest/rand.go
index 8584ce11698..eb7f47fda02 100644
--- a/libevm/ethtest/rand.go
+++ b/libevm/ethtest/rand.go
@@ -13,11 +13,13 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see
// .
+
package ethtest
import (
"math/big"
+ "github.com/holiman/uint256"
"golang.org/x/exp/rand"
"github.com/ethereum/go-ethereum/common"
@@ -33,9 +35,16 @@ func NewPseudoRand(seed uint64) *PseudoRand {
return &PseudoRand{rand.New(rand.NewSource(seed))}
}
+// Read is equivalent to [rand.Rand.Read] except that it doesn't return an error
+// because it is guaranteed to be nil.
+func (r *PseudoRand) Read(p []byte) int {
+ n, _ := r.Rand.Read(p) // Guaranteed nil error
+ return n
+}
+
// Address returns a pseudorandom address.
func (r *PseudoRand) Address() (a common.Address) {
- r.Read(a[:]) //nolint:gosec,errcheck // Guaranteed nil error
+ r.Read(a[:])
return a
}
@@ -47,14 +56,20 @@ func (r *PseudoRand) AddressPtr() *common.Address {
// Hash returns a pseudorandom hash.
func (r *PseudoRand) Hash() (h common.Hash) {
- r.Read(h[:]) //nolint:gosec,errcheck // Guaranteed nil error
+ r.Read(h[:])
return h
}
+// HashPtr returns a pointer to a pseudorandom hash.
+func (r *PseudoRand) HashPtr() *common.Hash {
+ h := r.Hash()
+ return &h
+}
+
// Bytes returns `n` pseudorandom bytes.
func (r *PseudoRand) Bytes(n uint) []byte {
b := make([]byte, n)
- r.Read(b) //nolint:gosec,errcheck // Guaranteed nil error
+ r.Read(b)
return b
}
@@ -62,3 +77,14 @@ func (r *PseudoRand) Bytes(n uint) []byte {
func (r *PseudoRand) BigUint64() *big.Int {
return new(big.Int).SetUint64(r.Uint64())
}
+
+// Uint64Ptr returns a pointer to a pseudorandom uint64.
+func (r *PseudoRand) Uint64Ptr() *uint64 {
+ u := r.Uint64()
+ return &u
+}
+
+// Uint256 returns a random 256-bit unsigned int.
+func (r *PseudoRand) Uint256() *uint256.Int {
+ return new(uint256.Int).SetBytes(r.Bytes(32))
+}
diff --git a/libevm/pseudo/constructor.go b/libevm/pseudo/constructor.go
index 42457278572..d72237494de 100644
--- a/libevm/pseudo/constructor.go
+++ b/libevm/pseudo/constructor.go
@@ -13,6 +13,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see
// .
+
package pseudo
// A Constructor returns newly constructed [Type] instances for a pre-registered
diff --git a/libevm/pseudo/reflect.go b/libevm/pseudo/reflect.go
new file mode 100644
index 00000000000..45f5a940ea0
--- /dev/null
+++ b/libevm/pseudo/reflect.go
@@ -0,0 +1,60 @@
+// Copyright 2024 the libevm authors.
+//
+// The libevm additions to go-ethereum are free software: you can redistribute
+// them and/or modify them under the terms of the GNU Lesser General Public License
+// as published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The libevm additions are distributed in the hope that they will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see
+// .
+
+package pseudo
+
+import (
+ "reflect"
+
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+// Reflection is used as a last resort in pseudo types so is limited to this
+// file to avoid being seen as the norm. If you are adding to this file, please
+// try to achieve the same results with type parameters.
+
+func (c *concrete[T]) isZero() bool {
+ // The alternative would require that T be comparable, which would bubble up
+ // and invade the rest of the code base.
+ return reflect.ValueOf(c.val).IsZero()
+}
+
+func (c *concrete[T]) equal(t *Type) bool {
+ d, ok := t.val.(*concrete[T])
+ if !ok {
+ return false
+ }
+ switch v := any(c.val).(type) {
+ case EqualityChecker[T]:
+ return v.Equal(d.val)
+ default:
+ // See rationale for reflection in [concrete.isZero].
+ return reflect.DeepEqual(c.val, d.val)
+ }
+}
+
+func (c *concrete[T]) DecodeRLP(s *rlp.Stream) error {
+ switch v := reflect.ValueOf(c.val); v.Kind() {
+ case reflect.Pointer:
+ if v.IsNil() {
+ el := v.Type().Elem()
+ c.val = reflect.New(el).Interface().(T) //nolint:forcetypeassert // Invariant scoped to the last few lines of code so simple to verify
+ }
+ return s.Decode(c.val)
+ default:
+ return s.Decode(&c.val)
+ }
+}
diff --git a/libevm/pseudo/rlp_test.go b/libevm/pseudo/rlp_test.go
new file mode 100644
index 00000000000..8d72bd6de1f
--- /dev/null
+++ b/libevm/pseudo/rlp_test.go
@@ -0,0 +1,86 @@
+// Copyright 2024 the libevm authors.
+//
+// The libevm additions to go-ethereum are free software: you can redistribute
+// them and/or modify them under the terms of the GNU Lesser General Public License
+// as published by the Free Software Foundation, either version 3 of the License,
+// or (at your option) any later version.
+//
+// The libevm additions are distributed in the hope that they will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
+// General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see
+// .
+
+package pseudo_test
+
+import (
+ "math/big"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/ethereum/go-ethereum/core/types"
+ "github.com/ethereum/go-ethereum/libevm/ethtest"
+ "github.com/ethereum/go-ethereum/libevm/pseudo"
+ "github.com/ethereum/go-ethereum/rlp"
+)
+
+func TestRLPEquivalence(t *testing.T) {
+ t.Parallel()
+
+ for seed := uint64(0); seed < 20; seed++ {
+ seed := seed
+
+ t.Run("fuzz pointer-type round trip", func(t *testing.T) {
+ t.Parallel()
+ rng := ethtest.NewPseudoRand(seed)
+
+ hdr := &types.Header{
+ ParentHash: rng.Hash(),
+ UncleHash: rng.Hash(),
+ Coinbase: rng.Address(),
+ Root: rng.Hash(),
+ TxHash: rng.Hash(),
+ ReceiptHash: rng.Hash(),
+ Difficulty: big.NewInt(rng.Int63()),
+ Number: big.NewInt(rng.Int63()),
+ GasLimit: rng.Uint64(),
+ GasUsed: rng.Uint64(),
+ Time: rng.Uint64(),
+ Extra: rng.Bytes(uint(rng.Uint64n(128))),
+ MixDigest: rng.Hash(),
+ }
+ rng.Read(hdr.Bloom[:])
+ rng.Read(hdr.Nonce[:])
+
+ want, err := rlp.EncodeToBytes(hdr)
+ require.NoErrorf(t, err, "rlp.EncodeToBytes(%T)", hdr)
+
+ typ := pseudo.From(hdr).Type
+ gotRLP, err := rlp.EncodeToBytes(typ)
+ require.NoErrorf(t, err, "rlp.EncodeToBytes(%T)", typ)
+
+ require.Equalf(t, want, gotRLP, "RLP encoding of %T (canonical) vs %T (under test)", hdr, typ)
+
+ t.Run("decode", func(t *testing.T) {
+ pseudo := pseudo.Zero[*types.Header]()
+ require.NoErrorf(t, rlp.DecodeBytes(gotRLP, pseudo.Type), "rlp.DecodeBytes(..., %T[%T])", pseudo.Type, hdr)
+ require.Equal(t, hdr, pseudo.Value.Get(), "RLP-decoded value")
+ })
+ })
+
+ t.Run("fuzz non-pointer decode", func(t *testing.T) {
+ rng := ethtest.NewPseudoRand(seed)
+ x := rng.Uint64()
+ buf, err := rlp.EncodeToBytes(x)
+ require.NoErrorf(t, err, "rlp.EncodeToBytes(%T)", x)
+
+ pseudo := pseudo.Zero[uint64]()
+ require.NoErrorf(t, rlp.DecodeBytes(buf, pseudo.Type), "rlp.DecodeBytes(..., %T[%T])", pseudo.Type, x)
+ require.Equal(t, x, pseudo.Value.Get(), "RLP-decoded value")
+ })
+ }
+}
diff --git a/libevm/pseudo/type.go b/libevm/pseudo/type.go
index 21eb31e2aa2..3f5e4f26677 100644
--- a/libevm/pseudo/type.go
+++ b/libevm/pseudo/type.go
@@ -31,6 +31,9 @@ package pseudo
import (
"encoding/json"
"fmt"
+ "io"
+
+ "github.com/ethereum/go-ethereum/rlp"
)
// A Type wraps a strongly-typed value without exposing information about its
@@ -121,6 +124,21 @@ func MustNewValue[T any](t *Type) *Value[T] {
return v
}
+// IsZero reports whether t carries the the zero value for its type.
+func (t *Type) IsZero() bool { return t.val.isZero() }
+
+// An EqualityChecker reports if it is equal to another value of the same type.
+type EqualityChecker[T any] interface {
+ Equal(T) bool
+}
+
+// Equal reports whether t carries a value equal to that carried by u. If t and
+// u carry different types then Equal returns false. If t and u carry the same
+// type and said type implements [EqualityChecker] then Equal propagates the
+// value returned by the checker. In all other cases, Equal returns
+// [reflect.DeepEqual] performed on the payloads carried by t and u.
+func (t *Type) Equal(u *Type) bool { return t.val.equal(u) }
+
// Get returns the value.
func (v *Value[T]) Get() T { return v.t.val.get().(T) } //nolint:forcetypeassert // invariant
@@ -139,6 +157,12 @@ func (v *Value[T]) MarshalJSON() ([]byte, error) { return v.t.MarshalJSON() }
// UnmarshalJSON implements the [json.Unmarshaler] interface.
func (v *Value[T]) UnmarshalJSON(b []byte) error { return v.t.UnmarshalJSON(b) }
+// EncodeRLP implements the [rlp.Encoder] interface.
+func (t *Type) EncodeRLP(w io.Writer) error { return t.val.EncodeRLP(w) }
+
+// DecodeRLP implements the [rlp.Decoder] interface.
+func (t *Type) DecodeRLP(s *rlp.Stream) error { return t.val.DecodeRLP(s) }
+
var _ = []interface {
json.Marshaler
json.Unmarshaler
@@ -148,15 +172,27 @@ var _ = []interface {
(*concrete[struct{}])(nil),
}
+var _ = []interface {
+ rlp.Encoder
+ rlp.Decoder
+}{
+ (*Type)(nil),
+ (*concrete[struct{}])(nil),
+}
+
// A value is a non-generic wrapper around a [concrete] struct.
type value interface {
get() any
+ isZero() bool
+ equal(*Type) bool
canSetTo(any) bool
set(any) error
mustSet(any)
json.Marshaler
json.Unmarshaler
+ rlp.Encoder
+ rlp.Decoder
}
type concrete[T any] struct {
@@ -210,3 +246,5 @@ func (c *concrete[T]) UnmarshalJSON(b []byte) error {
c.val = v
return nil
}
+
+func (c *concrete[T]) EncodeRLP(w io.Writer) error { return rlp.Encode(w, c.val) }
diff --git a/libevm/pseudo/type_test.go b/libevm/pseudo/type_test.go
index d68348cff06..2413ae421ed 100644
--- a/libevm/pseudo/type_test.go
+++ b/libevm/pseudo/type_test.go
@@ -13,6 +13,7 @@
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see
// .
+
package pseudo
import (
@@ -116,3 +117,58 @@ func TestPointer(t *testing.T) {
assert.Equal(t, 314159, val.Get().payload, "after setting via pointer")
})
}
+
+func TestIsZero(t *testing.T) {
+ tests := []struct {
+ typ *Type
+ want bool
+ }{
+ {From(0).Type, true},
+ {From(1).Type, false},
+ {From("").Type, true},
+ {From("x").Type, false},
+ {From((*testing.T)(nil)).Type, true},
+ {From(t).Type, false},
+ {From(false).Type, true},
+ {From(true).Type, false},
+ }
+
+ for _, tt := range tests {
+ assert.Equalf(t, tt.want, tt.typ.IsZero(), "%T(%[1]v) IsZero()", tt.typ.Interface())
+ }
+}
+
+type isEqualStub struct {
+ isEqual bool
+}
+
+var _ EqualityChecker[isEqualStub] = (*isEqualStub)(nil)
+
+func (s isEqualStub) Equal(isEqualStub) bool {
+ return s.isEqual
+}
+
+func TestEqual(t *testing.T) {
+ isEqual := isEqualStub{true}
+ notEqual := isEqualStub{false}
+
+ tests := []struct {
+ a, b *Type
+ want bool
+ }{
+ {From(42).Type, From(42).Type, true},
+ {From(99).Type, From("").Type, false},
+ {From(false).Type, From("").Type, false}, // sorry JavaScript, you're wrong
+ {From(isEqual).Type, From(isEqual).Type, true},
+ {From(notEqual).Type, From(notEqual).Type, false},
+ }
+
+ for _, tt := range tests {
+ t.Run("", func(t *testing.T) {
+ t.Logf("a = %+v", tt.a)
+ t.Logf("b = %+v", tt.b)
+ assert.Equal(t, tt.want, tt.a.Equal(tt.b), "a.Equals(b)")
+ assert.Equal(t, tt.want, tt.b.Equal(tt.a), "b.Equals(a)")
+ })
+ }
+}