wip: commits, nodes, and trie are all backed with storage.

This commit is contained in:
Tzu-Jung Lee 2018-08-05 17:43:38 -07:00
parent 4651558b98
commit a7fe59dd49
14 changed files with 729 additions and 637 deletions

41
cfg/cfg.go Normal file
View file

@ -0,0 +1,41 @@
package cfg
import (
"path/filepath"
"github.com/btcsuite/btcutil"
)
// Index ...
type Index int
// ...
const (
TrieDB Index = 1 << iota
CommitDB
NodeDB
ClaimScriptDB
)
var (
defaultHomeDir = btcutil.AppDataDir("lbrycrd.go", false)
defaultDataDir = filepath.Join(defaultHomeDir, "data")
)
// Config ...
type Config struct {
path string
}
var datastores = map[Index]string{
ClaimScriptDB: "cs.db", // Exported from BTCD
CommitDB: "commit.db",
TrieDB: "trie.db",
NodeDB: "nm.db",
}
// DefaultConfig ...
func DefaultConfig(idx Index) string {
return filepath.Join(defaultDataDir, datastores[idx])
}

65
change/chg.go Normal file
View file

@ -0,0 +1,65 @@
package change
import (
"fmt"
"github.com/lbryio/claimtrie/claim"
)
// Cmd defines the type of Change.
type Cmd int
// The list of command currently supported.
const (
AddClaim Cmd = 1 << iota
SpendClaim
UpdateClaim
AddSupport
SpendSupport
)
var names = map[Cmd]string{
AddClaim: "+C",
SpendClaim: "-C",
UpdateClaim: "+U",
AddSupport: "+S",
SpendSupport: "-S",
}
// Change represent a record of changes to the node of Name at Height.
type Change struct {
Height claim.Height
Cmd Cmd
Name string
OP claim.OutPoint
Amt claim.Amount
ID claim.ID
Value []byte
}
func (c Change) String() string {
return fmt.Sprintf("%6d %s %s %s %12d [%s]", c.Height, names[c.Cmd], c.OP, c.ID, c.Amt, c.Name)
}
// New returns a Change initialized with Cmd.
func New(cmd Cmd) *Change {
return &Change{Cmd: cmd}
}
// SetName sets name to the Change.
func (c *Change) SetName(name string) *Change { c.Name = name; return c }
// SetHeight sets height to the Change.
func (c *Change) SetHeight(h claim.Height) *Change { c.Height = h; return c }
// SetOP sets OP to the Change.
func (c *Change) SetOP(op claim.OutPoint) *Change { c.OP = op; return c }
// SetAmt sets amt to the Change.
func (c *Change) SetAmt(amt claim.Amount) *Change { c.Amt = amt; return c }
// SetID sets id to the Change.
func (c *Change) SetID(id claim.ID) *Change { c.ID = id; return c }
// SetValue sets value to the Change.
func (c *Change) SetValue(v []byte) *Change { c.Value = v; return c }

93
change/list.go Normal file
View file

@ -0,0 +1,93 @@
package change
import (
"bytes"
"encoding/gob"
"fmt"
"github.com/lbryio/claimtrie/claim"
"github.com/pkg/errors"
"github.com/syndtr/goleveldb/leveldb"
)
// List ...
type List struct {
db *leveldb.DB
name string
chgs []*Change
err error
}
// NewChangeList ...
func NewChangeList(db *leveldb.DB, name string) *List {
return &List{db: db, name: name}
}
// Changes returns the Changes in the list.
func (cl *List) Changes() []*Change {
return cl.chgs
}
// Load loads Changes from database.
func (cl *List) Load() *List {
if cl.err == nil {
cl.chgs, cl.err = loadChanges(cl.db, cl.name)
}
return cl
}
// Save saves Changes to database.
func (cl *List) Save() *List {
if cl.err == nil {
cl.err = saveChanges(cl.db, cl.name, cl.chgs)
}
return cl
}
// Append appenda a Change to the Changes in the list.
func (cl *List) Append(chg *Change) *List {
cl.chgs = append(cl.chgs, chg)
return cl
}
// Truncate truncates Changes that has Heiht larger than ht.
func (cl *List) Truncate(ht claim.Height) *List {
for i, chg := range cl.chgs {
if chg.Height > ht {
cl.chgs = cl.chgs[:i]
break
}
}
return cl
}
// Dump prints out the Changes in the list. (Debugging only.)
func (cl *List) Dump() *List {
for i, chg := range cl.chgs {
fmt.Printf("chgs[%d] %s\n", i, chg)
}
return cl
}
func loadChanges(db *leveldb.DB, name string) ([]*Change, error) {
data, err := db.Get([]byte(name), nil)
if err == leveldb.ErrNotFound {
return nil, nil
} else if err != nil {
return nil, errors.Wrapf(err, "db.Get(%s)", name)
}
var chgs []*Change
if err = gob.NewDecoder(bytes.NewBuffer(data)).Decode(&chgs); err != nil {
return nil, errors.Wrapf(err, "gob.Decode(&blk)")
}
return chgs, nil
}
func saveChanges(db *leveldb.DB, name string, chgs []*Change) error {
buf := bytes.NewBuffer(nil)
if err := gob.NewEncoder(buf).Encode(&chgs); err != nil {
return errors.Wrapf(err, "gob.Decode(&blk)")
}
return errors.Wrapf(db.Put([]byte(name), buf.Bytes(), nil), "db.put(%s, buf)", name)
}

View file

