quorum/trie.go

202 lines
4.7 KiB
Go

package main
import (
"fmt"
)
// Database interface
type Database interface {
Put(key []byte, value []byte)
Get(key []byte) ([]byte, error)
}
/*
* 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")
}
// 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
}
return nil
}
// A (modified) Radix Trie implementation
type Trie struct {
root string
db Database
}
func NewTrie(db Database, root string) *Trie {
return &Trie{db: db, root: root}
}
/*
* 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)
return t.GetState(t.root, k)
}
/*
* State functions (shouldn't be needed directly).
*/
// Wrapper around the regular db "Put" which generates a key and value
func (t *Trie) Put(node interface{}) []byte {
enc := Encode(node)
sha := Sha256Bin(enc)
t.db.Put([]byte(sha), enc)
return sha
}
// Helper function for printing a node (using fetch, decode and slice printing)
func (t *Trie) PrintNode(n string) {
data, _ := t.db.Get([]byte(n))
d := DecodeNode(data)
PrintSlice(d)
}
// Returns the state of an object
func (t *Trie) GetState(node string, key []int) string {
// Return the node if key is empty (= found)
if len(key) == 0 || node == "" {
return node
}
// Fetch the encoded node from the db
n, err := t.db.Get([]byte(node))
if err != nil { fmt.Println("Error in GetState for node", node, "with key", key); return "" }
// Decode it
currentNode := DecodeNode(n)
if len(currentNode) == 0 {
return ""
} else if len(currentNode) == 2 {
// Decode the key
k := CompactDecode(currentNode[0])
v := currentNode[1]
if len(key) >= len(k) && CompareIntSlice(k, key[:len(k)]) {
return t.GetState(v, key[len(k):])
} else {
return ""
}
} else if len(currentNode) == 17 {
return t.GetState(currentNode[key[0]], key[1:])
}
// It shouldn't come this far
fmt.Println("GetState unexpected return")
return ""
}
// Inserts a new sate or delete a state based on the value
func (t *Trie) UpdateState(node string, key []int, value string) string {
if value != "" {
return t.InsertState(node, key, value)
} else {
// delete it
}
return ""
}
func (t *Trie) InsertState(node string, key []int, value string) string {
if len(key) == 0 {
return value
}
// New node
if node == "" {
newNode := []string{ CompactEncode(key), value }
return string(t.Put(newNode))
}
// Fetch the encoded node from the db
n, err := t.db.Get([]byte(node))
if err != nil { fmt.Println("Error InsertState", err); return "" }
// Decode it
currentNode := DecodeNode(n)
// Check for "special" 2 slice type node
if len(currentNode) == 2 {
// Decode the key
k := CompactDecode(currentNode[0])
v := currentNode[1]
// Matching key pair (ie. there's already an object with this key)
if CompareIntSlice(k, key) {
return string(t.Put([]string{ CompactEncode(key), value }))
}
var newHash string
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 := make([]string, 17)
// Set the copied and new node
scaledSlice[k[matchingLength]] = oldNode
scaledSlice[key[matchingLength]] = newNode
newHash = string(t.Put(scaledSlice))
}
if matchingLength == 0 {
// End of the chain, return
return newHash
} else {
newNode := []string{ CompactEncode(key[:matchingLength]), newHash }
return string(t.Put(newNode))
}
} else {
// Copy the current node over to the new node and replace the first nibble in the key
newNode := make([]string, 17); copy(newNode, currentNode)
newNode[key[0]] = t.InsertState(currentNode[key[0]], key[1:], value)
return string(t.Put(newNode))
}
return ""
}