cleanup: refactor, bugfix, and comment.

This commit is contained in:
Tzu-Jung Lee 2018-07-09 11:34:12 -07:00
parent 8e1ae77aad
commit d3419ea775
8 changed files with 470 additions and 327 deletions

View file

@ -1,7 +1,6 @@
package claim
import (
"bytes"
"encoding/json"
"fmt"
@ -14,88 +13,74 @@ type Amount int64
// Height ...
type Height int64
var dbg bool
// Seq is a strictly increasing sequence number determine relative order between Claims and Supports.
type Seq uint64
// Claim ...
type Claim struct {
wire.OutPoint
OutPoint wire.OutPoint
ID ID
Amt Amount
EffAmt Amount
Accepted Height
ActiveAt Height
// TODO: Get rid of this. Implement ordered map in upper layer.
Seq Seq
}
func (c *Claim) String() string {
if dbg {
w := bytes.NewBuffer(nil)
fmt.Fprintf(w, "C%-3d amt: %2d, effamt: %v, accepted: %2d, active: %2d, id: %s", c.Index, c.Amt, c.EffAmt, c.Accepted, c.ActiveAt, c.ID)
return w.String()
}
b, err := json.MarshalIndent(c, "", " ")
if err != nil {
fmt.Printf("can't marshal, err :%s", err)
}
return string(b)
}
// Support ...
type Support struct {
wire.OutPoint
Amt Amount
Accepted Height
ActiveAt Height
SupportedID ID
return fmt.Sprintf("C-%-68s amt: %-3d effamt: %-3d accepted: %-3d active: %-3d id: %s",
c.OutPoint, c.Amt, c.EffAmt, c.Accepted, c.ActiveAt, c.ID)
}
// MarshalJSON customizes the representation of JSON.
func (c *Claim) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
OutPoint string
ID string
Amount Amount
EffectiveAmount Amount
Accepted Height
ActiveAt Height
OutPoint string
ID string
Amount Amount
EffAmount Amount
Accepted Height
ActiveAt Height
}{
OutPoint: c.OutPoint.String(),
ID: c.ID.String(),
Amount: c.Amt,
EffectiveAmount: c.EffAmt,
Accepted: c.Accepted,
ActiveAt: c.ActiveAt,
OutPoint: c.OutPoint.String(),
ID: c.ID.String(),
Amount: c.Amt,
EffAmount: c.EffAmt,
Accepted: c.Accepted,
ActiveAt: c.ActiveAt,
})
}
// MarshalJSON ...
func (s *Support) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
OutPoint string
SupportedClaimID string
Amount Amount
Accepted Height
ActiveAt Height
}{
OutPoint: s.OutPoint.String(),
SupportedClaimID: s.SupportedID.String(),
Amount: s.Amt,
Accepted: s.Accepted,
ActiveAt: s.ActiveAt,
})
// Support ...
type Support struct {
OutPoint wire.OutPoint
ClaimID ID
Amt Amount
Accepted Height
ActiveAt Height
Seq Seq
}
func (s *Support) String() string {
if dbg {
w := bytes.NewBuffer(nil)
fmt.Fprintf(w, "S%-3d amt: %2d, accepted: %2d, active: %2d, id: %s", s.Index, s.Amt, s.Accepted, s.ActiveAt, s.SupportedID)
return w.String()
}
b, err := json.MarshalIndent(s, "", " ")
if err != nil {
fmt.Printf("can't marshal, err :%s", err)
}
return string(b)
return fmt.Sprintf("S-%-68s amt: %-3d accepted: %-3d active: %-3d id: %s",
s.OutPoint, s.Amt, s.Accepted, s.ActiveAt, s.ClaimID)
}
// MarshalJSON customizes the representation of JSON.
func (s *Support) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
OutPoint string
ClaimID string
Amount Amount
Accepted Height
ActiveAt Height
}{
OutPoint: s.OutPoint.String(),
ClaimID: s.ClaimID.String(),
Amount: s.Amt,
Accepted: s.Accepted,
ActiveAt: s.ActiveAt,
})
}

View file

