Skip to content

Commit b8dc1e2

Browse files
authored
cmd/rlpdump: add support for text to rlp (#23745)
This PR adds support for the rlpdump tool to go from text format to RLP.
1 parent eaa24a8 commit b8dc1e2

File tree

2 files changed

+143
-15
lines changed

2 files changed

+143
-15
lines changed

cmd/rlpdump/main.go

Lines changed: 78 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,30 @@
1818
package main
1919

2020
import (
21+
"bufio"
2122
"bytes"
23+
"container/list"
2224
"encoding/hex"
2325
"flag"
2426
"fmt"
2527
"io"
2628
"os"
2729
"strings"
2830

31+
"github.com/ethereum/go-ethereum/common"
2932
"github.com/ethereum/go-ethereum/rlp"
3033
)
3134

3235
var (
33-
hexMode = flag.String("hex", "", "dump given hex data")
34-
noASCII = flag.Bool("noascii", false, "don't print ASCII strings readably")
35-
single = flag.Bool("single", false, "print only the first element, discard the rest")
36+
hexMode = flag.String("hex", "", "dump given hex data")
37+
reverseMode = flag.Bool("reverse", false, "convert ASCII to rlp")
38+
noASCII = flag.Bool("noascii", false, "don't print ASCII strings readably")
39+
single = flag.Bool("single", false, "print only the first element, discard the rest")
3640
)
3741

3842
func init() {
3943
flag.Usage = func() {
40-
fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[-noascii] [-hex <data>] [filename]")
44+
fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[-noascii] [-hex <data>][-reverse] [filename]")
4145
flag.PrintDefaults()
4246
fmt.Fprintln(os.Stderr, `
4347
Dumps RLP data from the given file in readable form.
@@ -73,23 +77,40 @@ func main() {
7377
flag.Usage()
7478
os.Exit(2)
7579
}
80+
out := os.Stdout
81+
if *reverseMode {
82+
data, err := textToRlp(r)
83+
if err != nil {
84+
die(err)
85+
}
86+
fmt.Printf("0x%x\n", data)
87+
return
88+
} else {
89+
err := rlpToText(r, out)
90+
if err != nil {
91+
die(err)
92+
}
93+
}
94+
}
7695

96+
func rlpToText(r io.Reader, out io.Writer) error {
7797
s := rlp.NewStream(r, 0)
7898
for {
79-
if err := dump(s, 0); err != nil {
99+
if err := dump(s, 0, out); err != nil {
80100
if err != io.EOF {
81-
die(err)
101+
return err
82102
}
83103
break
84104
}
85-
fmt.Println()
105+
fmt.Fprintln(out)
86106
if *single {
87107
break
88108
}
89109
}
110+
return nil
90111
}
91112

92-
func dump(s *rlp.Stream, depth int) error {
113+
func dump(s *rlp.Stream, depth int, out io.Writer) error {
93114
kind, size, err := s.Kind()
94115
if err != nil {
95116
return err
@@ -101,28 +122,28 @@ func dump(s *rlp.Stream, depth int) error {
101122
return err
102123
}
103124
if len(str) == 0 || !*noASCII && isASCII(str) {
104-
fmt.Printf("%s%q", ws(depth), str)
125+
fmt.Fprintf(out, "%s%q", ws(depth), str)
105126
} else {
106-
fmt.Printf("%s%x", ws(depth), str)
127+
fmt.Fprintf(out, "%s%x", ws(depth), str)
107128
}
108129
case rlp.List:
109130
s.List()
110131
defer s.ListEnd()
111132
if size == 0 {
112-
fmt.Print(ws(depth) + "[]")
133+
fmt.Fprintf(out, ws(depth)+"[]")
113134
} else {
114-
fmt.Println(ws(depth) + "[")
135+
fmt.Fprintln(out, ws(depth)+"[")
115136
for i := 0; ; i++ {
116137
if i > 0 {
117-
fmt.Print(",\n")
138+
fmt.Fprint(out, ",\n")
118139
}
119-
if err := dump(s, depth+1); err == rlp.EOL {
140+
if err := dump(s, depth+1, out); err == rlp.EOL {
120141
break
121142
} else if err != nil {
122143
return err
123144
}
124145
}
125-
fmt.Print(ws(depth) + "]")
146+
fmt.Fprint(out, ws(depth)+"]")
126147
}
127148
}
128149
return nil
@@ -145,3 +166,45 @@ func die(args ...interface{}) {
145166
fmt.Fprintln(os.Stderr, args...)
146167
os.Exit(1)
147168
}
169+
170+
// textToRlp converts text into RLP (best effort).
171+
func textToRlp(r io.Reader) ([]byte, error) {
172+
// We're expecting the input to be well-formed, meaning that
173+
// - each element is on a separate line
174+
// - each line is either an (element OR a list start/end) + comma
175+
// - an element is either hex-encoded bytes OR a quoted string
176+
var (
177+
scanner = bufio.NewScanner(r)
178+
obj []interface{}
179+
stack = list.New()
180+
)
181+
for scanner.Scan() {
182+
t := strings.TrimSpace(scanner.Text())
183+
if len(t) == 0 {
184+
continue
185+
}
186+
switch t {
187+
case "[": // list start
188+
stack.PushFront(obj)
189+
obj = make([]interface{}, 0)
190+
case "]", "],": // list end
191+
parent := stack.Remove(stack.Front()).([]interface{})
192+
obj = append(parent, obj)
193+
case "[],": // empty list
194+
obj = append(obj, make([]interface{}, 0))
195+
default: // element
196+
data := []byte(t)[:len(t)-1] // cut off comma
197+
if data[0] == '"' { // ascii string
198+
data = []byte(t)[1 : len(data)-1]
199+
} else { // hex data
200+
data = common.FromHex(string(data))
201+
}
202+
obj = append(obj, data)
203+
}
204+
}
205+
if err := scanner.Err(); err != nil {
206+
return nil, err
207+
}
208+
data, err := rlp.EncodeToBytes(obj[0])
209+
return data, err
210+
}

cmd/rlpdump/rlpdump_test.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"strings"
7+
"testing"
8+
9+
"github.com/ethereum/go-ethereum/common"
10+
"github.com/ethereum/go-ethereum/common/hexutil"
11+
)
12+
13+
func TestRoundtrip(t *testing.T) {
14+
for i, want := range []string{
15+
"0xf880806482520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a1010000000000000000000000000000000000000000000000000000000000000001801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28",
16+
"0xd5c0d3cb84746573742a2a808213378667617a6f6e6b",
17+
"0xc780c0c1c0825208",
18+
} {
19+
var out strings.Builder
20+
err := rlpToText(bytes.NewReader(common.FromHex(want)), &out)
21+
if err != nil {
22+
t.Fatal(err)
23+
}
24+
text := out.String()
25+
rlpBytes, err := textToRlp(strings.NewReader(text))
26+
if err != nil {
27+
t.Errorf("test %d: error %v", i, err)
28+
continue
29+
}
30+
have := fmt.Sprintf("0x%x", rlpBytes)
31+
if have != want {
32+
t.Errorf("test %d: have\n%v\nwant:\n%v\n", i, have, want)
33+
}
34+
}
35+
}
36+
37+
func TestTextToRlp(t *testing.T) {
38+
type tc struct {
39+
text string
40+
want string
41+
}
42+
cases := []tc{
43+
{
44+
text: `[
45+
"",
46+
[],
47+
[
48+
[],
49+
],
50+
5208,
51+
]`,
52+
want: "0xc780c0c1c0825208",
53+
},
54+
}
55+
for i, tc := range cases {
56+
have, err := textToRlp(strings.NewReader(tc.text))
57+
if err != nil {
58+
t.Errorf("test %d: error %v", i, err)
59+
continue
60+
}
61+
if hexutil.Encode(have) != tc.want {
62+
t.Errorf("test %d:\nhave %v\nwant %v", i, hexutil.Encode(have), tc.want)
63+
}
64+
}
65+
}

0 commit comments

Comments
 (0)