Skip to content

Commit

Permalink
feat: cmpeth package for testing C-chain Body RLP decoding
Browse files Browse the repository at this point in the history
  • Loading branch information
ARR4N committed Jan 31, 2025
1 parent 069b026 commit 3fac457
Show file tree
Hide file tree
Showing 4 changed files with 187 additions and 158 deletions.
7 changes: 7 additions & 0 deletions core/types/block.libevm.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"io"

"github.com/ava-labs/libevm/libevm/pseudo"
"github.com/ava-labs/libevm/libevm/testonly"
"github.com/ava-labs/libevm/rlp"
)

Expand Down Expand Up @@ -182,6 +183,12 @@ type BodyHooks interface {
DecodeExtraRLPFields(*rlp.Stream) error
}

func TestOnlyRegisterBodyHooks(h BodyHooks) {
testonly.OrPanic(func() {
todoRegisteredBodyHooks = h
})
}

var todoRegisteredBodyHooks BodyHooks = NOOPBodyHooks{}

func (b *Body) hooks() BodyHooks {
Expand Down
135 changes: 0 additions & 135 deletions core/types/cchain_compat.libevm_test.go

This file was deleted.

138 changes: 115 additions & 23 deletions core/types/rlp_backwards_compat.libevm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
package types_test

import (
"bytes"
"encoding/hex"
"fmt"
"testing"
Expand All @@ -26,7 +25,9 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

"github.com/ava-labs/libevm/common"
. "github.com/ava-labs/libevm/core/types"
"github.com/ava-labs/libevm/libevm/cmpeth"
"github.com/ava-labs/libevm/libevm/ethtest"
"github.com/ava-labs/libevm/rlp"
)
Expand Down Expand Up @@ -193,10 +194,8 @@ func testBodyRLPBackwardsCompatibility(t *testing.T, seed uint64) {
require.NoErrorf(t, err, "rlp.DecodeBytes(..., %T)", got)

opts := cmp.Options{
cmp.Comparer(func(a, b *Header) bool {
return a.Hash() == b.Hash()
}),
cmp.Comparer(txComparer(t)),
cmpeth.CompareHeadersByHash(),
cmpeth.CompareTransactionsByBinary(t),
}
if diff := cmp.Diff(body, got, opts); diff != "" {
t.Errorf("rlp.DecodeBytes(rlp.EncodeToBytes(%T)) diff (-want +got):\n%s", body, diff)
Expand All @@ -206,23 +205,116 @@ func testBodyRLPBackwardsCompatibility(t *testing.T, seed uint64) {
}
}

// txComparer returns an equality checker for use with [cmp.Comparer].
func txComparer(tb testing.TB) func(_, _ *Transaction) bool {
tb.Helper()
return func(a, b *Transaction) bool {
tb.Helper()

if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}

aBuf, err := a.MarshalBinary()
require.NoErrorf(tb, err, "%T.MarshalBinary()", a)
bBuf, err := b.MarshalBinary()
require.NoErrorf(tb, err, "%T.MarshalBinary()", b)
return bytes.Equal(aBuf, bBuf)
// cChainBodyExtras carries the same additional fields as the ava-labs/coreth
// [Body] and implements [BodyHooks] to achieve equivalent RLP {en,de}coding.
type cChainBodyExtras struct {
Version uint32
ExtData *[]byte
}

var _ BodyHooks = (*cChainBodyExtras)(nil)

func (e *cChainBodyExtras) AppendRLPFields(b rlp.EncoderBuffer, _ bool) error {
b.WriteUint64(uint64(e.Version))
if e.ExtData != nil {
b.WriteString(string(*e.ExtData))
} else {
b.WriteString("")
}
return nil
}

func (e *cChainBodyExtras) DecodeExtraRLPFields(s *rlp.Stream) error {
if err := s.Decode(&e.Version); err != nil {
return err
}

buf, err := s.Bytes()
if err != nil {
return err
}
if len(buf) > 0 {
e.ExtData = &buf
} else {
// Respect the `rlp:"nil"` field tag.
e.ExtData = nil
}

return nil
}

func TestBodyRLPCChainCompat(t *testing.T) {
// The inputs to this test were used to generate the expected RLP with
// ava-labs/coreth. This serves as both an example of how to use [BodyHooks]
// and a test of compatibility.

t.Cleanup(func() {
TestOnlyRegisterBodyHooks(NOOPBodyHooks{})
})

to := common.HexToAddress(`decafc0ffeebad`)
body := &Body{
Transactions: []*Transaction{
NewTx(&LegacyTx{
Nonce: 42,
To: &to,
}),
},
Uncles: []*Header{ /* RLP encoding differs in ava-labs/coreth */ },
}

const version = 314159
tests := []struct {
name string
extra *cChainBodyExtras
// WARNING: changing these values might break backwards compatibility of
// RLP encoding!
wantRLPHex string
}{
{
extra: &cChainBodyExtras{
Version: version,
},
wantRLPHex: `e5dedd2a80809400000000000000000000000000decafc0ffeebad8080808080c08304cb2f80`,
},
{
extra: &cChainBodyExtras{
Version: version,
ExtData: &[]byte{1, 4, 2, 8, 5, 7},
},
wantRLPHex: `ebdedd2a80809400000000000000000000000000decafc0ffeebad8080808080c08304cb2f86010402080507`,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
wantRLP, err := hex.DecodeString(tt.wantRLPHex)
require.NoError(t, err)

t.Run("Encode", func(t *testing.T) {
TestOnlyRegisterBodyHooks(tt.extra)
got, err := rlp.EncodeToBytes(body)
require.NoError(t, err)
assert.Equal(t, wantRLP, got)
})

t.Run("Decode", func(t *testing.T) {
var extra cChainBodyExtras
TestOnlyRegisterBodyHooks(&extra)

got := new(Body)
err := rlp.DecodeBytes(wantRLP, got)
require.NoError(t, err)
assert.Equal(t, tt.extra, &extra)

opts := cmp.Options{
cmpeth.CompareHeadersByHash(),
cmpeth.CompareTransactionsByBinary(t),
}
if diff := cmp.Diff(body, got, opts); diff != "" {
t.Errorf("%s", diff)
}
})
})
}
}
65 changes: 65 additions & 0 deletions libevm/cmpeth/cmpeth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2025 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 cmpeth provides ETH-specific options for the cmp package.
package cmpeth

import (
"bytes"
"testing"

"github.com/ava-labs/libevm/core/types"
"github.com/google/go-cmp/cmp"
)

// CompareHeadersByHash returns an option to compare Headers based on
// [types.Header.Hash] equality.
func CompareHeadersByHash() cmp.Option {
return cmp.Comparer(func(a, b *types.Header) bool {
return a.Hash() == b.Hash()
})
}

// CompareTransactionsByBinary returns an option to compare Transactions based
// on [types.Transaction.MarshalBinary] equality. Two nil pointers are
// considered equal.
//
// If MarshalBinary() returns an error, it will be reported with
// [testing.TB.Fatal].
func CompareTransactionsByBinary(tb testing.TB) cmp.Option {
tb.Helper()
return cmp.Comparer(func(a, b *types.Transaction) bool {
tb.Helper()

if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}

return bytes.Equal(marshalTxBinary(tb, a), marshalTxBinary(tb, b))
})
}

func marshalTxBinary(tb testing.TB, tx *types.Transaction) []byte {
tb.Helper()
buf, err := tx.MarshalBinary()
if err != nil {
tb.Fatalf("%T.MarshalBinary() error %v", tx, err)
}
return buf
}

0 comments on commit 3fac457

Please sign in to comment.