diff options
author | obscuren <geffobscura@gmail.com> | 2014-02-15 06:56:09 +0800 |
---|---|---|
committer | obscuren <geffobscura@gmail.com> | 2014-02-15 06:56:09 +0800 |
commit | f6d1bfe45bf3709d7bad40bf563b5c09228622e3 (patch) | |
tree | df48311a5a494c66c74dcd51f1056cc699f01507 /ethutil | |
parent | c2fb9f06ad018d01ce335c82b3542de16045a32d (diff) | |
download | dexon-f6d1bfe45bf3709d7bad40bf563b5c09228622e3.tar dexon-f6d1bfe45bf3709d7bad40bf563b5c09228622e3.tar.gz dexon-f6d1bfe45bf3709d7bad40bf563b5c09228622e3.tar.bz2 dexon-f6d1bfe45bf3709d7bad40bf563b5c09228622e3.tar.lz dexon-f6d1bfe45bf3709d7bad40bf563b5c09228622e3.tar.xz dexon-f6d1bfe45bf3709d7bad40bf563b5c09228622e3.tar.zst dexon-f6d1bfe45bf3709d7bad40bf563b5c09228622e3.zip |
The great merge
Diffstat (limited to 'ethutil')
-rw-r--r-- | ethutil/.gitignore | 12 | ||||
-rw-r--r-- | ethutil/.travil.yml | 3 | ||||
-rw-r--r-- | ethutil/README.md | 137 | ||||
-rw-r--r-- | ethutil/big.go | 37 | ||||
-rw-r--r-- | ethutil/bytes.go | 64 | ||||
-rw-r--r-- | ethutil/config.go | 106 | ||||
-rw-r--r-- | ethutil/db.go | 10 | ||||
-rw-r--r-- | ethutil/encoding.go | 62 | ||||
-rw-r--r-- | ethutil/encoding_test.go | 37 | ||||
-rw-r--r-- | ethutil/helpers.go | 61 | ||||
-rw-r--r-- | ethutil/parsing.go | 108 | ||||
-rw-r--r-- | ethutil/parsing_test.go | 32 | ||||
-rw-r--r-- | ethutil/rand.go | 24 | ||||
-rw-r--r-- | ethutil/rlp.go | 418 | ||||
-rw-r--r-- | ethutil/rlp_test.go | 170 | ||||
-rw-r--r-- | ethutil/trie.go | 354 | ||||
-rw-r--r-- | ethutil/trie_test.go | 40 | ||||
-rw-r--r-- | ethutil/value.go | 204 |
18 files changed, 1879 insertions, 0 deletions
diff --git a/ethutil/.gitignore b/ethutil/.gitignore new file mode 100644 index 000000000..f725d58d1 --- /dev/null +++ b/ethutil/.gitignore @@ -0,0 +1,12 @@ +# See http://help.github.com/ignore-files/ for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile ~/.gitignore_global + +/tmp +*/**/*un~ +*un~ +.DS_Store +*/**/.DS_Store + diff --git a/ethutil/.travil.yml b/ethutil/.travil.yml new file mode 100644 index 000000000..69359072d --- /dev/null +++ b/ethutil/.travil.yml @@ -0,0 +1,3 @@ +language: go +go: + - 1.2 diff --git a/ethutil/README.md b/ethutil/README.md new file mode 100644 index 000000000..c98612e1e --- /dev/null +++ b/ethutil/README.md @@ -0,0 +1,137 @@ +# 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 +``` + +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{}` + +`AppendLint()` 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) +``` diff --git a/ethutil/big.go b/ethutil/big.go new file mode 100644 index 000000000..979078bef --- /dev/null +++ b/ethutil/big.go @@ -0,0 +1,37 @@ +package ethutil + +import ( + "math/big" +) + +var BigInt0 *big.Int = big.NewInt(0) + +// True +var BigTrue *big.Int = big.NewInt(1) + +// False +var BigFalse *big.Int = big.NewInt(0) + +// Returns the power of two integers +func BigPow(a, b int) *big.Int { + c := new(big.Int) + c.Exp(big.NewInt(int64(a)), big.NewInt(int64(b)), big.NewInt(0)) + + return c +} + +// Like big.NewInt(uint64); this takes a string instead. +func Big(num string) *big.Int { + n := new(big.Int) + n.SetString(num, 0) + + return n +} + +// Like big.NewInt(uint64); this takes a byte buffer instead. +func BigD(data []byte) *big.Int { + n := new(big.Int) + n.SetBytes(data) + + return n +} diff --git a/ethutil/bytes.go b/ethutil/bytes.go new file mode 100644 index 000000000..40903a5f1 --- /dev/null +++ b/ethutil/bytes.go @@ -0,0 +1,64 @@ +package ethutil + +import ( + "bytes" + "encoding/binary" + "fmt" +) + +func NumberToBytes(num interface{}, bits int) []byte { + buf := new(bytes.Buffer) + err := binary.Write(buf, binary.BigEndian, num) + if err != nil { + fmt.Println("NumberToBytes failed:", err) + } + + return buf.Bytes()[buf.Len()-(bits/8):] +} + +func BytesToNumber(b []byte) uint64 { + var number uint64 + + // Make sure the buffer is 64bits + data := make([]byte, 8) + data = append(data[:len(b)], b...) + + buf := bytes.NewReader(data) + err := binary.Read(buf, binary.BigEndian, &number) + if err != nil { + fmt.Println("BytesToNumber failed:", err) + } + + return number +} + +// Read variable integer in big endian +func ReadVarint(reader *bytes.Reader) (ret uint64) { + if reader.Len() == 8 { + var num uint64 + binary.Read(reader, binary.BigEndian, &num) + ret = uint64(num) + } else if reader.Len() == 4 { + var num uint32 + binary.Read(reader, binary.BigEndian, &num) + ret = uint64(num) + } else if reader.Len() == 2 { + var num uint16 + binary.Read(reader, binary.BigEndian, &num) + ret = uint64(num) + } else { + var num uint8 + binary.Read(reader, binary.BigEndian, &num) + ret = uint64(num) + } + + return ret +} + +func BinaryLength(num int) int { + if num == 0 { + return 0 + } + + return 1 + BinaryLength(num>>8) +} diff --git a/ethutil/config.go b/ethutil/config.go new file mode 100644 index 000000000..7782e7daa --- /dev/null +++ b/ethutil/config.go @@ -0,0 +1,106 @@ +package ethutil + +import ( + "log" + "os" + "os/user" + "path" +) + +type LogType byte + +const ( + LogTypeStdIn = 1 + LogTypeFile = 2 +) + +// Config struct isn't exposed +type config struct { + Db Database + + Log Logger + ExecPath string + Debug bool + Ver string + Pubkey []byte + Seed bool +} + +var Config *config + +// Read config doesn't read anything yet. +func ReadConfig(base string) *config { + if Config == nil { + usr, _ := user.Current() + path := path.Join(usr.HomeDir, base) + + //Check if the logging directory already exists, create it if not + _, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + log.Printf("Debug logging directory %s doesn't exist, creating it", path) + os.Mkdir(path, 0777) + } + } + + Config = &config{ExecPath: path, Debug: true, Ver: "0.2.1"} + Config.Log = NewLogger(LogFile|LogStd, 0) + } + + return Config +} + +type LoggerType byte + +const ( + LogFile = 0x1 + LogStd = 0x2 +) + +type Logger struct { + logSys []*log.Logger + logLevel int +} + +func NewLogger(flag LoggerType, level int) Logger { + var loggers []*log.Logger + + flags := log.LstdFlags | log.Lshortfile + + if flag&LogFile > 0 { + file, err := os.OpenFile(path.Join(Config.ExecPath, "debug.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm) + if err != nil { + log.Panic("unable to create file logger", err) + } + + log := log.New(file, "[ETH]", flags) + + loggers = append(loggers, log) + } + if flag&LogStd > 0 { + log := log.New(os.Stdout, "[ETH]", flags) + loggers = append(loggers, log) + } + + return Logger{logSys: loggers, logLevel: level} +} + +func (log Logger) Debugln(v ...interface{}) { + if log.logLevel != 0 { + return + } + + for _, logger := range log.logSys { + logger.Println(v...) + } +} + +func (log Logger) Debugf(format string, v ...interface{}) { + if log.logLevel != 0 { + return + } + + for _, logger := range log.logSys { + logger.Printf(format, v...) + } +} diff --git a/ethutil/db.go b/ethutil/db.go new file mode 100644 index 000000000..3681c4b05 --- /dev/null +++ b/ethutil/db.go @@ -0,0 +1,10 @@ +package ethutil + +// Database interface +type Database interface { + Put(key []byte, value []byte) + Get(key []byte) ([]byte, error) + LastKnownTD() []byte + Close() + Print() +} diff --git a/ethutil/encoding.go b/ethutil/encoding.go new file mode 100644 index 000000000..207548c93 --- /dev/null +++ b/ethutil/encoding.go @@ -0,0 +1,62 @@ +package ethutil + +import ( + "bytes" + "encoding/hex" + _ "fmt" + "strings" +) + +func CompactEncode(hexSlice []int) string { + terminator := 0 + if hexSlice[len(hexSlice)-1] == 16 { + terminator = 1 + } + + if terminator == 1 { + hexSlice = hexSlice[:len(hexSlice)-1] + } + + oddlen := len(hexSlice) % 2 + flags := 2*terminator + oddlen + if oddlen != 0 { + hexSlice = append([]int{flags}, hexSlice...) + } else { + hexSlice = append([]int{flags, 0}, hexSlice...) + } + + var buff bytes.Buffer + for i := 0; i < len(hexSlice); i += 2 { + buff.WriteByte(byte(16*hexSlice[i] + hexSlice[i+1])) + } + + return buff.String() +} + +func CompactDecode(str string) []int { + base := CompactHexDecode(str) + base = base[:len(base)-1] + if base[0] >= 2 { // && base[len(base)-1] != 16 { + base = append(base, 16) + } + if base[0]%2 == 1 { + base = base[1:] + } else { + base = base[2:] + } + + return base +} + +func CompactHexDecode(str string) []int { + base := "0123456789abcdef" + hexSlice := make([]int, 0) + + enc := hex.EncodeToString([]byte(str)) + for _, v := range enc { + hexSlice = append(hexSlice, strings.IndexByte(base, byte(v))) + } + hexSlice = append(hexSlice, 16) + + return hexSlice +} diff --git a/ethutil/encoding_test.go b/ethutil/encoding_test.go new file mode 100644 index 000000000..bcabab0b1 --- /dev/null +++ b/ethutil/encoding_test.go @@ -0,0 +1,37 @@ +package ethutil + +import ( + "fmt" + "testing" +) + +func TestCompactEncode(t *testing.T) { + test1 := []int{1, 2, 3, 4, 5} + if res := CompactEncode(test1); res != "\x11\x23\x45" { + t.Error(fmt.Sprintf("even compact encode failed. Got: %q", res)) + } + + test2 := []int{0, 1, 2, 3, 4, 5} + if res := CompactEncode(test2); res != "\x00\x01\x23\x45" { + t.Error(fmt.Sprintf("odd compact encode failed. Got: %q", res)) + } + + test3 := []int{0, 15, 1, 12, 11, 8 /*term*/, 16} + if res := CompactEncode(test3); res != "\x20\x0f\x1c\xb8" { + t.Error(fmt.Sprintf("odd terminated compact encode failed. Got: %q", res)) + } + + test4 := []int{15, 1, 12, 11, 8 /*term*/, 16} + if res := CompactEncode(test4); res != "\x3f\x1c\xb8" { + t.Error(fmt.Sprintf("even terminated compact encode failed. Got: %q", res)) + } +} + +func TestCompactHexDecode(t *testing.T) { + exp := []int{7, 6, 6, 5, 7, 2, 6, 2, 16} + res := CompactHexDecode("verb") + + if !CompareIntSlice(res, exp) { + t.Error("Error compact hex decode. Expected", exp, "got", res) + } +} diff --git a/ethutil/helpers.go b/ethutil/helpers.go new file mode 100644 index 000000000..1c6adf256 --- /dev/null +++ b/ethutil/helpers.go @@ -0,0 +1,61 @@ +package ethutil + +import ( + "code.google.com/p/go.crypto/ripemd160" + "crypto/sha256" + "encoding/hex" + "github.com/obscuren/sha3" + "strconv" +) + +func Uitoa(i uint32) string { + return strconv.FormatUint(uint64(i), 10) +} + +func Sha256Bin(data []byte) []byte { + hash := sha256.Sum256(data) + + return hash[:] +} + +func Ripemd160(data []byte) []byte { + ripemd := ripemd160.New() + ripemd.Write(data) + + return ripemd.Sum(nil) +} + +func Sha3Bin(data []byte) []byte { + d := sha3.NewKeccak256() + d.Reset() + d.Write(data) + + return d.Sum(nil) +} + +// Helper function for comparing slices +func CompareIntSlice(a, b []int) bool { + if len(a) != len(b) { + return false + } + for i, v := range a { + if v != b[i] { + return false + } + } + return true +} + +// Returns the amount of nibbles that match each other from 0 ... +func MatchingNibbleLength(a, b []int) int { + i := 0 + for CompareIntSlice(a[:i+1], b[:i+1]) && i < len(b) { + i += 1 + } + + return i +} + +func Hex(d []byte) string { + return hex.EncodeToString(d) +} diff --git a/ethutil/parsing.go b/ethutil/parsing.go new file mode 100644 index 000000000..2c41fb4df --- /dev/null +++ b/ethutil/parsing.go @@ -0,0 +1,108 @@ +package ethutil + +import ( + "errors" + "fmt" + "math/big" + "strconv" + "strings" +) + +// Op codes +var OpCodes = map[string]string{ + "STOP": "0", + "ADD": "1", + "MUL": "2", + "SUB": "3", + "DIV": "4", + "SDIV": "5", + "MOD": "6", + "SMOD": "7", + "EXP": "8", + "NEG": "9", + "LT": "10", + "LE": "11", + "GT": "12", + "GE": "13", + "EQ": "14", + "NOT": "15", + "MYADDRESS": "16", + "TXSENDER": "17", + + "PUSH": "48", + "POP": "49", + "LOAD": "54", +} + +func CompileInstr(s string) (string, error) { + tokens := strings.Split(s, " ") + if OpCodes[tokens[0]] == "" { + return s, errors.New(fmt.Sprintf("OP not found: %s", tokens[0])) + } + + code := OpCodes[tokens[0]] // Replace op codes with the proper numerical equivalent + op := new(big.Int) + op.SetString(code, 0) + + args := make([]*big.Int, 6) + for i, val := range tokens[1:len(tokens)] { + num := new(big.Int) + num.SetString(val, 0) + args[i] = num + } + + // Big int equation = op + x * 256 + y * 256**2 + z * 256**3 + a * 256**4 + b * 256**5 + c * 256**6 + base := new(big.Int) + x := new(big.Int) + y := new(big.Int) + z := new(big.Int) + a := new(big.Int) + b := new(big.Int) + c := new(big.Int) + + if args[0] != nil { + x.Mul(args[0], big.NewInt(256)) + } + if args[1] != nil { + y.Mul(args[1], BigPow(256, 2)) + } + if args[2] != nil { + z.Mul(args[2], BigPow(256, 3)) + } + if args[3] != nil { + a.Mul(args[3], BigPow(256, 4)) + } + if args[4] != nil { + b.Mul(args[4], BigPow(256, 5)) + } + if args[5] != nil { + c.Mul(args[5], BigPow(256, 6)) + } + + base.Add(op, x) + base.Add(base, y) + base.Add(base, z) + base.Add(base, a) + base.Add(base, b) + base.Add(base, c) + + return base.String(), nil +} + +func Instr(instr string) (int, []string, error) { + base := new(big.Int) + base.SetString(instr, 0) + + args := make([]string, 7) + for i := 0; i < 7; i++ { + // int(int(val) / int(math.Pow(256,float64(i)))) % 256 + exp := BigPow(256, i) + num := new(big.Int) + num.Div(base, exp) + + args[i] = num.Mod(num, big.NewInt(256)).String() + } + op, _ := strconv.Atoi(args[0]) + + return op, args[1:7], nil +} diff --git a/ethutil/parsing_test.go b/ethutil/parsing_test.go new file mode 100644 index 000000000..482eef3ee --- /dev/null +++ b/ethutil/parsing_test.go @@ -0,0 +1,32 @@ +package ethutil + +import ( + "math" + "testing" +) + +func TestCompile(t *testing.T) { + instr, err := CompileInstr("PUSH") + + if err != nil { + t.Error("Failed compiling instruction") + } + + calc := (48 + 0*256 + 0*int64(math.Pow(256, 2))) + if Big(instr).Int64() != calc { + t.Error("Expected", calc, ", got:", instr) + } +} + +func TestValidInstr(t *testing.T) { + /* + op, args, err := Instr("68163") + if err != nil { + t.Error("Error decoding instruction") + } + */ + +} + +func TestInvalidInstr(t *testing.T) { +} diff --git a/ethutil/rand.go b/ethutil/rand.go new file mode 100644 index 000000000..91dafec7e --- /dev/null +++ b/ethutil/rand.go @@ -0,0 +1,24 @@ +package ethutil + +import ( + "crypto/rand" + "encoding/binary" + "io" +) + +func randomUint64(r io.Reader) (uint64, error) { + b := make([]byte, 8) + n, err := r.Read(b) + if n != len(b) { + return 0, io.ErrShortBuffer + } + if err != nil { + return 0, err + } + return binary.BigEndian.Uint64(b), nil +} + +// RandomUint64 returns a cryptographically random uint64 value. +func RandomUint64() (uint64, error) { + return randomUint64(rand.Reader) +} diff --git a/ethutil/rlp.go b/ethutil/rlp.go new file mode 100644 index 000000000..0f88933b3 --- /dev/null +++ b/ethutil/rlp.go @@ -0,0 +1,418 @@ +package ethutil + +import ( + "bytes" + _ "encoding/binary" + "fmt" + _ "log" + _ "math" + "math/big" + "reflect" +) + +/////////////////////////////////////// +type EthEncoder interface { + EncodeData(rlpData interface{}) []byte +} +type EthDecoder interface { + Get(idx int) *RlpValue +} + +////////////////////////////////////// + +type RlpEncoder struct { + rlpData []byte +} + +func NewRlpEncoder() *RlpEncoder { + encoder := &RlpEncoder{} + + return encoder +} +func (coder *RlpEncoder) EncodeData(rlpData interface{}) []byte { + return Encode(rlpData) +} + +// Data rlpValueutes are returned by the rlp decoder. The data rlpValueutes represents +// one item within the rlp data structure. It's responsible for all the casting +// It always returns something rlpValueid +type RlpValue struct { + Value interface{} + kind reflect.Value +} + +func (rlpValue *RlpValue) String() string { + return fmt.Sprintf("%q", rlpValue.Value) +} + +func Conv(rlpValue interface{}) *RlpValue { + return &RlpValue{Value: rlpValue, kind: reflect.ValueOf(rlpValue)} +} + +func NewRlpValue(rlpValue interface{}) *RlpValue { + return &RlpValue{Value: rlpValue} +} + +func (rlpValue *RlpValue) Type() reflect.Kind { + return reflect.TypeOf(rlpValue.Value).Kind() +} + +func (rlpValue *RlpValue) IsNil() bool { + return rlpValue.Value == nil +} + +func (rlpValue *RlpValue) Length() int { + //return rlpValue.kind.Len() + if data, ok := rlpValue.Value.([]interface{}); ok { + return len(data) + } + + return 0 +} + +func (rlpValue *RlpValue) AsRaw() interface{} { + return rlpValue.Value +} + +func (rlpValue *RlpValue) AsUint() uint64 { + if Value, ok := rlpValue.Value.(uint8); ok { + return uint64(Value) + } else if Value, ok := rlpValue.Value.(uint16); ok { + return uint64(Value) + } else if Value, ok := rlpValue.Value.(uint32); ok { + return uint64(Value) + } else if Value, ok := rlpValue.Value.(uint64); ok { + return Value + } + + return 0 +} + +func (rlpValue *RlpValue) AsByte() byte { + if Value, ok := rlpValue.Value.(byte); ok { + return Value + } + + return 0x0 +} + +func (rlpValue *RlpValue) AsBigInt() *big.Int { + if a, ok := rlpValue.Value.([]byte); ok { + b := new(big.Int) + b.SetBytes(a) + return b + } + + return big.NewInt(0) +} + +func (rlpValue *RlpValue) AsString() string { + if a, ok := rlpValue.Value.([]byte); ok { + return string(a) + } else if a, ok := rlpValue.Value.(string); ok { + return a + } else { + //panic(fmt.Sprintf("not string %T: %v", rlpValue.Value, rlpValue.Value)) + } + + return "" +} + +func (rlpValue *RlpValue) AsBytes() []byte { + if a, ok := rlpValue.Value.([]byte); ok { + return a + } + + return make([]byte, 0) +} + +func (rlpValue *RlpValue) AsSlice() []interface{} { + if d, ok := rlpValue.Value.([]interface{}); ok { + return d + } + + return []interface{}{} +} + +func (rlpValue *RlpValue) AsSliceFrom(from int) *RlpValue { + slice := rlpValue.AsSlice() + + return NewRlpValue(slice[from:]) +} + +func (rlpValue *RlpValue) AsSliceTo(to int) *RlpValue { + slice := rlpValue.AsSlice() + + return NewRlpValue(slice[:to]) +} + +func (rlpValue *RlpValue) AsSliceFromTo(from, to int) *RlpValue { + slice := rlpValue.AsSlice() + + return NewRlpValue(slice[from:to]) +} + +// Threat the rlpValueute as a slice +func (rlpValue *RlpValue) Get(idx int) *RlpValue { + if d, ok := rlpValue.Value.([]interface{}); ok { + // Guard for oob + if len(d) <= idx { + return NewRlpValue(nil) + } + + if idx < 0 { + panic("negative idx for Rlp Get") + } + + return NewRlpValue(d[idx]) + } + + // If this wasn't a slice you probably shouldn't be using this function + return NewRlpValue(nil) +} + +func (rlpValue *RlpValue) Cmp(o *RlpValue) bool { + return reflect.DeepEqual(rlpValue.Value, o.Value) +} + +func (rlpValue *RlpValue) Encode() []byte { + return Encode(rlpValue.Value) +} + +func NewRlpValueFromBytes(rlpData []byte) *RlpValue { + if len(rlpData) != 0 { + data, _ := Decode(rlpData, 0) + return NewRlpValue(data) + } + + return NewRlpValue(nil) +} + +// RlpValue value setters +// An empty rlp value is always a list +func EmptyRlpValue() *RlpValue { + return NewRlpValue([]interface{}{}) +} + +func (rlpValue *RlpValue) AppendList() *RlpValue { + list := EmptyRlpValue() + rlpValue.Value = append(rlpValue.AsSlice(), list) + + return list +} + +func (rlpValue *RlpValue) Append(v interface{}) *RlpValue { + rlpValue.Value = append(rlpValue.AsSlice(), v) + + return rlpValue +} + +/* +func FromBin(data []byte) uint64 { + if len(data) == 0 { + return 0 + } + + return FromBin(data[:len(data)-1])*256 + uint64(data[len(data)-1]) +} +*/ + +const ( + RlpEmptyList = 0x80 + RlpEmptyStr = 0x40 +) + +func Char(c []byte) int { + if len(c) > 0 { + return int(c[0]) + } + + return 0 +} + +func DecodeWithReader(reader *bytes.Buffer) interface{} { + var slice []interface{} + + // Read the next byte + char := Char(reader.Next(1)) + switch { + case char == 0: + return nil + case char <= 0x7c: + return char + + case char <= 0xb7: + return reader.Next(int(char - 0x80)) + + case char <= 0xbf: + buff := bytes.NewReader(reader.Next(int(char - 0xb8))) + length := ReadVarint(buff) + + return reader.Next(int(length)) + + case char <= 0xf7: + length := int(char - 0xc0) + for i := 0; i < length; i++ { + obj := DecodeWithReader(reader) + if obj != nil { + slice = append(slice, obj) + } else { + break + } + } + + return slice + + } + + return slice +} + +// 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) { + /* + if pos > uint64(len(data)-1) { + log.Println(data) + log.Panicf("index out of range %d for data %q, l = %d", pos, data, len(data)) + } + */ + + 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(bytes.NewReader(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 := BigD(data[pos+1 : pos+1+l]).Uint64() + b := ReadVarint(bytes.NewReader(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 +} + +var ( + directRlp = big.NewInt(0x7f) + numberRlp = big.NewInt(0xb7) + zeroRlp = big.NewInt(0x0) +) + +func Encode(object interface{}) []byte { + var buff bytes.Buffer + + if object != nil { + switch t := object.(type) { + case *RlpValue: + buff.Write(Encode(t.AsRaw())) + // 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: + buff.Write(Encode(t.Bytes())) + 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()) + } + } else { + // Empty list for nil + buff.WriteByte(0xc0) + } + + return buff.Bytes() +} diff --git a/ethutil/rlp_test.go b/ethutil/rlp_test.go new file mode 100644 index 000000000..32bcbdce1 --- /dev/null +++ b/ethutil/rlp_test.go @@ -0,0 +1,170 @@ +package ethutil + +import ( + "bytes" + "encoding/hex" + "fmt" + "math/big" + "reflect" + "testing" +) + +func TestRlpValueEncoding(t *testing.T) { + val := EmptyRlpValue() + val.AppendList().Append(1).Append(2).Append(3) + val.Append("4").AppendList().Append(5) + + res := val.Encode() + exp := Encode([]interface{}{[]interface{}{1, 2, 3}, "4", []interface{}{5}}) + if bytes.Compare(res, exp) != 0 { + t.Errorf("expected %q, got %q", res, exp) + } +} + +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 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.Error(fmt.Sprintf("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(fmt.Sprintf("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) + fmt.Println(value.BigInt(), bigInt) + if value.BigInt().Cmp(bigInt) != 0 { + t.Errorf("Expected %v, got %v", bigInt, value.BigInt()) + } + + dec, _ := hex.DecodeString("52f4fc1e") + fmt.Println(NewValueFromBytes(dec).BigInt()) +} + +func TestEncodeDecodeBytes(t *testing.T) { + b := NewValue([]interface{}{[]byte{1, 2, 3, 4, 5}, byte(6)}) + val := NewValueFromBytes(b.Encode()) + if !b.Cmp(val) { + t.Errorf("Expected %v, got %v", val, b) + } +} + +/* +var ZeroHash256 = make([]byte, 32) +var ZeroHash160 = make([]byte, 20) +var EmptyShaList = Sha3Bin(Encode([]interface{}{})) + +var GenisisHeader = []interface{}{ + // Previous hash (none) + //"", + ZeroHash256, + // Sha of uncles + Sha3Bin(Encode([]interface{}{})), + // Coinbase + ZeroHash160, + // Root state + "", + // Sha of transactions + //EmptyShaList, + Sha3Bin(Encode([]interface{}{})), + // Difficulty + BigPow(2, 22), + // Time + //big.NewInt(0), + int64(0), + // extra + "", + // Nonce + big.NewInt(42), +} + +func TestEnc(t *testing.T) { + //enc := Encode(GenisisHeader) + //fmt.Printf("%x (%d)\n", enc, len(enc)) + h, _ := hex.DecodeString("f8a0a00000000000000000000000000000000000000000000000000000000000000000a01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347940000000000000000000000000000000000000000a06d076baa9c4074fb2df222dd16a96b0155a1e6686b3e5748b4e9ca0a208a425ca01dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d493478340000080802a") + fmt.Printf("%x\n", Sha3Bin(h)) +} +*/ + +func BenchmarkEncodeDecode(b *testing.B) { + for i := 0; i < b.N; i++ { + bytes := Encode([]interface{}{"dog", "god", "cat"}) + Decode(bytes, 0) + } +} diff --git a/ethutil/trie.go b/ethutil/trie.go new file mode 100644 index 000000000..44d2d5774 --- /dev/null +++ b/ethutil/trie.go @@ -0,0 +1,354 @@ +package ethutil + +import ( + "fmt" + "reflect" +) + +type Node struct { + Key []byte + Value *Value + Dirty bool +} + +func NewNode(key []byte, val *Value, dirty bool) *Node { + return &Node{Key: key, Value: val, Dirty: dirty} +} + +func (n *Node) Copy() *Node { + return NewNode(n.Key, n.Value, n.Dirty) +} + +type Cache struct { + nodes map[string]*Node + db Database +} + +func NewCache(db Database) *Cache { + return &Cache{db: db, nodes: make(map[string]*Node)} +} + +func (cache *Cache) Put(v interface{}) interface{} { + value := NewValue(v) + + enc := value.Encode() + if len(enc) >= 32 { + sha := Sha3Bin(enc) + + cache.nodes[string(sha)] = NewNode(sha, value, true) + + return sha + } + + return v +} + +func (cache *Cache) Get(key []byte) *Value { + // First check if the key is the cache + if cache.nodes[string(key)] != nil { + return cache.nodes[string(key)].Value + } + + // Get the key of the database instead and cache it + data, _ := cache.db.Get(key) + // Create the cached value + value := NewValueFromBytes(data) + // Create caching node + cache.nodes[string(key)] = NewNode(key, value, false) + + return value +} + +func (cache *Cache) Commit() { + for key, node := range cache.nodes { + if node.Dirty { + cache.db.Put([]byte(key), node.Value.Encode()) + node.Dirty = false + } + } + + // If the nodes grows beyond the 200 entries we simple empty it + // FIXME come up with something better + if len(cache.nodes) > 200 { + cache.nodes = make(map[string]*Node) + } +} + +func (cache *Cache) Undo() { + for key, node := range cache.nodes { + if node.Dirty { + delete(cache.nodes, key) + } + } +} + +// A (modified) Radix Trie implementation +type Trie struct { + Root interface{} + //db Database + cache *Cache +} + +func NewTrie(db Database, Root interface{}) *Trie { + return &Trie{cache: NewCache(db), Root: Root} +} + +func (t *Trie) Sync() { + t.cache.Commit() +} + +/* + * Public (query) interface functions + */ +func (t *Trie) Update(key string, value string) { + k := CompactHexDecode(key) + + t.Root = t.UpdateState(t.Root, k, value) +} + +func (t *Trie) Get(key string) string { + k := CompactHexDecode(key) + c := NewValue(t.GetState(t.Root, k)) + + return c.Str() +} + +func (t *Trie) GetState(node interface{}, key []int) interface{} { + n := NewValue(node) + // Return the node if key is empty (= found) + if len(key) == 0 || n.IsNil() || n.Len() == 0 { + return node + } + + currentNode := t.GetNode(node) + length := currentNode.Len() + + if length == 0 { + return "" + } else if length == 2 { + // Decode the key + k := CompactDecode(currentNode.Get(0).Str()) + v := currentNode.Get(1).Raw() + + if len(key) >= len(k) && CompareIntSlice(k, key[:len(k)]) { + return t.GetState(v, key[len(k):]) + } else { + return "" + } + } else if length == 17 { + return t.GetState(currentNode.Get(key[0]).Raw(), key[1:]) + } + + // It shouldn't come this far + fmt.Println("GetState unexpected return") + return "" +} + +func (t *Trie) GetNode(node interface{}) *Value { + n := NewValue(node) + + if !n.Get(0).IsNil() { + return n + } + + str := n.Str() + if len(str) == 0 { + return n + } else if len(str) < 32 { + return NewValueFromBytes([]byte(str)) + } + /* + else { + // Fetch the encoded node from the db + o, err := t.db.Get(n.Bytes()) + if err != nil { + fmt.Println("Error InsertState", err) + return NewValue("") + } + + return NewValueFromBytes(o) + } + */ + return t.cache.Get(n.Bytes()) + +} + +func (t *Trie) UpdateState(node interface{}, key []int, value string) interface{} { + if value != "" { + return t.InsertState(node, key, value) + } else { + // delete it + } + + return "" +} + +func (t *Trie) Put(node interface{}) interface{} { + /* + enc := Encode(node) + if len(enc) >= 32 { + var sha []byte + sha = Sha3Bin(enc) + //t.db.Put([]byte(sha), enc) + + return sha + } + return node + */ + + /* + TODO? + c := Conv(t.Root) + fmt.Println(c.Type(), c.Length()) + if c.Type() == reflect.String && c.AsString() == "" { + return enc + } + */ + + return t.cache.Put(node) + +} + +func EmptyStringSlice(l int) []interface{} { + slice := make([]interface{}, l) + for i := 0; i < l; i++ { + slice[i] = "" + } + return slice +} + +func (t *Trie) InsertState(node interface{}, key []int, value interface{}) interface{} { + if len(key) == 0 { + return value + } + + // New node + n := NewValue(node) + if node == nil || (n.Type() == reflect.String && (n.Str() == "" || n.Get(0).IsNil())) || n.Len() == 0 { + newNode := []interface{}{CompactEncode(key), value} + + return t.Put(newNode) + } + + currentNode := t.GetNode(node) + // Check for "special" 2 slice type node + if currentNode.Len() == 2 { + // Decode the key + k := CompactDecode(currentNode.Get(0).Str()) + v := currentNode.Get(1).Raw() + + // Matching key pair (ie. there's already an object with this key) + if CompareIntSlice(k, key) { + newNode := []interface{}{CompactEncode(key), value} + return t.Put(newNode) + } + + var newHash interface{} + matchingLength := MatchingNibbleLength(key, k) + if matchingLength == len(k) { + // Insert the hash, creating a new node + newHash = t.InsertState(v, key[matchingLength:], value) + } else { + // Expand the 2 length slice to a 17 length slice + oldNode := t.InsertState("", k[matchingLength+1:], v) + newNode := t.InsertState("", key[matchingLength+1:], value) + // Create an expanded slice + scaledSlice := EmptyStringSlice(17) + // Set the copied and new node + scaledSlice[k[matchingLength]] = oldNode + scaledSlice[key[matchingLength]] = newNode + + newHash = t.Put(scaledSlice) + } + + if matchingLength == 0 { + // End of the chain, return + return newHash + } else { + newNode := []interface{}{CompactEncode(key[:matchingLength]), newHash} + return t.Put(newNode) + } + } else { + + // Copy the current node over to the new node and replace the first nibble in the key + newNode := EmptyStringSlice(17) + + for i := 0; i < 17; i++ { + cpy := currentNode.Get(i).Raw() + if cpy != nil { + newNode[i] = cpy + } + } + + newNode[key[0]] = t.InsertState(currentNode.Get(key[0]).Raw(), key[1:], value) + + return t.Put(newNode) + } + + return "" +} + +// Simple compare function which creates a rlp value out of the evaluated objects +func (t *Trie) Cmp(trie *Trie) bool { + return NewValue(t.Root).Cmp(NewValue(trie.Root)) +} + +// Returns a copy of this trie +func (t *Trie) Copy() *Trie { + trie := NewTrie(t.cache.db, t.Root) + for key, node := range t.cache.nodes { + trie.cache.nodes[key] = node.Copy() + } + + return trie +} + +/* + * Trie helper functions + */ +// Helper function for printing out the raw contents of a slice +func PrintSlice(slice []string) { + fmt.Printf("[") + for i, val := range slice { + fmt.Printf("%q", val) + if i != len(slice)-1 { + fmt.Printf(",") + } + } + fmt.Printf("]\n") +} + +func PrintSliceT(slice interface{}) { + c := Conv(slice) + for i := 0; i < c.Length(); i++ { + val := c.Get(i) + if val.Type() == reflect.Slice { + PrintSliceT(val.AsRaw()) + } else { + fmt.Printf("%q", val) + if i != c.Length()-1 { + fmt.Printf(",") + } + } + } +} + +// RLP Decodes a node in to a [2] or [17] string slice +func DecodeNode(data []byte) []string { + dec, _ := Decode(data, 0) + if slice, ok := dec.([]interface{}); ok { + strSlice := make([]string, len(slice)) + + for i, s := range slice { + if str, ok := s.([]byte); ok { + strSlice[i] = string(str) + } + } + + return strSlice + } else { + fmt.Printf("It wasn't a []. It's a %T\n", dec) + } + + return nil +} diff --git a/ethutil/trie_test.go b/ethutil/trie_test.go new file mode 100644 index 000000000..b87d35e1a --- /dev/null +++ b/ethutil/trie_test.go @@ -0,0 +1,40 @@ +package ethutil + +import ( + _ "encoding/hex" + _ "fmt" + "testing" +) + +type MemDatabase struct { + db map[string][]byte +} + +func NewMemDatabase() (*MemDatabase, error) { + db := &MemDatabase{db: make(map[string][]byte)} + return db, nil +} +func (db *MemDatabase) Put(key []byte, value []byte) { + db.db[string(key)] = value +} +func (db *MemDatabase) Get(key []byte) ([]byte, error) { + return db.db[string(key)], nil +} +func (db *MemDatabase) Print() {} +func (db *MemDatabase) Close() {} +func (db *MemDatabase) LastKnownTD() []byte { return nil } + +func TestTrieSync(t *testing.T) { + db, _ := NewMemDatabase() + trie := NewTrie(db, "") + + trie.Update("dog", "kindofalongsentencewhichshouldbeencodedinitsentirety") + if len(db.db) != 0 { + t.Error("Expected no data in database") + } + + trie.Sync() + if len(db.db) == 0 { + t.Error("Expected data to be persisted") + } +} diff --git a/ethutil/value.go b/ethutil/value.go new file mode 100644 index 000000000..2a990783e --- /dev/null +++ b/ethutil/value.go @@ -0,0 +1,204 @@ +package ethutil + +import ( + "bytes" + "fmt" + "math/big" + "reflect" +) + +// Data values are returned by the rlp decoder. The data values represents +// one item within the rlp data structure. It's responsible for all the casting +// It always returns something valid +type Value struct { + Val interface{} + kind reflect.Value +} + +func (val *Value) String() string { + return fmt.Sprintf("%q", val.Val) +} + +func NewValue(val interface{}) *Value { + return &Value{Val: val} +} + +func (val *Value) Type() reflect.Kind { + return reflect.TypeOf(val.Val).Kind() +} + +func (val *Value) IsNil() bool { + return val.Val == nil +} + +func (val *Value) Len() int { + //return val.kind.Len() + if data, ok := val.Val.([]interface{}); ok { + return len(data) + } else if data, ok := val.Val.([]byte); ok { + // FIXME + return len(data) + } + + return 0 +} + +func (val *Value) Raw() interface{} { + return val.Val +} + +func (val *Value) Interface() interface{} { + return val.Val +} + +func (val *Value) Uint() uint64 { + if Val, ok := val.Val.(uint8); ok { + return uint64(Val) + } else if Val, ok := val.Val.(uint16); ok { + return uint64(Val) + } else if Val, ok := val.Val.(uint32); ok { + return uint64(Val) + } else if Val, ok := val.Val.(uint64); ok { + return Val + } else if Val, ok := val.Val.([]byte); ok { + return ReadVarint(bytes.NewReader(Val)) + } + + return 0 +} + +func (val *Value) Byte() byte { + if Val, ok := val.Val.(byte); ok { + return Val + } + + return 0x0 +} + +func (val *Value) BigInt() *big.Int { + if a, ok := val.Val.([]byte); ok { + b := new(big.Int).SetBytes(a) + + return b + } else { + return big.NewInt(int64(val.Uint())) + } + + return big.NewInt(0) +} + +func (val *Value) Str() string { + if a, ok := val.Val.([]byte); ok { + return string(a) + } else if a, ok := val.Val.(string); ok { + return a + } + + return "" +} + +func (val *Value) Bytes() []byte { + if a, ok := val.Val.([]byte); ok { + return a + } + + return make([]byte, 0) +} + +func (val *Value) Slice() []interface{} { + if d, ok := val.Val.([]interface{}); ok { + return d + } + + return []interface{}{} +} + +func (val *Value) SliceFrom(from int) *Value { + slice := val.Slice() + + return NewValue(slice[from:]) +} + +func (val *Value) SliceTo(to int) *Value { + slice := val.Slice() + + return NewValue(slice[:to]) +} + +func (val *Value) SliceFromTo(from, to int) *Value { + slice := val.Slice() + + return NewValue(slice[from:to]) +} + +// Threat the value as a slice +func (val *Value) Get(idx int) *Value { + if d, ok := val.Val.([]interface{}); ok { + // Guard for oob + if len(d) <= idx { + return NewValue(nil) + } + + if idx < 0 { + panic("negative idx for Rlp Get") + } + + return NewValue(d[idx]) + } + + // If this wasn't a slice you probably shouldn't be using this function + return NewValue(nil) +} + +func (val *Value) Cmp(o *Value) bool { + return reflect.DeepEqual(val.Val, o.Val) +} + +func (val *Value) Encode() []byte { + return Encode(val.Val) +} + +func NewValueFromBytes(rlpData []byte) *Value { + if len(rlpData) != 0 { + data, _ := Decode(rlpData, 0) + return NewValue(data) + } + + return NewValue(nil) +} + +// Value setters +func NewSliceValue(s interface{}) *Value { + list := EmptyValue() + + if s != nil { + if slice, ok := s.([]interface{}); ok { + for _, val := range slice { + list.Append(val) + } + } else if slice, ok := s.([]string); ok { + for _, val := range slice { + list.Append(val) + } + } + } + + return list +} + +func EmptyValue() *Value { + return NewValue([]interface{}{}) +} + +func (val *Value) AppendList() *Value { + list := EmptyValue() + val.Val = append(val.Slice(), list) + + return list +} + +func (val *Value) Append(v interface{}) *Value { + val.Val = append(val.Slice(), v) + + return val +} |