@ -35,8 +35,8 @@ type Claim struct {
func (c *Claim) setOutPoint(op OutPoint) *Claim { c.OutPoint = op; return c }
func (c *Claim) setID(id ID) *Claim { c.ID = id; return c }
func (c *Claim) setAmt(amt Amount) *Claim { c.Amt = amt; return c }
func (c *Claim) setAccepted(h Height) *Claim { c.Accepted = h; return c }
func (c *Claim) setActiveAt(h Height) *Claim { c.ActiveAt = h; return c }
func (c *Claim) setAccepted(ht Height) *Claim { c.Accepted = ht; return c }
func (c *Claim) setActiveAt(ht Height) *Claim { c.ActiveAt = ht; return c }
func (c *Claim) String() string { return claimToString(c) }
func (c *Claim) expireAt() Height {
@ -46,8 +46,8 @@ func (c *Claim) expireAt() Height {
return c.Accepted + paramOriginalClaimExpirationTime
}
func isActiveAt(c *Claim, h Height) bool {
return c != nil && c.ActiveAt <= h && c.expireAt() > h
func isActiveAt(c *Claim, ht Height) bool {
return c != nil && c.ActiveAt <= ht && c.expireAt() > ht
}
func equal(a, b *Claim) bool {

View file

@ -1,7 +1,6 @@
package claim
import (
"fmt"
"math"
"github.com/btcsuite/btcd/chaincfg/chainhash"
@ -22,8 +21,6 @@ type Node struct {
// refer to updateClaim.
removed list
records []*Cmd
}
// NewNode returns a new Node.
@ -41,36 +38,11 @@ func (n *Node) BestClaim() *Claim {
return n.best
}
// AddClaim adds a claim to the node.
// AddClaim adds a Claim to the Node.
func (n *Node) AddClaim(op OutPoint, amt Amount) error {
return n.execute(n.record(CmdAddClaim, op, amt, ID{}))
}
// SpendClaim spends a claim in the node.
func (n *Node) SpendClaim(op OutPoint) error {
return n.execute(n.record(CmdSpendClaim, op, 0, ID{}))
}
// UpdateClaim updates a claim in the node.
func (n *Node) UpdateClaim(op OutPoint, amt Amount, id ID) error {
return n.execute(n.record(CmdUpdateClaim, op, amt, id))
}
// AddSupport adds a support in the node.
func (n *Node) AddSupport(op OutPoint, amt Amount, id ID) error {
return n.execute(n.record(CmdAddSupport, op, amt, id))
}
// SpendSupport spends a spport in the node.
func (n *Node) SpendSupport(op OutPoint) error {
return n.execute(n.record(CmdSpendSupport, op, 0, ID{}))
}
func (n *Node) addClaim(op OutPoint, amt Amount) error {
if find(byOP(op), n.claims, n.supports) != nil {
return ErrDuplicate
}
accepted := n.height + 1
c := New(op, amt).setID(NewID(op)).setAccepted(accepted)
c.setActiveAt(accepted + calDelay(accepted, n.tookover))
@ -82,7 +54,8 @@ func (n *Node) addClaim(op OutPoint, amt Amount) error {
return nil
}
func (n *Node) spendClaim(op OutPoint) error {
// SpendClaim spends a Claim in the Node.
func (n *Node) SpendClaim(op OutPoint) error {
var c *Claim
if n.claims, c = remove(n.claims, byOP(op)); c == nil {
return ErrNotFound
@ -91,6 +64,7 @@ func (n *Node) spendClaim(op OutPoint) error {
return nil
}
// UpdateClaim updates a Claim in the Node.
// A claim update is composed of two separate commands (2 & 3 below).
//
// (1) blk 500: Add Claim (opA, amtA, NewID(opA)
@ -100,7 +74,7 @@ func (n *Node) spendClaim(op OutPoint) error {
//
// For each block, all the spent claims are kept in n.removed until committed.
// The paired (spend, update) commands has to happen in the same trasaction.
func (n *Node) updateClaim(op OutPoint, amt Amount, id ID) error {
func (n *Node) UpdateClaim(op OutPoint, amt Amount, id ID) error {
if find(byOP(op), n.claims, n.supports) != nil {
return ErrDuplicate
}
@ -119,14 +93,15 @@ func (n *Node) updateClaim(op OutPoint, amt Amount, id ID) error {
return nil
}
func (n *Node) addSupport(op OutPoint, amt Amount, id ID) error {
// AddSupport adds a Support to the Node.
func (n *Node) AddSupport(op OutPoint, amt Amount, id ID) error {
if find(byOP(op), n.claims, n.supports) != nil {
return ErrDuplicate
}
// Accepted by rules. No effects on bidding result though.
// It may be spent later.
if find(byID(id), n.claims, n.removed) == nil {
fmt.Printf("INFO: can't find suooported claim ID: %s\n", id)
// fmt.Printf("INFO: can't find suooported claim ID: %s for %s\n", id, n.name)
}
accepted := n.height + 1
@ -139,7 +114,8 @@ func (n *Node) addSupport(op OutPoint, amt Amount, id ID) error {
return nil
}
func (n *Node) spendSupport(op OutPoint) error {
// SpendSupport spends a support in the Node.
func (n *Node) SpendSupport(op OutPoint) error {
var s *Claim
if n.supports, s = remove(n.supports, byOP(op)); s != nil {
return nil
@ -147,6 +123,26 @@ func (n *Node) spendSupport(op OutPoint) error {
return ErrNotFound
}
// AdjustTo increments current height until it reaches the specific height.
func (n *Node) AdjustTo(ht Height) *Node {
if ht <= n.height {
return n
}
for n.height < ht {
n.height++
n.bid()
next := n.NextUpdate()
if next > ht || next == n.height {
n.height = ht
break
}
n.height = next
n.bid()
}
n.bid()
return n
}
// NextUpdate returns the height at which pending updates should happen.
// When no pending updates exist, current height is returned.
func (n *Node) NextUpdate() Height {
@ -191,15 +187,15 @@ func (n *Node) bid() {
n.removed = nil
}
func updateEffectiveAmounts(h Height, claims, supports list) {
func updateEffectiveAmounts(ht Height, claims, supports list) {
for _, c := range claims {
c.EffAmt = 0
if !isActiveAt(c, h) {
if !isActiveAt(c, ht) {
continue
}
c.EffAmt = c.Amt
for _, s := range supports {
if !isActiveAt(s, h) || s.ID != c.ID {
if !isActiveAt(s, ht) || s.ID != c.ID {
continue
}
c.EffAmt += s.Amt
@ -215,11 +211,11 @@ func updateActiveHeights(n *Node, lists ...list) {
}
}
func findCandiadte(h Height, claims list) *Claim {
func findCandiadte(ht Height, claims list) *Claim {
var c *Claim
for _, v := range claims {
switch {
case !isActiveAt(v, h):
case !isActiveAt(v, ht):
continue
case c == nil:
c = v

View file

@ -1,135 +0,0 @@
package claim
import (
"fmt"
"github.com/pkg/errors"
)
type cmd int
// ...
const (
CmdAddClaim cmd = 1 << iota
CmdSpendClaim
CmdUpdateClaim
CmdAddSupport
CmdSpendSupport
)
var cmdName = map[cmd]string{
CmdAddClaim: "+C",
CmdSpendClaim: "-C",
CmdUpdateClaim: "+U",
CmdAddSupport: "+S",
CmdSpendSupport: "-S",
}
// Cmd ...
type Cmd struct {
Height Height
Cmd cmd
Name string
OP OutPoint
Amt Amount
ID ID
Value []byte
}
func (c Cmd) String() string {
return fmt.Sprintf("%6d %s %s %s %12d [%s]", c.Height, cmdName[c.Cmd], c.OP, c.ID, c.Amt, c.Name)
}
func (n *Node) record(c cmd, op OutPoint, amt Amount, id ID) *Cmd {
r := &Cmd{Height: n.height + 1, Name: n.name, Cmd: c, OP: op, Amt: amt, ID: id}
n.records = append(n.records, r)
return r
}
// AdjustTo increments current height until it reaches the specific height.
func (n *Node) AdjustTo(h Height) error {
if h < n.height {
return errors.Wrapf(ErrInvalidHeight, "adjust n.height: %d > %d", n.height, h)
}
if h == n.height {
return nil
}
for n.height < h {
n.height++
n.bid()
next := n.NextUpdate()
if next > h {
n.height = h
break
}
n.height = next
}
n.bid()
return nil
}
// Recall ...
func (n *Node) Recall(h Height) error {
if h >= n.height {
return errors.Wrapf(ErrInvalidHeight, "h: %d >= n.height: %d", h, n.height)
}
fmt.Printf("n.Recall from %d to %d\n", n.height, h)
err := n.replay(h, false)
return errors.Wrapf(err, "reply(%d, false)", h)
}
// Reset rests ...
func (n *Node) Reset(h Height) error {
if h > n.height {
return nil
}
fmt.Printf("n.Reset from %d to %d\n", n.height, h)
err := n.replay(h, true)
return errors.Wrapf(err, "reply(%d, true)", h)
}
func (n *Node) replay(h Height, truncate bool) error {
fmt.Printf("replay %s from %d to %d:\n", n.name, n.height, h)
backup := n.records
*n = *NewNode(n.name)
n.records = backup
var i int
var r *Cmd
for i < len(n.records) {
r = n.records[i]
if n.height == r.Height-1 {
if err := n.execute(r); err != nil {
return err
}
i++
continue
}
n.height++
n.bid()
if n.height == h {
break
}
}
if truncate {
n.records = n.records[:i]
}
return nil
}
func (n *Node) execute(c *Cmd) error {
var err error
switch c.Cmd {
case CmdAddClaim:
err = n.addClaim(c.OP, c.Amt)
case CmdSpendClaim:
err = n.spendClaim(c.OP)
case CmdUpdateClaim:
err = n.updateClaim(c.OP, c.Amt, c.ID)
case CmdAddSupport:
err = n.addSupport(c.OP, c.Amt, c.ID)
case CmdSpendSupport:
err = n.spendSupport(c.OP)
}
return errors.Wrapf(err, "cmd %s", c)
}

View file

@ -22,8 +22,8 @@ func export(n *Node) interface{} {
}{
Height: n.height,
Hash: hash,
NextUpdate: n.NextUpdate(),
Tookover: n.tookover,
NextUpdate: n.NextUpdate(),
BestClaim: n.best,
Claims: n.claims,
Supports: n.supports,
@ -39,7 +39,7 @@ func nodeToString(n *Node) string {
{{end}}
{{- end}}
{{- if .Supports}}
S {{range .Supports}}{{.}}
{{range .Supports}}S {{.}}
{{end}}
{{- end}}`
@ -54,6 +54,6 @@ func nodeToString(n *Node) string {
}
func claimToString(c *Claim) string {
return fmt.Sprintf("%-68s id: %s accepted: %3d active: %3d, amt: %12d effamt: %3d",
return fmt.Sprintf("%-68s id: %s accepted: %6d active: %6d, amt: %12d effamt: %12d",
c.OutPoint, c.ID, c.Accepted, c.ActiveAt, c.Amt, c.EffAmt)
}

View file

@ -3,6 +3,7 @@ package claimtrie
import (
"fmt"
"github.com/lbryio/claimtrie/change"
"github.com/lbryio/claimtrie/claim"
"github.com/lbryio/claimtrie/nodemgr"
"github.com/lbryio/claimtrie/trie"
@ -14,140 +15,135 @@ import (
// ClaimTrie implements a Merkle Trie supporting linear history of commits.
type ClaimTrie struct {
height claim.Height
head *Commit
stg *trie.Trie
nm *nodemgr.NodeMgr
cm *CommitMgr
nm *nodemgr.NodeMgr
tr *trie.Trie
}
// New returns a ClaimTrie.
func New(dbTrie, dbNodeMgr *leveldb.DB) *ClaimTrie {
func New(dbCommit, dbTrie, dbNodeMgr *leveldb.DB) *ClaimTrie {
nm := nodemgr.New(dbNodeMgr)
cm := NewCommitMgr(dbCommit)
return &ClaimTrie{
head: newCommit(nil, CommitMeta{0}, trie.EmptyTrieHash),
nm: nm,
stg: trie.New(nm, dbTrie),
cm: cm,
nm: nm,
tr: trie.New(nm, dbTrie),
}
}
// Load loads ClaimTrie, NodeManager, Trie from databases.
func (ct *ClaimTrie) Load() error {
if err := ct.cm.Load(); err != nil {
return errors.Wrapf(err, "cm.Load()")
}
fmt.Printf("%d of commits loaded. Head: %d\n", len(ct.cm.commits), ct.cm.head.Meta.Height)
ct.nm.Load(ct.Height())
fmt.Printf("%d of nodes loaded.\n", ct.nm.Size())
ct.tr.SetRoot(ct.cm.Head().MerkleRoot)
fmt.Printf("Trie root: %s.\n", ct.MerkleHash())
return nil
}
// Save saves ClaimTrie state to database.
func (ct *ClaimTrie) Save() error {
if err := ct.cm.Save(); err != nil {
return errors.Wrapf(err, "cm.Save()")
}
return nil
}
// Height returns the highest height of blocks commited to the ClaimTrie.
func (ct *ClaimTrie) Height() claim.Height {
return ct.height
return ct.cm.Head().Meta.Height
}
// Head returns the tip commit in the commit database.
func (ct *ClaimTrie) Head() *Commit {
return ct.head
return ct.cm.Head()
}
// Trie returns the Stage of the claimtrie .
// Trie returns the MerkleTrie of the ClaimTrie .
func (ct *ClaimTrie) Trie() *trie.Trie {
return ct.stg
return ct.tr
}
// NodeMgr returns the Node Manager of the claimtrie .
// NodeMgr returns the Node Manager of the ClaimTrie .
func (ct *ClaimTrie) NodeMgr() *nodemgr.NodeMgr {
return ct.nm
}
// AddClaim adds a Claim to the Stage.
// CommitMgr returns the Commit Manager of the ClaimTrie .
func (ct *ClaimTrie) CommitMgr() *CommitMgr {
return ct.cm
}
// AddClaim adds a Claim to the ClaimTrie.
func (ct *ClaimTrie) AddClaim(name string, op claim.OutPoint, amt claim.Amount) error {
modifier := func(n *claim.Node) error {
return n.AddClaim(op, amt)
}
return ct.updateNode(name, modifier)
c := change.New(change.AddClaim).SetOP(op).SetAmt(amt)
return ct.modify(name, c)
}
// SpendClaim spend a Claim in the Stage.
// SpendClaim spend a Claim in the ClaimTrie.
func (ct *ClaimTrie) SpendClaim(name string, op claim.OutPoint) error {
modifier := func(n *claim.Node) error {
return n.SpendClaim(op)
}
return ct.updateNode(name, modifier)
c := change.New(change.SpendClaim).SetOP(op)
return ct.modify(name, c)
}
// UpdateClaim updates a Claim in the Stage.
// UpdateClaim updates a Claim in the ClaimTrie.
func (ct *ClaimTrie) UpdateClaim(name string, op claim.OutPoint, amt claim.Amount, id claim.ID) error {
modifier := func(n *claim.Node) error {
return n.UpdateClaim(op, amt, id)
}
return ct.updateNode(name, modifier)
c := change.New(change.UpdateClaim).SetOP(op).SetAmt(amt).SetID(id)
return ct.modify(name, c)
}
// AddSupport adds a Support to the Stage.
// AddSupport adds a Support to the ClaimTrie.
func (ct *ClaimTrie) AddSupport(name string, op claim.OutPoint, amt claim.Amount, id claim.ID) error {
modifier := func(n *claim.Node) error {
return n.AddSupport(op, amt, id)
}
return ct.updateNode(name, modifier)
c := change.New(change.AddSupport).SetOP(op).SetAmt(amt).SetID(id)
return ct.modify(name, c)
}
// SpendSupport spend a support in the Stage.
// SpendSupport spend a support in the ClaimTrie.
func (ct *ClaimTrie) SpendSupport(name string, op claim.OutPoint) error {
modifier := func(n *claim.Node) error {
return n.SpendSupport(op)
}
return ct.updateNode(name, modifier)
c := change.New(change.SpendSupport).SetOP(op)
return ct.modify(name, c)
}
// Traverse visits Nodes in the Stage.
func (ct *ClaimTrie) Traverse(visit trie.Visit) error {
return ct.stg.Traverse(visit)
}
// MerkleHash returns the Merkle Hash of the Stage.
func (ct *ClaimTrie) MerkleHash() (*chainhash.Hash, error) {
// ct.nm.UpdateAll(ct.stg.Update)
return ct.stg.MerkleHash()
}
// Commit commits the current Stage into database.
func (ct *ClaimTrie) Commit(h claim.Height) error {
if h < ct.height {
return errors.Wrapf(ErrInvalidHeight, "%d < ct.height %d", h, ct.height)
func (ct *ClaimTrie) modify(name string, c *change.Change) error {
c.SetHeight(ct.Height() + 1).SetName(name)
if err := ct.nm.ModifyNode(name, c); err != nil {
return err
}
for i := ct.height + 1; i <= h; i++ {
if err := ct.nm.CatchUp(i, ct.stg.Update); err != nil {
return errors.Wrapf(err, "nm.CatchUp(%d, stg.Update)", i)
}
}
hash, err := ct.MerkleHash()
if err != nil {
return errors.Wrapf(err, "MerkleHash()")
}
commit := newCommit(ct.head, CommitMeta{Height: h}, hash)
ct.head = commit
ct.height = h
ct.stg.SetRoot(hash)
ct.tr.Update([]byte(name))
return nil
}
// Reset reverts the Stage to the current or previous height specified.
func (ct *ClaimTrie) Reset(h claim.Height) error {
if h > ct.height {
return errors.Wrapf(ErrInvalidHeight, "%d > ct.height %d", h, ct.height)
}
fmt.Printf("ct.Reset from %d to %d\n", ct.height, h)
commit := ct.head
for commit.Meta.Height > h {
commit = commit.Prev
}
if err := ct.nm.Reset(h); err != nil {
return errors.Wrapf(err, "nm.Reset(%d)", h)
}
ct.head = commit
ct.height = h
ct.stg.SetRoot(commit.MerkleRoot)
return nil
// MerkleHash returns the Merkle Hash of the ClaimTrie.
func (ct *ClaimTrie) MerkleHash() *chainhash.Hash {
return ct.tr.MerkleHash()
}
func (ct *ClaimTrie) updateNode(name string, modifier func(n *claim.Node) error) error {
if err := ct.nm.ModifyNode(name, ct.height, modifier); err != nil {
return errors.Wrapf(err, "nm.ModifyNode(%s, %d)", name, ct.height)
// Commit commits the current changes into database.
func (ct *ClaimTrie) Commit(ht claim.Height) {
if ht < ct.Height() {
return
}
if err := ct.stg.Update(trie.Key(name)); err != nil {
return errors.Wrapf(err, "stg.Update(%s)", name)
for i := ct.Height() + 1; i <= ht; i++ {
ct.nm.CatchUp(i, ct.tr.Update)
}
h := ct.MerkleHash()
ct.cm.Commit(ht, h)
ct.tr.SetRoot(h)
}
// Reset resets the tip commit to a previous height specified.
func (ct *ClaimTrie) Reset(ht claim.Height) error {
if ht > ct.Height() {
return ErrInvalidHeight
}
ct.cm.Reset(ht)
ct.nm.Reset(ht)
ct.tr.SetRoot(ct.Head().MerkleRoot)
return nil
}

View file

@ -3,46 +3,45 @@ package main
import (
"bufio"
"crypto/rand"
"errors"
"fmt"
"log"
"math/big"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"github.com/lbryio/claimtrie"
"github.com/lbryio/claimtrie/cfg"
"github.com/lbryio/claimtrie/claim"
"github.com/lbryio/claimtrie/trie"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcutil"
"github.com/pkg/errors"
"github.com/syndtr/goleveldb/leveldb"
"github.com/urfave/cli"
)
var (
ct *claimtrie.ClaimTrie
defaultHomeDir = btcutil.AppDataDir("lbrycrd.go", false)
defaultDataDir = filepath.Join(defaultHomeDir, "data")
dbTriePath = filepath.Join(defaultDataDir, "dbTrie")
)
var (
all bool
chk bool
name string
height claim.Height
amt claim.Amount
op claim.OutPoint
id claim.ID
all bool
chk bool
dump bool
verbose bool
name string
height claim.Height
amt claim.Amount
op claim.OutPoint
id claim.ID
)
var (
flagAll = cli.BoolFlag{Name: "all, a", Usage: "Show all nodes", Destination: &all}
flagCheck = cli.BoolFlag{Name: "chk, c", Usage: "Check Merkle Hash during importing", Destination: &chk}
flagCheck = cli.BoolTFlag{Name: "chk, c", Usage: "Check Merkle Hash during importing", Destination: &chk}
flagDump = cli.BoolFlag{Name: "dump, d", Usage: "Dump cmds", Destination: &dump}
flagVerbose = cli.BoolFlag{Name: "verbose, v", Usage: "Verbose (will be replaced by loglevel)", Destination: &verbose}
flagAmount = cli.Int64Flag{Name: "amount, a", Usage: "Amount", Destination: (*int64)(&amt)}
flagHeight = cli.Int64Flag{Name: "height, ht", Usage: "Height"}
flagName = cli.StringFlag{Name: "name, n", Value: "Hello", Usage: "Name", Destination: &name}
@ -105,22 +104,22 @@ func main() {
{
Name: "show",
Aliases: []string{"s"},
Usage: "Show the status of Stage)",
Usage: "Show the status of nodes)",
Before: parseArgs,
Action: cmdShow,
Flags: []cli.Flag{flagAll, flagName, flagHeight},
Flags: []cli.Flag{flagAll, flagName, flagHeight, flagDump},
},
{
Name: "merkle",
Aliases: []string{"m"},
Usage: "Show the Merkle Hash of the Stage.",
Usage: "Show the Merkle Hash of the ClaimTrie.",
Before: parseArgs,
Action: cmdMerkle,
},
{
Name: "commit",
Aliases: []string{"c"},
Usage: "Commit the current Stage to database.",
Usage: "Commit the current changes to database.",
Before: parseArgs,
Action: cmdCommit,
Flags: []cli.Flag{flagHeight},
@ -128,7 +127,7 @@ func main() {
{
Name: "reset",
Aliases: []string{"r"},
Usage: "Reset the Head commit and Stage to a specified commit.",
Usage: "Reset the Head commit and a specified commit (by Height).",
Before: parseArgs,
Action: cmdReset,
Flags: []cli.Flag{flagHeight},
@ -140,13 +139,36 @@ func main() {
Before: parseArgs,
Action: cmdLog,
},
{
Name: "ipmort",
Aliases: []string{"i"},
Usage: "Import changes from datbase.",
Before: parseArgs,
Action: cmdImport,
Flags: []cli.Flag{flagHeight, flagCheck, flagVerbose},
},
{
Name: "load",
Aliases: []string{"ld"},
Usage: "Load prerecorded command from datbase.",
Usage: "Load nodes from datbase.",
Before: parseArgs,
Action: cmdLoad,
Flags: []cli.Flag{flagHeight, flagCheck},
Flags: []cli.Flag{},
},
{
Name: "save",
Aliases: []string{"sv"},
Usage: "Save nodes to datbase.",
Before: parseArgs,
Action: cmdSave,
Flags: []cli.Flag{},
},
{
Name: "erase",
Usage: "Erase datbase",
Before: parseArgs,
Action: cmdErase,
Flags: []cli.Flag{},
},
{
Name: "shell",
@ -157,12 +179,28 @@ func main() {
},
}
dbTrie, err := leveldb.OpenFile(dbTriePath, nil)
path := cfg.DefaultConfig(cfg.TrieDB)
dbTrie, err := leveldb.OpenFile(path, nil)
if err != nil {
log.Fatalf("can't open dbTrie at %s, err: %s\n", dbTriePath, err)
log.Fatalf("can't open %s, err: %s\n", path, err)
}
fmt.Printf("dbTriePath: %q\n", dbTriePath)
ct = claimtrie.New(dbTrie, nil)
fmt.Printf("opened %q\n", path)
path = cfg.DefaultConfig(cfg.NodeDB)
dbNodeMgr, err := leveldb.OpenFile(path, nil)
if err != nil {
log.Fatalf("can't open %s, err: %s\n", path, err)
}
fmt.Printf("opened %q\n", path)
path = cfg.DefaultConfig(cfg.CommitDB)
dbCommit, err := leveldb.OpenFile(path, nil)
if err != nil {
log.Fatalf("can't open %s, err: %s\n", path, err)
}
fmt.Printf("opened %q\n", path)
ct = claimtrie.New(dbCommit, dbTrie, dbNodeMgr)
if err := app.Run(os.Args); err != nil {
fmt.Printf("error: %s\n", err)
}
@ -195,22 +233,18 @@ func cmdSpendSupport(c *cli.Context) error {
}
func cmdShow(c *cli.Context) error {
fmt.Printf("\n<ClaimTrie Height %d (Nodes) >\n\n", ct.Height())
fmt.Printf("\n<ClaimTrie Height %d >\n\n", ct.Height())
if all {
name = ""
}
return ct.NodeMgr().Show(name)
// fmt.Printf("\n<ClaimTrie Height %d (Stage) >\n\n", ct.Height())
// return ct.Traverse(showNode())
if !c.IsSet("height") {
height = ct.Height()
}
return ct.NodeMgr().Show(name, height, dump)
}
func cmdMerkle(c *cli.Context) error {
h, err := ct.MerkleHash()
if err != nil {
return err
}
fmt.Printf("%s at %d\n", h, ct.Height())
fmt.Printf("%s at %d\n", ct.MerkleHash(), ct.Height())
return nil
}
@ -218,7 +252,8 @@ func cmdCommit(c *cli.Context) error {
if !c.IsSet("height") {
height = ct.Height() + 1
}
return ct.Commit(height)
ct.Commit(height)
return nil
}
func cmdReset(c *cli.Context) error {
@ -227,14 +262,46 @@ func cmdReset(c *cli.Context) error {
func cmdLog(c *cli.Context) error {
visit := func(c *claimtrie.Commit) {
meta := c.Meta
fmt.Printf("%s at %d\n", c.MerkleRoot, meta.Height)
fmt.Printf("%s at %d\n", c.MerkleRoot, c.Meta.Height)
}
return claimtrie.Log(ct.Head(), visit)
ct.CommitMgr().Log(ct.Height(), visit)
return nil
}
func cmdImport(c *cli.Context) error {
path := cfg.DefaultConfig(cfg.ClaimScriptDB)
db, err := leveldb.OpenFile(path, nil)
if err != nil {
return errors.Wrapf(err, "path %s", path)
}
defer db.Close()
if err = claimtrie.Load(db, ct, height, verbose, chk); err != nil {
return err
}
return nil
}
func cmdLoad(c *cli.Context) error {
return claimtrie.Load(ct, height, chk)
return ct.Load()
}
func cmdSave(c *cli.Context) error {
return ct.Save()
}
func cmdErase(c *cli.Context) error {
if err := os.RemoveAll(cfg.DefaultConfig(cfg.CommitDB)); err != nil {
return err
}
if err := os.RemoveAll(cfg.DefaultConfig(cfg.NodeDB)); err != nil {
return err
}
if err := os.RemoveAll(cfg.DefaultConfig(cfg.TrieDB)); err != nil {
return err
}
fmt.Printf("Databses erased. Exiting...\n")
os.Exit(0)
return nil
}
func cmdShell(app *cli.App) {
@ -248,7 +315,7 @@ func cmdShell(app *cli.App) {
}
}()
defer close(sigs)
// signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
for {
fmt.Printf("%s > ", app.Name)
text, err := reader.ReadString('\n')
@ -260,6 +327,9 @@ func cmdShell(app *cli.App) {
continue
}
if text == "quit" || text == "q" {
if err = ct.Save(); err != nil {
fmt.Printf("ct.Save() failed, err: %s\n", err)
}
break
}
err = app.Run(append(os.Args[1:], strings.Split(text, " ")...))
@ -335,29 +405,3 @@ func parseID(c *cli.Context) error {
id, err = claim.NewIDFromString(c.String("id"))
return err
}
var showNode = func() trie.Visit {
return func(prefix trie.Key, val trie.Value) error {
if val == nil || val.Hash() == nil {
fmt.Printf("%-8s:\n", prefix)
return nil
}
fmt.Printf("%-8s: %v\n", prefix, val)
return nil
}
}
var recall = func(h claim.Height, visit trie.Visit) trie.Visit {
return func(prefix trie.Key, val trie.Value) error {
n := val.(*claim.Node)
old := n.Height()
err := n.Recall(h)
if err == nil {
err = visit(prefix, val)
}
if err == nil {
err = n.AdjustTo(old)
}
return err
}
}

112
commit.go
View file

@ -1,11 +1,20 @@
package claimtrie
import (
"bytes"
"encoding/gob"
"github.com/lbryio/claimtrie/claim"
"github.com/lbryio/claimtrie/trie"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/pkg/errors"
"github.com/syndtr/goleveldb/leveldb"
)
// CommitVisit ...
type CommitVisit func(c *Commit)
// CommitMeta represent the meta associated with each commit.
type CommitMeta struct {
Height claim.Height
@ -13,7 +22,6 @@ type CommitMeta struct {
func newCommit(head *Commit, meta CommitMeta, h *chainhash.Hash) *Commit {
return &Commit{
Prev: head,
MerkleRoot: h,
Meta: meta,
}
@ -21,19 +29,105 @@ func newCommit(head *Commit, meta CommitMeta, h *chainhash.Hash) *Commit {
// Commit ...
type Commit struct {
Prev *Commit
MerkleRoot *chainhash.Hash
Meta CommitMeta
}
// CommitVisit ...
type CommitVisit func(c *Commit)
// CommitMgr ...
type CommitMgr struct {
db *leveldb.DB
commits []*Commit
head *Commit
}
// Log ...
func Log(commit *Commit, visit CommitVisit) error {
for commit != nil {
visit(commit)
commit = commit.Prev
// NewCommitMgr ...
func NewCommitMgr(db *leveldb.DB) *CommitMgr {
head := newCommit(nil, CommitMeta{0}, trie.EmptyTrieHash)
cm := CommitMgr{
db: db,
head: head,
}
cm.commits = append(cm.commits, head)
return &cm
}
// Head ...
func (cm *CommitMgr) Head() *Commit {
return cm.head
}
// Commit ...
func (cm *CommitMgr) Commit(ht claim.Height, merkle *chainhash.Hash) {
if ht == 0 {
return
}
c := newCommit(cm.head, CommitMeta{ht}, merkle)
cm.commits = append(cm.commits, c)
cm.head = c
}
// Reset ...
func (cm *CommitMgr) Reset(ht claim.Height) {
for i := len(cm.commits) - 1; i >= 0; i-- {
c := cm.commits[i]
if c.Meta.Height <= ht {
cm.head = c
cm.commits = cm.commits[:i+1]
break
}
}
if cm.head.Meta.Height == ht {
return
}
cm.Commit(ht, cm.head.MerkleRoot)
}
// Save ...
func (cm *CommitMgr) Save() error {
exported := struct {
Commits []*Commit
Head *Commit
}{
Commits: cm.commits,
Head: cm.head,
}
buf := bytes.NewBuffer(nil)
if err := gob.NewEncoder(buf).Encode(exported); err != nil {
return errors.Wrapf(err, "gob.Encode()", err)
}
if err := cm.db.Put([]byte("CommitMgr"), buf.Bytes(), nil); err != nil {
return errors.Wrapf(err, "db.Put(CommitMgr)")
}
return nil
}
// Load ...
func (cm *CommitMgr) Load() error {
exported := struct {
Commits []*Commit
Head *Commit
}{}
data, err := cm.db.Get([]byte("CommitMgr"), nil)
if err != nil {
return errors.Wrapf(err, "db.Get(CommitMgr)")
}
if err := gob.NewDecoder(bytes.NewBuffer(data)).Decode(&exported); err != nil {
return errors.Wrapf(err, "gob.Encode()", err)
}
cm.commits = exported.Commits
cm.head = exported.Head
return nil
}
// Log ...
func (cm *CommitMgr) Log(ht claim.Height, visit CommitVisit) {
for i := len(cm.commits) - 1; i >= 0; i-- {
c := cm.commits[i]
if c.Meta.Height > ht {
continue
}
visit(c)
}
}

156
import.go
View file

@ -5,130 +5,76 @@ import (
"encoding/gob"
"fmt"
"log"
"path/filepath"
"strconv"
"github.com/lbryio/claimtrie/change"
"github.com/lbryio/claimtrie/claim"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcutil"
"github.com/pkg/errors"
"github.com/syndtr/goleveldb/leveldb"
)
var (
defaultHomeDir = btcutil.AppDataDir("lbrycrd.go", false)
defaultDataDir = filepath.Join(defaultHomeDir, "data")
dbCmdPath = filepath.Join(defaultDataDir, "dbCmd")
)
type block struct {
Hash chainhash.Hash
Cmds []claim.Cmd
}
// Load ...
func Load(ct *ClaimTrie, h claim.Height, chk bool) error {
db := DefaultRecorder()
defer db.Close()
for i := ct.height + 1; i <= h; i++ {
key := strconv.Itoa(int(i))
data, err := db.Get([]byte(key), nil)
if err == leveldb.ErrNotFound {
func Load(db *leveldb.DB, ct *ClaimTrie, ht claim.Height, verbose, chk bool) error {
for i := ct.Height() + 1; i <= ht; i++ {
blk, err := getBlock(db, i)
if err != nil {
return errors.Wrapf(err, "getBlock(db, %d)", i)
}
if blk == nil {
continue
} else if err != nil {
return err
}
var blk block
if err = gob.NewDecoder(bytes.NewBuffer(data)).Decode(&blk); err != nil {
return err
}
if err = ct.Commit(i - 1); err != nil {
return err
}
for _, cmd := range blk.Cmds {
if err = execute(ct, &cmd); err != nil {
fmt.Printf("execute faile: err %s\n", err)
return err
ct.Commit(i - 1)
for _, chg := range blk.Changes {
if err = apply(ct, chg, verbose); err != nil {
return errors.Wrapf(err, "apply(%s)", chg)
}
}
if err = ct.Commit(i); err != nil {
return err
}
ct.Commit(i)
if !chk {
continue
}
hash, err := ct.MerkleHash()
if err != nil {
return err
}
if *hash != blk.Hash {
return fmt.Errorf("block %d hash: got %s, want %s", i, *hash, blk.Hash)
if hash := ct.MerkleHash(); *hash != blk.Hash {
return fmt.Errorf("blk %d hash: got %s, want %s", i, *hash, blk.Hash)
}
}
return ct.Commit(h)
ct.Commit(ht)
return nil
}
func execute(ct *ClaimTrie, c *claim.Cmd) error {
// Value []byte
fmt.Printf("%s\n", c)
func getBlock(db *leveldb.DB, ht claim.Height) (*change.Block, error) {
key := strconv.Itoa(int(ht))
data, err := db.Get([]byte(key), nil)
if err == leveldb.ErrNotFound {
return nil, nil
} else if err != nil {
return nil, errors.Wrapf(err, "db.Get(%s)", key)
}
var blk change.Block
if err = gob.NewDecoder(bytes.NewBuffer(data)).Decode(&blk); err != nil {
return nil, errors.Wrapf(err, "gob.Decode(&blk)")
}
return &blk, nil
}
func apply(ct *ClaimTrie, c *change.Change, verbose bool) error {
if verbose {
log.Printf("%s", c)
}
var err error
switch c.Cmd {
case claim.CmdAddClaim:
return ct.AddClaim(c.Name, c.OP, c.Amt)
case claim.CmdSpendClaim:
return ct.SpendClaim(c.Name, c.OP)
case claim.CmdUpdateClaim:
return ct.UpdateClaim(c.Name, c.OP, c.Amt, c.ID)
case claim.CmdAddSupport:
return ct.AddSupport(c.Name, c.OP, c.Amt, c.ID)
case claim.CmdSpendSupport:
return ct.SpendSupport(c.Name, c.OP)
case change.AddClaim:
err = ct.AddClaim(c.Name, c.OP, c.Amt)
case change.SpendClaim:
err = ct.SpendClaim(c.Name, c.OP)
case change.UpdateClaim:
err = ct.UpdateClaim(c.Name, c.OP, c.Amt, c.ID)
case change.AddSupport:
err = ct.AddSupport(c.Name, c.OP, c.Amt, c.ID)
case change.SpendSupport:
err = ct.SpendSupport(c.Name, c.OP)
}
return nil
}
// Recorder ..
type Recorder struct {
db *leveldb.DB
}
// Put sets the value for the given key. It overwrites any previous value for that key.
func (r *Recorder) Put(key []byte, data interface{}) error {
buf := bytes.NewBuffer(nil)
if err := gob.NewEncoder(buf).Encode(data); err != nil {
return fmt.Errorf("can't encode cmds, err: %s", err)
}
if err := r.db.Put(key, buf.Bytes(), nil); err != nil {
return fmt.Errorf("can't put to db, err: %s", err)
}
return nil
}
// Get ...
func (r *Recorder) Get(key []byte, data interface{}) ([]byte, error) {
return r.db.Get(key, nil)
}
// Close ...
func (r *Recorder) Close() error {
err := r.db.Close()
r.db = nil
return err
}
var recorder Recorder
// DefaultRecorder ...
func DefaultRecorder() *Recorder {
if recorder.db == nil {
db, err := leveldb.OpenFile(dbCmdPath, nil)
if err != nil {
log.Fatalf("can't open :%s, err: %s\n", dbCmdPath, err)
}
fmt.Printf("dbCmds %s opened\n", dbCmdPath)
recorder.db = db
}
return &recorder
return errors.Wrapf(err, "exec %s", c)
}

View file

@ -3,20 +3,21 @@ package nodemgr
import (
"fmt"
"sort"
"sync"
"github.com/lbryio/claimtrie/change"
"github.com/lbryio/claimtrie/claim"
"github.com/lbryio/claimtrie/trie"
"github.com/pkg/errors"
"github.com/syndtr/goleveldb/leveldb"
)
// NodeMgr ...
type NodeMgr struct {
sync.RWMutex
height claim.Height
db *leveldb.DB
nodes map[string]*claim.Node
dirty map[string]bool
cache map[string]*claim.Node
nextUpdates todos
}
@ -24,132 +25,154 @@ type NodeMgr struct {
func New(db *leveldb.DB) *NodeMgr {
nm := &NodeMgr{
db: db,
nodes: map[string]*claim.Node{},
dirty: map[string]bool{},
cache: map[string]*claim.Node{},
nextUpdates: todos{},
}
return nm
}
// Get ...
func (nm *NodeMgr) Get(key trie.Key) (trie.Value, error) {
nm.Lock()
defer nm.Unlock()
if n, ok := nm.nodes[string(key)]; ok {
return n, nil
// Load loads the nodes from the database up to height ht.
func (nm *NodeMgr) Load(ht claim.Height) {
nm.height = ht
iter := nm.db.NewIterator(nil, nil)
for iter.Next() {
name := string(iter.Key())
nm.cache[name] = nm.load(name, ht)
}
if nm.db != nil {
b, err := nm.db.Get(key, nil)
if err == nil {
_ = b // TODO: Loaded. Deserialize it.
} else if err != leveldb.ErrNotFound {
// DB error. Propagated.
return nil, err
}
}
// New node.
n := claim.NewNode(string(key))
nm.nodes[string(key)] = n
return n, nil
}
// Set ...
func (nm *NodeMgr) Set(key trie.Key, val trie.Value) {
n := val.(*claim.Node)
nm.Lock()
defer nm.Unlock()
nm.nodes[string(key)] = n
nm.dirty[string(key)] = true
// TODO: flush to disk.
// Get returns the latest node with name specified by key.
func (nm *NodeMgr) Get(key []byte) trie.Value {
return nm.nodeAt(string(key), nm.height)
}
// Reset resets all nodes to specified height.
func (nm *NodeMgr) Reset(h claim.Height) error {
for _, n := range nm.nodes {
if err := n.Reset(h); err != nil {
return err
func (nm *NodeMgr) Reset(ht claim.Height) {
nm.height = ht
for name, n := range nm.cache {
if n.Height() >= ht {
nm.cache[name] = nm.load(name, ht)
}
}
return nil
}
// NodeAt returns the node adjusted to specified height.
func (nm *NodeMgr) NodeAt(name string, h claim.Height) (*claim.Node, error) {
v, err := nm.Get(trie.Key(name))
if err != nil {
return nil, err
// Size returns the number of nodes loaded into the cache.
func (nm *NodeMgr) Size() int {
return len(nm.cache)
}
func (nm *NodeMgr) load(name string, ht claim.Height) *claim.Node {
c := change.NewChangeList(nm.db, name).Load().Truncate(ht).Changes()
return NewFromChanges(name, c, ht)
}
// nodeAt returns the node adjusted to specified height.
func (nm *NodeMgr) nodeAt(name string, ht claim.Height) *claim.Node {
n, ok := nm.cache[name]
if !ok {
n = claim.NewNode(name)
nm.cache[name] = n
}
n := v.(*claim.Node)
if err = n.AdjustTo(h); err != nil {
return nil, err
// Cached version is too new.
if n.Height() > nm.height || n.Height() > ht {
n = nm.load(name, ht)
}
return n, nil
return n.AdjustTo(ht)
}
// ModifyNode returns the node adjusted to specified height.
func (nm *NodeMgr) ModifyNode(name string, h claim.Height, modifier func(*claim.Node) error) error {
n, err := nm.NodeAt(name, h)
if err != nil {
return err
func (nm *NodeMgr) ModifyNode(name string, chg *change.Change) error {
ht := nm.height
n := nm.nodeAt(name, ht)
n.AdjustTo(ht)
if err := execute(n, chg); err != nil {
return errors.Wrapf(err, "claim.execute(n,chg)")
}
if err = modifier(n); err != nil {
return err
}
nm.nextUpdates.set(name, h+1)
nm.cache[name] = n
nm.nextUpdates.set(name, ht+1)
change.NewChangeList(nm.db, name).Load().Append(chg).Save()
return nil
}
// CatchUp ...
func (nm *NodeMgr) CatchUp(h claim.Height, notifier func(key trie.Key) error) error {
for name := range nm.nextUpdates[h] {
n, err := nm.NodeAt(name, h)
if err != nil {
return err
}
if err = notifier(trie.Key(name)); err != nil {
return err
}
if next := n.NextUpdate(); next > h {
func (nm *NodeMgr) CatchUp(ht claim.Height, notifier func(key []byte)) {
nm.height = ht
for name := range nm.nextUpdates[ht] {
notifier([]byte(name))
if next := nm.nodeAt(name, ht).NextUpdate(); next > ht {
nm.nextUpdates.set(name, next)
}
}
return nil
}
// Show ...
func (nm *NodeMgr) Show(name string) error {
if len(name) != 0 {
fmt.Printf("[%s] %s\n", name, nm.nodes[name])
return nil
}
// Show is a conevenient function for debugging and velopment purpose.
// The proper way to handle user request would be a query function with filters specified.
func (nm *NodeMgr) Show(name string, ht claim.Height, dump bool) error {
names := []string{}
for name := range nm.nodes {
if len(name) != 0 {
names = append(names, name)
} else {
for name := range nm.cache {
names = append(names, name)
}
}
sort.Strings(names)
for _, name := range names {
fmt.Printf("[%s] %s\n", name, nm.nodes[name])
n := nm.nodeAt(name, ht)
if n.BestClaim() == nil {
continue
}
fmt.Printf("[%s] %s\n", name, n)
if dump {
change.NewChangeList(nm.db, name).Load().Dump()
}
}
return nil
}
// UpdateAll ...
func (nm *NodeMgr) UpdateAll(m func(key trie.Key) error) error {
for name := range nm.nodes {
m(trie.Key(name))
// NewFromChanges ...
func NewFromChanges(name string, chgs []*change.Change, ht claim.Height) *claim.Node {
return replay(name, chgs).AdjustTo(ht)
}
func replay(name string, chgs []*change.Change) *claim.Node {
n := claim.NewNode(name)
for _, chg := range chgs {
if n.Height() < chg.Height-1 {
n.AdjustTo(chg.Height - 1)
}
if n.Height() == chg.Height-1 {
if err := execute(n, chg); err != nil {
panic(err)
}
}
}
return nil
return n
}
func execute(n *claim.Node, c *change.Change) error {
var err error
switch c.Cmd {
case change.AddClaim:
err = n.AddClaim(c.OP, c.Amt)
case change.SpendClaim:
err = n.SpendClaim(c.OP)
case change.UpdateClaim:
err = n.UpdateClaim(c.OP, c.Amt, c.ID)
case change.AddSupport:
err = n.AddSupport(c.OP, c.Amt, c.ID)
case change.SpendSupport:
err = n.SpendSupport(c.OP)
}
return errors.Wrapf(err, "chg %s", c)
}
type todos map[claim.Height]map[string]bool
func (t todos) set(name string, h claim.Height) {
if t[h] == nil {
t[h] = map[string]bool{}
func (t todos) set(name string, ht claim.Height) {
if t[ht] == nil {
t[ht] = map[string]bool{}
}
t[h][name] = true
t[ht][name] = true
}

View file

@ -4,9 +4,6 @@ import (
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// Key defines the key type of the MerkleTrie.
type Key []byte
// Value defines value for the MerkleTrie.
type Value interface {
Hash() *chainhash.Hash
@ -14,6 +11,5 @@ type Value interface {
// KeyValue ...
type KeyValue interface {
Get(k Key) (Value, error)
Set(k Key, v Value)
Get(key []byte) Value
}

View file

@ -2,18 +2,12 @@ package trie
import (
"bytes"
"fmt"
"sync"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/pkg/errors"
"github.com/syndtr/goleveldb/leveldb"
)
var (
// ErrResolve is returned when an error occured during resolve.
ErrResolve = fmt.Errorf("can't resolve")
)
var (
// EmptyTrieHash represents the Merkle Hash of an empty Trie.
// "0000000000000000000000000000000000000000000000000000000000000001"
@ -52,35 +46,30 @@ func (t *Trie) SetRoot(h *chainhash.Hash) {
// Update updates the nodes along the path to the key.
// Each node is resolved or created with their Hash cleared.
func (t *Trie) Update(key Key) error {
func (t *Trie) Update(key []byte) {
n := t.root
for _, ch := range key {
if err := t.resolve(n); err != nil {
return ErrResolve
}
t.resolve(n)
if n.links[ch] == nil {
n.links[ch] = newNode()
}
n.hash = nil
n = n.links[ch]
}
if err := t.resolve(n); err != nil {
return ErrResolve
}
t.resolve(n)
n.hasValue = true
n.hash = nil
return nil
}
func (t *Trie) resolve(n *node) error {
func (t *Trie) resolve(n *node) {
if n.hash == nil {
return nil
return
}
b, err := t.db.Get(n.hash[:], nil)
if err == leveldb.ErrNotFound {
return nil
return
} else if err != nil {
return errors.Wrapf(err, "db.Get(%s)", n.hash)
panic(err)
}
nb := nbuf(b)
@ -90,68 +79,29 @@ func (t *Trie) resolve(n *node) error {
n.links[p] = newNode()
n.links[p].hash = h
}
return nil
}
// Visit implements callback function invoked when the Value is visited.
// During the traversal, if a non-nil error is returned, the traversal ends early.
type Visit func(prefix Key, val Value) error
// Traverse implements preorder traversal visiting each Value node.
func (t *Trie) Traverse(visit Visit) error {
var traverse func(prefix Key, n *node) error
traverse = func(prefix Key, n *node) error {
if n == nil {
return nil
}
for ch, n := range n.links {
if n == nil || !n.hasValue {
continue
}
p := append(prefix, byte(ch))
val, err := t.kv.Get(p)
if err != nil {
return errors.Wrapf(err, "kv.Get(%s)", p)
}
if err := visit(p, val); err != nil {
return err
}
if err := traverse(p, n); err != nil {
return err
}
}
return nil
}
buf := make([]byte, 0, 4096)
return traverse(buf, t.root)
}
// MerkleHash returns the Merkle Hash of the Trie.
// All nodes must have been resolved before calling this function.
func (t *Trie) MerkleHash() (*chainhash.Hash, error) {
func (t *Trie) MerkleHash() *chainhash.Hash {
t.batch = &leveldb.Batch{}
buf := make([]byte, 0, 4096)
if err := t.merkle(buf, t.root); err != nil {
return nil, err
if h := t.merkle(buf, t.root); h == nil {
return EmptyTrieHash
}
if t.root.hash == nil {
return EmptyTrieHash, nil
}
if t.db != nil && t.batch.Len() != 0 {
if t.batch.Len() != 0 {
if err := t.db.Write(t.batch, nil); err != nil {
return nil, errors.Wrapf(err, "db.Write(t.batch, nil)")
panic(err)
}
}
return t.root.hash, nil
return t.root.hash
}
// merkle recursively resolves the hashes of the node.
// All nodes must have been resolved before calling this function.
func (t *Trie) merkle(prefix Key, n *node) error {
func (t *Trie) merkle(prefix []byte, n *node) *chainhash.Hash {
if n.hash != nil {
return nil
return n.hash
}
b := t.bufs.Get().(*bytes.Buffer)
defer t.bufs.Put(b)
@ -162,39 +112,22 @@ func (t *Trie) merkle(prefix Key, n *node) error {
continue
}
p := append(prefix, byte(ch))
if err := t.merkle(p, n); err != nil {
return err
}
if n.hash == nil {
continue
}
if err := b.WriteByte(byte(ch)); err != nil {
panic(err) // Can't happen. Kepp linter happy.
}
if _, err := b.Write(n.hash[:]); err != nil {
panic(err) // Can't happen. Kepp linter happy.
if h := t.merkle(p, n); h != nil {
b.WriteByte(byte(ch)) // nolint : errchk
b.Write(h[:]) // nolint : errchk
}
}
if n.hasValue {
val, err := t.kv.Get(prefix)
if err != nil {
return errors.Wrapf(err, "t.kv.get(%s)", prefix)
}
if h := val.Hash(); h != nil {
if _, err = b.Write(h[:]); err != nil {
panic(err) // Can't happen. Kepp linter happy.
}
if h := t.kv.Get(prefix).Hash(); h != nil {
b.Write(h[:]) // nolint : errchk
}
}
if b.Len() == 0 {
return nil
if b.Len() != 0 {
h := chainhash.DoubleHashH(b.Bytes())
n.hash = &h
t.batch.Put(h[:], b.Bytes())
}
h := chainhash.DoubleHashH(b.Bytes())
n.hash = &h
if t.db != nil {
t.batch.Put(n.hash[:], b.Bytes())
}
return nil
return n.hash
}