Skip to content

Commit 7cdd301

Browse files
holimanfjl
authored andcommitted
rlp: support for uint256 (ethereum#26898)
This adds built-in support in package rlp for encoding, decoding and generating code dealing with uint256.Int. --------- Co-authored-by: Felix Lange <fjl@twurst.com>
1 parent cc85c3f commit 7cdd301

File tree

10 files changed

+316
-2
lines changed

10 files changed

+316
-2
lines changed

rlp/decode.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import (
2929
"sync"
3030

3131
"github.com/ethereum/go-ethereum/rlp/internal/rlpstruct"
32+
"github.com/holiman/uint256"
3233
)
3334

3435
//lint:ignore ST1012 EOL is not an error.
@@ -52,6 +53,7 @@ var (
5253
errUintOverflow = errors.New("rlp: uint overflow")
5354
errNoPointer = errors.New("rlp: interface given to Decode must be a pointer")
5455
errDecodeIntoNil = errors.New("rlp: pointer given to Decode must not be nil")
56+
errUint256Large = errors.New("rlp: value too large for uint256")
5557

5658
streamPool = sync.Pool{
5759
New: func() interface{} { return new(Stream) },
@@ -148,6 +150,7 @@ func addErrorContext(err error, ctx string) error {
148150
var (
149151
decoderInterface = reflect.TypeOf(new(Decoder)).Elem()
150152
bigInt = reflect.TypeOf(big.Int{})
153+
u256Int = reflect.TypeOf(uint256.Int{})
151154
)
152155

153156
func makeDecoder(typ reflect.Type, tags rlpstruct.Tags) (dec decoder, err error) {
@@ -159,6 +162,10 @@ func makeDecoder(typ reflect.Type, tags rlpstruct.Tags) (dec decoder, err error)
159162
return decodeBigInt, nil
160163
case typ.AssignableTo(bigInt):
161164
return decodeBigIntNoPtr, nil
165+
case typ == reflect.PtrTo(u256Int):
166+
return decodeU256, nil
167+
case typ == u256Int:
168+
return decodeU256NoPtr, nil
162169
case kind == reflect.Ptr:
163170
return makePtrDecoder(typ, tags)
164171
case reflect.PtrTo(typ).Implements(decoderInterface):
@@ -235,6 +242,24 @@ func decodeBigInt(s *Stream, val reflect.Value) error {
235242
return nil
236243
}
237244

245+
func decodeU256NoPtr(s *Stream, val reflect.Value) error {
246+
return decodeU256(s, val.Addr())
247+
}
248+
249+
func decodeU256(s *Stream, val reflect.Value) error {
250+
i := val.Interface().(*uint256.Int)
251+
if i == nil {
252+
i = new(uint256.Int)
253+
val.Set(reflect.ValueOf(i))
254+
}
255+
256+
err := s.ReadUint256(i)
257+
if err != nil {
258+
return wrapStreamError(err, val.Type())
259+
}
260+
return nil
261+
}
262+
238263
func makeListDecoder(typ reflect.Type, tag rlpstruct.Tags) (decoder, error) {
239264
etype := typ.Elem()
240265
if etype.Kind() == reflect.Uint8 && !reflect.PtrTo(etype).Implements(decoderInterface) {
@@ -863,6 +888,45 @@ func (s *Stream) decodeBigInt(dst *big.Int) error {
863888
return nil
864889
}
865890

891+
// ReadUint256 decodes the next value as a uint256.
892+
func (s *Stream) ReadUint256(dst *uint256.Int) error {
893+
var buffer []byte
894+
kind, size, err := s.Kind()
895+
switch {
896+
case err != nil:
897+
return err
898+
case kind == List:
899+
return ErrExpectedString
900+
case kind == Byte:
901+
buffer = s.uintbuf[:1]
902+
buffer[0] = s.byteval
903+
s.kind = -1 // re-arm Kind
904+
case size == 0:
905+
// Avoid zero-length read.
906+
s.kind = -1
907+
case size <= uint64(len(s.uintbuf)):
908+
// All possible uint256 values fit into s.uintbuf.
909+
buffer = s.uintbuf[:size]
910+
if err := s.readFull(buffer); err != nil {
911+
return err
912+
}
913+
// Reject inputs where single byte encoding should have been used.
914+
if size == 1 && buffer[0] < 128 {
915+
return ErrCanonSize
916+
}
917+
default:
918+
return errUint256Large
919+
}
920+
921+
// Reject leading zero bytes.
922+
if len(buffer) > 0 && buffer[0] == 0 {
923+
return ErrCanonInt
924+
}
925+
// Set the integer bytes.
926+
dst.SetBytes(buffer)
927+
return nil
928+
}
929+
866930
// Decode decodes a value and stores the result in the value pointed
867931
// to by val. Please see the documentation for the Decode function
868932
// to learn about the decoding rules.

rlp/decode_test.go

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828
"testing"
2929

3030
"github.com/ethereum/go-ethereum/common/math"
31+
"github.com/holiman/uint256"
3132
)
3233

3334
func TestStreamKind(t *testing.T) {
@@ -468,6 +469,10 @@ var (
468469
veryVeryBigInt = new(big.Int).Exp(veryBigInt, big.NewInt(8), nil)
469470
)
470471

472+
var (
473+
veryBigInt256, _ = uint256.FromBig(veryBigInt)
474+
)
475+
471476
var decodeTests = []decodeTest{
472477
// booleans
473478
{input: "01", ptr: new(bool), value: true},
@@ -541,11 +546,27 @@ var decodeTests = []decodeTest{
541546
{input: "89FFFFFFFFFFFFFFFFFF", ptr: new(*big.Int), value: veryBigInt},
542547
{input: "B848FFFFFFFFFFFFFFFFF800000000000000001BFFFFFFFFFFFFFFFFC8000000000000000045FFFFFFFFFFFFFFFFC800000000000000001BFFFFFFFFFFFFFFFFF8000000000000000001", ptr: new(*big.Int), value: veryVeryBigInt},
543548
{input: "10", ptr: new(big.Int), value: *big.NewInt(16)}, // non-pointer also works
549+
550+
// big int errors
544551
{input: "C0", ptr: new(*big.Int), error: "rlp: expected input string or byte for *big.Int"},
545552
{input: "00", ptr: new(*big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"},
546553
{input: "820001", ptr: new(*big.Int), error: "rlp: non-canonical integer (leading zero bytes) for *big.Int"},
547554
{input: "8105", ptr: new(*big.Int), error: "rlp: non-canonical size information for *big.Int"},
548555

556+
// uint256
557+
{input: "80", ptr: new(*uint256.Int), value: uint256.NewInt(0)},
558+
{input: "01", ptr: new(*uint256.Int), value: uint256.NewInt(1)},
559+
{input: "88FFFFFFFFFFFFFFFF", ptr: new(*uint256.Int), value: uint256.NewInt(math.MaxUint64)},
560+
{input: "89FFFFFFFFFFFFFFFFFF", ptr: new(*uint256.Int), value: veryBigInt256},
561+
{input: "10", ptr: new(uint256.Int), value: *uint256.NewInt(16)}, // non-pointer also works
562+
563+
// uint256 errors
564+
{input: "C0", ptr: new(*uint256.Int), error: "rlp: expected input string or byte for *uint256.Int"},
565+
{input: "00", ptr: new(*uint256.Int), error: "rlp: non-canonical integer (leading zero bytes) for *uint256.Int"},
566+
{input: "820001", ptr: new(*uint256.Int), error: "rlp: non-canonical integer (leading zero bytes) for *uint256.Int"},
567+
{input: "8105", ptr: new(*uint256.Int), error: "rlp: non-canonical size information for *uint256.Int"},
568+
{input: "A1FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00", ptr: new(*uint256.Int), error: "rlp: value too large for uint256"},
569+
549570
// structs
550571
{
551572
input: "C50583343434",
@@ -1223,6 +1244,27 @@ func BenchmarkDecodeBigInts(b *testing.B) {
12231244
}
12241245
}
12251246

1247+
func BenchmarkDecodeU256Ints(b *testing.B) {
1248+
ints := make([]*uint256.Int, 200)
1249+
for i := range ints {
1250+
ints[i], _ = uint256.FromBig(math.BigPow(2, int64(i)))
1251+
}
1252+
enc, err := EncodeToBytes(ints)
1253+
if err != nil {
1254+
b.Fatal(err)
1255+
}
1256+
b.SetBytes(int64(len(enc)))
1257+
b.ReportAllocs()
1258+
b.ResetTimer()
1259+
1260+
var out []*uint256.Int
1261+
for i := 0; i < b.N; i++ {
1262+
if err := DecodeBytes(enc, &out); err != nil {
1263+
b.Fatal(err)
1264+
}
1265+
}
1266+
}
1267+
12261268
func encodeTestSlice(n uint) []byte {
12271269
s := make([]uint, n)
12281270
for i := uint(0); i < n; i++ {

rlp/encbuffer.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,13 @@
1717
package rlp
1818

1919
import (
20+
"encoding/binary"
2021
"io"
2122
"math/big"
2223
"reflect"
2324
"sync"
25+
26+
"github.com/holiman/uint256"
2427
)
2528

2629
type encBuffer struct {
@@ -169,6 +172,23 @@ func (w *encBuffer) writeBigInt(i *big.Int) {
169172
}
170173
}
171174

175+
// writeUint256 writes z as an integer.
176+
func (w *encBuffer) writeUint256(z *uint256.Int) {
177+
bitlen := z.BitLen()
178+
if bitlen <= 64 {
179+
w.writeUint64(z.Uint64())
180+
return
181+
}
182+
nBytes := byte((bitlen + 7) / 8)
183+
var b [33]byte
184+
binary.BigEndian.PutUint64(b[1:9], z[3])
185+
binary.BigEndian.PutUint64(b[9:17], z[2])
186+
binary.BigEndian.PutUint64(b[17:25], z[1])
187+
binary.BigEndian.PutUint64(b[25:33], z[0])
188+
b[32-nBytes] = 0x80 + nBytes
189+
w.str = append(w.str, b[32-nBytes:]...)
190+
}
191+
172192
// list adds a new list header to the header stack. It returns the index of the header.
173193
// Call listEnd with this index after encoding the content of the list.
174194
func (buf *encBuffer) list() int {
@@ -376,6 +396,11 @@ func (w EncoderBuffer) WriteBigInt(i *big.Int) {
376396
w.buf.writeBigInt(i)
377397
}
378398

399+
// WriteUint256 encodes uint256.Int as an RLP string.
400+
func (w EncoderBuffer) WriteUint256(i *uint256.Int) {
401+
w.buf.writeUint256(i)
402+
}
403+
379404
// WriteBytes encodes b as an RLP string.
380405
func (w EncoderBuffer) WriteBytes(b []byte) {
381406
w.buf.writeBytes(b)

rlp/encode.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import (
2424
"reflect"
2525

2626
"github.com/ethereum/go-ethereum/rlp/internal/rlpstruct"
27+
"github.com/holiman/uint256"
2728
)
2829

2930
var (
@@ -144,6 +145,10 @@ func makeWriter(typ reflect.Type, ts rlpstruct.Tags) (writer, error) {
144145
return writeBigIntPtr, nil
145146
case typ.AssignableTo(bigInt):
146147
return writeBigIntNoPtr, nil
148+
case typ == reflect.PtrTo(u256Int):
149+
return writeU256IntPtr, nil
150+
case typ == u256Int:
151+
return writeU256IntNoPtr, nil
147152
case kind == reflect.Ptr:
148153
return makePtrWriter(typ, ts)
149154
case reflect.PtrTo(typ).Implements(encoderInterface):
@@ -206,6 +211,22 @@ func writeBigIntNoPtr(val reflect.Value, w *encBuffer) error {
206211
return nil
207212
}
208213

214+
func writeU256IntPtr(val reflect.Value, w *encBuffer) error {
215+
ptr := val.Interface().(*uint256.Int)
216+
if ptr == nil {
217+
w.str = append(w.str, 0x80)
218+
return nil
219+
}
220+
w.writeUint256(ptr)
221+
return nil
222+
}
223+
224+
func writeU256IntNoPtr(val reflect.Value, w *encBuffer) error {
225+
i := val.Interface().(uint256.Int)
226+
w.writeUint256(&i)
227+
return nil
228+
}
229+
209230
func writeBytes(val reflect.Value, w *encBuffer) error {
210231
w.writeBytes(val.Bytes())
211232
return nil

rlp/encode_test.go

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"testing"
2828

2929
"github.com/ethereum/go-ethereum/common/math"
30+
"github.com/holiman/uint256"
3031
)
3132

3233
type testEncoder struct {
@@ -147,6 +148,30 @@ var encTests = []encTest{
147148
{val: big.NewInt(-1), error: "rlp: cannot encode negative big.Int"},
148149
{val: *big.NewInt(-1), error: "rlp: cannot encode negative big.Int"},
149150

151+
// uint256
152+
{val: uint256.NewInt(0), output: "80"},
153+
{val: uint256.NewInt(1), output: "01"},
154+
{val: uint256.NewInt(127), output: "7F"},
155+
{val: uint256.NewInt(128), output: "8180"},
156+
{val: uint256.NewInt(256), output: "820100"},
157+
{val: uint256.NewInt(1024), output: "820400"},
158+
{val: uint256.NewInt(0xFFFFFF), output: "83FFFFFF"},
159+
{val: uint256.NewInt(0xFFFFFFFF), output: "84FFFFFFFF"},
160+
{val: uint256.NewInt(0xFFFFFFFFFF), output: "85FFFFFFFFFF"},
161+
{val: uint256.NewInt(0xFFFFFFFFFFFF), output: "86FFFFFFFFFFFF"},
162+
{val: uint256.NewInt(0xFFFFFFFFFFFFFF), output: "87FFFFFFFFFFFFFF"},
163+
{
164+
val: new(uint256.Int).SetBytes(unhex("102030405060708090A0B0C0D0E0F2")),
165+
output: "8F102030405060708090A0B0C0D0E0F2",
166+
},
167+
{
168+
val: new(uint256.Int).SetBytes(unhex("0100020003000400050006000700080009000A000B000C000D000E01")),
169+
output: "9C0100020003000400050006000700080009000A000B000C000D000E01",
170+
},
171+
// non-pointer uint256.Int
172+
{val: *uint256.NewInt(0), output: "80"},
173+
{val: *uint256.NewInt(0xFFFFFF), output: "83FFFFFF"},
174+
150175
// byte arrays
151176
{val: [0]byte{}, output: "80"},
152177
{val: [1]byte{0}, output: "00"},
@@ -256,6 +281,12 @@ var encTests = []encTest{
256281
output: "F90200CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376CF84617364668471776572847A786376",
257282
},
258283

284+
// Non-byte arrays are encoded as lists.
285+
// Note that it is important to test [4]uint64 specifically,
286+
// because that's the underlying type of uint256.Int.
287+
{val: [4]uint32{1, 2, 3, 4}, output: "C401020304"},
288+
{val: [4]uint64{1, 2, 3, 4}, output: "C401020304"},
289+
259290
// RawValue
260291
{val: RawValue(unhex("01")), output: "01"},
261292
{val: RawValue(unhex("82FFFF")), output: "82FFFF"},
@@ -301,6 +332,7 @@ var encTests = []encTest{
301332
{val: (*[]byte)(nil), output: "80"},
302333
{val: (*[10]byte)(nil), output: "80"},
303334
{val: (*big.Int)(nil), output: "80"},
335+
{val: (*uint256.Int)(nil), output: "80"},
304336
{val: (*[]string)(nil), output: "C0"},
305337
{val: (*[10]string)(nil), output: "C0"},
306338
{val: (*[]interface{})(nil), output: "C0"},
@@ -509,6 +541,23 @@ func BenchmarkEncodeBigInts(b *testing.B) {
509541
}
510542
}
511543

544+
func BenchmarkEncodeU256Ints(b *testing.B) {
545+
ints := make([]*uint256.Int, 200)
546+
for i := range ints {
547+
ints[i], _ = uint256.FromBig(math.BigPow(2, int64(i)))
548+
}
549+
out := bytes.NewBuffer(make([]byte, 0, 4096))
550+
b.ResetTimer()
551+
b.ReportAllocs()
552+
553+
for i := 0; i < b.N; i++ {
554+
out.Reset()
555+
if err := Encode(out, ints); err != nil {
556+
b.Fatal(err)
557+
}
558+
}
559+
}
560+
512561
func BenchmarkEncodeConcurrentInterface(b *testing.B) {
513562
type struct1 struct {
514563
A string

0 commit comments

Comments
 (0)