This commit is contained in:
Jeff R. Allen 2015-09-14 19:24:27 +00:00
commit d44c939ca7
9 changed files with 18 additions and 621 deletions

View file

@ -1,139 +0,0 @@
# ethutil
[![Build
Status](https://travis-ci.org/ethereum/go-ethereum.png?branch=master)](https://travis-ci.org/ethereum/go-ethereum)
The ethutil package contains the ethereum utility library.
# Installation
`go get github.com/ethereum/ethutil-go`
# Usage
## RLP (Recursive Linear Prefix) Encoding
RLP Encoding is an encoding scheme utilized by the Ethereum project. It
encodes any native value or list to string.
More in depth information about the Encoding scheme see the [Wiki](http://wiki.ethereum.org/index.php/RLP)
article.
```go
rlp := ethutil.Encode("doge")
fmt.Printf("%q\n", rlp) // => "\0x83dog"
rlp = ethutil.Encode([]interface{}{"dog", "cat"})
fmt.Printf("%q\n", rlp) // => "\0xc8\0x83dog\0x83cat"
decoded := ethutil.Decode(rlp)
fmt.Println(decoded) // => ["dog" "cat"]
```
## Patricia Trie
Patricie Tree is a merkle trie utilized by the Ethereum project.
More in depth information about the (modified) Patricia Trie can be
found on the [Wiki](http://wiki.ethereum.org/index.php/Patricia_Tree).
The patricia trie uses a db as backend and could be anything as long as
it satisfies the Database interface found in `ethutil/db.go`.
```go
db := NewDatabase()
// db, root
trie := ethutil.NewTrie(db, "")
trie.Put("puppy", "dog")
trie.Put("horse", "stallion")
trie.Put("do", "verb")
trie.Put("doge", "coin")
// Look up the key "do" in the trie
out := trie.Get("do")
fmt.Println(out) // => verb
trie.Delete("puppy")
```
The patricia trie, in combination with RLP, provides a robust,
cryptographically authenticated data structure that can be used to store
all (key, value) bindings.
```go
// ... Create db/trie
// Note that RLP uses interface slices as list
value := ethutil.Encode([]interface{}{"one", 2, "three", []interface{}{42}})
// Store the RLP encoded value of the list
trie.Put("mykey", value)
```
## Value
Value is a Generic Value which is used in combination with RLP data or
`([])interface{}` structures. It may serve as a bridge between RLP data
and actual real values and takes care of all the type checking and
casting. Unlike Go's `reflect.Value` it does not panic if it's unable to
cast to the requested value. It simple returns the base value of that
type (e.g. `Slice()` returns []interface{}, `Uint()` return 0, etc).
### Creating a new Value
`NewEmptyValue()` returns a new \*Value with it's initial value set to a
`[]interface{}`
`AppendList()` appends a list to the current value.
`Append(v)` appends the value (v) to the current value/list.
```go
val := ethutil.NewEmptyValue().Append(1).Append("2")
val.AppendList().Append(3)
```
### Retrieving values
`Get(i)` returns the `i` item in the list.
`Uint()` returns the value as an unsigned int64.
`Slice()` returns the value as a interface slice.
`Str()` returns the value as a string.
`Bytes()` returns the value as a byte slice.
`Len()` assumes current to be a slice and returns its length.
`Byte()` returns the value as a single byte.
```go
val := ethutil.NewValue([]interface{}{1,"2",[]interface{}{3}})
val.Get(0).Uint() // => 1
val.Get(1).Str() // => "2"
s := val.Get(2) // => Value([]interface{}{3})
s.Get(0).Uint() // => 3
```
## Decoding
Decoding streams of RLP data is simplified
```go
val := ethutil.NewValueFromBytes(rlpData)
val.Get(0).Uint()
```
## Encoding
Encoding from Value to RLP is done with the `Encode` method. The
underlying value can be anything RLP can encode (int, str, lists, bytes)
```go
val := ethutil.NewValue([]interface{}{1,"2",[]interface{}{3}})
rlp := val.Encode()
// Store the rlp data
Store(rlp)
```

View file

@ -36,7 +36,7 @@ func MakeName(name, version string) string {
func ExpandHomePath(p string) (path string) { func ExpandHomePath(p string) (path string) {
path = p path = p
sep := fmt.Sprintf("%s", os.PathSeparator) sep := fmt.Sprintf("%v", os.PathSeparator)
// Check in case of paths like "/something/~/something/" // Check in case of paths like "/something/~/something/"
if len(p) > 1 && p[:1+len(sep)] == "~"+sep { if len(p) > 1 && p[:1+len(sep)] == "~"+sep {

View file

@ -1,292 +0,0 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package common
import (
"bytes"
"fmt"
"math/big"
"reflect"
)
type RlpEncode interface {
RlpEncode() []byte
}
type RlpEncodeDecode interface {
RlpEncode
RlpValue() []interface{}
}
type RlpEncodable interface {
RlpData() interface{}
}
func Rlp(encoder RlpEncode) []byte {
return encoder.RlpEncode()
}
type RlpEncoder struct {
rlpData []byte
}
func NewRlpEncoder() *RlpEncoder {
encoder := &RlpEncoder{}
return encoder
}
func (coder *RlpEncoder) EncodeData(rlpData interface{}) []byte {
return Encode(rlpData)
}
const (
RlpEmptyList = 0x80
RlpEmptyStr = 0x40
)
const rlpEof = -1
func Char(c []byte) int {
if len(c) > 0 {
return int(c[0])
}
return rlpEof
}
func DecodeWithReader(reader *bytes.Buffer) interface{} {
var slice []interface{}
// Read the next byte
char := Char(reader.Next(1))
switch {
case char <= 0x7f:
return char
case char <= 0xb7:
return reader.Next(int(char - 0x80))
case char <= 0xbf:
length := ReadVarInt(reader.Next(int(char - 0xb7)))
return reader.Next(int(length))
case char <= 0xf7:
length := int(char - 0xc0)
for i := 0; i < length; i++ {
obj := DecodeWithReader(reader)
slice = append(slice, obj)
}
return slice
case char <= 0xff:
length := ReadVarInt(reader.Next(int(char - 0xf7)))
for i := uint64(0); i < length; i++ {
obj := DecodeWithReader(reader)
slice = append(slice, obj)
}
return slice
default:
panic(fmt.Sprintf("byte not supported: %q", char))
}
return slice
}
var (
directRlp = big.NewInt(0x7f)
numberRlp = big.NewInt(0xb7)
zeroRlp = big.NewInt(0x0)
)
func intlen(i int64) (length int) {
for i > 0 {
i = i >> 8
length++
}
return
}
func Encode(object interface{}) []byte {
var buff bytes.Buffer
if object != nil {
switch t := object.(type) {
case *Value:
buff.Write(Encode(t.Val))
case RlpEncodable:
buff.Write(Encode(t.RlpData()))
// Code dup :-/
case int:
buff.Write(Encode(big.NewInt(int64(t))))
case uint:
buff.Write(Encode(big.NewInt(int64(t))))
case int8:
buff.Write(Encode(big.NewInt(int64(t))))
case int16:
buff.Write(Encode(big.NewInt(int64(t))))
case int32:
buff.Write(Encode(big.NewInt(int64(t))))
case int64:
buff.Write(Encode(big.NewInt(t)))
case uint16:
buff.Write(Encode(big.NewInt(int64(t))))
case uint32:
buff.Write(Encode(big.NewInt(int64(t))))
case uint64:
buff.Write(Encode(big.NewInt(int64(t))))
case byte:
buff.Write(Encode(big.NewInt(int64(t))))
case *big.Int:
// Not sure how this is possible while we check for nil
if t == nil {
buff.WriteByte(0xc0)
} else {
buff.Write(Encode(t.Bytes()))
}
case Bytes:
buff.Write(Encode([]byte(t)))
case []byte:
if len(t) == 1 && t[0] <= 0x7f {
buff.Write(t)
} else if len(t) < 56 {
buff.WriteByte(byte(len(t) + 0x80))
buff.Write(t)
} else {
b := big.NewInt(int64(len(t)))
buff.WriteByte(byte(len(b.Bytes()) + 0xb7))
buff.Write(b.Bytes())
buff.Write(t)
}
case string:
buff.Write(Encode([]byte(t)))
case []interface{}:
// Inline function for writing the slice header
WriteSliceHeader := func(length int) {
if length < 56 {
buff.WriteByte(byte(length + 0xc0))
} else {
b := big.NewInt(int64(length))
buff.WriteByte(byte(len(b.Bytes()) + 0xf7))
buff.Write(b.Bytes())
}
}
var b bytes.Buffer
for _, val := range t {
b.Write(Encode(val))
}
WriteSliceHeader(len(b.Bytes()))
buff.Write(b.Bytes())
default:
// This is how it should have been from the start
// needs refactoring (@fjl)
v := reflect.ValueOf(t)
switch v.Kind() {
case reflect.Slice:
var b bytes.Buffer
for i := 0; i < v.Len(); i++ {
b.Write(Encode(v.Index(i).Interface()))
}
blen := b.Len()
if blen < 56 {
buff.WriteByte(byte(blen) + 0xc0)
} else {
ilen := byte(intlen(int64(blen)))
buff.WriteByte(ilen + 0xf7)
t := make([]byte, ilen)
for i := byte(0); i < ilen; i++ {
t[ilen-i-1] = byte(blen >> (i * 8))
}
buff.Write(t)
}
buff.ReadFrom(&b)
}
}
} else {
// Empty list for nil
buff.WriteByte(0xc0)
}
return buff.Bytes()
}
// TODO Use a bytes.Buffer instead of a raw byte slice.
// Cleaner code, and use draining instead of seeking the next bytes to read
func Decode(data []byte, pos uint64) (interface{}, uint64) {
var slice []interface{}
char := int(data[pos])
switch {
case char <= 0x7f:
return data[pos], pos + 1
case char <= 0xb7:
b := uint64(data[pos]) - 0x80
return data[pos+1 : pos+1+b], pos + 1 + b
case char <= 0xbf:
b := uint64(data[pos]) - 0xb7
b2 := ReadVarInt(data[pos+1 : pos+1+b])
return data[pos+1+b : pos+1+b+b2], pos + 1 + b + b2
case char <= 0xf7:
b := uint64(data[pos]) - 0xc0
prevPos := pos
pos++
for i := uint64(0); i < b; {
var obj interface{}
// Get the next item in the data list and append it
obj, prevPos = Decode(data, pos)
slice = append(slice, obj)
// Increment i by the amount bytes read in the previous
// read
i += (prevPos - pos)
pos = prevPos
}
return slice, pos
case char <= 0xff:
l := uint64(data[pos]) - 0xf7
b := ReadVarInt(data[pos+1 : pos+1+l])
pos = pos + l + 1
prevPos := b
for i := uint64(0); i < uint64(b); {
var obj interface{}
obj, prevPos = Decode(data, pos)
slice = append(slice, obj)
i += (prevPos - pos)
pos = prevPos
}
return slice, pos
default:
panic(fmt.Sprintf("byte not supported: %q", char))
}
return slice, 0
}

View file

@ -1,176 +0,0 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package common
import (
"bytes"
"math/big"
"reflect"
"testing"
"github.com/ethereum/go-ethereum/rlp"
)
func TestNonInterfaceSlice(t *testing.T) {
vala := []string{"value1", "value2", "value3"}
valb := []interface{}{"value1", "value2", "value3"}
resa := Encode(vala)
resb := Encode(valb)
if !bytes.Equal(resa, resb) {
t.Errorf("expected []string & []interface{} to be equal")
}
}
func TestRlpValueEncoding(t *testing.T) {
val := EmptyValue()
val.AppendList().Append(byte(1)).Append(byte(2)).Append(byte(3))
val.Append("4").AppendList().Append(byte(5))
res, err := rlp.EncodeToBytes(val)
if err != nil {
t.Fatalf("encode error: %v", err)
}
exp := Encode([]interface{}{[]interface{}{1, 2, 3}, "4", []interface{}{5}})
if bytes.Compare(res, exp) != 0 {
t.Errorf("expected %x, got %x", exp, res)
}
}
func TestValueSlice(t *testing.T) {
val := []interface{}{
"value1",
"valeu2",
"value3",
}
value := NewValue(val)
splitVal := value.SliceFrom(1)
if splitVal.Len() != 2 {
t.Error("SliceFrom: Expected len", 2, "got", splitVal.Len())
}
splitVal = value.SliceTo(2)
if splitVal.Len() != 2 {
t.Error("SliceTo: Expected len", 2, "got", splitVal.Len())
}
splitVal = value.SliceFromTo(1, 3)
if splitVal.Len() != 2 {
t.Error("SliceFromTo: Expected len", 2, "got", splitVal.Len())
}
}
func TestLargeData(t *testing.T) {
data := make([]byte, 100000)
enc := Encode(data)
value := NewValueFromBytes(enc)
if value.Len() != len(data) {
t.Error("Expected data to be", len(data), "got", value.Len())
}
}
func TestValue(t *testing.T) {
value := NewValueFromBytes([]byte("\xcd\x83dog\x83god\x83cat\x01"))
if value.Get(0).Str() != "dog" {
t.Errorf("expected '%v', got '%v'", value.Get(0).Str(), "dog")
}
if value.Get(3).Uint() != 1 {
t.Errorf("expected '%v', got '%v'", value.Get(3).Uint(), 1)
}
}
func TestEncode(t *testing.T) {
strRes := "\x83dog"
bytes := Encode("dog")
str := string(bytes)
if str != strRes {
t.Errorf("Expected %q, got %q", strRes, str)
}
sliceRes := "\xcc\x83dog\x83god\x83cat"
strs := []interface{}{"dog", "god", "cat"}
bytes = Encode(strs)
slice := string(bytes)
if slice != sliceRes {
t.Error("Expected %q, got %q", sliceRes, slice)
}
intRes := "\x82\x04\x00"
bytes = Encode(1024)
if string(bytes) != intRes {
t.Errorf("Expected %q, got %q", intRes, bytes)
}
}
func TestDecode(t *testing.T) {
single := []byte("\x01")
b, _ := Decode(single, 0)
if b.(uint8) != 1 {
t.Errorf("Expected 1, got %q", b)
}
str := []byte("\x83dog")
b, _ = Decode(str, 0)
if bytes.Compare(b.([]byte), []byte("dog")) != 0 {
t.Errorf("Expected dog, got %q", b)
}
slice := []byte("\xcc\x83dog\x83god\x83cat")
res := []interface{}{"dog", "god", "cat"}
b, _ = Decode(slice, 0)
if reflect.DeepEqual(b, res) {
t.Errorf("Expected %q, got %q", res, b)
}
}
func TestEncodeDecodeBigInt(t *testing.T) {
bigInt := big.NewInt(1391787038)
encoded := Encode(bigInt)
value := NewValueFromBytes(encoded)
if value.BigInt().Cmp(bigInt) != 0 {
t.Errorf("Expected %v, got %v", bigInt, value.BigInt())
}
}
func TestEncodeDecodeBytes(t *testing.T) {
bv := NewValue([]interface{}{[]byte{1, 2, 3, 4, 5}, []byte{6}})
b, _ := rlp.EncodeToBytes(bv)
val := NewValueFromBytes(b)
if !bv.Cmp(val) {
t.Errorf("Expected %#v, got %#v", bv, val)
}
}
func TestEncodeZero(t *testing.T) {
b, _ := rlp.EncodeToBytes(NewValue(0))
exp := []byte{0xc0}
if bytes.Compare(b, exp) == 0 {
t.Error("Expected", exp, "got", b)
}
}
func BenchmarkEncodeDecode(b *testing.B) {
for i := 0; i < b.N; i++ {
bytes := Encode([]interface{}{"dog", "god", "cat"})
Decode(bytes, 0)
}
}

View file

@ -146,8 +146,6 @@ func (val *Value) BigInt() *big.Int {
} else { } else {
return big.NewInt(int64(val.Uint())) return big.NewInt(int64(val.Uint()))
} }
return big.NewInt(0)
} }
func (val *Value) Str() string { func (val *Value) Str() string {
@ -174,8 +172,6 @@ func (val *Value) Bytes() []byte {
} else { } else {
return big.NewInt(val.Int()).Bytes() return big.NewInt(val.Int()).Bytes()
} }
return []byte{}
} }
func (val *Value) Err() error { func (val *Value) Err() error {
@ -267,8 +263,6 @@ func (self *Value) Copy() *Value {
default: default:
return NewValue(self.Val) return NewValue(self.Val)
} }
return nil
} }
func (val *Value) Cmp(o *Value) bool { func (val *Value) Cmp(o *Value) bool {

View file

@ -17,7 +17,7 @@
/* /*
Package rlp implements the RLP serialization format. Package rlp implements the RLP serialization format.
The purpose of RLP (Recursive Linear Prefix) qis to encode arbitrarily The purpose of RLP (Recursive Linear Prefix) is to encode arbitrarily
nested arrays of binary data, and RLP is the main encoding method used nested arrays of binary data, and RLP is the main encoding method used
to serialize objects in Ethereum. The only purpose of RLP is to encode to serialize objects in Ethereum. The only purpose of RLP is to encode
structure; encoding specific atomic data types (eg. strings, ints, structure; encoding specific atomic data types (eg. strings, ints,

View file

@ -300,7 +300,6 @@ func (r *encReader) Read(b []byte) (n int, err error) {
} }
r.piece = nil r.piece = nil
} }
panic("not reached")
} }
// next returns the next piece of data to be read. // next returns the next piece of data to be read.
@ -649,5 +648,4 @@ func intsize(i uint64) (size int) {
return size return size
} }
} }
panic("not reached")
} }

View file

@ -25,6 +25,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
) )
func ParanoiaCheck(t1 *Trie, backend Backend) (bool, *Trie) { func ParanoiaCheck(t1 *Trie, backend Backend) (bool, *Trie) {
@ -88,10 +89,16 @@ func (self *Trie) Hash() []byte {
if byts, ok := t.([]byte); ok && len(byts) > 0 { if byts, ok := t.([]byte); ok && len(byts) > 0 {
hash = byts hash = byts
} else { } else {
hash = crypto.Sha3(common.Encode(self.root.RlpData())) e, err := rlp.EncodeToBytes(self.root.RlpData())
if err != nil {
return nil
}
hash = crypto.Sha3(e)
} }
} else { } else {
hash = crypto.Sha3(common.Encode("")) e, _ := rlp.EncodeToBytes("")
hash = crypto.Sha3(e)
} }
if !bytes.Equal(hash, self.roothash) { if !bytes.Equal(hash, self.roothash) {
@ -369,7 +376,10 @@ func (self *Trie) trans(node Node) Node {
} }
func (self *Trie) store(node Node) interface{} { func (self *Trie) store(node Node) interface{} {
data := common.Encode(node) data, err := rlp.EncodeToBytes(node)
if err != nil {
return nil
}
if len(data) >= 32 { if len(data) >= 32 {
key := crypto.Sha3(data) key := crypto.Sha3(data)
if node.Dirty() { if node.Dirty() {

View file

@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
) )
type Db map[string][]byte type Db map[string][]byte
@ -42,7 +43,8 @@ func NewEmptySecure() *SecureTrie {
func TestEmptyTrie(t *testing.T) { func TestEmptyTrie(t *testing.T) {
trie := NewEmpty() trie := NewEmpty()
res := trie.Hash() res := trie.Hash()
exp := crypto.Sha3(common.Encode("")) e, _ := rlp.EncodeToBytes("")
exp := crypto.Sha3(e)
if !bytes.Equal(res, exp) { if !bytes.Equal(res, exp) {
t.Errorf("expected %x got %x", exp, res) t.Errorf("expected %x got %x", exp, res)
} }