Skip to content

Commit e470b2d

Browse files
committed
feat: rlp.ParseTree()
1 parent 127c631 commit e470b2d

File tree

3 files changed

+400
-6
lines changed

3 files changed

+400
-6
lines changed

core/types/rlp_backwards_compat.libevm_test.go

Lines changed: 62 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,37 @@
1+
// Copyright 2024 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+
117
package types_test
218

319
import (
4-
"bytes"
520
"encoding/hex"
21+
"math/big"
622
"testing"
723

824
"github.com/stretchr/testify/assert"
925
"github.com/stretchr/testify/require"
1026

1127
. "github.com/ava-labs/libevm/core/types"
1228
"github.com/ava-labs/libevm/libevm/ethtest"
29+
"github.com/ava-labs/libevm/rlp"
1330
)
1431

1532
func TestHeaderRLPBackwardsCompatibility(t *testing.T) {
33+
// This is a deliberate change-detector test that locks in backwards
34+
// compatibility of RLP encoding.
1635
rng := ethtest.NewPseudoRand(42)
1736

1837
const numExtraBytes = 16
@@ -43,11 +62,48 @@ func TestHeaderRLPBackwardsCompatibility(t *testing.T) {
4362
require.Equal(t, len(BlockNonce{}), rng.Read(hdr.Nonce[:]))
4463
require.Equal(t, numExtraBytes, rng.Read(hdr.Extra))
4564

46-
var got bytes.Buffer
47-
require.NoError(t, hdr.EncodeRLP(&got))
48-
65+
// WARNING: changing this hex might break backwards compatibility of RLP
66+
// encoding (i.e. block hashes might change)!
4967
const wantHex = `f9029aa01a571e7e4d774caf46053201cfe0001b3c355ffcc93f510e671e8809741f0eeda0756095410506ec72a2c287fe83ebf68efb0be177e61acec1c985277e90e52087941bfc3bc193012ba58912c01fb35a3454831a8971a00bc9f064144eb5965c5e5d1020f9f90392e7e06ded9225966abc7c754b410e61a0d942eab201424f4320ec1e1ffa9390baf941629b9349977b5d48e0502dbb9386a035d9d550a9c113f78689b4c161c4605609bb57b83061914c42ad244daa7fc38eb90100718d155798390a6c6782181d1bac1dd64cd956332b008412ddc735f2994e297c8a088c6bb4c637542295ba3cbc3cd399c8127076f4d834d74d5b11a36b6d02e2fe3a583216aa4ccef052df9a96e7a454256bebabdfc38c429079f25913e0f1d7416b2f056c4a115fc757012b1757d2d69f0e5fb87c08605098d9031fa37cd0df6942c5a2da12a4424b978febf5479896165caf573cf82fb3aa10f6ebf6b62bef8ed36b8ea3d4b1ddb80c99afafa37cb8f3393eb6d802f5bc6c8cd6bcd168a7e0061a718218b848d945135b6dff228a4e66bade4717e6f4d318ac98fca12a053af6f98805a764fb5d523cb6f69029522cab9ced907cc75718f7e2c79154ef3fc7a04b31d39ae246d689f23176d679a62ff328f530407cbafd0146f45b2ed635282e88b36f6a5752feff5b881fc7fa9ef217f81d889f073433138e6ba58857515405d28f2a8e904bcda3066d382675f37dd1a18507b5fba02812f2701021506f27190adb52a1313f6d28c77d66ae1aa3d3d6757a762476f488294c7768cddd9ccf881b5da1b6a47970a3a0c8a2b7b2c44161190c82d5e1c8b55e05c7354f1e5f6512924c941fb3d93667dc889bc9df25654e163c88859405c51041475fa03a8c304a732153e20300c3482832d07b65f97958360da414cb438ce252aec6c2`
5068
want, err := hex.DecodeString(wantHex)
51-
require.NoError(t, err)
52-
assert.Equal(t, want, got.Bytes())
69+
require.NoError(t, err, "hex.DecodeString()")
70+
71+
got, err := rlp.EncodeToBytes(hdr)
72+
require.NoErrorf(t, err, "rlp.EncodeToBytes(%T)", hdr)
73+
assert.Equalf(t, want, got, "rlp.EncodeToBytes(%T)", hdr)
74+
75+
t.Run("ParseTree", func(t *testing.T) {
76+
got, err := rlp.ParseTree(got)
77+
require.NoErrorf(t, err, "rlp.ParseTree(rlp.EncodeToBytes(%T))", hdr)
78+
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)
108+
})
53109
}

