2018-06-28 07:14:30 +02:00
|
|
|
package claimtrie
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
|
2018-07-09 18:56:15 +02:00
|
|
|
"github.com/lbryio/claimtrie/claim"
|
|
|
|
|
2018-07-09 19:17:33 +02:00
|
|
|
"github.com/lbryio/claimtrie/trie"
|
2018-06-28 07:14:30 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
// ClaimTrie implements a Merkle Trie supporting linear history of commits.
|
|
|
|
type ClaimTrie struct {
|
|
|
|
|
|
|
|
// The highest block number commited to the ClaimTrie.
|
2018-07-09 20:34:12 +02:00
|
|
|
height claim.Height
|
2018-06-28 07:14:30 +02:00
|
|
|
|
|
|
|
// Immutable linear history.
|
2018-07-09 19:17:33 +02:00
|
|
|
head *trie.Commit
|
2018-06-28 07:14:30 +02:00
|
|
|
|
|
|
|
// An overlay supporting Copy-on-Write to the current tip commit.
|
2018-07-09 19:17:33 +02:00
|
|
|
stg *trie.Stage
|
2018-07-05 22:17:40 +02:00
|
|
|
|
2018-07-09 20:34:12 +02:00
|
|
|
// todos tracks pending updates for future block height.
|
|
|
|
//
|
|
|
|
// A claim or support has a dynamic active peroid (ActiveAt, ExipresAt).
|
|
|
|
// This makes the state of each node dynamic as the ClaimTrie increases/decreases its height.
|
|
|
|
// Instead of polling every node for updates everytime ClaimTrie changes, the node is evaluated
|
|
|
|
// for the nearest future height it may change the states, and add that height to the todos.
|
|
|
|
//
|
|
|
|
// When a ClaimTrie at height h1 is committed with h2, the pending updates from todos (h1, h2]
|
|
|
|
// will be applied to bring the nodes up to date.
|
|
|
|
todos map[claim.Height][]string
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
|
2018-07-09 19:17:33 +02:00
|
|
|
// CommitMeta implements trie.CommitMeta with commit-specific metadata.
|
2018-06-28 07:14:30 +02:00
|
|
|
type CommitMeta struct {
|
2018-07-09 18:56:15 +02:00
|
|
|
Height claim.Height
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// New returns a ClaimTrie.
|
|
|
|
func New() *ClaimTrie {
|
2018-07-09 19:17:33 +02:00
|
|
|
mt := trie.New()
|
2018-06-28 07:14:30 +02:00
|
|
|
return &ClaimTrie{
|
2018-07-09 20:34:12 +02:00
|
|
|
head: trie.NewCommit(nil, CommitMeta{0}, mt),
|
|
|
|
stg: trie.NewStage(mt),
|
|
|
|
todos: map[claim.Height][]string{},
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-09 20:34:12 +02:00
|
|
|
// Height returns the highest height of blocks commited to the ClaimTrie.
|
|
|
|
func (ct *ClaimTrie) Height() claim.Height {
|
|
|
|
return ct.height
|
|
|
|
}
|
|
|
|
|
|
|
|
// Head returns the tip commit in the commit database.
|
|
|
|
func (ct *ClaimTrie) Head() *trie.Commit {
|
|
|
|
return ct.head
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddClaim adds a Claim to the Stage of ClaimTrie.
|
2018-07-09 18:56:15 +02:00
|
|
|
func (ct *ClaimTrie) AddClaim(name string, op wire.OutPoint, amt claim.Amount) error {
|
2018-07-14 07:10:23 +02:00
|
|
|
modifier := func(n *claim.Node) error {
|
|
|
|
return n.AddClaim(claim.New(op, amt))
|
2018-07-09 20:34:12 +02:00
|
|
|
}
|
|
|
|
return updateNode(ct, ct.height, name, modifier)
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddSupport adds a Support to the Stage of ClaimTrie.
|
2018-07-09 18:56:15 +02:00
|
|
|
func (ct *ClaimTrie) AddSupport(name string, op wire.OutPoint, amt claim.Amount, supported claim.ID) error {
|
2018-07-14 07:10:23 +02:00
|
|
|
modifier := func(n *claim.Node) error {
|
|
|
|
return n.AddSupport(claim.NewSupport(op, amt, supported))
|
2018-07-09 20:34:12 +02:00
|
|
|
}
|
|
|
|
return updateNode(ct, ct.height, name, modifier)
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SpendClaim removes a Claim in the Stage.
|
|
|
|
func (ct *ClaimTrie) SpendClaim(name string, op wire.OutPoint) error {
|
2018-07-14 07:10:23 +02:00
|
|
|
modifier := func(n *claim.Node) error {
|
2018-07-09 18:56:15 +02:00
|
|
|
return n.RemoveClaim(op)
|
2018-07-09 20:34:12 +02:00
|
|
|
}
|
|
|
|
return updateNode(ct, ct.height, name, modifier)
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// SpendSupport removes a Support in the Stage.
|
|
|
|
func (ct *ClaimTrie) SpendSupport(name string, op wire.OutPoint) error {
|
2018-07-14 07:10:23 +02:00
|
|
|
modifier := func(n *claim.Node) error {
|
2018-07-09 18:56:15 +02:00
|
|
|
return n.RemoveSupport(op)
|
2018-07-09 20:34:12 +02:00
|
|
|
}
|
|
|
|
return updateNode(ct, ct.height, name, modifier)
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
|
2018-07-09 20:34:12 +02:00
|
|
|
// Traverse visits Nodes in the Stage.
|
2018-07-09 19:17:33 +02:00
|
|
|
func (ct *ClaimTrie) Traverse(visit trie.Visit, update, valueOnly bool) error {
|
2018-07-05 22:17:40 +02:00
|
|
|
return ct.stg.Traverse(visit, update, valueOnly)
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// MerkleHash returns the Merkle Hash of the Stage.
|
|
|
|
func (ct *ClaimTrie) MerkleHash() chainhash.Hash {
|
|
|
|
return ct.stg.MerkleHash()
|
|
|
|
}
|
|
|
|
|
2018-07-09 20:34:12 +02:00
|
|
|
// Commit commits the current Stage into commit database.
|
|
|
|
// If h is lower than the current height, ErrInvalidHeight is returned.
|
|
|
|
//
|
|
|
|
// As Stage can be always cleanly reset to a specific commited snapshot,
|
|
|
|
// any error occurred during the commit would leave the Stage partially updated
|
|
|
|
// so the caller can inspect the status if interested.
|
|
|
|
//
|
|
|
|
// Changes to the ClaimTrie status, such as height or todos, are all or nothing.
|
2018-07-09 18:56:15 +02:00
|
|
|
func (ct *ClaimTrie) Commit(h claim.Height) error {
|
2018-07-09 20:34:12 +02:00
|
|
|
|
|
|
|
// Already caught up.
|
|
|
|
if h <= ct.height {
|
2018-06-28 07:14:30 +02:00
|
|
|
return ErrInvalidHeight
|
|
|
|
}
|
|
|
|
|
2018-07-09 20:34:12 +02:00
|
|
|
// Apply pending updates in todos (ct.Height, h].
|
|
|
|
// Note that ct.Height is excluded while h is included.
|
|
|
|
for i := ct.height + 1; i <= h; i++ {
|
|
|
|
for _, name := range ct.todos[i] {
|
|
|
|
// dummy modifier to have the node brought up to date.
|
2018-07-14 07:10:23 +02:00
|
|
|
modifier := func(n *claim.Node) error { return nil }
|
2018-07-09 20:34:12 +02:00
|
|
|
if err := updateNode(ct, i, name, modifier); err != nil {
|
2018-07-05 22:17:40 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-06-28 07:14:30 +02:00
|
|
|
commit, err := ct.stg.Commit(ct.head, CommitMeta{Height: h})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-07-09 20:34:12 +02:00
|
|
|
|
|
|
|
// No more errors. Change the ClaimTrie status.
|
2018-06-28 07:14:30 +02:00
|
|
|
ct.head = commit
|
2018-07-09 20:34:12 +02:00
|
|
|
for i := ct.height + 1; i <= h; i++ {
|
|
|
|
delete(ct.todos, i)
|
|
|
|
}
|
|
|
|
ct.height = h
|
2018-06-28 07:14:30 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Reset reverts the Stage to a specified commit by height.
|
2018-07-09 18:56:15 +02:00
|
|
|
func (ct *ClaimTrie) Reset(h claim.Height) error {
|
2018-07-09 20:34:12 +02:00
|
|
|
if h > ct.height {
|
|
|
|
return ErrInvalidHeight
|
|
|
|
}
|
|
|
|
|
|
|
|
// Find the most recent commit that is equal or earlier than h.
|
|
|
|
commit := ct.head
|
|
|
|
for commit != nil {
|
|
|
|
if commit.Meta.(CommitMeta).Height <= h {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
commit = commit.Prev
|
|
|
|
}
|
|
|
|
|
|
|
|
// The commit history is not deep enough.
|
|
|
|
if commit == nil {
|
|
|
|
return ErrInvalidHeight
|
|
|
|
}
|
|
|
|
|
|
|
|
// Drop (rollback) any uncommited change, and adjust to the specified height.
|
|
|
|
rollback := func(prefix trie.Key, value trie.Value) error {
|
2018-07-14 07:10:23 +02:00
|
|
|
n := value.(*claim.Node)
|
|
|
|
n.RollbackExecuted()
|
2018-07-09 20:34:12 +02:00
|
|
|
return n.AdjustTo(h)
|
2018-07-05 22:17:40 +02:00
|
|
|
}
|
2018-07-09 20:34:12 +02:00
|
|
|
if err := ct.stg.Traverse(rollback, true, true); err != nil {
|
|
|
|
// Rollback a node to a known state can't go wrong.
|
2018-07-14 07:10:23 +02:00
|
|
|
// It's a programming error that can't be recovered.
|
2018-07-09 20:34:12 +02:00
|
|
|
panic(err)
|
2018-07-05 22:17:40 +02:00
|
|
|
}
|
2018-07-09 20:34:12 +02:00
|
|
|
|
|
|
|
// Update ClaimTrie status
|
|
|
|
ct.head = commit
|
|
|
|
ct.height = h
|
|
|
|
for k := range ct.todos {
|
|
|
|
if k >= h {
|
|
|
|
delete(ct.todos, k)
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
}
|
2018-07-09 20:34:12 +02:00
|
|
|
ct.stg = trie.NewStage(commit.MerkleTrie)
|
|
|
|
return nil
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|
|
|
|
|
2018-07-09 20:34:12 +02:00
|
|
|
// updateNode implements a get-modify-set sequence to the node associated with name.
|
|
|
|
// After the modifier is applied, the node is evaluated for how soon in the
|
|
|
|
// nearest future change. And register it, if any, to the todos for the next updateNode.
|
2018-07-14 07:10:23 +02:00
|
|
|
func updateNode(ct *ClaimTrie, h claim.Height, name string, modifier func(n *claim.Node) error) error {
|
2018-07-09 20:34:12 +02:00
|
|
|
|
|
|
|
// Get the node from the Stage, or create one if it did not exist yet.
|
|
|
|
v, err := ct.stg.Get(trie.Key(name))
|
|
|
|
if err == trie.ErrKeyNotFound {
|
2018-07-14 07:10:23 +02:00
|
|
|
v = claim.NewNode()
|
2018-07-09 20:34:12 +02:00
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2018-07-14 07:10:23 +02:00
|
|
|
n := v.(*claim.Node)
|
2018-07-09 20:34:12 +02:00
|
|
|
|
|
|
|
// Bring the node state up to date.
|
|
|
|
if err = n.AdjustTo(h); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Apply the modifier on the node.
|
|
|
|
if err = modifier(n); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Register pending update, if any, for future height.
|
|
|
|
next := n.FindNextUpdateHeight()
|
|
|
|
if next > h {
|
|
|
|
ct.todos[next] = append(ct.todos[next], name)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Store the modified value back to the Stage, clearing out all the Merkle Hash on the path.
|
|
|
|
return ct.stg.Update(trie.Key(name), n)
|
2018-06-28 07:14:30 +02:00
|
|
|
}
|