Skip to content

Commit 3a491f9

Browse files
committed
feat: rlp.ItemNode.EncodeRLP()
1 parent e470b2d commit 3a491f9

File tree

4 files changed

+269
-54
lines changed

4 files changed

+269
-54
lines changed

core/types/rlp_backwards_compat.libevm_test.go

Lines changed: 39 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package types_test
1818

1919
import (
2020
"encoding/hex"
21+
"fmt"
2122
"math/big"
2223
"testing"
2324

@@ -65,45 +66,50 @@ func TestHeaderRLPBackwardsCompatibility(t *testing.T) {
6566
// WARNING: changing this hex might break backwards compatibility of RLP
6667
// encoding (i.e. block hashes might change)!
6768
const wantHex = `f9029aa01a571e7e4d774caf46053201cfe0001b3c355ffcc93f510e671e8809741f0eeda0756095410506ec72a2c287fe83ebf68efb0be177e61acec1c985277e90e52087941bfc3bc193012ba58912c01fb35a3454831a8971a00bc9f064144eb5965c5e5d1020f9f90392e7e06ded9225966abc7c754b410e61a0d942eab201424f4320ec1e1ffa9390baf941629b9349977b5d48e0502dbb9386a035d9d550a9c113f78689b4c161c4605609bb57b83061914c42ad244daa7fc38eb90100718d155798390a6c6782181d1bac1dd64cd956332b008412ddc735f2994e297c8a088c6bb4c637542295ba3cbc3cd399c8127076f4d834d74d5b11a36b6d02e2fe3a583216aa4ccef052df9a96e7a454256bebabdfc38c429079f25913e0f1d7416b2f056c4a115fc757012b1757d2d69f0e5fb87c08605098d9031fa37cd0df6942c5a2da12a4424b978febf5479896165caf573cf82fb3aa10f6ebf6b62bef8ed36b8ea3d4b1ddb80c99afafa37cb8f3393eb6d802f5bc6c8cd6bcd168a7e0061a718218b848d945135b6dff228a4e66bade4717e6f4d318ac98fca12a053af6f98805a764fb5d523cb6f69029522cab9ced907cc75718f7e2c79154ef3fc7a04b31d39ae246d689f23176d679a62ff328f530407cbafd0146f45b2ed635282e88b36f6a5752feff5b881fc7fa9ef217f81d889f073433138e6ba58857515405d28f2a8e904bcda3066d382675f37dd1a18507b5fba02812f2701021506f27190adb52a1313f6d28c77d66ae1aa3d3d6757a762476f488294c7768cddd9ccf881b5da1b6a47970a3a0c8a2b7b2c44161190c82d5e1c8b55e05c7354f1e5f6512924c941fb3d93667dc889bc9df25654e163c88859405c51041475fa03a8c304a732153e20300c3482832d07b65f97958360da414cb438ce252aec6c2`
68-
want, err := hex.DecodeString(wantHex)
69+
wantRLP, err := hex.DecodeString(wantHex)
6970
require.NoError(t, err, "hex.DecodeString()")
7071

71-
got, err := rlp.EncodeToBytes(hdr)
72+
gotRLP, err := rlp.EncodeToBytes(hdr)
7273
require.NoErrorf(t, err, "rlp.EncodeToBytes(%T)", hdr)
73-
assert.Equalf(t, want, got, "rlp.EncodeToBytes(%T)", hdr)
74+
assert.Equalf(t, wantRLP, gotRLP, "rlp.EncodeToBytes(%T)", hdr)
75+
76+
type (
77+
l = rlp.ListNode
78+
s = rlp.StringNode
79+
)
80+
u64Bytes := func(u uint64) []byte { return new(big.Int).SetUint64(u).Bytes() }
81+
headerAsTree := l{
82+
s(hdr.ParentHash[:]),
83+
s(hdr.UncleHash[:]),
84+
s(hdr.Coinbase[:]),
85+
s(hdr.Root[:]),
86+
s(hdr.TxHash[:]),
87+
s(hdr.ReceiptHash[:]),
88+
s(hdr.Bloom[:]),
89+
s(hdr.Difficulty.Bytes()),
90+
s(hdr.Number.Bytes()),
91+
s(u64Bytes(hdr.GasLimit)),
92+
s(u64Bytes(hdr.GasUsed)),
93+
s(u64Bytes(hdr.Time)),
94+
s(hdr.Extra[:]),
95+
s(hdr.MixDigest[:]),
96+
s(hdr.Nonce[:]),
97+
s(hdr.BaseFee.Bytes()),
98+
s(hdr.WithdrawalsHash[:]),
99+
s(u64Bytes(*hdr.BlobGasUsed)),
100+
s(u64Bytes(*hdr.ExcessBlobGas)),
101+
s(hdr.ParentBeaconRoot[:]),
102+
}
74103

75104
t.Run("ParseTree", func(t *testing.T) {
76-
got, err := rlp.ParseTree(got)
105+
got, err := rlp.ParseTree(gotRLP)
77106
require.NoErrorf(t, err, "rlp.ParseTree(rlp.EncodeToBytes(%T))", hdr)
107+
assert.Equal(t, headerAsTree, got)
108+
})
78109

79-
type (
80-
l = rlp.ListNode
81-
s = rlp.StringNode
82-
)
83-
u64Bytes := func(u uint64) []byte { return new(big.Int).SetUint64(u).Bytes() }
84-
want := l{
85-
s(hdr.ParentHash[:]),
86-
s(hdr.UncleHash[:]),
87-
s(hdr.Coinbase[:]),
88-
s(hdr.Root[:]),
89-
s(hdr.TxHash[:]),
90-
s(hdr.ReceiptHash[:]),
91-
s(hdr.Bloom[:]),
92-
s(hdr.Difficulty.Bytes()),
93-
s(hdr.Number.Bytes()),
94-
s(u64Bytes(hdr.GasLimit)),
95-
s(u64Bytes(hdr.GasUsed)),
96-
s(u64Bytes(hdr.Time)),
97-
s(hdr.Extra[:]),
98-
s(hdr.MixDigest[:]),
99-
s(hdr.Nonce[:]),
100-
s(hdr.BaseFee.Bytes()),
101-
s(hdr.WithdrawalsHash[:]),
102-
s(u64Bytes(*hdr.BlobGasUsed)),
103-
s(u64Bytes(*hdr.ExcessBlobGas)),
104-
s(hdr.ParentBeaconRoot[:]),
105-
}
106-
107-
assert.Equal(t, want, got)
110+
t.Run(fmt.Sprintf("%T.EncodeRLP()", headerAsTree), func(t *testing.T) {
111+
got, err := rlp.EncodeToBytes(headerAsTree)
112+
require.NoError(t, err)
113+
assert.Equal(t, wantRLP, got)
108114
})
109115
}

rlp/tree.libevm.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ import (
2626
// root node. Nodes contain only their unpacked values, not their length- and
2727
// type-denoting tags.
2828
type ItemNode interface {
29-
rlpItem()
29+
headerAndDataLengths() (uint64, uint64)
30+
Encoder
3031
}
3132

3233
var _ = []ItemNode{ListNode(nil), StringNode(nil), ByteNode(0)}
@@ -45,10 +46,6 @@ type StringNode []byte
4546
// but an ByteNode MAY be outside of this range for the purpose of re-encoding.
4647
type ByteNode byte
4748

48-
func (ListNode) rlpItem() {}
49-
func (StringNode) rlpItem() {}
50-
func (ByteNode) rlpItem() {}
51-
5249
var (
5350
errConcatenated = errors.New("concatenated items outside of list")
5451
errTrailingBytes = errors.New("trailing bytes after parsing")

rlp/tree.libevm_test.go

Lines changed: 105 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,19 @@ package rlp
1818

1919
import (
2020
"bytes"
21+
"encoding/binary"
2122
"encoding/hex"
2223
"math/rand"
2324
"runtime"
2425
"testing"
2526

2627
"github.com/google/go-cmp/cmp"
2728
"github.com/google/go-cmp/cmp/cmpopts"
29+
"github.com/stretchr/testify/assert"
2830
"github.com/stretchr/testify/require"
2931
)
3032

31-
func TestParseTreeNoErrorOnUpstreamTests(t *testing.T) {
33+
func TestTreeRoundTripOnUpstream(t *testing.T) {
3234
for _, tt := range encTests {
3335
t.Run("", func(t *testing.T) {
3436
if tt.error != "" {
@@ -47,24 +49,28 @@ func TestParseTreeNoErrorOnUpstreamTests(t *testing.T) {
4749
buf, err := hex.DecodeString(tt.output)
4850
require.NoErrorf(t, err, "hex.DecodeString(%T.output)", tt)
4951

50-
_, err = ParseTree(buf)
52+
node, err := ParseTree(buf)
5153
require.NoErrorf(t, err, "ParseTree(%T => %#x)", tt.val, buf)
54+
55+
var got bytes.Buffer
56+
require.NoError(t, node.EncodeRLP(&got))
57+
assert.Equal(t, buf, got.Bytes())
5258
})
5359
}
5460
}
5561

56-
func TestParseTree(t *testing.T) {
62+
func TestTreeRoundTripFuzzed(t *testing.T) {
5763
t.Parallel()
5864

5965
type test struct {
6066
fuzzed bool
6167
fuzzSeed int64
6268
value any
63-
want ItemNode
69+
node ItemNode
6470
}
6571
tests := []test{{
6672
value: []byte(nil),
67-
want: StringNode{},
73+
node: StringNode{},
6874
}}
6975

7076
for i := 0; i < 3e3; i++ {
@@ -75,7 +81,7 @@ func TestParseTree(t *testing.T) {
7581
fuzzed: true,
7682
fuzzSeed: seed,
7783
value: val,
78-
want: node,
84+
node: node,
7985
})
8086
}
8187

@@ -91,16 +97,25 @@ func TestParseTree(t *testing.T) {
9197
t.Logf("Fuzzing seed: %d", tt.fuzzSeed)
9298
}
9399

94-
buf := bytes.NewBuffer(nil)
95-
require.NoError(t, Encode(buf, tt.value))
96-
t.Logf("RLP encoding of %T: %#x\nValue: %x", tt.value, buf.Bytes(), tt.value)
97-
98-
got, err := ParseTree(buf.Bytes())
99-
require.NoError(t, err)
100-
101-
if diff := cmp.Diff(tt.want, got, cmpopts.EquateEmpty()); diff != "" {
102-
t.Errorf("%s", diff)
103-
}
100+
rlp, err := EncodeToBytes(tt.value)
101+
require.NoError(t, err, "EncodeToBytes([concrete value])")
102+
t.Logf("RLP encoding of %T: %#x\nValue: %x", tt.value, rlp, tt.value)
103+
104+
t.Run("ParseTree", func(t *testing.T) {
105+
got, err := ParseTree(rlp)
106+
require.NoError(t, err, "ParseTree()")
107+
if diff := cmp.Diff(tt.node, got, cmpopts.EquateEmpty()); diff != "" {
108+
t.Errorf("ParseTree() diff (-want +got): \n%s", diff)
109+
}
110+
})
111+
112+
t.Run("ItemNode.EncodeRLP()", func(t *testing.T) {
113+
got, err := EncodeToBytes(tt.node)
114+
require.NoErrorf(t, err, "EncodeToBytes(..., %T)", tt.node)
115+
if diff := cmp.Diff(rlp, got); diff != "" {
116+
t.Errorf("diff -Encode([concrete values]) +Encode(%T):\n%s", tt.node, diff)
117+
}
118+
})
104119
})
105120
}
106121
}
@@ -142,3 +157,77 @@ func randomItemNode(rng *rand.Rand) (any, ItemNode) {
142157
return vals, list
143158
}
144159
}
160+
161+
func TestEncodeItemNote(t *testing.T) {
162+
encodeToHex := func(v any) string {
163+
b, err := EncodeToBytes(v)
164+
require.NoError(t, err)
165+
return hex.EncodeToString(b)
166+
}
167+
makeBytes := func(n int) []byte {
168+
return append([]byte{1}, make([]byte, n-1)...)
169+
}
170+
171+
tests := []struct {
172+
node ItemNode
173+
wantHex string
174+
}{
175+
{ByteNode(0), "00"},
176+
{ByteNode(1), "01"},
177+
{ByteNode(127), "7f"},
178+
{ByteNode(128), "8180"},
179+
{ByteNode(255), "81ff"},
180+
{StringNode{}, "80"},
181+
{StringNode{0}, "8100"},
182+
{StringNode{1, 2, 3, 4, 5, 6, 7, 8, 9}, "89010203040506070809"},
183+
{
184+
StringNode(makeBytes(55)),
185+
encodeToHex(makeBytes(55)),
186+
},
187+
{
188+
StringNode(makeBytes(56)),
189+
encodeToHex(makeBytes(56)),
190+
},
191+
}
192+
193+
for _, tt := range tests {
194+
var got bytes.Buffer
195+
require.NoError(t, tt.node.EncodeRLP(&got))
196+
assert.Equal(t, unhex(tt.wantHex), got.Bytes())
197+
}
198+
}
199+
200+
func TestByteLength(t *testing.T) {
201+
tests := []struct{ n, want uint64 }{
202+
{0, 0},
203+
{1<<8 - 1, 1},
204+
{1 << 8, 2},
205+
{1<<16 - 1, 2},
206+
{1 << 16, 3},
207+
{1<<24 - 1, 3},
208+
{1 << 24, 4},
209+
}
210+
for _, tt := range tests {
211+
assert.Equalf(t, tt.want, byteLength(tt.n), "byteLength(%d)", tt.n)
212+
}
213+
}
214+
215+
func TestBigEndian(t *testing.T) {
216+
assert.Empty(t, bigEndian(0), "bigEndian(0)")
217+
218+
rng := rand.New(rand.NewSource(42)) //nolint:gosec // Reproducible fuzzing required
219+
220+
for i := 1; i <= 8; i++ {
221+
prefix := make([]byte, 8-i)
222+
for j := 0; j < 100; j++ {
223+
suffix := make([]byte, i)
224+
for suffix[0] == 0 {
225+
rng.Read(suffix) //nolint:gosec // Documented as always returning nil error
226+
}
227+
228+
n := binary.BigEndian.Uint64(append(prefix, suffix...))
229+
assert.Equalf(t, suffix, bigEndian(n), "bigEndian(%d)", n)
230+
}
231+
}
232+
233+
}

0 commit comments

Comments
 (0)