rlp/tree.libevm.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
// Copyright 2024 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 rlp
18+
19+
import (
20+
"encoding/binary"
21+
"errors"
22+
"io"
23+
)
24+
25+
// An ItemNode is a parsed RLP item as part of a tree, which may have only a
26+
// root node. Nodes contain only their unpacked values, not their length- and
27+
// type-denoting tags.
28+
type ItemNode interface {
29+
rlpItem()
30+
}
31+
32+
var _ = []ItemNode{ListNode(nil), StringNode(nil), ByteNode(0)}
33+
34+
// A ListNode is a slice of RLP items. It is the ItemNode equivalent of [List].
35+
type ListNode []ItemNode
36+
37+
// A StringNode is an RLP [ItemNode] holding an arbitrary byte slice. It is the
38+
// ItemNode equivalent of [String].
39+
type StringNode []byte
40+
41+
// An ByteNode is an RLP [ItemNode] representing an unsigned integer <= 127. It
42+
// is the ItemNode equivalent of [Byte].
43+
//
44+
// [ParseTree] will only return an ByteNode if the value is in the range [0,127]
45+
// but an ByteNode MAY be outside of this range for the purpose of re-encoding.
46+
type ByteNode byte
47+
48+
func (ListNode) rlpItem() {}
49+
func (StringNode) rlpItem() {}
50+
func (ByteNode) rlpItem() {}
51+
52+
var (
53+
errConcatenated = errors.New("concatenated items outside of list")
54+
errTrailingBytes = errors.New("trailing bytes after parsing")
55+
errTooLong = errors.New("parsing >8 big-endian bytes")
56+
)
57+
58+
// ParseTree parses the RLP-encoded buffer and returns one of the concrete
59+
// ItemNode types. All [StringNode] instances will be backed by the same memory
60+
// as the argument received by ParseTree.
61+
func ParseTree(rlp []byte) (ItemNode, error) {
62+
return parse(rlp, false /*inList*/)
63+
}
64+
65+
// parseList is a convenience wrapper around [slicer.short] and [slicer.long],
66+
// returning their return buffer as a [ListNode].
67+
func parseList(str []byte, err error) (ItemNode, error) {
68+
if err != nil {
69+
return nil, err
70+
}
71+
return parse(str, true)
72+
}
73+
74+
func parse(rlp []byte, inList bool) (ItemNode, error) {
75+
buf := &slicer{buf: rlp, i: 0}
76+
var items []ItemNode
77+
78+
for eof := false; !eof; {
79+
switch tag, err := buf.byte(); {
80+
case err == io.EOF:
81+
eof = true
82+
83+
case err != nil:
84+
// Impossible but being defensive in case of a future refactor.
85+
return nil, err
86+
87+
case tag <= 0x7f:
88+
items = append(items, ByteNode(tag))
89+
90+
case tag <= 0xb7:
91+
str, err := buf.short(tag, 0x80)
92+
if err != nil {
93+
return nil, err
94+
}
95+
items = append(items, StringNode(str))
96+
97+
case tag <= 0xbf:
98+
str, err := buf.long(tag, 0xb7)
99+
if err != nil {
100+
return nil, err
101+
}
102+
items = append(items, StringNode(str))
103+
104+
case tag <= 0xf7:
105+
list, err := parseList(buf.short(tag, 0xc0))
106+
if err != nil {
107+
return nil, err
108+
}
109+
items = append(items, list)
110+
111+
default:
112+
list, err := parseList(buf.long(tag, 0xf7))
113+
if err != nil {
114+
return nil, err
115+
}
116+
items = append(items, list)
117+
}
118+
119+
if !inList && len(items) > 1 {
120+
return nil, errConcatenated
121+
}
122+
}
123+
124+
if n := buf.left(); n > 0 {
125+
return nil, errTrailingBytes
126+
}
127+
if inList {
128+
return ListNode(items), nil
129+
}
130+
return items[0], nil
131+
}
132+
133+
// A slicer is a byte-slice reader that returns slices backed by the same memory
134+
// as its buffer.
135+
type slicer struct {
136+
buf []byte
137+
i uint64
138+
}
139+
140+
func (s *slicer) len() uint64 {
141+
return uint64(len(s.buf))
142+
}
143+
144+
func (s *slicer) left() uint64 {
145+
return s.len() - s.i
146+
}
147+
148+
// next returns the next `n` bytes.
149+
func (s *slicer) next(n uint64) ([]byte, error) {
150+
if n > s.left() {
151+
return nil, io.EOF
152+
}
153+
b := s.buf[s.i : s.i+n]
154+
s.i += n
155+
return b, nil
156+
}
157+
158+
func (s *slicer) byte() (byte, error) {
159+
b, err := s.next(1)
160+
if err != nil {
161+
return 0, err
162+
}
163+
return b[0], nil
164+
}
165+
166+
// short returns the bytes encoding either a string or a list of <=55 bytes.
167+
func (s *slicer) short(tag, base byte) ([]byte, error) {
168+
return s.next(uint64(tag - base))
169+
}
170+
171+
// long returns the bytes encoding either a string or a list of >55 bytes, first
172+
// reading the length.
173+
func (s *slicer) long(tag, base byte) ([]byte, error) {
174+
n, err := s.bigEndian(uint64(tag - base))
175+
if err != nil {
176+
return nil, err
177+
}
178+
return s.next(n)
179+
}
180+
181+
// bigEndian returns the next `nBytes` bytes interpreted as a big-endian uint64.
182+
func (s *slicer) bigEndian(nBytes uint64) (uint64, error) {
183+
if nBytes > 8 {
184+
return 0, errTooLong
185+
}
186+
buf, err := s.next(nBytes)
187+
if err != nil {
188+
return 0, err
189+
}
190+
191+
var padded [8]byte
192+
copy(padded[8-len(buf):], buf)
193+
return binary.BigEndian.Uint64(padded[:]), nil
194+
}

0 commit comments

Comments
 (0)