cmd/rlpdump: add support for text to rlp (#23745)

This PR adds support for the rlpdump tool to go from text format to RLP.
This commit is contained in:
Martin Holst Swende 2021-10-18 13:38:00 +02:00 committed by GitHub
parent eaa24a8a15
commit b8dc1e2705
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 151 additions and 23 deletions

@ -18,7 +18,9 @@
package main package main
import ( import (
"bufio"
"bytes" "bytes"
"container/list"
"encoding/hex" "encoding/hex"
"flag" "flag"
"fmt" "fmt"
@ -26,18 +28,20 @@ import (
"os" "os"
"strings" "strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
) )
var ( var (
hexMode = flag.String("hex", "", "dump given hex data") hexMode = flag.String("hex", "", "dump given hex data")
reverseMode = flag.Bool("reverse", false, "convert ASCII to rlp")
noASCII = flag.Bool("noascii", false, "don't print ASCII strings readably") noASCII = flag.Bool("noascii", false, "don't print ASCII strings readably")
single = flag.Bool("single", false, "print only the first element, discard the rest") single = flag.Bool("single", false, "print only the first element, discard the rest")
) )
func init() { func init() {
flag.Usage = func() { flag.Usage = func() {
fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[-noascii] [-hex <data>] [filename]") fmt.Fprintln(os.Stderr, "Usage:", os.Args[0], "[-noascii] [-hex <data>][-reverse] [filename]")
flag.PrintDefaults() flag.PrintDefaults()
fmt.Fprintln(os.Stderr, ` fmt.Fprintln(os.Stderr, `
Dumps RLP data from the given file in readable form. Dumps RLP data from the given file in readable form.
@ -73,23 +77,40 @@ func main() {
flag.Usage() flag.Usage()
os.Exit(2) os.Exit(2)
} }
out := os.Stdout
s := rlp.NewStream(r, 0) if *reverseMode {
for { data, err := textToRlp(r)
if err := dump(s, 0); err != nil { if err != nil {
if err != io.EOF {
die(err) die(err)
} }
break fmt.Printf("0x%x\n", data)
} return
fmt.Println() } else {
if *single { err := rlpToText(r, out)
break if err != nil {
die(err)
} }
} }
} }
func dump(s *rlp.Stream, depth int) error { func rlpToText(r io.Reader, out io.Writer) error {
s := rlp.NewStream(r, 0)
for {
if err := dump(s, 0, out); err != nil {
if err != io.EOF {
return err
}
break
}
fmt.Fprintln(out)
if *single {
break
}
}
return nil
}
func dump(s *rlp.Stream, depth int, out io.Writer) error {
kind, size, err := s.Kind() kind, size, err := s.Kind()
if err != nil { if err != nil {
return err return err
@ -101,28 +122,28 @@ func dump(s *rlp.Stream, depth int) error {
return err return err
} }
if len(str) == 0 || !*noASCII && isASCII(str) { if len(str) == 0 || !*noASCII && isASCII(str) {
fmt.Printf("%s%q", ws(depth), str) fmt.Fprintf(out, "%s%q", ws(depth), str)
} else { } else {
fmt.Printf("%s%x", ws(depth), str) fmt.Fprintf(out, "%s%x", ws(depth), str)
} }
case rlp.List: case rlp.List:
s.List() s.List()
defer s.ListEnd() defer s.ListEnd()
if size == 0 { if size == 0 {
fmt.Print(ws(depth) + "[]") fmt.Fprintf(out, ws(depth)+"[]")
} else { } else {
fmt.Println(ws(depth) + "[") fmt.Fprintln(out, ws(depth)+"[")
for i := 0; ; i++ { for i := 0; ; i++ {
if i > 0 { if i > 0 {
fmt.Print(",\n") fmt.Fprint(out, ",\n")
} }
if err := dump(s, depth+1); err == rlp.EOL { if err := dump(s, depth+1, out); err == rlp.EOL {
break break
} else if err != nil { } else if err != nil {
return err return err
} }
} }
fmt.Print(ws(depth) + "]") fmt.Fprint(out, ws(depth)+"]")
} }
} }
return nil return nil
@ -145,3 +166,45 @@ func die(args ...interface{}) {
fmt.Fprintln(os.Stderr, args...) fmt.Fprintln(os.Stderr, args...)
os.Exit(1) os.Exit(1)
} }
// textToRlp converts text into RLP (best effort).
func textToRlp(r io.Reader) ([]byte, error) {
// We're expecting the input to be well-formed, meaning that
// - each element is on a separate line
// - each line is either an (element OR a list start/end) + comma
// - an element is either hex-encoded bytes OR a quoted string
var (
scanner = bufio.NewScanner(r)
obj []interface{}
stack = list.New()
)
for scanner.Scan() {
t := strings.TrimSpace(scanner.Text())
if len(t) == 0 {
continue
}
switch t {
case "[": // list start
stack.PushFront(obj)
obj = make([]interface{}, 0)
case "]", "],": // list end
parent := stack.Remove(stack.Front()).([]interface{})
obj = append(parent, obj)
case "[],": // empty list
obj = append(obj, make([]interface{}, 0))
default: // element
data := []byte(t)[:len(t)-1] // cut off comma
if data[0] == '"' { // ascii string
data = []byte(t)[1 : len(data)-1]
} else { // hex data
data = common.FromHex(string(data))
}
obj = append(obj, data)
}
}
if err := scanner.Err(); err != nil {
return nil, err
}
data, err := rlp.EncodeToBytes(obj[0])
return data, err
}

@ -0,0 +1,65 @@
package main
import (
"bytes"
"fmt"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
)
func TestRoundtrip(t *testing.T) {
for i, want := range []string{
"0xf880806482520894d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0d0a1010000000000000000000000000000000000000000000000000000000000000001801ba0c16787a8e25e941d67691954642876c08f00996163ae7dfadbbfd6cd436f549da06180e5626cae31590f40641fe8f63734316c4bfeb4cdfab6714198c1044d2e28",
"0xd5c0d3cb84746573742a2a808213378667617a6f6e6b",
"0xc780c0c1c0825208",
} {
var out strings.Builder
err := rlpToText(bytes.NewReader(common.FromHex(want)), &out)
if err != nil {
t.Fatal(err)
}
text := out.String()
rlpBytes, err := textToRlp(strings.NewReader(text))
if err != nil {
t.Errorf("test %d: error %v", i, err)
continue
}
have := fmt.Sprintf("0x%x", rlpBytes)
if have != want {
t.Errorf("test %d: have\n%v\nwant:\n%v\n", i, have, want)
}
}
}
func TestTextToRlp(t *testing.T) {
type tc struct {
text string
want string
}
cases := []tc{
{
text: `[
"",
[],
[
[],
],
5208,
]`,
want: "0xc780c0c1c0825208",
},
}
for i, tc := range cases {
have, err := textToRlp(strings.NewReader(tc.text))
if err != nil {
t.Errorf("test %d: error %v", i, err)
continue
}
if hexutil.Encode(have) != tc.want {
t.Errorf("test %d:\nhave %v\nwant %v", i, hexutil.Encode(have), tc.want)
}
}
}