Skip to content

Commit 3fac457

Browse files
committed
feat: cmpeth package for testing C-chain Body RLP decoding
1 parent 069b026 commit 3fac457

File tree

4 files changed

+187
-158
lines changed

4 files changed

+187
-158
lines changed

core/types/block.libevm.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"io"
2323

2424
"github.com/ava-labs/libevm/libevm/pseudo"
25+
"github.com/ava-labs/libevm/libevm/testonly"
2526
"github.com/ava-labs/libevm/rlp"
2627
)
2728

@@ -182,6 +183,12 @@ type BodyHooks interface {
182183
DecodeExtraRLPFields(*rlp.Stream) error
183184
}
184185

186+
func TestOnlyRegisterBodyHooks(h BodyHooks) {
187+
testonly.OrPanic(func() {
188+
todoRegisteredBodyHooks = h
189+
})
190+
}
191+
185192
var todoRegisteredBodyHooks BodyHooks = NOOPBodyHooks{}
186193

187194
func (b *Body) hooks() BodyHooks {

core/types/cchain_compat.libevm_test.go

Lines changed: 0 additions & 135 deletions
This file was deleted.

core/types/rlp_backwards_compat.libevm_test.go

Lines changed: 115 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package types_test
1818

1919
import (
20-
"bytes"
2120
"encoding/hex"
2221
"fmt"
2322
"testing"
@@ -26,7 +25,9 @@ import (
2625
"github.com/stretchr/testify/assert"
2726
"github.com/stretchr/testify/require"
2827

28+
"github.com/ava-labs/libevm/common"
2929
. "github.com/ava-labs/libevm/core/types"
30+
"github.com/ava-labs/libevm/libevm/cmpeth"
3031
"github.com/ava-labs/libevm/libevm/ethtest"
3132
"github.com/ava-labs/libevm/rlp"
3233
)
@@ -193,10 +194,8 @@ func testBodyRLPBackwardsCompatibility(t *testing.T, seed uint64) {
193194
require.NoErrorf(t, err, "rlp.DecodeBytes(..., %T)", got)
194195

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

209-
// txComparer returns an equality checker for use with [cmp.Comparer].
210-
func txComparer(tb testing.TB) func(_, _ *Transaction) bool {
211-
tb.Helper()
212-
return func(a, b *Transaction) bool {
213-
tb.Helper()
214-
215-
if a == nil && b == nil {
216-
return true
217-
}
218-
if a == nil || b == nil {
219-
return false
220-
}
221-
222-
aBuf, err := a.MarshalBinary()
223-
require.NoErrorf(tb, err, "%T.MarshalBinary()", a)
224-
bBuf, err := b.MarshalBinary()
225-
require.NoErrorf(tb, err, "%T.MarshalBinary()", b)
226-
return bytes.Equal(aBuf, bBuf)
208+
// cChainBodyExtras carries the same additional fields as the ava-labs/coreth
209+
// [Body] and implements [BodyHooks] to achieve equivalent RLP {en,de}coding.
210+
type cChainBodyExtras struct {
211+
Version uint32
212+
ExtData *[]byte
213+
}
214+
215+
var _ BodyHooks = (*cChainBodyExtras)(nil)
216+
217+
func (e *cChainBodyExtras) AppendRLPFields(b rlp.EncoderBuffer, _ bool) error {
218+
b.WriteUint64(uint64(e.Version))
219+
if e.ExtData != nil {
220+
b.WriteString(string(*e.ExtData))
221+
} else {
222+
b.WriteString("")
223+
}
224+
return nil
225+
}
226+
227+
func (e *cChainBodyExtras) DecodeExtraRLPFields(s *rlp.Stream) error {
228+
if err := s.Decode(&e.Version); err != nil {
229+
return err
230+
}
231+
232+
buf, err := s.Bytes()
233+
if err != nil {
234+
return err
235+
}
236+
if len(buf) > 0 {
237+
e.ExtData = &buf
238+
} else {
239+
// Respect the `rlp:"nil"` field tag.
240+
e.ExtData = nil
241+
}
242+
243+
return nil
244+
}
245+
246+
func TestBodyRLPCChainCompat(t *testing.T) {
247+
// The inputs to this test were used to generate the expected RLP with
248+
// ava-labs/coreth. This serves as both an example of how to use [BodyHooks]
249+
// and a test of compatibility.
250+
251+
t.Cleanup(func() {
252+
TestOnlyRegisterBodyHooks(NOOPBodyHooks{})
253+
})
254+
255+
to := common.HexToAddress(`decafc0ffeebad`)
256+
body := &Body{
257+
Transactions: []*Transaction{
258+
NewTx(&LegacyTx{
259+
Nonce: 42,
260+
To: &to,
261+
}),
262+
},
263+
Uncles: []*Header{ /* RLP encoding differs in ava-labs/coreth */ },
264+
}
265+
266+
const version = 314159
267+
tests := []struct {
268+
name string
269+
extra *cChainBodyExtras
270+
// WARNING: changing these values might break backwards compatibility of
271+
// RLP encoding!
272+
wantRLPHex string
273+
}{
274+
{
275+
extra: &cChainBodyExtras{
276+
Version: version,
277+
},
278+
wantRLPHex: `e5dedd2a80809400000000000000000000000000decafc0ffeebad8080808080c08304cb2f80`,
279+
},
280+
{
281+
extra: &cChainBodyExtras{
282+
Version: version,
283+
ExtData: &[]byte{1, 4, 2, 8, 5, 7},
284+
},
285+
wantRLPHex: `ebdedd2a80809400000000000000000000000000decafc0ffeebad8080808080c08304cb2f86010402080507`,
286+
},
287+
}
288+
289+
for _, tt := range tests {
290+
t.Run(tt.name, func(t *testing.T) {
291+
wantRLP, err := hex.DecodeString(tt.wantRLPHex)
292+
require.NoError(t, err)
293+
294+
t.Run("Encode", func(t *testing.T) {
295+
TestOnlyRegisterBodyHooks(tt.extra)
296+
got, err := rlp.EncodeToBytes(body)
297+
require.NoError(t, err)
298+
assert.Equal(t, wantRLP, got)
299+
})
300+
301+
t.Run("Decode", func(t *testing.T) {
302+
var extra cChainBodyExtras
303+
TestOnlyRegisterBodyHooks(&extra)
304+
305+
got := new(Body)
306+
err := rlp.DecodeBytes(wantRLP, got)
307+
require.NoError(t, err)
308+
assert.Equal(t, tt.extra, &extra)
309+
310+
opts := cmp.Options{
311+
cmpeth.CompareHeadersByHash(),
312+
cmpeth.CompareTransactionsByBinary(t),
313+
}
314+
if diff := cmp.Diff(body, got, opts); diff != "" {
315+
t.Errorf("%s", diff)
316+
}
317+
})
318+
})
227319
}
228320
}

libevm/cmpeth/cmpeth.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// Copyright 2025 the libevm authors.
2+
//
3+
// The libevm additions to go-ethereum are free software: you can redistribute
4+
// them and/or modify them under the terms of the GNU Lesser General Public License
5+
// as published by the Free Software Foundation, either version 3 of the License,
6+
// or (at your option) any later version.
7+
//
8+
// The libevm additions are distributed in the hope that they will be useful,
9+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
11+
// General Public License for more details.
12+
//
13+
// You should have received a copy of the GNU Lesser General Public License
14+
// along with the go-ethereum library. If not, see
15+
// <http://www.gnu.org/licenses/>.
16+
17+
// Package cmpeth provides ETH-specific options for the cmp package.
18+
package cmpeth
19+
20+
import (
21+
"bytes"
22+
"testing"
23+
24+
"github.com/ava-labs/libevm/core/types"
25+
"github.com/google/go-cmp/cmp"
26+
)
27+
28+
// CompareHeadersByHash returns an option to compare Headers based on
29+
// [types.Header.Hash] equality.
30+
func CompareHeadersByHash() cmp.Option {
31+
return cmp.Comparer(func(a, b *types.Header) bool {
32+
return a.Hash() == b.Hash()
33+
})
34+
}
35+
36+
// CompareTransactionsByBinary returns an option to compare Transactions based
37+
// on [types.Transaction.MarshalBinary] equality. Two nil pointers are
38+
// considered equal.
39+
//
40+
// If MarshalBinary() returns an error, it will be reported with
41+
// [testing.TB.Fatal].
42+
func CompareTransactionsByBinary(tb testing.TB) cmp.Option {
43+
tb.Helper()
44+
return cmp.Comparer(func(a, b *types.Transaction) bool {
45+
tb.Helper()
46+
47+
if a == nil && b == nil {
48+
return true
49+
}
50+
if a == nil || b == nil {
51+
return false
52+
}
53+
54+
return bytes.Equal(marshalTxBinary(tb, a), marshalTxBinary(tb, b))
55+
})
56+
}
57+
58+
func marshalTxBinary(tb testing.TB, tx *types.Transaction) []byte {
59+
tb.Helper()
60+
buf, err := tx.MarshalBinary()
61+
if err != nil {
62+
tb.Fatalf("%T.MarshalBinary() error %v", tx, err)
63+
}
64+
return buf
65+
}

0 commit comments

Comments
 (0)