@ -1,11 +1,8 @@
package claimnode
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/json"
"fmt"
"math"
"strconv"
@ -16,94 +13,84 @@ import (
"github.com/lbryio/claimtrie/memento"
)
var dbg bool
// Node ...
type Node struct {
memento.Memento
mem memento.Memento
height claim.Height
bestClaims map[claim.Height]*claim.Claim
claims map[wire.OutPoint]*claim.Claim
supports map[wire.OutPoint]*claim.Support
// To ensure the Claims and Supports are totally ordered, we assign a
// strictly increasing seq to each Claim or Support added to the node.
seq claim.Seq
claims map[wire.OutPoint]*claim.Claim
supports map[wire.OutPoint]*claim.Support
updateNext bool
}
// NewNode ...
// NewNode returns a new Node.
func NewNode() *Node {
return &Node{
Memento: memento.Memento{},
mem: memento.Memento{},
bestClaims: map[claim.Height]*claim.Claim{0: nil},
claims: map[wire.OutPoint]*claim.Claim{},
supports: map[wire.OutPoint]*claim.Support{},
}
}
// Height ...
// Height returns the current height.
func (n *Node) Height() claim.Height {
return n.height
}
// BestClaim ...
// BestClaim returns the best claim at the current height.
func (n *Node) BestClaim() *claim.Claim {
var latest claim.Height
for k := range n.bestClaims {
if k > latest {
latest = k
}
}
return n.bestClaims[latest]
c, _ := BestClaimAt(n, n.height)
return c
}
// Tookover ...
// Tookover returns the height at which current best claim tookover.
func (n *Node) Tookover() claim.Height {
var latest claim.Height
for k := range n.bestClaims {
if k > latest {
latest = k
}
}
return latest
_, since := BestClaimAt(n, n.height)
return since
}
// IncrementBlock ...
func (n *Node) IncrementBlock(h claim.Height) error {
if h < 0 {
return ErrInvalidHeight
}
for i := claim.Height(0); i < h; i++ {
// AdjustTo increments or decrements current height until it reaches the specific height.
func (n *Node) AdjustTo(h claim.Height) error {
for n.height < h {
n.height++
n.processBlock()
n.Commit()
n.mem.Commit()
}
for n.height > h {
n.height--
n.mem.Rollback()
}
return nil
}
// DecrementBlock ...
func (n *Node) DecrementBlock(h claim.Height) error {
if h < 0 {
return ErrInvalidHeight
}
for i := claim.Height(0); i < h; i++ {
n.height--
n.Rollback()
}
// Reset ...
func (n *Node) Reset() error {
n.mem.RollbackUncommited()
return nil
}
// AddClaim ...
func (n *Node) AddClaim(op wire.OutPoint, amt claim.Amount) (*claim.Claim, error) {
n.seq++
c := &claim.Claim{
OutPoint: op,
ID: claim.NewID(op),
Amt: amt,
Accepted: n.height + 1,
ActiveAt: n.height + 1,
Seq: n.seq,
}
if n.BestClaim() != nil {
c.ActiveAt = calActiveHeight(c.Accepted, c.Accepted, n.Tookover())
}
n.Execute(cmdAddClaim{node: n, claim: c})
n.mem.Execute(cmdAddClaim{node: n, claim: c})
return c, nil
}
@ -113,24 +100,27 @@ func (n *Node) RemoveClaim(op wire.OutPoint) error {
if !ok {
return ErrNotFound
}
n.Execute(cmdRemoveClaim{node: n, claim: c})
n.mem.Execute(cmdRemoveClaim{node: n, claim: c})
if n.BestClaim() != c {
return nil
}
n.Execute(updateNodeBestClaim{node: n, height: n.Tookover(), old: c, new: nil})
n.mem.Execute(updateNodeBestClaim{node: n, height: n.Tookover(), old: c, new: nil})
n.updateActiveHeights()
n.updateNext = true
return nil
}
// AddSupport ...
func (n *Node) AddSupport(op wire.OutPoint, amt claim.Amount, supported claim.ID) (*claim.Support, error) {
n.seq++
s := &claim.Support{
OutPoint: op,
Amt: amt,
SupportedID: supported,
Accepted: n.height + 1,
ActiveAt: n.height + 1,
OutPoint: op,
Amt: amt,
ClaimID: supported,
Accepted: n.height + 1,
ActiveAt: n.height + 1,
Seq: n.seq,
}
if n.BestClaim() == nil || n.BestClaim().OutPoint != op {
s.ActiveAt = calActiveHeight(s.Accepted, s.Accepted, n.Tookover())
@ -140,7 +130,7 @@ func (n *Node) AddSupport(op wire.OutPoint, amt claim.Amount, supported claim.ID
if c.ID != supported {
continue
}
n.Execute(cmdAddSupport{node: n, support: s})
n.mem.Execute(cmdAddSupport{node: n, support: s})
return s, nil
}
@ -154,12 +144,18 @@ func (n *Node) RemoveSupport(op wire.OutPoint) error {
if !ok {
return ErrNotFound
}
n.Execute(cmdRemoveSupport{node: n, support: s})
n.mem.Execute(cmdRemoveSupport{node: n, support: s})
return nil
}
// FindNextUpdateHeights ...
func (n *Node) FindNextUpdateHeights() claim.Height {
// FindNextUpdateHeight returns the smallest height in the future that the the state of the node might change.
// If no such height exists, the current height of the node is returned.
func (n *Node) FindNextUpdateHeight() claim.Height {
if n.updateNext {
n.updateNext = false
return n.height + 1
}
next := claim.Height(math.MaxInt64)
for _, v := range n.claims {
if v.ActiveAt > n.height && v.ActiveAt < next {
@ -187,63 +183,14 @@ func (n *Node) Hash() chainhash.Hash {
// MarshalJSON customizes JSON marshaling of the Node.
func (n *Node) MarshalJSON() ([]byte, error) {
c := make([]*claim.Claim, 0, len(n.claims))
for _, v := range n.claims {
c = append(c, v)
}
s := make([]*claim.Support, 0, len(n.supports))
for _, v := range n.supports {
s = append(s, v)
}
return json.Marshal(&struct {
Height claim.Height
Hash string
Tookover claim.Height
BestClaim *claim.Claim
Claims []*claim.Claim
Supports []*claim.Support
}{
Height: n.height,
Hash: n.Hash().String(),
Tookover: n.Tookover(),
BestClaim: n.BestClaim(),
Claims: c,
Supports: s,
})
return toJSON(n)
}
// String implements Stringer interface.
func (n *Node) String() string {
if dbg {
w := bytes.NewBuffer(nil)
fmt.Fprintf(w, "H: %2d BestClaims: ", n.height)
for k, v := range n.bestClaims {
if v == nil {
fmt.Fprintf(w, "{%d, nil}, ", k)
continue
}
fmt.Fprintf(w, "{%d, %d}, ", k, v.Index)
}
fmt.Fprintf(w, "\n")
for _, v := range n.claims {
fmt.Fprintf(w, "\n %v", v)
if v == n.BestClaim() {
fmt.Fprintf(w, " <B> ")
}
}
for _, v := range n.supports {
fmt.Fprintf(w, "\n %v", v)
}
fmt.Fprintf(w, "\n")
return w.String()
}
b, err := json.MarshalIndent(n, "", " ")
if err != nil {
panic("can't marshal Node")
}
return string(b)
return toString(n)
}
func (n *Node) updateEffectiveAmounts() {
for _, c := range n.claims {
c.EffAmt = c.Amt
@ -252,7 +199,7 @@ func (n *Node) updateEffectiveAmounts() {
continue
}
for _, s := range n.supports {
if s.ActiveAt > n.height || s.SupportedID != c.ID {
if s.ActiveAt > n.height || s.ClaimID != c.ID {
continue
}
c.EffAmt += s.Amt
@ -263,40 +210,80 @@ func (n *Node) updateEffectiveAmounts() {
func (n *Node) updateActiveHeights() {
for _, v := range n.claims {
if old, new := v.ActiveAt, calActiveHeight(v.Accepted, n.height, n.height); old != new {
n.Execute(cmdUpdateClaimActiveHeight{claim: v, old: old, new: new})
n.mem.Execute(cmdUpdateClaimActiveHeight{claim: v, old: old, new: new})
}
}
for _, v := range n.supports {
if old, new := v.ActiveAt, calActiveHeight(v.Accepted, n.height, n.height); old != new {
n.Execute(cmdUpdateSupportActiveHeight{support: v, old: old, new: new})
n.mem.Execute(cmdUpdateSupportActiveHeight{support: v, old: old, new: new})
}
}
}
func (n *Node) processBlock() {
for {
n.updateEffectiveAmounts()
candidate := findCandiadte(n)
if n.BestClaim() == candidate {
return
}
n.Execute(updateNodeBestClaim{node: n, height: n.height, old: n.bestClaims[n.height], new: candidate})
n.mem.Execute(updateNodeBestClaim{node: n, height: n.height, old: n.bestClaims[n.height], new: candidate})
n.updateActiveHeights()
}
}
func findCandiadte(n *Node) *claim.Claim {
n.updateEffectiveAmounts()
var candidate *claim.Claim
for _, v := range n.claims {
if v.ActiveAt > n.height {
switch {
case v.ActiveAt > n.height:
continue
}
if candidate == nil || v.EffAmt > candidate.EffAmt {
case candidate == nil:
candidate = v
case v.EffAmt > candidate.EffAmt:
candidate = v
case v.EffAmt == candidate.EffAmt && v.Seq < candidate.Seq:
candidate = v
}
}
return candidate
}
// BestClaimAt returns the BestClaim at specified Height along with the height when the claim tookover.
func BestClaimAt(n *Node, at claim.Height) (best *claim.Claim, since claim.Height) {
var latest claim.Height
for k := range n.bestClaims {
if k > at {
continue
}
if k > latest {
latest = k
}
}
return n.bestClaims[latest], latest
}
// clone copies (deeply) the contents (except memento) of src to dst.
func clone(dst, src *Node) {
dst.height = src.height
for k, v := range src.bestClaims {
if v == nil {
dst.bestClaims[k] = nil
continue
}
dup := *v
dst.bestClaims[k] = &dup
}
for k, v := range src.claims {
dup := *v
dst.claims[k] = &dup
}
for k, v := range src.supports {
dup := *v
dst.supports[k] = &dup
}
}
func calNodeHash(op wire.OutPoint, tookover claim.Height) chainhash.Hash {
txHash := chainhash.DoubleHashH(op.Hash[:])

View file

@ -76,10 +76,10 @@ func Test_History1(t *testing.T) {
// no competing bids
test1 := func() {
c1, _ = n.AddClaim(*newOutPoint(1), 1)
n.IncrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c1, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
@ -87,49 +87,49 @@ func Test_History1(t *testing.T) {
test2 := func() {
n.AddClaim(*newOutPoint(2), 1)
c3, _ = n.AddClaim(*newOutPoint(3), 2)
n.IncrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c3, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
// make two claims , one older
test3 := func() {
c4, _ = n.AddClaim(*newOutPoint(4), 1)
n.IncrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c4, n.BestClaim())
n.AddClaim(*newOutPoint(5), 1)
n.IncrementBlock(1)
n.AdjustTo(2)
assert.Equal(t, c4, n.BestClaim())
n.IncrementBlock(1)
n.AdjustTo(3)
assert.Equal(t, c4, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(2)
assert.Equal(t, c4, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c4, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
// check claim takeover
test4 := func() {
c6, _ = n.AddClaim(*newOutPoint(6), 1)
n.IncrementBlock(10)
n.AdjustTo(10)
assert.Equal(t, c6, n.BestClaim())
c7, _ = n.AddClaim(*newOutPoint(7), 2)
n.IncrementBlock(1)
n.AdjustTo(11)
assert.Equal(t, c6, n.BestClaim())
n.IncrementBlock(10)
n.AdjustTo(21)
assert.Equal(t, c7, n.BestClaim())
n.DecrementBlock(10)
n.AdjustTo(11)
assert.Equal(t, c6, n.BestClaim())
n.DecrementBlock(10)
n.AdjustTo(1)
assert.Equal(t, c6, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
@ -137,51 +137,51 @@ func Test_History1(t *testing.T) {
test5 := func() {
c1, _ = n.AddClaim(*newOutPoint(1), 2)
c2, _ = n.AddClaim(*newOutPoint(2), 1)
n.IncrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c1, n.BestClaim())
n.RemoveClaim(c1.OutPoint)
n.IncrementBlock(1)
n.AdjustTo(2)
assert.Equal(t, c2, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c1, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
// spending winning claim will make inactive claim winner
test6 := func() {
c3, _ = n.AddClaim(*newOutPoint(3), 2)
n.IncrementBlock(10)
n.AdjustTo(10)
assert.Equal(t, c3, n.BestClaim())
c4, _ = n.AddClaim(*newOutPoint(4), 2)
n.IncrementBlock(1)
n.AdjustTo(11)
assert.Equal(t, c3, n.BestClaim())
n.RemoveClaim(c3.OutPoint)
n.IncrementBlock(1)
n.AdjustTo(12)
assert.Equal(t, c4, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(11)
assert.Equal(t, c3, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(10)
assert.Equal(t, c3, n.BestClaim())
n.DecrementBlock(10)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
// spending winning claim will empty out claim trie
test7 := func() {
c5, _ = n.AddClaim(*newOutPoint(5), 2)
n.IncrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c5, n.BestClaim())
n.RemoveClaim(c5.OutPoint)
n.IncrementBlock(1)
n.AdjustTo(2)
assert.NotEqual(t, c5, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c5, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
@ -191,49 +191,49 @@ func Test_History1(t *testing.T) {
c2, _ = n.AddClaim(*newOutPoint(2), 1)
s1, _ = n.AddSupport(*newOutPoint(11), 1, c1.ID)
s2, _ = n.AddSupport(*newOutPoint(12), 10, c2.ID)
n.IncrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c2, n.BestClaim())
assert.Equal(t, claim.Amount(11), n.BestClaim().EffAmt)
n.DecrementBlock(1)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
// check support delay
test9 := func() {
c3, _ = n.AddClaim(*newOutPoint(3), 1)
c4, _ = n.AddClaim(*newOutPoint(4), 2)
n.IncrementBlock(10)
n.AdjustTo(10)
assert.Equal(t, c4, n.BestClaim())
assert.Equal(t, claim.Amount(2), n.BestClaim().EffAmt)
s4, _ = n.AddSupport(*newOutPoint(14), 10, c3.ID)
n.IncrementBlock(10)
n.AdjustTo(20)
assert.Equal(t, c4, n.BestClaim())
assert.Equal(t, claim.Amount(2), n.BestClaim().EffAmt)
n.IncrementBlock(1)
n.AdjustTo(21)
assert.Equal(t, c3, n.BestClaim())
assert.Equal(t, claim.Amount(11), n.BestClaim().EffAmt)
n.DecrementBlock(1)
n.AdjustTo(20)
assert.Equal(t, c4, n.BestClaim())
assert.Equal(t, claim.Amount(2), n.BestClaim().EffAmt)
n.DecrementBlock(10)
n.AdjustTo(10)
assert.Equal(t, c4, n.BestClaim())
assert.Equal(t, claim.Amount(2), n.BestClaim().EffAmt)
n.DecrementBlock(10)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
// supporting and abandoing on the same block will cause it to crash
test10 := func() {
c1, _ = n.AddClaim(*newOutPoint(1), 2)
n.IncrementBlock(1)
n.AdjustTo(1)
s1, _ = n.AddSupport(*newOutPoint(11), 1, c1.ID)
n.RemoveClaim(c1.OutPoint)
n.IncrementBlock(1)
n.AdjustTo(2)
assert.NotEqual(t, c1, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c1, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
@ -241,20 +241,44 @@ func Test_History1(t *testing.T) {
test11 := func() {
c1, _ = n.AddClaim(*newOutPoint(1), 2)
s1, _ = n.AddSupport(*newOutPoint(11), 1, c1.ID)
n.IncrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c1, n.BestClaim())
//abandoning a support and abandoing claim on the same block will cause it to crash
n.RemoveClaim(c1.OutPoint)
n.RemoveSupport(s1.OutPoint)
n.IncrementBlock(1)
n.AdjustTo(2)
assert.Nil(t, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(1)
assert.Equal(t, c1, n.BestClaim())
n.DecrementBlock(1)
n.AdjustTo(0)
assert.Nil(t, n.BestClaim())
}
test12 := func() {
c1, _ = n.AddClaim(*newOutPoint(1), 3)
c2, _ = n.AddClaim(*newOutPoint(2), 2)
n.AdjustTo(10)
// c1 tookover since 1
assert.Equal(t, c1, n.BestClaim())
// C3 will takeover at 11 + 11 - 1 = 21
c3, _ = n.AddClaim(*newOutPoint(3), 5)
s1, _ = n.AddSupport(*newOutPoint(11), 2, c2.ID)
n.AdjustTo(20)
assert.Equal(t, c1, n.BestClaim())
n.AdjustTo(21)
assert.Equal(t, c3, n.BestClaim())
n.RemoveClaim(c3.OutPoint)
n.AdjustTo(22)
// c2 (3+4) should bid over c1(5) at 21
assert.Equal(t, c2, n.BestClaim())
}
tests := []func(){
test1,
test2,
@ -267,9 +291,10 @@ func Test_History1(t *testing.T) {
test9,
test10,
test11,
test12,
}
for _, tt := range tests {
tt()
}
_ = []func(){test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11}
_ = []func(){test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test12}
}

86
claimnode/ui.go Normal file
View file

@ -0,0 +1,86 @@
package claimnode
import (
"bytes"
"encoding/json"
"fmt"
"html/template"
"sort"
"github.com/lbryio/claimtrie/claim"
)
func sortedBestClaims(n *Node) []string {
var s []string
for i := claim.Height(0); i <= n.Tookover(); i++ {
v, ok := n.bestClaims[i]
if !ok || v == nil {
continue
}
s = append(s, fmt.Sprintf("{%d, %d}, ", i, v.OutPoint.Index))
}
return s
}
func sortedClaims(n *Node) []*claim.Claim {
c := make([]*claim.Claim, 0, len(n.claims))
for _, v := range n.claims {
c = append(c, v)
}
sort.Slice(c, func(i, j int) bool { return c[i].Seq < c[j].Seq })
return c
}
func sortedSupports(n *Node) []*claim.Support {
s := make([]*claim.Support, 0, len(n.supports))
for _, v := range n.supports {
s = append(s, v)
}
sort.Slice(s, func(i, j int) bool { return s[i].Seq < s[j].Seq })
return s
}
func export(n *Node) interface{} {
return &struct {
Height claim.Height
Hash string
BestClaims []string
BestClaim *claim.Claim
Claims []*claim.Claim
Supports []*claim.Support
}{
Height: n.height,
Hash: n.Hash().String(),
BestClaims: sortedBestClaims(n),
BestClaim: n.BestClaim(),
Claims: sortedClaims(n),
Supports: sortedSupports(n),
}
}
func toString(n *Node) string {
ui := ` Height {{.Height}}, {{.Hash}} BestClaims: {{range .BestClaims}}{{.}}{{end}}
{{$best := .BestClaim}}
{{- if .Claims}}
{{range .Claims -}}
{{.}} {{if (CMP . $best)}} <B> {{end}}
{{end}}
{{- end}}
{{- if .Supports}}
{{range .Supports}}{{.}}
{{end}}
{{- end}}`
w := bytes.NewBuffer(nil)
t := template.Must(template.New("").Funcs(template.FuncMap{
"CMP": func(a, b *claim.Claim) bool { return a == b },
}).Parse(ui))
if err := t.Execute(w, export(n)); err != nil {
fmt.Printf("can't execute template, err: %s\n", err)
}
return w.String()
}
func toJSON(n *Node) ([]byte, error) {
return json.Marshal(export(n))
}

View file

@ -1,8 +1,6 @@
package claimtrie
import (
"fmt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
@ -16,7 +14,7 @@ import (
type ClaimTrie struct {
// The highest block number commited to the ClaimTrie.
bestBlock claim.Height
height claim.Height
// Immutable linear history.
head *trie.Commit
@ -24,8 +22,16 @@ type ClaimTrie struct {
// An overlay supporting Copy-on-Write to the current tip commit.
stg *trie.Stage
// pending keeps track update for future block height.
pending map[claim.Height][]string
// 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
}
// CommitMeta implements trie.CommitMeta with commit-specific metadata.
@ -37,71 +43,57 @@ type CommitMeta struct {
func New() *ClaimTrie {
mt := trie.New()
return &ClaimTrie{
head: trie.NewCommit(nil, CommitMeta{0}, mt),
stg: trie.NewStage(mt),
pending: map[claim.Height][]string{},
head: trie.NewCommit(nil, CommitMeta{0}, mt),
stg: trie.NewStage(mt),
todos: map[claim.Height][]string{},
}
}
func updateStageNode(stg *trie.Stage, name string, modifier func(n *claimnode.Node) error) error {
v, err := stg.Get(trie.Key(name))
if err != nil && err != trie.ErrKeyNotFound {
return err
}
var n *claimnode.Node
if v == nil {
n = claimnode.NewNode()
} else {
n = v.(*claimnode.Node)
}
if err = modifier(n); err != nil {
return err
}
return stg.Update(trie.Key(name), n)
// 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
}
// AddClaim adds a Claim to the Stage of ClaimTrie.
func (ct *ClaimTrie) AddClaim(name string, op wire.OutPoint, amt claim.Amount) error {
return updateStageNode(ct.stg, name, func(n *claimnode.Node) error {
if err := n.IncrementBlock(claim.Height(ct.bestBlock) - n.Height()); err != nil {
return err
}
_, err := n.AddClaim(op, claim.Amount(amt))
next := ct.bestBlock + 1
ct.pending[next] = append(ct.pending[next], name)
modifier := func(n *claimnode.Node) error {
_, err := n.AddClaim(op, amt)
return err
})
}
return updateNode(ct, ct.height, name, modifier)
}
// AddSupport adds a Support to the Stage of ClaimTrie.
func (ct *ClaimTrie) AddSupport(name string, op wire.OutPoint, amt claim.Amount, supported claim.ID) error {
return updateStageNode(ct.stg, name, func(n *claimnode.Node) error {
modifier := func(n *claimnode.Node) error {
_, err := n.AddSupport(op, amt, supported)
next := ct.bestBlock + 1
ct.pending[next] = append(ct.pending[next], name)
return err
})
}
return updateNode(ct, ct.height, name, modifier)
}
// SpendClaim removes a Claim in the Stage.
func (ct *ClaimTrie) SpendClaim(name string, op wire.OutPoint) error {
return updateStageNode(ct.stg, name, func(n *claimnode.Node) error {
next := ct.bestBlock + 1
ct.pending[next] = append(ct.pending[next], name)
modifier := func(n *claimnode.Node) error {
return n.RemoveClaim(op)
})
}
return updateNode(ct, ct.height, name, modifier)
}
// SpendSupport removes a Support in the Stage.
func (ct *ClaimTrie) SpendSupport(name string, op wire.OutPoint) error {
return updateStageNode(ct.stg, name, func(n *claimnode.Node) error {
next := ct.bestBlock + 1
ct.pending[next] = append(ct.pending[next], name)
modifier := func(n *claimnode.Node) error {
return n.RemoveSupport(op)
})
}
return updateNode(ct, ct.height, name, modifier)
}
// Traverse visits Nodes in the Stage of the ClaimTrie.
// Traverse visits Nodes in the Stage.
func (ct *ClaimTrie) Traverse(visit trie.Visit, update, valueOnly bool) error {
return ct.stg.Traverse(visit, update, valueOnly)
}
@ -111,72 +103,121 @@ func (ct *ClaimTrie) MerkleHash() chainhash.Hash {
return ct.stg.MerkleHash()
}
// BestBlock returns the highest height of blocks commited to the ClaimTrie.
func (ct *ClaimTrie) BestBlock() claim.Height {
return ct.bestBlock
}
// Commit commits the current Stage into commit database, and updates the BestBlock with the associated height.
// The height must be higher than the current BestBlock, or ErrInvalidHeight is returned.
// 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.
func (ct *ClaimTrie) Commit(h claim.Height) error {
if h <= ct.bestBlock {
// Already caught up.
if h <= ct.height {
return ErrInvalidHeight
}
for i := claim.Height(ct.bestBlock) + 1; i <= h; i++ {
for _, prefix := range ct.pending[i] {
// Brings the value node to date.
catchup := func(n *claimnode.Node) error {
if err := n.IncrementBlock(i - n.Height()); err != nil {
return err
}
// After the update, the node may subscribe to another pending update.
if next := n.FindNextUpdateHeights(); next > i {
fmt.Printf("Subscribe pendings for %v to future Height at %d\n", prefix, next)
ct.pending[next] = append(ct.pending[next], prefix)
}
return nil
}
// Update the node with the catchup modifier, and clear the Merkle Hash along the way.
if err := updateStageNode(ct.stg, prefix, catchup); err != nil {
// 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.
modifier := func(n *claimnode.Node) error { return nil }
if err := updateNode(ct, i, name, modifier); err != nil {
return err
}
}
delete(ct.pending, i)
}
commit, err := ct.stg.Commit(ct.head, CommitMeta{Height: h})
if err != nil {
return err
}
// No more errors. Change the ClaimTrie status.
ct.head = commit
ct.bestBlock = h
for i := ct.height + 1; i <= h; i++ {
delete(ct.todos, i)
}
ct.height = h
return nil
}
// Reset reverts the Stage to a specified commit by height.
func (ct *ClaimTrie) Reset(h claim.Height) error {
visit := func(prefix trie.Key, value trie.Value) error {
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 {
n := value.(*claimnode.Node)
return n.DecrementBlock(n.Height() - claim.Height(h))
n.Reset()
return n.AdjustTo(h)
}
if err := ct.stg.Traverse(visit, true, true); err != nil {
return err
if err := ct.stg.Traverse(rollback, true, true); err != nil {
// Rollback a node to a known state can't go wrong.
// It's a programming error, and can't recover.
panic(err)
}
for commit := ct.head; commit != nil; commit = commit.Prev {
meta := commit.Meta.(CommitMeta)
if meta.Height <= h {
ct.head = commit
ct.bestBlock = h
ct.stg = trie.NewStage(commit.MerkleTrie)
return nil
// Update ClaimTrie status
ct.head = commit
ct.height = h
for k := range ct.todos {
if k >= h {
delete(ct.todos, k)
}
}
return ErrInvalidHeight
ct.stg = trie.NewStage(commit.MerkleTrie)
return nil
}
// Head returns the current tip commit in the commit database.
func (ct *ClaimTrie) Head() *trie.Commit {
return ct.head
// 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.
func updateNode(ct *ClaimTrie, h claim.Height, name string, modifier func(n *claimnode.Node) error) error {
// 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 {
v = claimnode.NewNode()
} else if err != nil {
return err
}
n := v.(*claimnode.Node)
// 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)
}

View file

@ -3,6 +3,7 @@ package main
import (
"bufio"
"crypto/rand"
"encoding/json"
"fmt"
"math/big"
"os"
@ -26,7 +27,8 @@ var (
flagHeight = cli.Int64Flag{Name: "height, ht", Usage: "Height"}
flagName = cli.StringFlag{Name: "name, n", Value: "Hello", Usage: "Name"}
flagID = cli.StringFlag{Name: "id", Usage: "Claim ID"}
flagOutPoint = cli.StringFlag{Name: "outpoint, op", Usage: "Outpoint. (HASH:INDEX) "}
flagOutPoint = cli.StringFlag{Name: "outpoint, op", Usage: "Outpoint. (HASH:INDEX)"}
flagJSON = cli.BoolFlag{Name: "json, j", Usage: "Show Claim / Support in JSON format."}
)
var (
@ -40,7 +42,6 @@ func main() {
app.Usage = "A CLI tool for ClaimTrie"
app.Version = "0.0.1"
app.Action = cli.ShowAppHelp
app.Commands = []cli.Command{
{
Name: "add-claim",
@ -75,7 +76,7 @@ func main() {
Aliases: []string{"s"},
Usage: "Show the Key-Value pairs of the Stage or specified commit. (links nodes are showed if -a is also specified)",
Action: cmdShow,
Flags: []cli.Flag{flagAll, flagHeight},
Flags: []cli.Flag{flagAll, flagJSON, flagHeight},
},
{
Name: "merkle",
@ -154,7 +155,7 @@ func cmdAddClaim(c *cli.Context) error {
// height := claim.Height(c.Int64("height"))
// if !c.IsSet("height") {
// height = ct.BestBlock()
// height = ct.Height()
// }
outPoint, err := newOutPoint(c.String("outpoint"))
@ -176,7 +177,7 @@ func cmdAddSupport(c *cli.Context) error {
// height := claim.Height(c.Int64("height"))
// if !c.IsSet("height") {
// height = ct.BestBlock()
// height = ct.Height()
// }
outPoint, err := newOutPoint(c.String("outpoint"))
@ -215,14 +216,23 @@ func cmdShow(c *cli.Context) error {
fmt.Printf("%-8s:\n", prefix)
return nil
}
fmt.Printf("%-8s: %v\n", prefix, val)
if !c.IsSet("json") {
fmt.Printf("%-8s: %v\n", prefix, val)
return nil
}
b, err := json.MarshalIndent(val, "", " ")
if err != nil {
return err
}
fmt.Printf("%-8s: %s\n", prefix, b)
return nil
}
height := claim.Height(c.Int64("height"))
if !c.IsSet("height") {
fmt.Printf("<ClaimTrie Height %d>\n", ct.BestBlock())
fmt.Printf("<ClaimTrie Height %d>\n", ct.Height())
return ct.Traverse(dump, false, !c.Bool("all"))
}
height := claim.Height(c.Int64("height"))
fmt.Printf("NOTE: peeking to the past is broken for now. Try RESET command instead\n")
for commit := ct.Head(); commit != nil; commit = commit.Prev {
meta := commit.Meta.(claimtrie.CommitMeta)
@ -243,7 +253,7 @@ func cmdMerkle(c *cli.Context) error {
func cmdCommit(c *cli.Context) error {
height := claim.Height(c.Int64("height"))
if !c.IsSet("height") {
height = ct.BestBlock() + 1
height = ct.Height() + 1
}
return ct.Commit(height)
}

View file

@ -28,9 +28,19 @@ func (m *Memento) Commit() {
// Rollback ...
func (m *Memento) Rollback() {
cmds := m.stack[len(m.stack)-1]
m.stack = m.stack[:len(m.stack)-1]
n := len(m.stack)
cmds := m.stack[n-1]
m.stack = m.stack[:n-1]
m.cmds = nil
for i := len(cmds) - 1; i >= 0; i-- {
cmds[i].Undo()
}
}
// RollbackUncommited ...
func (m *Memento) RollbackUncommited() {
for i := len(m.cmds) - 1; i >= 0; i-- {
m.cmds[i].Undo()
}
m.cmds = nil
}

View file

@ -22,6 +22,7 @@ func (s *Stage) Update(key Key, val Value) error {
defer s.mu.Unlock()
n := s.root
n.hash = nil
for _, k := range key {
org := n.links[k]
n.links[k] = newNode(nil)
@ -31,10 +32,8 @@ func (s *Stage) Update(key Key, val Value) error {
n.hash = nil
n = n.links[k]
}
if n.value != val {
n.value = val
n.hash = nil
}
n.value = val
n.hash = nil
return nil
}