mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-21 14:14:30 +00:00
feat: pseudo.Type RLP round-tripping (#43)
All commits except the last two constitute PRs #43 and #44. The last two reverted files such that only changes to the `pseudo` and `ethtest` packages remain; once this is merged into the `libevm` branch then `libevm` will be merged into the branch for #44 too. Cherry-picking commits was not possible as some touched both halves of the changes; the squash-merges will, however, make this convoluted history irrelevant. * feat: `types.StateAccount` pseudo-generic payload * feat: registration of `StateAccount` payload type * chore: mark `eth/tracers/logger` flaky * chore: copyright header + `gci` * test: lock default `types.SlimAccount` RLP encoding * feat: `vm.SlimAccount.Extra` from `StateAccount` equiv * chore: placate the linter * test: `pseudo.Type.EncodeRLP()` * test: `pseudo.Type.DecodeRLP()` * fix: `pseudo.Type.DecodeRLP()` with non-pointer type * feat: `pseudo.Type.IsZero()` and `Type.Equal(*Type)` * feat: `types.StateAccountExtra.DecodeRLP()` * chore: revert non-pseudo-package modifications * chore: delete non-pseudo-package additions
This commit is contained in:
parent
1478c18b9a
commit
336a289f42
7 changed files with 271 additions and 4 deletions
2
.github/workflows/go.yml
vendored
2
.github/workflows/go.yml
vendored
|
|
@ -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}");
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
package pseudo
|
||||
|
||||
// A Constructor returns newly constructed [Type] instances for a pre-registered
|
||||
|
|
|
|||
60
libevm/pseudo/reflect.go
Normal file
60
libevm/pseudo/reflect.go
Normal file
|
|
@ -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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
86
libevm/pseudo/rlp_test.go
Normal file
86
libevm/pseudo/rlp_test.go
Normal file
|
|
@ -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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
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")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
// <http://www.gnu.org/licenses/>.
|
||||
|
||||
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)")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue