misc changes
This commit is contained in:
parent
a5627ecfcc
commit
374a75fea6
18 changed files with 1514 additions and 1076 deletions
65
claim/builder.go
Normal file
65
claim/builder.go
Normal file
|
@ -0,0 +1,65 @@
|
|||
package claim
|
||||
|
||||
import (
|
||||
"github.com/lbryio/claimtrie/memento"
|
||||
)
|
||||
|
||||
type nodeBuildable interface {
|
||||
build() Node
|
||||
|
||||
setMemento(mem memento.Memento) nodeBuildable
|
||||
setBestClaims(claims ...Claim) nodeBuildable
|
||||
setClaims(claims ...Claim) nodeBuildable
|
||||
setSupports(supports ...Support) nodeBuildable
|
||||
setHeight(h Height) nodeBuildable
|
||||
setUpdateNext(b bool) nodeBuildable
|
||||
}
|
||||
|
||||
func newNodeBuilder() nodeBuildable {
|
||||
return &nodeBuilder{n: NewNode()}
|
||||
}
|
||||
|
||||
type nodeBuilder struct{ n *Node }
|
||||
|
||||
func (nb *nodeBuilder) build() Node {
|
||||
return *nb.n
|
||||
}
|
||||
|
||||
func (nb *nodeBuilder) setMemento(mem memento.Memento) nodeBuildable {
|
||||
nb.n.mem = mem
|
||||
return nb
|
||||
}
|
||||
|
||||
func (nb *nodeBuilder) setHeight(h Height) nodeBuildable {
|
||||
nb.n.height = h
|
||||
return nb
|
||||
}
|
||||
|
||||
func (nb *nodeBuilder) setUpdateNext(b bool) nodeBuildable {
|
||||
nb.n.updateNext = b
|
||||
return nb
|
||||
}
|
||||
|
||||
func (nb *nodeBuilder) setBestClaims(claims ...Claim) nodeBuildable {
|
||||
for i := range claims {
|
||||
c := claims[i] // Copy value, instead of holding reference to the slice.
|
||||
nb.n.bestClaims[c.ActiveAt] = &c
|
||||
}
|
||||
return nb
|
||||
}
|
||||
|
||||
func (nb *nodeBuilder) setClaims(claims ...Claim) nodeBuildable {
|
||||
for i := range claims {
|
||||
c := claims[i] // Copy value, instead of holding reference to the slice.
|
||||
nb.n.claims = append(nb.n.claims, &c)
|
||||
}
|
||||
return nb
|
||||
}
|
||||
|
||||
func (nb *nodeBuilder) setSupports(supports ...Support) nodeBuildable {
|
||||
for i := range supports {
|
||||
s := supports[i] // Copy value, instead of holding reference to the slice.
|
||||
nb.n.supports = append(nb.n.supports, &s)
|
||||
}
|
||||
return nb
|
||||
}
|
172
claim/claim.go
172
claim/claim.go
|
@ -1,20 +1,26 @@
|
|||
package claim
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
// Amount ...
|
||||
type Amount int64
|
||||
type (
|
||||
// Amount ...
|
||||
Amount int64
|
||||
|
||||
// Height ...
|
||||
type Height int64
|
||||
// Height ...
|
||||
Height int64
|
||||
)
|
||||
|
||||
// Seq is a strictly increasing sequence number determine relative order between Claims and Supports.
|
||||
type Seq uint64
|
||||
// seq is a strictly increasing sequence number determine relative order between Claims and Supports.
|
||||
var seq uint64
|
||||
|
||||
// New ...
|
||||
func New(op wire.OutPoint, amt Amount) *Claim {
|
||||
return &Claim{OutPoint: op, ID: NewID(op), Amt: amt, seq: atomic.AddUint64(&seq, 1)}
|
||||
}
|
||||
|
||||
// Claim ...
|
||||
type Claim struct {
|
||||
|
@ -25,32 +31,45 @@ type Claim struct {
|
|||
Accepted Height
|
||||
ActiveAt Height
|
||||
|
||||
// TODO: Get rid of this. Implement ordered map in upper layer.
|
||||
Seq Seq
|
||||
seq uint64
|
||||
}
|
||||
|
||||
// SetOutPoint ...
|
||||
func (c *Claim) SetOutPoint(op wire.OutPoint) *Claim {
|
||||
c.OutPoint = op
|
||||
c.ID = NewID(op)
|
||||
return c
|
||||
}
|
||||
|
||||
// SetAmt ...
|
||||
func (c *Claim) SetAmt(amt Amount) *Claim {
|
||||
c.Amt = amt
|
||||
return c
|
||||
}
|
||||
|
||||
// SetAccepted ...
|
||||
func (c *Claim) SetAccepted(h Height) *Claim {
|
||||
c.Accepted = h
|
||||
return c
|
||||
}
|
||||
|
||||
// SetActiveAt ...
|
||||
func (c *Claim) SetActiveAt(h Height) *Claim {
|
||||
c.ActiveAt = h
|
||||
return c
|
||||
}
|
||||
|
||||
// String ...
|
||||
func (c *Claim) String() string {
|
||||
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)
|
||||
return claimToString(c)
|
||||
}
|
||||
|
||||
// MarshalJSON customizes the representation of JSON.
|
||||
func (c *Claim) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(&struct {
|
||||
OutPoint string
|
||||
ID string
|
||||
Amount Amount
|
||||
EffAmount Amount
|
||||
Accepted Height
|
||||
ActiveAt Height
|
||||
}{
|
||||
OutPoint: c.OutPoint.String(),
|
||||
ID: c.ID.String(),
|
||||
Amount: c.Amt,
|
||||
EffAmount: c.EffAmt,
|
||||
Accepted: c.Accepted,
|
||||
ActiveAt: c.ActiveAt,
|
||||
})
|
||||
func (c *Claim) MarshalJSON() ([]byte, error) { return claimToJSON(c) }
|
||||
|
||||
// NewSupport ...
|
||||
func NewSupport(op wire.OutPoint, amt Amount, claimID ID) *Support {
|
||||
return &Support{OutPoint: op, Amt: amt, ClaimID: claimID, seq: atomic.AddUint64(&seq, 1)}
|
||||
}
|
||||
|
||||
// Support ...
|
||||
|
@ -60,27 +79,92 @@ type Support struct {
|
|||
Amt Amount
|
||||
Accepted Height
|
||||
ActiveAt Height
|
||||
Seq Seq
|
||||
|
||||
seq uint64
|
||||
}
|
||||
|
||||
// SetOutPoint ...
|
||||
func (s *Support) SetOutPoint(op wire.OutPoint) *Support {
|
||||
s.OutPoint = op
|
||||
return s
|
||||
}
|
||||
|
||||
// SetAmt ...
|
||||
func (s *Support) SetAmt(amt Amount) *Support {
|
||||
s.Amt = amt
|
||||
return s
|
||||
}
|
||||
|
||||
// SetClaimID ...
|
||||
func (s *Support) SetClaimID(id ID) *Support {
|
||||
s.ClaimID = id
|
||||
return s
|
||||
}
|
||||
|
||||
// SetAccepted ...
|
||||
func (s *Support) SetAccepted(h Height) *Support {
|
||||
s.Accepted = h
|
||||
return s
|
||||
}
|
||||
|
||||
// SetActiveAt ...
|
||||
func (s *Support) SetActiveAt(h Height) *Support {
|
||||
s.ActiveAt = h
|
||||
return s
|
||||
}
|
||||
|
||||
// String ...
|
||||
func (s *Support) String() string {
|
||||
return fmt.Sprintf("S-%-68s amt: %-3d accepted: %-3d active: %-3d id: %s",
|
||||
s.OutPoint, s.Amt, s.Accepted, s.ActiveAt, s.ClaimID)
|
||||
return supportToString(s)
|
||||
}
|
||||
|
||||
// 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,
|
||||
})
|
||||
return supportToJSON(s)
|
||||
}
|
||||
|
||||
type claims []*Claim
|
||||
|
||||
func (cc claims) remove(op wire.OutPoint) claims {
|
||||
for i, v := range cc {
|
||||
if v.OutPoint != op {
|
||||
continue
|
||||
}
|
||||
cc[i] = cc[len(cc)-1]
|
||||
cc[len(cc)-1] = nil
|
||||
return cc[:len(cc)-1]
|
||||
}
|
||||
return cc
|
||||
}
|
||||
|
||||
func (cc claims) has(op wire.OutPoint) (*Claim, bool) {
|
||||
for _, v := range cc {
|
||||
if v.OutPoint == op {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
type supports []*Support
|
||||
|
||||
func (ss supports) remove(op wire.OutPoint) supports {
|
||||
for i, v := range ss {
|
||||
if v.OutPoint != op {
|
||||
continue
|
||||
}
|
||||
ss[i] = ss[len(ss)-1]
|
||||
ss[len(ss)-1] = nil
|
||||
return ss[:len(ss)-1]
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
func (ss supports) has(op wire.OutPoint) (*Support, bool) {
|
||||
for _, v := range ss {
|
||||
if v.OutPoint == op {
|
||||
return v, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package claimnode
|
||||
package claim
|
||||
|
||||
import "fmt"
|
||||
|
28
claim/hash.go
Normal file
28
claim/hash.go
Normal file
|
@ -0,0 +1,28 @@
|
|||
package claim
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"strconv"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
)
|
||||
|
||||
func calNodeHash(op wire.OutPoint, tookover Height) chainhash.Hash {
|
||||
txHash := chainhash.DoubleHashH(op.Hash[:])
|
||||
|
||||
nOut := []byte(strconv.Itoa(int(op.Index)))
|
||||
nOutHash := chainhash.DoubleHashH(nOut)
|
||||
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, uint64(tookover))
|
||||
heightHash := chainhash.DoubleHashH(buf)
|
||||
|
||||
h := make([]byte, 0, sha256.Size*3)
|
||||
h = append(h, txHash[:]...)
|
||||
h = append(h, nOutHash[:]...)
|
||||
h = append(h, heightHash[:]...)
|
||||
|
||||
return chainhash.DoubleHashH(h)
|
||||
}
|
59
claim/hash_test.go
Normal file
59
claim/hash_test.go
Normal file
|
@ -0,0 +1,59 @@
|
|||
package claim
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func Test_calNodeHash(t *testing.T) {
|
||||
type args struct {
|
||||
op wire.OutPoint
|
||||
h Height
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want chainhash.Hash
|
||||
}{
|
||||
{
|
||||
name: "0-1",
|
||||
args: args{
|
||||
op: wire.OutPoint{Hash: newHash("c73232a755bf015f22eaa611b283ff38100f2a23fb6222e86eca363452ba0c51"), Index: 0},
|
||||
h: 0,
|
||||
},
|
||||
want: newHash("48a312fc5141ad648cb5dca99eaf221f7b1bc4d2fc559e1cde4664a46d8688a4"),
|
||||
},
|
||||
{
|
||||
name: "0-2",
|
||||
args: args{
|
||||
op: wire.OutPoint{Hash: newHash("71c7b8d35b9a3d7ad9a1272b68972979bbd18589f1efe6f27b0bf260a6ba78fa"), Index: 1},
|
||||
h: 1,
|
||||
},
|
||||
want: newHash("9132cc5ff95ae67bee79281438e7d00c25c9ec8b526174eb267c1b63a55be67c"),
|
||||
},
|
||||
{
|
||||
name: "0-3",
|
||||
args: args{
|
||||
op: wire.OutPoint{Hash: newHash("c4fc0e2ad56562a636a0a237a96a5f250ef53495c2cb5edd531f087a8de83722"), Index: 0x12345678},
|
||||
h: 0x87654321,
|
||||
},
|
||||
want: newHash("023c73b8c9179ffcd75bd0f2ed9784aab2a62647585f4b38e4af1d59cf0665d2"),
|
||||
},
|
||||
{
|
||||
name: "0-4",
|
||||
args: args{
|
||||
op: wire.OutPoint{Hash: newHash("baf52472bd7da19fe1e35116cfb3bd180d8770ffbe3ae9243df1fb58a14b0975"), Index: 0x11223344},
|
||||
h: 0x88776655,
|
||||
},
|
||||
want: newHash("6a2d40f37cb2afea3b38dea24e1532e18cade5d1dc9c2f8bd635aca2bc4ac980"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
assert.Equal(t, tt.want, calNodeHash(tt.args.op, tt.args.h))
|
||||
})
|
||||
}
|
||||
}
|
74
claim/memento.go
Normal file
74
claim/memento.go
Normal file
|
@ -0,0 +1,74 @@
|
|||
package claim
|
||||
|
||||
type cmdAddClaim struct {
|
||||
node *Node
|
||||
claim *Claim
|
||||
}
|
||||
|
||||
func (c cmdAddClaim) Execute() { c.node.claims = append(c.node.claims, c.claim) }
|
||||
func (c cmdAddClaim) Undo() { c.node.claims = c.node.claims.remove(c.claim.OutPoint) }
|
||||
|
||||
type cmdRemoveClaim struct {
|
||||
node *Node
|
||||
claim *Claim
|
||||
}
|
||||
|
||||
func (c cmdRemoveClaim) Execute() { c.node.claims = c.node.claims.remove(c.claim.OutPoint) }
|
||||
func (c cmdRemoveClaim) Undo() { c.node.claims = append(c.node.claims, c.claim) }
|
||||
|
||||
type cmdAddSupport struct {
|
||||
node *Node
|
||||
support *Support
|
||||
}
|
||||
|
||||
func (c cmdAddSupport) Execute() { c.node.supports = append(c.node.supports, c.support) }
|
||||
func (c cmdAddSupport) Undo() { c.node.supports = c.node.supports.remove(c.support.OutPoint) }
|
||||
|
||||
type cmdRemoveSupport struct {
|
||||
node *Node
|
||||
support *Support
|
||||
}
|
||||
|
||||
func (c cmdRemoveSupport) Execute() {
|
||||
c.node.supports = c.node.supports.remove(c.support.OutPoint)
|
||||
}
|
||||
func (c cmdRemoveSupport) Undo() { c.node.supports = append(c.node.supports, c.support) }
|
||||
|
||||
type cmdUpdateClaimActiveHeight struct {
|
||||
claim *Claim
|
||||
old Height
|
||||
new Height
|
||||
}
|
||||
|
||||
func (c cmdUpdateClaimActiveHeight) Execute() { c.claim.ActiveAt = c.new }
|
||||
func (c cmdUpdateClaimActiveHeight) Undo() { c.claim.ActiveAt = c.old }
|
||||
|
||||
type cmdUpdateSupportActiveHeight struct {
|
||||
support *Support
|
||||
old Height
|
||||
new Height
|
||||
}
|
||||
|
||||
func (c cmdUpdateSupportActiveHeight) Execute() { c.support.ActiveAt = c.new }
|
||||
func (c cmdUpdateSupportActiveHeight) Undo() { c.support.ActiveAt = c.old }
|
||||
|
||||
type updateNodeBestClaim struct {
|
||||
node *Node
|
||||
height Height
|
||||
old *Claim
|
||||
new *Claim
|
||||
}
|
||||
|
||||
func (c updateNodeBestClaim) Execute() {
|
||||
c.node.bestClaims[c.height] = c.new
|
||||
if c.node.bestClaims[c.height] == nil {
|
||||
delete(c.node.bestClaims, c.height)
|
||||
}
|
||||
}
|
||||
|
||||
func (c updateNodeBestClaim) Undo() {
|
||||
c.node.bestClaims[c.height] = c.old
|
||||
if c.node.bestClaims[c.height] == nil {
|
||||
delete(c.node.bestClaims, c.height)
|
||||
}
|
||||
}
|
301
claim/node.go
Normal file
301
claim/node.go
Normal file
|
@ -0,0 +1,301 @@
|
|||
package claim
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
"github.com/lbryio/claimtrie/memento"
|
||||
)
|
||||
|
||||
// Node ...
|
||||
type Node struct {
|
||||
mem memento.Memento
|
||||
height Height
|
||||
bestClaims map[Height]*Claim
|
||||
|
||||
claims claims
|
||||
supports supports
|
||||
|
||||
updateNext bool
|
||||
}
|
||||
|
||||
// NewNode returns a new Node.
|
||||
func NewNode() *Node {
|
||||
return &Node{
|
||||
mem: memento.Memento{},
|
||||
bestClaims: map[Height]*Claim{0: nil},
|
||||
claims: claims{},
|
||||
supports: supports{},
|
||||
}
|
||||
}
|
||||
|
||||
// Height returns the current height.
|
||||
func (n *Node) Height() Height {
|
||||
return n.height
|
||||
}
|
||||
|
||||
// BestClaim returns the best claim at the current height.
|
||||
func (n *Node) BestClaim() *Claim {
|
||||
c, _ := bestClaim(n.height, n.bestClaims)
|
||||
return c
|
||||
}
|
||||
|
||||
// Tookover returns the height at which current best claim took over.
|
||||
func (n *Node) Tookover() Height {
|
||||
_, since := bestClaim(n.height, n.bestClaims)
|
||||
return since
|
||||
}
|
||||
|
||||
// AdjustTo increments or decrements current height until it reaches the specific height.
|
||||
func (n *Node) AdjustTo(h Height) error {
|
||||
for n.height < h {
|
||||
n.Increment()
|
||||
}
|
||||
for n.height > h {
|
||||
n.Decrement()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Increment ...
|
||||
// Increment also clears out the undone stack if it wasn't empty.
|
||||
func (n *Node) Increment() error {
|
||||
n.height++
|
||||
n.processBlock()
|
||||
n.mem.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Decrement ...
|
||||
func (n *Node) Decrement() error {
|
||||
n.height--
|
||||
n.mem.Undo()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Redo ...
|
||||
func (n *Node) Redo() error {
|
||||
if err := n.mem.Redo(); err != nil {
|
||||
return err
|
||||
}
|
||||
n.height++
|
||||
return nil
|
||||
}
|
||||
|
||||
// RollbackExecuted ...
|
||||
func (n *Node) RollbackExecuted() error {
|
||||
n.mem.RollbackExecuted()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddClaim ...
|
||||
func (n *Node) AddClaim(c *Claim) error {
|
||||
if _, ok := n.claims.has(c.OutPoint); ok {
|
||||
return ErrDuplicate
|
||||
}
|
||||
next := n.height + 1
|
||||
c.SetAccepted(next).SetActiveAt(next)
|
||||
if n.BestClaim() != nil {
|
||||
c.SetActiveAt(calActiveHeight(next, next, n.Tookover()))
|
||||
}
|
||||
|
||||
n.mem.Execute(cmdAddClaim{node: n, claim: c})
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveClaim ...
|
||||
func (n *Node) RemoveClaim(op wire.OutPoint) error {
|
||||
c, ok := n.claims.has(op)
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
n.mem.Execute(cmdRemoveClaim{node: n, claim: c})
|
||||
|
||||
if *n.BestClaim() != *c {
|
||||
return nil
|
||||
}
|
||||
n.mem.Execute(updateNodeBestClaim{node: n, height: n.Tookover(), old: c, new: nil})
|
||||
updateActiveHeights(n.height, n.claims, n.supports, &n.mem)
|
||||
n.updateNext = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddSupport ...
|
||||
func (n *Node) AddSupport(s *Support) error {
|
||||
next := n.height + 1
|
||||
s.SetAccepted(next).SetActiveAt(next)
|
||||
if n.BestClaim() == nil || n.BestClaim().ID != s.ClaimID {
|
||||
s.SetActiveAt(calActiveHeight(next, next, n.Tookover()))
|
||||
}
|
||||
|
||||
for _, c := range n.claims {
|
||||
if c.ID != s.ClaimID {
|
||||
continue
|
||||
}
|
||||
n.mem.Execute(cmdAddSupport{node: n, support: s})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Is supporting an non-existing Claim aceepted?
|
||||
return ErrNotFound
|
||||
}
|
||||
|
||||
// RemoveSupport ...
|
||||
func (n *Node) RemoveSupport(op wire.OutPoint) error {
|
||||
s, ok := n.supports.has(op)
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
n.supports = n.supports.remove(op)
|
||||
n.mem.Execute(cmdRemoveSupport{node: n, support: s})
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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() Height {
|
||||
if n.updateNext {
|
||||
n.updateNext = false
|
||||
return n.height + 1
|
||||
}
|
||||
|
||||
return findNextUpdateHeight(n.height, n.claims, n.supports)
|
||||
}
|
||||
|
||||
// Hash calculates the Hash value based on the OutPoint and at which height it tookover.
|
||||
func (n *Node) Hash() chainhash.Hash {
|
||||
if n.BestClaim() == nil {
|
||||
return chainhash.Hash{}
|
||||
}
|
||||
return calNodeHash(n.BestClaim().OutPoint, n.Tookover())
|
||||
}
|
||||
|
||||
// MarshalJSON customizes JSON marshaling of the Node.
|
||||
func (n *Node) MarshalJSON() ([]byte, error) {
|
||||
return nodeToJSON(n)
|
||||
}
|
||||
|
||||
// String implements Stringer interface.
|
||||
func (n *Node) String() string {
|
||||
return nodeToString(n)
|
||||
}
|
||||
|
||||
func (n *Node) processBlock() {
|
||||
for {
|
||||
if c := n.BestClaim(); c != nil && !isActive(n.height, c.Accepted, c.ActiveAt) {
|
||||
n.mem.Execute(updateNodeBestClaim{node: n, height: n.height, old: n.bestClaims[n.height], new: nil})
|
||||
updateActiveHeights(n.height, n.claims, n.supports, &n.mem)
|
||||
}
|
||||
updateEffectiveAmounts(n.height, n.claims, n.supports)
|
||||
candidate := findCandiadte(n.height, n.claims)
|
||||
if n.BestClaim() == candidate {
|
||||
return
|
||||
}
|
||||
n.mem.Execute(updateNodeBestClaim{node: n, height: n.height, old: n.bestClaims[n.height], new: candidate})
|
||||
updateActiveHeights(n.height, n.claims, n.supports, &n.mem)
|
||||
}
|
||||
}
|
||||
|
||||
func updateEffectiveAmounts(h Height, claims claims, supports supports) {
|
||||
for _, c := range claims {
|
||||
c.EffAmt = 0
|
||||
if !isActive(h, c.Accepted, c.ActiveAt) {
|
||||
continue
|
||||
}
|
||||
c.EffAmt = c.Amt
|
||||
for _, s := range supports {
|
||||
if !isActive(h, s.Accepted, s.ActiveAt) || s.ClaimID != c.ID {
|
||||
continue
|
||||
}
|
||||
c.EffAmt += s.Amt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateActiveHeights(h Height, claims claims, supports supports, mem *memento.Memento) {
|
||||
for _, v := range claims {
|
||||
if old, new := v.ActiveAt, calActiveHeight(v.Accepted, h, h); old != new {
|
||||
mem.Execute(cmdUpdateClaimActiveHeight{claim: v, old: old, new: new})
|
||||
}
|
||||
}
|
||||
for _, v := range supports {
|
||||
if old, new := v.ActiveAt, calActiveHeight(v.Accepted, h, h); old != new {
|
||||
mem.Execute(cmdUpdateSupportActiveHeight{support: v, old: old, new: new})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// bestClaim returns the best claim at specified height and since when it took over.
|
||||
func bestClaim(at Height, bestClaims map[Height]*Claim) (*Claim, Height) {
|
||||
var latest Height
|
||||
for k := range bestClaims {
|
||||
if k > at {
|
||||
continue
|
||||
}
|
||||
if k > latest {
|
||||
latest = k
|
||||
}
|
||||
}
|
||||
return bestClaims[latest], latest
|
||||
}
|
||||
|
||||
func findNextUpdateHeight(h Height, claims claims, supports supports) Height {
|
||||
next := Height(math.MaxInt64)
|
||||
for _, v := range claims {
|
||||
if v.ActiveAt > h && v.ActiveAt < next {
|
||||
next = v.ActiveAt
|
||||
}
|
||||
}
|
||||
for _, v := range supports {
|
||||
if v.ActiveAt > h && v.ActiveAt < next {
|
||||
next = v.ActiveAt
|
||||
}
|
||||
}
|
||||
if next == Height(math.MaxInt64) {
|
||||
return h
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
func findCandiadte(h Height, claims claims) *Claim {
|
||||
var candidate *Claim
|
||||
for _, v := range claims {
|
||||
switch {
|
||||
case v.ActiveAt > h:
|
||||
continue
|
||||
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
|
||||
}
|
||||
|
||||
func isActive(h, accepted, activeAt Height) bool {
|
||||
if activeAt > h {
|
||||
// Accepted, but not active yet.
|
||||
return false
|
||||
}
|
||||
if h >= paramExtendedClaimExpirationForkHeight && accepted+paramExtendedClaimExpirationTime <= h {
|
||||
// Expired on post-HF1807 duration
|
||||
return false
|
||||
}
|
||||
if h < paramExtendedClaimExpirationForkHeight && accepted+paramOriginalClaimExpirationTime <= h {
|
||||
// Expired on pre-HF1807 duration
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func calActiveHeight(Accepted, curr, tookover Height) Height {
|
||||
delay := (curr - tookover) / paramActiveDelayFactor
|
||||
if delay > paramMaxActiveDelay {
|
||||
delay = paramMaxActiveDelay
|
||||
}
|
||||
return Accepted + delay
|
||||
}
|
312
claim/node_test.go
Normal file
312
claim/node_test.go
Normal file
|
@ -0,0 +1,312 @@
|
|||
package claim
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lbryio/claimtrie/memento"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
var (
|
||||
opA = wire.OutPoint{Hash: newHash("0000000000000000000000000000000011111111111111111111111111111111"), Index: 1}
|
||||
opB = wire.OutPoint{Hash: newHash("0000000000000000000000000000000022222222222222222222222222222222"), Index: 2}
|
||||
opC = wire.OutPoint{Hash: newHash("0000000000000000000000000000000033333333333333333333333333333333"), Index: 3}
|
||||
opD = wire.OutPoint{Hash: newHash("0000000000000000000000000000000044444444444444444444444444444444"), Index: 4}
|
||||
opE = wire.OutPoint{Hash: newHash("0000000000000000000000000000000555555555555555555555555555555555"), Index: 5}
|
||||
opF = wire.OutPoint{Hash: newHash("0000000000000000000000000000000666666666666666666666666666666666"), Index: 6}
|
||||
opX = wire.OutPoint{Hash: newHash("0000000000000000000000000000000777777777777777777777777777777777"), Index: 7}
|
||||
opY = wire.OutPoint{Hash: newHash("0000000000000000000000000000000888888888888888888888888888888888"), Index: 8}
|
||||
opZ = wire.OutPoint{Hash: newHash("0000000000000000000000000000000999999999999999999999999999999999"), Index: 9}
|
||||
|
||||
cA = New(opA, 0)
|
||||
cB = New(opB, 0)
|
||||
cC = New(opC, 0)
|
||||
cD = New(opD, 0)
|
||||
cE = New(opE, 0)
|
||||
sX = NewSupport(opX, 0, ID{})
|
||||
sY = NewSupport(opY, 0, ID{})
|
||||
sZ = NewSupport(opZ, 0, ID{})
|
||||
)
|
||||
|
||||
func newHash(s string) chainhash.Hash {
|
||||
h, _ := chainhash.NewHashFromStr(s)
|
||||
return *h
|
||||
}
|
||||
|
||||
// The example on the [whitepaper](https://beta.lbry.tech/whitepaper.html)
|
||||
func Test_BestClaimExample(t *testing.T) {
|
||||
|
||||
SetParams(ActiveDelayFactor(32))
|
||||
defer SetParams(ResetParams())
|
||||
|
||||
n := NewNode()
|
||||
at := func(h Height) *Node {
|
||||
if err := n.AdjustTo(h - 1); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
bestAt := func(at Height) *Claim {
|
||||
if len(n.mem.Executed()) != 0 {
|
||||
n.Increment()
|
||||
n.Decrement()
|
||||
}
|
||||
for n.height < at {
|
||||
if err := n.Redo(); err == memento.ErrCommandStackEmpty {
|
||||
n.Increment()
|
||||
}
|
||||
}
|
||||
for n.height > at {
|
||||
n.Decrement()
|
||||
}
|
||||
return n.BestClaim()
|
||||
}
|
||||
|
||||
sX.SetAmt(14).SetClaimID(cA.ID)
|
||||
|
||||
at(13).AddClaim(cA.SetAmt(10)) // Claim A for 10LBC is accepted. It is the first claim, so it immediately becomes active and controlling.
|
||||
at(1001).AddClaim(cB.SetAmt(20)) // Claim B for 20LBC is accepted. It’s activation height is 1001+min(4032,floor(1001−1332))=1001+30=1031
|
||||
at(1010).AddSupport(sX) // Support X for 14LBC for claim A is accepted. Since it is a support for the controlling claim, it activates immediately.
|
||||
at(1020).AddClaim(cC.SetAmt(50)) // Claim C for 50LBC is accepted. The activation height is 1020+min(4032,floor(1020−1332))=1020+31=1051
|
||||
at(1040).AddClaim(cD.SetAmt(300)) // Claim D for 300LBC is accepted. The activation height is 1040+min(4032,floor(1040−1332))=1040+32=1072
|
||||
|
||||
assert.Equal(t, cA, bestAt(13)) // A(10) is controlling.
|
||||
assert.Equal(t, cA, bestAt(1001)) // A(10) is controlling, B(20) is accepted.
|
||||
assert.Equal(t, cA, bestAt(1010)) // A(10+14) is controlling, B(20) is accepted.
|
||||
assert.Equal(t, cA, bestAt(1020)) // A(10+14) is controlling, B(20) is accepted, C(50) is accepted.
|
||||
|
||||
// Claim B activates. It has 20LBC, while claim A has 24LBC (10 original + 14 from support X). There is no takeover, and claim A remains controlling.
|
||||
assert.Equal(t, cA, bestAt(1031)) // A(10+14) is controlling, B(20) is active, C(50) is accepted.
|
||||
assert.Equal(t, cA, bestAt(1040)) //A(10+14) is controlling, B(20) is active, C(50) is accepted, D(300) is accepted.
|
||||
|
||||
// Claim C activates. It has 50LBC, while claim A has 24LBC, so a takeover is initiated.
|
||||
// The takeover height for this name is set to 1051, and therefore the activation delay for all the claims becomes min(4032, floor(1051−1051/32)) = 0.
|
||||
// All the claims become active.
|
||||
// The totals for each claim are recalculated, and claim D becomes controlling because it has the highest total.
|
||||
assert.Equal(t, cD, bestAt(1051)) // A(10+14) is active, B(20) is active, C(50) is active, D(300) is controlling.
|
||||
}
|
||||
|
||||
func Test_BestClaim(t *testing.T) {
|
||||
|
||||
SetParams(ActiveDelayFactor(1))
|
||||
defer SetParams(ResetParams())
|
||||
|
||||
n := NewNode()
|
||||
at := func(h Height) *Node {
|
||||
if err := n.AdjustTo(h - 1); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
bestAt := func(at Height) *Claim {
|
||||
if len(n.mem.Executed()) != 0 {
|
||||
n.Increment()
|
||||
n.Decrement()
|
||||
}
|
||||
for n.height < at {
|
||||
if err := n.Redo(); err == memento.ErrCommandStackEmpty {
|
||||
n.Increment()
|
||||
}
|
||||
}
|
||||
for n.height > at {
|
||||
n.Decrement()
|
||||
}
|
||||
return n.BestClaim()
|
||||
}
|
||||
|
||||
tests := []func(t *testing.T){
|
||||
// No competing bids.
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(1))
|
||||
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
// Competing bids inserted at the same height.
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(1))
|
||||
at(1).AddClaim(cB.SetAmt(2))
|
||||
|
||||
assert.Equal(t, cB, bestAt(1))
|
||||
assert.Nil(t, bestAt(0))
|
||||
|
||||
},
|
||||
// Two claims with the same amount. The older one wins.
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(1))
|
||||
at(2).AddClaim(cB.SetAmt(1))
|
||||
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Equal(t, cA, bestAt(2))
|
||||
assert.Equal(t, cA, bestAt(3))
|
||||
assert.Equal(t, cA, bestAt(2))
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
// Check claim takeover.
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(1))
|
||||
at(10).AddClaim(cB.SetAmt(2))
|
||||
|
||||
assert.Equal(t, cA, bestAt(10))
|
||||
assert.Equal(t, cA, bestAt(11))
|
||||
assert.Equal(t, cB, bestAt(21))
|
||||
assert.Equal(t, cA, bestAt(11))
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
// Spending winning claim will make losing active claim winner.
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(2))
|
||||
at(1).AddClaim(cB.SetAmt(1))
|
||||
at(2).RemoveClaim(cA.OutPoint)
|
||||
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Equal(t, cB, bestAt(2))
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
// spending winning claim will make inactive claim winner
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(2))
|
||||
at(11).AddClaim(cB.SetAmt(1))
|
||||
at(12).RemoveClaim(cA.OutPoint)
|
||||
|
||||
assert.Equal(t, cA, bestAt(10))
|
||||
assert.Equal(t, cA, bestAt(11))
|
||||
assert.Equal(t, cB, bestAt(12))
|
||||
assert.Equal(t, cA, bestAt(11))
|
||||
assert.Equal(t, cA, bestAt(10))
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
// spending winning claim will empty out claim trie
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(2))
|
||||
at(2).RemoveClaim(cA.OutPoint)
|
||||
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.NotEqual(t, cA, bestAt(2))
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
// check claim with more support wins
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(2))
|
||||
at(1).AddClaim(cB.SetAmt(1))
|
||||
at(1).AddSupport(sX.SetAmt(1).SetClaimID(cA.ID))
|
||||
at(1).AddSupport(sY.SetAmt(10).SetClaimID(cB.ID))
|
||||
|
||||
assert.Equal(t, cB, bestAt(1))
|
||||
assert.Equal(t, Amount(11), bestAt(1).EffAmt)
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
// check support delay
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(1))
|
||||
at(1).AddClaim(cB.SetAmt(2))
|
||||
at(11).AddSupport(sX.SetAmt(10).SetClaimID(cA.ID))
|
||||
|
||||
assert.Equal(t, cB, bestAt(10))
|
||||
assert.Equal(t, Amount(2), bestAt(10).EffAmt)
|
||||
assert.Equal(t, cB, bestAt(20))
|
||||
assert.Equal(t, Amount(2), bestAt(20).EffAmt)
|
||||
assert.Equal(t, cA, bestAt(21))
|
||||
assert.Equal(t, Amount(11), bestAt(21).EffAmt)
|
||||
assert.Equal(t, cB, bestAt(20))
|
||||
assert.Equal(t, Amount(2), bestAt(20).EffAmt)
|
||||
assert.Equal(t, cB, bestAt(10))
|
||||
assert.Equal(t, Amount(2), bestAt(10).EffAmt)
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
// supporting and abandoing on the same block will cause it to crash
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(2))
|
||||
at(2).AddSupport(sX.SetAmt(1).SetClaimID(cA.ID))
|
||||
at(2).RemoveClaim(cA.OutPoint)
|
||||
|
||||
assert.NotEqual(t, cA, bestAt(2))
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
// support on abandon2
|
||||
func(t *testing.T) {
|
||||
at(1).AddClaim(cA.SetAmt(2))
|
||||
at(1).AddSupport(sX.SetAmt(1).SetClaimID(cA.ID))
|
||||
|
||||
// abandoning a support and abandoing claim on the same block will cause it to crash
|
||||
at(2).RemoveClaim(cA.OutPoint)
|
||||
at(2).RemoveSupport(sX.OutPoint)
|
||||
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Nil(t, bestAt(2))
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
// expiration
|
||||
func(t *testing.T) {
|
||||
SetParams(OriginalClaimExpirationTime(5))
|
||||
defer SetParams(OriginalClaimExpirationTime(DefaultOriginalClaimExpirationTime))
|
||||
|
||||
at(1).AddClaim(cA.SetAmt(2))
|
||||
at(5).AddClaim(cB.SetAmt(1))
|
||||
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Equal(t, cA, bestAt(5))
|
||||
assert.Equal(t, cB, bestAt(6))
|
||||
assert.Equal(t, cB, bestAt(7))
|
||||
assert.Equal(t, cB, bestAt(6))
|
||||
assert.Equal(t, cA, bestAt(5))
|
||||
assert.Equal(t, cA, bestAt(1))
|
||||
assert.Nil(t, bestAt(0))
|
||||
},
|
||||
|
||||
// check claims expire and is not updateable (may be changed in future soft fork)
|
||||
// CMutableTransaction tx3 = fixture.MakeClaim(fixture.GetCoinbase(),"test","one",2);
|
||||
// fixture.IncrementBlocks(1);
|
||||
// BOOST_CHECK(is_best_claim("test",tx3));
|
||||
// fixture.IncrementBlocks(pclaimTrie->nExpirationTime);
|
||||
// CMutableTransaction u1 = fixture.MakeUpdate(tx3,"test","two",ClaimIdHash(tx3.GetHash(),0) ,2);
|
||||
// BOOST_CHECK(!is_best_claim("test",u1));
|
||||
|
||||
// fixture.DecrementBlocks(pclaimTrie->nExpirationTime);
|
||||
// BOOST_CHECK(is_best_claim("test",tx3));
|
||||
// fixture.DecrementBlocks(1);
|
||||
|
||||
// check supports expire and can cause supported bid to lose claim
|
||||
// CMutableTransaction tx4 = fixture.MakeClaim(fixture.GetCoinbase(),"test","one",1);
|
||||
// CMutableTransaction tx5 = fixture.MakeClaim(fixture.GetCoinbase(),"test","one",2);
|
||||
// CMutableTransaction s1 = fixture.MakeSupport(fixture.GetCoinbase(),tx4,"test",2);
|
||||
// fixture.IncrementBlocks(1);
|
||||
// BOOST_CHECK(is_best_claim("test",tx4));
|
||||
// CMutableTransaction u2 = fixture.MakeUpdate(tx4,"test","two",ClaimIdHash(tx4.GetHash(),0) ,1);
|
||||
// CMutableTransaction u3 = fixture.MakeUpdate(tx5,"test","two",ClaimIdHash(tx5.GetHash(),0) ,2);
|
||||
// fixture.IncrementBlocks(pclaimTrie->nExpirationTime);
|
||||
// BOOST_CHECK(is_best_claim("test",u3));
|
||||
// fixture.DecrementBlocks(pclaimTrie->nExpirationTime);
|
||||
// BOOST_CHECK(is_best_claim("test",tx4));
|
||||
// fixture.DecrementBlocks(1);
|
||||
|
||||
// check updated claims will extend expiration
|
||||
// CMutableTransaction tx6 = fixture.MakeClaim(fixture.GetCoinbase(),"test","one",2);
|
||||
// fixture.IncrementBlocks(1);
|
||||
// BOOST_CHECK(is_best_claim("test",tx6));
|
||||
// CMutableTransaction u4 = fixture.MakeUpdate(tx6,"test","two",ClaimIdHash(tx6.GetHash(),0) ,2);
|
||||
// fixture.IncrementBlocks(1);
|
||||
// BOOST_CHECK(is_best_claim("test",u4));
|
||||
// fixture.IncrementBlocks(pclaimTrie->nExpirationTime-1);
|
||||
// BOOST_CHECK(is_best_claim("test",u4));
|
||||
// fixture.IncrementBlocks(1);
|
||||
// BOOST_CHECK(!is_best_claim("test",u4));
|
||||
// fixture.DecrementBlocks(1);
|
||||
// BOOST_CHECK(is_best_claim("test",u4));
|
||||
// fixture.DecrementBlocks(pclaimTrie->nExpirationTime);
|
||||
// BOOST_CHECK(is_best_claim("test",tx6));
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run("BestClaim", tt)
|
||||
}
|
||||
}
|
69
claim/param.go
Normal file
69
claim/param.go
Normal file
|
@ -0,0 +1,69 @@
|
|||
package claim
|
||||
|
||||
// Param ...
|
||||
type Param func()
|
||||
|
||||
// SetParams ...
|
||||
func SetParams(params ...Param) {
|
||||
for _, p := range params {
|
||||
p()
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
const (
|
||||
DefaultMaxActiveDelay Height = 4032
|
||||
DefaultActiveDelayFactor Height = 32
|
||||
)
|
||||
|
||||
// https://lbry.io/news/hf1807
|
||||
const (
|
||||
DefaultOriginalClaimExpirationTime Height = 262974
|
||||
DefaultExtendedClaimExpirationTime Height = 2102400
|
||||
DefaultExtendedClaimExpirationForkHeight Height = 278160
|
||||
)
|
||||
|
||||
var (
|
||||
paramMaxActiveDelay = DefaultMaxActiveDelay
|
||||
paramActiveDelayFactor = DefaultActiveDelayFactor
|
||||
paramOriginalClaimExpirationTime = DefaultOriginalClaimExpirationTime
|
||||
paramExtendedClaimExpirationTime = DefaultExtendedClaimExpirationTime
|
||||
paramExtendedClaimExpirationForkHeight = DefaultExtendedClaimExpirationForkHeight
|
||||
)
|
||||
|
||||
// ResetParams ...
|
||||
func ResetParams() Param {
|
||||
return func() {
|
||||
paramMaxActiveDelay = DefaultMaxActiveDelay
|
||||
paramActiveDelayFactor = DefaultActiveDelayFactor
|
||||
|
||||
paramOriginalClaimExpirationTime = DefaultOriginalClaimExpirationTime
|
||||
paramExtendedClaimExpirationTime = DefaultExtendedClaimExpirationTime
|
||||
paramExtendedClaimExpirationForkHeight = DefaultExtendedClaimExpirationForkHeight
|
||||
}
|
||||
}
|
||||
|
||||
// MaxActiveDelay ...
|
||||
func MaxActiveDelay(h Height) Param {
|
||||
return func() { paramMaxActiveDelay = h }
|
||||
}
|
||||
|
||||
// ActiveDelayFactor ...
|
||||
func ActiveDelayFactor(f Height) Param {
|
||||
return func() { paramActiveDelayFactor = f }
|
||||
}
|
||||
|
||||
// OriginalClaimExpirationTime ...
|
||||
func OriginalClaimExpirationTime(h Height) Param {
|
||||
return func() { paramOriginalClaimExpirationTime = h }
|
||||
}
|
||||
|
||||
// ExtendedClaimExpirationTime ...
|
||||
func ExtendedClaimExpirationTime(h Height) Param {
|
||||
return func() { paramExtendedClaimExpirationTime = h }
|
||||
}
|
||||
|
||||
// ExtendedClaimExpirationForkHeight ...
|
||||
func ExtendedClaimExpirationForkHeight(at Height) Param {
|
||||
return func() { paramExtendedClaimExpirationForkHeight = at }
|
||||
}
|
123
claim/ui.go
Normal file
123
claim/ui.go
Normal file
|
@ -0,0 +1,123 @@
|
|||
package claim
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"html/template"
|
||||
"sort"
|
||||
)
|
||||
|
||||
func sortedBestClaims(n *Node) []string {
|
||||
var s []string
|
||||
for i := 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 {
|
||||
c := make([]*Claim, len(n.claims))
|
||||
copy(c, n.claims)
|
||||
sort.Slice(c, func(i, j int) bool { return c[i].seq < c[j].seq })
|
||||
return c
|
||||
}
|
||||
|
||||
func sortedSupports(n *Node) []*Support {
|
||||
s := make([]*Support, len(n.supports))
|
||||
copy(s, n.supports)
|
||||
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 Height
|
||||
Hash string
|
||||
BestClaims []string
|
||||
BestClaim *Claim
|
||||
Claims []*Claim
|
||||
Supports []*Support
|
||||
}{
|
||||
Height: n.height,
|
||||
Hash: n.Hash().String(),
|
||||
BestClaims: sortedBestClaims(n),
|
||||
BestClaim: n.BestClaim(),
|
||||
Claims: sortedClaims(n),
|
||||
Supports: sortedSupports(n),
|
||||
}
|
||||
}
|
||||
|
||||
func nodeToString(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}}`
|
||||
|
||||
t := template.Must(template.New("").Funcs(template.FuncMap{
|
||||
"CMP": func(a, b *Claim) bool { return a == b },
|
||||
}).Parse(ui))
|
||||
w := bytes.NewBuffer(nil)
|
||||
if err := t.Execute(w, export(n)); err != nil {
|
||||
fmt.Printf("can't execute template, err: %s\n", err)
|
||||
}
|
||||
return w.String()
|
||||
}
|
||||
|
||||
func nodeToJSON(n *Node) ([]byte, error) {
|
||||
return json.Marshal(export(n))
|
||||
}
|
||||
|
||||
func claimToString(c *Claim) string {
|
||||
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)
|
||||
}
|
||||
|
||||
func claimToJSON(c *Claim) ([]byte, error) {
|
||||
return json.Marshal(&struct {
|
||||
OutPoint string
|
||||
ID string
|
||||
Amount Amount
|
||||
EffAmount Amount
|
||||
Accepted Height
|
||||
ActiveAt Height
|
||||
}{
|
||||
OutPoint: c.OutPoint.String(),
|
||||
ID: c.ID.String(),
|
||||
Amount: c.Amt,
|
||||
EffAmount: c.EffAmt,
|
||||
Accepted: c.Accepted,
|
||||
ActiveAt: c.ActiveAt,
|
||||
})
|
||||
}
|
||||
|
||||
func supportToString(s *Support) string {
|
||||
return fmt.Sprintf("S-%-68s amt: %-3d accepted: %-3d active: %-3d id: %s",
|
||||
s.OutPoint, s.Amt, s.Accepted, s.ActiveAt, s.ClaimID)
|
||||
}
|
||||
func supportToJSON(s *Support) ([]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,
|
||||
})
|
||||
}
|
|
@ -1,74 +0,0 @@
|
|||
package claimnode
|
||||
|
||||
import "github.com/lbryio/claimtrie/claim"
|
||||
|
||||
type cmdAddClaim struct {
|
||||
node *Node
|
||||
claim *claim.Claim
|
||||
}
|
||||
|
||||
func (c cmdAddClaim) Execute() { c.node.claims[c.claim.OutPoint] = c.claim }
|
||||
func (c cmdAddClaim) Undo() { delete(c.node.claims, c.claim.OutPoint) }
|
||||
|
||||
type cmdRemoveClaim struct {
|
||||
node *Node
|
||||
claim *claim.Claim
|
||||
}
|
||||
|
||||
func (c cmdRemoveClaim) Execute() { delete(c.node.claims, c.claim.OutPoint) }
|
||||
func (c cmdRemoveClaim) Undo() { c.node.claims[c.claim.OutPoint] = c.claim }
|
||||
|
||||
type cmdAddSupport struct {
|
||||
node *Node
|
||||
support *claim.Support
|
||||
}
|
||||
|
||||
func (c cmdAddSupport) Execute() { c.node.supports[c.support.OutPoint] = c.support }
|
||||
func (c cmdAddSupport) Undo() { delete(c.node.supports, c.support.OutPoint) }
|
||||
|
||||
type cmdRemoveSupport struct {
|
||||
node *Node
|
||||
support *claim.Support
|
||||
}
|
||||
|
||||
func (c cmdRemoveSupport) Execute() { delete(c.node.supports, c.support.OutPoint) }
|
||||
func (c cmdRemoveSupport) Undo() { c.node.supports[c.support.OutPoint] = c.support }
|
||||
|
||||
type cmdUpdateClaimActiveHeight struct {
|
||||
claim *claim.Claim
|
||||
old claim.Height
|
||||
new claim.Height
|
||||
}
|
||||
|
||||
func (c cmdUpdateClaimActiveHeight) Execute() { c.claim.ActiveAt = c.new }
|
||||
func (c cmdUpdateClaimActiveHeight) Undo() { c.claim.ActiveAt = c.old }
|
||||
|
||||
type cmdUpdateSupportActiveHeight struct {
|
||||
support *claim.Support
|
||||
old claim.Height
|
||||
new claim.Height
|
||||
}
|
||||
|
||||
func (c cmdUpdateSupportActiveHeight) Execute() { c.support.ActiveAt = c.new }
|
||||
func (c cmdUpdateSupportActiveHeight) Undo() { c.support.ActiveAt = c.old }
|
||||
|
||||
type updateNodeBestClaim struct {
|
||||
node *Node
|
||||
height claim.Height
|
||||
old *claim.Claim
|
||||
new *claim.Claim
|
||||
}
|
||||
|
||||
func (c updateNodeBestClaim) Execute() {
|
||||
c.node.bestClaims[c.height] = c.new
|
||||
if c.node.bestClaims[c.height] == nil {
|
||||
delete(c.node.bestClaims, c.height)
|
||||
}
|
||||
}
|
||||
|
||||
func (c updateNodeBestClaim) Undo() {
|
||||
c.node.bestClaims[c.height] = c.old
|
||||
if c.node.bestClaims[c.height] == nil {
|
||||
delete(c.node.bestClaims, c.height)
|
||||
}
|
||||
}
|
|
@ -1,313 +0,0 @@
|
|||
package claimnode
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
"github.com/lbryio/claimtrie/claim"
|
||||
"github.com/lbryio/claimtrie/memento"
|
||||
)
|
||||
|
||||
// Node ...
|
||||
type Node struct {
|
||||
mem memento.Memento
|
||||
height claim.Height
|
||||
bestClaims map[claim.Height]*claim.Claim
|
||||
|
||||
// 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 returns a new Node.
|
||||
func NewNode() *Node {
|
||||
return &Node{
|
||||
mem: memento.Memento{},
|
||||
bestClaims: map[claim.Height]*claim.Claim{0: nil},
|
||||
claims: map[wire.OutPoint]*claim.Claim{},
|
||||
supports: map[wire.OutPoint]*claim.Support{},
|
||||
}
|
||||
}
|
||||
|
||||
// Height returns the current height.
|
||||
func (n *Node) Height() claim.Height {
|
||||
return n.height
|
||||
}
|
||||
|
||||
// BestClaim returns the best claim at the current height.
|
||||
func (n *Node) BestClaim() *claim.Claim {
|
||||
c, _ := BestClaimAt(n, n.height)
|
||||
return c
|
||||
}
|
||||
|
||||
// Tookover returns the height at which current best claim tookover.
|
||||
func (n *Node) Tookover() claim.Height {
|
||||
_, since := BestClaimAt(n, n.height)
|
||||
return since
|
||||
}
|
||||
|
||||
// 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.mem.Commit()
|
||||
}
|
||||
for n.height > h {
|
||||
n.height--
|
||||
n.mem.Rollback()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.mem.Execute(cmdAddClaim{node: n, claim: c})
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// RemoveClaim ...
|
||||
func (n *Node) RemoveClaim(op wire.OutPoint) error {
|
||||
c, ok := n.claims[op]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
n.mem.Execute(cmdRemoveClaim{node: n, claim: c})
|
||||
|
||||
if n.BestClaim() != c {
|
||||
return 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,
|
||||
ClaimID: supported,
|
||||
Accepted: n.height + 1,
|
||||
ActiveAt: n.height + 1,
|
||||
Seq: n.seq,
|
||||
}
|
||||
if n.BestClaim() == nil || n.BestClaim().ID != supported {
|
||||
s.ActiveAt = calActiveHeight(s.Accepted, s.Accepted, n.Tookover())
|
||||
}
|
||||
|
||||
for _, c := range n.claims {
|
||||
if c.ID != supported {
|
||||
continue
|
||||
}
|
||||
n.mem.Execute(cmdAddSupport{node: n, support: s})
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Is supporting an non-existing Claim aceepted?
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
// RemoveSupport ...
|
||||
func (n *Node) RemoveSupport(op wire.OutPoint) error {
|
||||
s, ok := n.supports[op]
|
||||
if !ok {
|
||||
return ErrNotFound
|
||||
}
|
||||
n.mem.Execute(cmdRemoveSupport{node: n, support: s})
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
next = v.ActiveAt
|
||||
}
|
||||
}
|
||||
for _, v := range n.supports {
|
||||
if v.ActiveAt > n.height && v.ActiveAt < next {
|
||||
next = v.ActiveAt
|
||||
}
|
||||
}
|
||||
if next == claim.Height(math.MaxInt64) {
|
||||
return n.height
|
||||
}
|
||||
return next
|
||||
}
|
||||
|
||||
// Hash calculates the Hash value based on the OutPoint and at which height it tookover.
|
||||
func (n *Node) Hash() chainhash.Hash {
|
||||
if n.BestClaim() == nil {
|
||||
return chainhash.Hash{}
|
||||
}
|
||||
return calNodeHash(n.BestClaim().OutPoint, n.Tookover())
|
||||
}
|
||||
|
||||
// MarshalJSON customizes JSON marshaling of the Node.
|
||||
func (n *Node) MarshalJSON() ([]byte, error) {
|
||||
return toJSON(n)
|
||||
}
|
||||
|
||||
// String implements Stringer interface.
|
||||
func (n *Node) String() string {
|
||||
return toString(n)
|
||||
}
|
||||
|
||||
func (n *Node) updateEffectiveAmounts() {
|
||||
for _, c := range n.claims {
|
||||
c.EffAmt = c.Amt
|
||||
if c.ActiveAt > n.height {
|
||||
c.EffAmt = 0
|
||||
continue
|
||||
}
|
||||
for _, s := range n.supports {
|
||||
if s.ActiveAt > n.height || s.ClaimID != c.ID {
|
||||
continue
|
||||
}
|
||||
c.EffAmt += s.Amt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *Node) updateActiveHeights() {
|
||||
for _, v := range n.claims {
|
||||
if old, new := v.ActiveAt, calActiveHeight(v.Accepted, n.height, n.height); old != 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.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.mem.Execute(updateNodeBestClaim{node: n, height: n.height, old: n.bestClaims[n.height], new: candidate})
|
||||
n.updateActiveHeights()
|
||||
}
|
||||
}
|
||||
|
||||
func findCandiadte(n *Node) *claim.Claim {
|
||||
var candidate *claim.Claim
|
||||
for _, v := range n.claims {
|
||||
switch {
|
||||
case v.ActiveAt > n.height:
|
||||
continue
|
||||
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[:])
|
||||
|
||||
nOut := []byte(strconv.Itoa(int(op.Index)))
|
||||
nOutHash := chainhash.DoubleHashH(nOut)
|
||||
|
||||
buf := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(buf, uint64(tookover))
|
||||
heightHash := chainhash.DoubleHashH(buf)
|
||||
|
||||
h := make([]byte, 0, sha256.Size*3)
|
||||
h = append(h, txHash[:]...)
|
||||
h = append(h, nOutHash[:]...)
|
||||
h = append(h, heightHash[:]...)
|
||||
|
||||
return chainhash.DoubleHashH(h)
|
||||
}
|
||||
|
||||
var proportionalDelayFactor = claim.Height(32)
|
||||
|
||||
func calActiveHeight(Accepted, curr, tookover claim.Height) claim.Height {
|
||||
delay := (curr - tookover) / proportionalDelayFactor
|
||||
if delay > 4032 {
|
||||
delay = 4032
|
||||
}
|
||||
return Accepted + delay
|
||||
}
|
|
@ -1,441 +0,0 @@
|
|||
package claimnode
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/lbryio/claimtrie/claim"
|
||||
)
|
||||
|
||||
func newHash(s string) *chainhash.Hash {
|
||||
h, _ := chainhash.NewHashFromStr(s)
|
||||
return h
|
||||
}
|
||||
|
||||
func newOutPoint(idx int) *wire.OutPoint {
|
||||
// var h chainhash.Hash
|
||||
// if _, err := rand.Read(h[:]); err != nil {
|
||||
// return nil
|
||||
// }
|
||||
// return wire.NewOutPoint(&h, uint32(idx))
|
||||
return wire.NewOutPoint(new(chainhash.Hash), uint32(idx))
|
||||
}
|
||||
|
||||
func Test_calNodeHash(t *testing.T) {
|
||||
type args struct {
|
||||
op wire.OutPoint
|
||||
h claim.Height
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want chainhash.Hash
|
||||
}{
|
||||
{
|
||||
name: "0-1",
|
||||
args: args{op: wire.OutPoint{Hash: *newHash("c73232a755bf015f22eaa611b283ff38100f2a23fb6222e86eca363452ba0c51"), Index: 0}, h: 0},
|
||||
want: *newHash("48a312fc5141ad648cb5dca99eaf221f7b1bc4d2fc559e1cde4664a46d8688a4"),
|
||||
},
|
||||
{
|
||||
name: "0-2",
|
||||
args: args{op: wire.OutPoint{Hash: *newHash("71c7b8d35b9a3d7ad9a1272b68972979bbd18589f1efe6f27b0bf260a6ba78fa"), Index: 1}, h: 1},
|
||||
want: *newHash("9132cc5ff95ae67bee79281438e7d00c25c9ec8b526174eb267c1b63a55be67c"),
|
||||
},
|
||||
{
|
||||
name: "0-3",
|
||||
args: args{op: wire.OutPoint{Hash: *newHash("c4fc0e2ad56562a636a0a237a96a5f250ef53495c2cb5edd531f087a8de83722"), Index: 0x12345678}, h: 0x87654321},
|
||||
want: *newHash("023c73b8c9179ffcd75bd0f2ed9784aab2a62647585f4b38e4af1d59cf0665d2"),
|
||||
},
|
||||
{
|
||||
name: "0-4",
|
||||
args: args{op: wire.OutPoint{Hash: *newHash("baf52472bd7da19fe1e35116cfb3bd180d8770ffbe3ae9243df1fb58a14b0975"), Index: 0x11223344}, h: 0x88776655},
|
||||
want: *newHash("6a2d40f37cb2afea3b38dea24e1532e18cade5d1dc9c2f8bd635aca2bc4ac980"),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := calNodeHash(tt.args.op, tt.args.h); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("calNodeHash() = %X, want %X", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// The example on the [whitepaper](https://beta.lbry.tech/whitepaper.html)
|
||||
func Test_History0(t *testing.T) {
|
||||
|
||||
proportionalDelayFactor = 32
|
||||
n := NewNode()
|
||||
|
||||
n.AdjustTo(12)
|
||||
claimA, _ := n.AddClaim(*newOutPoint(1), 10) // Claim A
|
||||
n.AdjustTo(13)
|
||||
// Claim A for 10LBC is accepted. It is the first claim, so it immediately becomes active and controlling.
|
||||
assert.Equal(t, claimA, n.BestClaim()) // A(10) is controlling.
|
||||
|
||||
n.AdjustTo(1000)
|
||||
n.AddClaim(*newOutPoint(2), 20) // Claim B
|
||||
n.AdjustTo(1001)
|
||||
// Claim B for 20LBC is accepted. It’s activation height is 1001+min(4032,floor(1001−1332))=1001+30=1031
|
||||
assert.Equal(t, claimA, n.BestClaim()) // A(10) is controlling, B(20) is accepted.
|
||||
|
||||
n.AdjustTo(1009)
|
||||
n.AddSupport(*newOutPoint(1001), 14, claimA.ID) // Support X
|
||||
n.AdjustTo(1010)
|
||||
// Support X for 14LBC for claim A is accepted. Since it is a support for the controlling claim, it activates immediately.
|
||||
assert.Equal(t, claimA, n.BestClaim()) // A(10+14) is controlling, B(20) is accepted.
|
||||
|
||||
n.AdjustTo(1019)
|
||||
n.AddClaim(*newOutPoint(3), 50) // Claim C
|
||||
n.AdjustTo(1020)
|
||||
// Claim C for 50LBC is accepted. The activation height is 1020+min(4032,floor(1020−1332))=1020+31=1051
|
||||
assert.Equal(t, claimA, n.BestClaim()) // A(10+14) is controlling, B(20) is accepted, C(50) is accepted.
|
||||
|
||||
n.AdjustTo(1031)
|
||||
// Claim B activates. It has 20LBC, while claim A has 24LBC (10 original + 14 from support X). There is no takeover, and claim A remains controlling.
|
||||
assert.Equal(t, claimA, n.BestClaim()) // A(10+14) is controlling, B(20) is active, C(50) is accepted.
|
||||
|
||||
n.AdjustTo(1039)
|
||||
claimD, _ := n.AddClaim(*newOutPoint(4), 300) // Claim D
|
||||
n.AdjustTo(1040)
|
||||
// Claim D for 300LBC is accepted. The activation height is 1040+min(4032,floor(1040−1332))=1040+32=1072
|
||||
assert.Equal(t, claimA, n.BestClaim()) //A(10+14) is controlling, B(20) is active, C(50) is accepted, D(300) is accepted.
|
||||
|
||||
n.AdjustTo(1051)
|
||||
// Claim C activates. It has 50LBC, while claim A has 24LBC, so a takeover is initiated.
|
||||
// The takeover height for this name is set to 1051, and therefore the activation delay for all the claims becomes min(4032, floor(1051−1051/32)) = 0.
|
||||
// All the claims become active.
|
||||
// The totals for each claim are recalculated, and claim D becomes controlling because it has the highest total.
|
||||
assert.Equal(t, claimD, n.BestClaim()) // A(10+14) is active, B(20) is active, C(50) is active, D(300) is controlling.
|
||||
|
||||
// The following are the corresponding commands in the CLI to reproduce the test.
|
||||
// Note that when a Toakeover happens, the {TakeoverHeight, BestClaim(OutPoint:Indx)} is pushed to the BestClaims stack.
|
||||
// This provides sufficient info to update and backtracking the BestClaim at any height.
|
||||
|
||||
// claimtrie > c -ht 12
|
||||
// claimtrie > ac -a 10
|
||||
// claimtrie > c -ht 13
|
||||
// claimtrie > s
|
||||
//
|
||||
// <ClaimTrie Height 13>
|
||||
// Hello : Height 13, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
//
|
||||
// C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 10 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
|
||||
// claimtrie > c -ht 1000
|
||||
// claimtrie > ac -a 20
|
||||
// claimtrie > c -ht 1001
|
||||
// claimtrie > s
|
||||
//
|
||||
// <ClaimTrie Height 1001>
|
||||
// Hello : Height 1000, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
//
|
||||
// C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 10 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
// C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 0 accepted: 1001 active: 1031 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
|
||||
// claimtrie > c -ht 1009
|
||||
// claimtrie > as -a 14 -id ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
// claimtrie > c -ht 1010
|
||||
// claimtrie > s
|
||||
//
|
||||
// <ClaimTrie Height 1010>
|
||||
// Hello : Height 1010, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
//
|
||||
// C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 24 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
// C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 0 accepted: 1001 active: 1031 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
//
|
||||
// S-087acb4f22ab6eb2e6c827624deab7beb02c190056376e0b3a3c3546b79bf216:22 amt: 14 accepted: 1010 active: 1010 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
|
||||
// claimtrie > c -ht 1019
|
||||
// claimtrie > ac -a 50
|
||||
// claimtrie > c -ht 1020
|
||||
// claimtrie > s
|
||||
//
|
||||
// <ClaimTrie Height 1020>
|
||||
// Hello : Height 1019, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
//
|
||||
// C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 24 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
// C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 0 accepted: 1001 active: 1031 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
// C-864dc683ed1fbe3c072c2387bca7d74e60c2505f1987054fd70955a2e1c9490b:11 amt: 50 effamt: 0 accepted: 1020 active: 1051 id: 0f2ba15a5c66b978df4a27f52525680a6009185e
|
||||
//
|
||||
// S-087acb4f22ab6eb2e6c827624deab7beb02c190056376e0b3a3c3546b79bf216:22 amt: 14 accepted: 1010 active: 1010 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
|
||||
// claimtrie > c -ht 1031
|
||||
// claimtrie > s
|
||||
//
|
||||
// <ClaimTrie Height 1031>
|
||||
// Hello : Height 1031, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
//
|
||||
// C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 24 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
// C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 20 accepted: 1001 active: 1031 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
// C-864dc683ed1fbe3c072c2387bca7d74e60c2505f1987054fd70955a2e1c9490b:11 amt: 50 effamt: 0 accepted: 1020 active: 1051 id: 0f2ba15a5c66b978df4a27f52525680a6009185e
|
||||
//
|
||||
// S-087acb4f22ab6eb2e6c827624deab7beb02c190056376e0b3a3c3546b79bf216:22 amt: 14 accepted: 1010 active: 1010 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
|
||||
// claimtrie > c -ht 1039
|
||||
// claimtrie > ac -a 300
|
||||
// claimtrie > c -ht 1040
|
||||
// claimtrie > s
|
||||
//
|
||||
// <ClaimTrie Height 1040>
|
||||
// Hello : Height 1039, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
//
|
||||
// C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 24 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
// C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 20 accepted: 1001 active: 1031 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
// C-864dc683ed1fbe3c072c2387bca7d74e60c2505f1987054fd70955a2e1c9490b:11 amt: 50 effamt: 0 accepted: 1020 active: 1051 id: 0f2ba15a5c66b978df4a27f52525680a6009185e
|
||||
// C-26292b27122d04d08fee4e4cc5a5f94681832204cc29d61039c09af9a5298d16:22 amt: 300 effamt: 0 accepted: 1040 active: 1072 id: 270496c0710e525156510e60e4be2ffa6fe2f507
|
||||
//
|
||||
// S-087acb4f22ab6eb2e6c827624deab7beb02c190056376e0b3a3c3546b79bf216:22 amt: 14 accepted: 1010 active: 1010 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
|
||||
// claimtrie > c -ht 1051
|
||||
// claimtrie > s
|
||||
//
|
||||
// <ClaimTrie Height 1051>
|
||||
// Hello : Height 1051, 68dff86c9450e3cf96570f31b6ad8f8d35ae0cbce6cdcb3761910e25a815ee8b BestClaims: {13, 31}, {1051, 22},
|
||||
//
|
||||
// C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 24 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
// C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 20 accepted: 1001 active: 1001 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
// C-864dc683ed1fbe3c072c2387bca7d74e60c2505f1987054fd70955a2e1c9490b:11 amt: 50 effamt: 50 accepted: 1020 active: 1020 id: 0f2ba15a5c66b978df4a27f52525680a6009185e
|
||||
// C-26292b27122d04d08fee4e4cc5a5f94681832204cc29d61039c09af9a5298d16:22 amt: 300 effamt: 300 accepted: 1040 active: 1040 id: 270496c0710e525156510e60e4be2ffa6fe2f507 <B>
|
||||
//
|
||||
// S-087acb4f22ab6eb2e6c827624deab7beb02c190056376e0b3a3c3546b79bf216:22 amt: 14 accepted: 1010 active: 1010 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
}
|
||||
|
||||
var c1, c2, c3, c4, c5, c6, c7, c8, c9, c10 *claim.Claim
|
||||
var s1, s2, s3, s4, s5, s6, s7, s8, s9, s10 *claim.Support
|
||||
|
||||
func Test_History1(t *testing.T) {
|
||||
|
||||
proportionalDelayFactor = 1
|
||||
n := NewNode()
|
||||
|
||||
// no competing bids
|
||||
test1 := func() {
|
||||
c1, _ = n.AddClaim(*newOutPoint(1), 1)
|
||||
n.AdjustTo(1)
|
||||
assert.Equal(t, c1, n.BestClaim())
|
||||
|
||||
n.AdjustTo(0)
|
||||
assert.Nil(t, n.BestClaim())
|
||||
}
|
||||
|
||||
// there is a competing bid inserted same height
|
||||
test2 := func() {
|
||||
n.AddClaim(*newOutPoint(2), 1)
|
||||
c3, _ = n.AddClaim(*newOutPoint(3), 2)
|
||||
n.AdjustTo(1)
|
||||
assert.Equal(t, c3, n.BestClaim())
|
||||
|
||||
n.AdjustTo(0)
|
||||
assert.Nil(t, n.BestClaim())
|
||||
|
||||
}
|
||||
// make two claims , one older
|
||||
test3 := func() {
|
||||
c4, _ = n.AddClaim(*newOutPoint(4), 1)
|
||||
n.AdjustTo(1)
|
||||
assert.Equal(t, c4, n.BestClaim())
|
||||
n.AddClaim(*newOutPoint(5), 1)
|
||||
n.AdjustTo(2)
|
||||
assert.Equal(t, c4, n.BestClaim())
|
||||
n.AdjustTo(3)
|
||||
assert.Equal(t, c4, n.BestClaim())
|
||||
n.AdjustTo(2)
|
||||
assert.Equal(t, c4, n.BestClaim())
|
||||
n.AdjustTo(1)
|
||||
|
||||
assert.Equal(t, c4, n.BestClaim())
|
||||
n.AdjustTo(0)
|
||||
assert.Nil(t, n.BestClaim())
|
||||
}
|
||||
|
||||
// check claim takeover
|
||||
test4 := func() {
|
||||
c6, _ = n.AddClaim(*newOutPoint(6), 1)
|
||||
n.AdjustTo(10)
|
||||
assert.Equal(t, c6, n.BestClaim())
|
||||
|
||||
c7, _ = n.AddClaim(*newOutPoint(7), 2)
|
||||
n.AdjustTo(11)
|
||||
assert.Equal(t, c6, n.BestClaim())
|
||||
n.AdjustTo(21)
|
||||
assert.Equal(t, c7, n.BestClaim())
|
||||
|
||||
n.AdjustTo(11)
|
||||
assert.Equal(t, c6, n.BestClaim())
|
||||
n.AdjustTo(1)
|
||||
assert.Equal(t, c6, n.BestClaim())
|
||||
n.AdjustTo(0)
|
||||
assert.Nil(t, n.BestClaim())
|
||||
}
|
||||
|
||||
// spending winning claim will make losing active claim winner
|
||||
test5 := func() {
|
||||
c1, _ = n.AddClaim(*newOutPoint(1), 2)
|
||||
c2, _ = n.AddClaim(*newOutPoint(2), 1)
|
||||
n.AdjustTo(1)
|
||||
assert.Equal(t, c1, n.BestClaim())
|
||||
n.RemoveClaim(c1.OutPoint)
|
||||
n.AdjustTo(2)
|
||||
assert.Equal(t, c2, n.BestClaim())
|
||||
|
||||
n.AdjustTo(1)
|
||||
assert.Equal(t, c1, n.BestClaim())
|
||||
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.AdjustTo(10)
|
||||
assert.Equal(t, c3, n.BestClaim())
|
||||
|
||||
c4, _ = n.AddClaim(*newOutPoint(4), 2)
|
||||
n.AdjustTo(11)
|
||||
assert.Equal(t, c3, n.BestClaim())
|
||||
n.RemoveClaim(c3.OutPoint)
|
||||
n.AdjustTo(12)
|
||||
assert.Equal(t, c4, n.BestClaim())
|
||||
|
||||
n.AdjustTo(11)
|
||||
assert.Equal(t, c3, n.BestClaim())
|
||||
n.AdjustTo(10)
|
||||
assert.Equal(t, c3, n.BestClaim())
|
||||
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.AdjustTo(1)
|
||||
assert.Equal(t, c5, n.BestClaim())
|
||||
n.RemoveClaim(c5.OutPoint)
|
||||
n.AdjustTo(2)
|
||||
assert.NotEqual(t, c5, n.BestClaim())
|
||||
|
||||
n.AdjustTo(1)
|
||||
assert.Equal(t, c5, n.BestClaim())
|
||||
n.AdjustTo(0)
|
||||
assert.Nil(t, n.BestClaim())
|
||||
}
|
||||
|
||||
// check claim with more support wins
|
||||
test8 := func() {
|
||||
c1, _ = n.AddClaim(*newOutPoint(1), 2)
|
||||
c2, _ = n.AddClaim(*newOutPoint(2), 1)
|
||||
s1, _ = n.AddSupport(*newOutPoint(11), 1, c1.ID)
|
||||
s2, _ = n.AddSupport(*newOutPoint(12), 10, c2.ID)
|
||||
n.AdjustTo(1)
|
||||
assert.Equal(t, c2, n.BestClaim())
|
||||
assert.Equal(t, claim.Amount(11), n.BestClaim().EffAmt)
|
||||
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.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.AdjustTo(20)
|
||||
assert.Equal(t, c4, n.BestClaim())
|
||||
assert.Equal(t, claim.Amount(2), n.BestClaim().EffAmt)
|
||||
n.AdjustTo(21)
|
||||
assert.Equal(t, c3, n.BestClaim())
|
||||
assert.Equal(t, claim.Amount(11), n.BestClaim().EffAmt)
|
||||
|
||||
n.AdjustTo(20)
|
||||
assert.Equal(t, c4, n.BestClaim())
|
||||
assert.Equal(t, claim.Amount(2), n.BestClaim().EffAmt)
|
||||
n.AdjustTo(10)
|
||||
assert.Equal(t, c4, n.BestClaim())
|
||||
assert.Equal(t, claim.Amount(2), n.BestClaim().EffAmt)
|
||||
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.AdjustTo(1)
|
||||
s1, _ = n.AddSupport(*newOutPoint(11), 1, c1.ID)
|
||||
n.RemoveClaim(c1.OutPoint)
|
||||
n.AdjustTo(2)
|
||||
assert.NotEqual(t, c1, n.BestClaim())
|
||||
|
||||
n.AdjustTo(1)
|
||||
assert.Equal(t, c1, n.BestClaim())
|
||||
n.AdjustTo(0)
|
||||
assert.Nil(t, n.BestClaim())
|
||||
}
|
||||
|
||||
// support on abandon2
|
||||
test11 := func() {
|
||||
c1, _ = n.AddClaim(*newOutPoint(1), 2)
|
||||
s1, _ = n.AddSupport(*newOutPoint(11), 1, c1.ID)
|
||||
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.AdjustTo(2)
|
||||
assert.Nil(t, n.BestClaim())
|
||||
|
||||
n.AdjustTo(1)
|
||||
assert.Equal(t, c1, n.BestClaim())
|
||||
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,
|
||||
test3,
|
||||
test4,
|
||||
test5,
|
||||
test6,
|
||||
test7,
|
||||
test8,
|
||||
test9,
|
||||
test10,
|
||||
test11,
|
||||
test12,
|
||||
}
|
||||
for _, tt := range tests {
|
||||
tt()
|
||||
}
|
||||
_ = []func(){test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test12}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
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))
|
||||
}
|
29
claimtrie.go
29
claimtrie.go
|
@ -5,7 +5,6 @@ import (
|
|||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
"github.com/lbryio/claimtrie/claim"
|
||||
"github.com/lbryio/claimtrie/claimnode"
|
||||
|
||||
"github.com/lbryio/claimtrie/trie"
|
||||
)
|
||||
|
@ -61,25 +60,23 @@ func (ct *ClaimTrie) Head() *trie.Commit {
|
|||
|
||||
// AddClaim adds a Claim to the Stage of ClaimTrie.
|
||||
func (ct *ClaimTrie) AddClaim(name string, op wire.OutPoint, amt claim.Amount) error {
|
||||
modifier := func(n *claimnode.Node) error {
|
||||
_, err := n.AddClaim(op, amt)
|
||||
return err
|
||||
modifier := func(n *claim.Node) error {
|
||||
return n.AddClaim(claim.New(op, amt))
|
||||
}
|
||||
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 {
|
||||
modifier := func(n *claimnode.Node) error {
|
||||
_, err := n.AddSupport(op, amt, supported)
|
||||
return err
|
||||
modifier := func(n *claim.Node) error {
|
||||
return n.AddSupport(claim.NewSupport(op, amt, supported))
|
||||
}
|
||||
return updateNode(ct, ct.height, name, modifier)
|
||||
}
|
||||
|
||||
// SpendClaim removes a Claim in the Stage.
|
||||
func (ct *ClaimTrie) SpendClaim(name string, op wire.OutPoint) error {
|
||||
modifier := func(n *claimnode.Node) error {
|
||||
modifier := func(n *claim.Node) error {
|
||||
return n.RemoveClaim(op)
|
||||
}
|
||||
return updateNode(ct, ct.height, name, modifier)
|
||||
|
@ -87,7 +84,7 @@ func (ct *ClaimTrie) SpendClaim(name string, op wire.OutPoint) error {
|
|||
|
||||
// SpendSupport removes a Support in the Stage.
|
||||
func (ct *ClaimTrie) SpendSupport(name string, op wire.OutPoint) error {
|
||||
modifier := func(n *claimnode.Node) error {
|
||||
modifier := func(n *claim.Node) error {
|
||||
return n.RemoveSupport(op)
|
||||
}
|
||||
return updateNode(ct, ct.height, name, modifier)
|
||||
|
@ -123,7 +120,7 @@ func (ct *ClaimTrie) Commit(h claim.Height) error {
|
|||
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 }
|
||||
modifier := func(n *claim.Node) error { return nil }
|
||||
if err := updateNode(ct, i, name, modifier); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -165,13 +162,13 @@ func (ct *ClaimTrie) Reset(h claim.Height) error {
|
|||
|
||||
// Drop (rollback) any uncommited change, and adjust to the specified height.
|
||||
rollback := func(prefix trie.Key, value trie.Value) error {
|
||||
n := value.(*claimnode.Node)
|
||||
n.Reset()
|
||||
n := value.(*claim.Node)
|
||||
n.RollbackExecuted()
|
||||
return n.AdjustTo(h)
|
||||
}
|
||||
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.
|
||||
// It's a programming error that can't be recovered.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
|
@ -190,17 +187,17 @@ func (ct *ClaimTrie) Reset(h claim.Height) error {
|
|||
// 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 {
|
||||
func updateNode(ct *ClaimTrie, h claim.Height, name string, modifier func(n *claim.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()
|
||||
v = claim.NewNode()
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n := v.(*claimnode.Node)
|
||||
n := v.(*claim.Node)
|
||||
|
||||
// Bring the node state up to date.
|
||||
if err = n.AdjustTo(h); err != nil {
|
||||
|
|
|
@ -193,6 +193,100 @@ Hello : {
|
|||
claimtrie >
|
||||
```
|
||||
|
||||
The following are the corresponding commands in the CLI to reproduce the test.
|
||||
Note that when a Toakeover happens, the {TakeoverHeight, BestClaim(OutPoint:Indx)} is pushed to the BestClaims stack.
|
||||
This provides sufficient info to update and backtracking the BestClaim at any height.
|
||||
|
||||
```block
|
||||
claimtrie > c -ht 12
|
||||
claimtrie > ac -a 10
|
||||
claimtrie > c -ht 13
|
||||
claimtrie > s
|
||||
|
||||
<ClaimTrie Height 13>
|
||||
Hello : Height 13, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
|
||||
C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 10 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
|
||||
claimtrie > c -ht 1000
|
||||
claimtrie > ac -a 20
|
||||
claimtrie > c -ht 1001
|
||||
claimtrie > s
|
||||
|
||||
<ClaimTrie Height 1001>
|
||||
Hello : Height 1000, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
|
||||
C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 10 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 0 accepted: 1001 active: 1031 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
|
||||
claimtrie > c -ht 1009
|
||||
claimtrie > as -a 14 -id ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
claimtrie > c -ht 1010
|
||||
claimtrie > s
|
||||
|
||||
<ClaimTrie Height 1010>
|
||||
Hello : Height 1010, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
|
||||
C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 24 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 0 accepted: 1001 active: 1031 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
|
||||
S-087acb4f22ab6eb2e6c827624deab7beb02c190056376e0b3a3c3546b79bf216:22 amt: 14 accepted: 1010 active: 1010 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
|
||||
claimtrie > c -ht 1019
|
||||
claimtrie > ac -a 50
|
||||
claimtrie > c -ht 1020
|
||||
claimtrie > s
|
||||
|
||||
<ClaimTrie Height 1020>
|
||||
Hello : Height 1019, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
|
||||
C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 24 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 0 accepted: 1001 active: 1031 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
C-864dc683ed1fbe3c072c2387bca7d74e60c2505f1987054fd70955a2e1c9490b:11 amt: 50 effamt: 0 accepted: 1020 active: 1051 id: 0f2ba15a5c66b978df4a27f52525680a6009185e
|
||||
|
||||
S-087acb4f22ab6eb2e6c827624deab7beb02c190056376e0b3a3c3546b79bf216:22 amt: 14 accepted: 1010 active: 1010 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
|
||||
claimtrie > c -ht 1031
|
||||
claimtrie > s
|
||||
|
||||
<ClaimTrie Height 1031>
|
||||
Hello : Height 1031, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
|
||||
C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 24 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 20 accepted: 1001 active: 1031 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
C-864dc683ed1fbe3c072c2387bca7d74e60c2505f1987054fd70955a2e1c9490b:11 amt: 50 effamt: 0 accepted: 1020 active: 1051 id: 0f2ba15a5c66b978df4a27f52525680a6009185e
|
||||
|
||||
S-087acb4f22ab6eb2e6c827624deab7beb02c190056376e0b3a3c3546b79bf216:22 amt: 14 accepted: 1010 active: 1010 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
|
||||
claimtrie > c -ht 1039
|
||||
claimtrie > ac -a 300
|
||||
claimtrie > c -ht 1040
|
||||
claimtrie > s
|
||||
|
||||
<ClaimTrie Height 1040>
|
||||
Hello : Height 1039, 6f5970c9c13f00c77054d98e5b2c50b1a1bb723d91676cc03f984fac763ec6c3 BestClaims: {13, 31},
|
||||
|
||||
C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 24 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb <B>
|
||||
C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 20 accepted: 1001 active: 1031 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
C-864dc683ed1fbe3c072c2387bca7d74e60c2505f1987054fd70955a2e1c9490b:11 amt: 50 effamt: 0 accepted: 1020 active: 1051 id: 0f2ba15a5c66b978df4a27f52525680a6009185e
|
||||
C-26292b27122d04d08fee4e4cc5a5f94681832204cc29d61039c09af9a5298d16:22 amt: 300 effamt: 0 accepted: 1040 active: 1072 id: 270496c0710e525156510e60e4be2ffa6fe2f507
|
||||
|
||||
S-087acb4f22ab6eb2e6c827624deab7beb02c190056376e0b3a3c3546b79bf216:22 amt: 14 accepted: 1010 active: 1010 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
|
||||
claimtrie > c -ht 1051
|
||||
claimtrie > s
|
||||
|
||||
<ClaimTrie Height 1051>
|
||||
Hello : Height 1051, 68dff86c9450e3cf96570f31b6ad8f8d35ae0cbce6cdcb3761910e25a815ee8b BestClaims: {13, 31}, {1051, 22},
|
||||
|
||||
C-2f9b2ca28b30c97122de584e8d784e784bc7bdfb43b3b4bf9de69fc31196571f:31 amt: 10 effamt: 24 accepted: 13 active: 13 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
C-74fd7c15b445e7ac2de49a8058ad7dbb070ef31838345feff168dd40c2ef422e:46 amt: 20 effamt: 20 accepted: 1001 active: 1001 id: 2edc6338e9f3654f8b2b878817e029a2b2ecfa9e
|
||||
C-864dc683ed1fbe3c072c2387bca7d74e60c2505f1987054fd70955a2e1c9490b:11 amt: 50 effamt: 50 accepted: 1020 active: 1020 id: 0f2ba15a5c66b978df4a27f52525680a6009185e
|
||||
C-26292b27122d04d08fee4e4cc5a5f94681832204cc29d61039c09af9a5298d16:22 amt: 300 effamt: 300 accepted: 1040 active: 1040 id: 270496c0710e525156510e60e4be2ffa6fe2f507 <B>
|
||||
|
||||
S-087acb4f22ab6eb2e6c827624deab7beb02c190056376e0b3a3c3546b79bf216:22 amt: 14 accepted: 1010 active: 1010 id: ae8b3adc8c8b378c76eae12edf3878357b31c0eb
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
coming soon
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"bufio"
|
||||
"crypto/rand"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -14,6 +15,7 @@ import (
|
|||
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/wire"
|
||||
|
||||
"github.com/urfave/cli"
|
||||
|
||||
"github.com/lbryio/claimtrie"
|
||||
|
@ -32,7 +34,9 @@ var (
|
|||
)
|
||||
|
||||
var (
|
||||
errNotImplemented = fmt.Errorf("not implemented")
|
||||
errNotImplemented = errors.New("not implemented")
|
||||
errInvalidHeight = errors.New("invalid height")
|
||||
errCommitNotFound = errors.New("commit not found")
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -117,6 +121,14 @@ func main() {
|
|||
}
|
||||
}
|
||||
|
||||
func randInt(min, max int64) int64 {
|
||||
i, err := rand.Int(rand.Reader, big.NewInt(100))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return min + i.Int64()
|
||||
}
|
||||
|
||||
var ct = claimtrie.New()
|
||||
|
||||
// newOutPoint generates random OutPoint for the ease of testing.
|
||||
|
@ -143,80 +155,79 @@ func newOutPoint(s string) (*wire.OutPoint, error) {
|
|||
return wire.NewOutPoint(h, uint32(idx)), nil
|
||||
}
|
||||
|
||||
func cmdAddClaim(c *cli.Context) error {
|
||||
amount := claim.Amount(c.Int64("amount"))
|
||||
if !c.IsSet("amount") {
|
||||
i, err := rand.Int(rand.Reader, big.NewInt(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount = 1 + claim.Amount(i.Int64())
|
||||
}
|
||||
|
||||
// height := claim.Height(c.Int64("height"))
|
||||
// if !c.IsSet("height") {
|
||||
// height = ct.Height()
|
||||
// }
|
||||
|
||||
outPoint, err := newOutPoint(c.String("outpoint"))
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
return ct.AddClaim(c.String("name"), *outPoint, amount)
|
||||
type args struct {
|
||||
*cli.Context
|
||||
err error
|
||||
}
|
||||
|
||||
func cmdAddSupport(c *cli.Context) error {
|
||||
amount := claim.Amount(c.Int64("amount"))
|
||||
if !c.IsSet("amount") {
|
||||
i, err := rand.Int(rand.Reader, big.NewInt(100))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
amount = 1 + claim.Amount(i.Int64())
|
||||
func (a *args) amount() claim.Amount {
|
||||
if a.err != nil {
|
||||
return 0
|
||||
}
|
||||
|
||||
// height := claim.Height(c.Int64("height"))
|
||||
// if !c.IsSet("height") {
|
||||
// height = ct.Height()
|
||||
// }
|
||||
|
||||
outPoint, err := newOutPoint(c.String("outpoint"))
|
||||
if err != nil {
|
||||
return err
|
||||
amt := a.Int64("amount")
|
||||
if !a.IsSet("amount") {
|
||||
amt = randInt(1, 500)
|
||||
}
|
||||
|
||||
if !c.IsSet("id") {
|
||||
return fmt.Errorf("flag -id is required")
|
||||
}
|
||||
cid, err := claim.NewIDFromString(c.String("id"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ct.AddSupport(c.String("name"), *outPoint, amount, cid)
|
||||
return claim.Amount(amt)
|
||||
}
|
||||
|
||||
func cmdSpendClaim(c *cli.Context) error {
|
||||
outPoint, err := newOutPoint(c.String("outpoint"))
|
||||
if err != nil {
|
||||
return err
|
||||
func (a *args) outPoint() wire.OutPoint {
|
||||
if a.err != nil {
|
||||
return wire.OutPoint{}
|
||||
}
|
||||
return ct.SpendClaim(c.String("name"), *outPoint)
|
||||
}
|
||||
func cmdSpendSupport(c *cli.Context) error {
|
||||
outPoint, err := newOutPoint(c.String("outpoint"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ct.SpendSupport(c.String("name"), *outPoint)
|
||||
op, err := newOutPoint(a.String("outpoint"))
|
||||
a.err = err
|
||||
|
||||
return *op
|
||||
}
|
||||
|
||||
func cmdShow(c *cli.Context) error {
|
||||
dump := func(prefix trie.Key, val trie.Value) error {
|
||||
func (a *args) name() (name string) {
|
||||
if a.err != nil {
|
||||
return
|
||||
}
|
||||
return a.String("name")
|
||||
}
|
||||
|
||||
func (a *args) id() (id claim.ID) {
|
||||
if a.err != nil {
|
||||
return
|
||||
}
|
||||
if !a.IsSet("id") {
|
||||
a.err = fmt.Errorf("flag -id is required")
|
||||
return
|
||||
}
|
||||
id, a.err = claim.NewIDFromString(a.String("id"))
|
||||
return
|
||||
}
|
||||
|
||||
func (a *args) height() (h claim.Height, ok bool) {
|
||||
if a.err != nil {
|
||||
return 0, false
|
||||
}
|
||||
return claim.Height(a.Int64("height")), a.IsSet("height")
|
||||
}
|
||||
|
||||
func (a *args) json() bool {
|
||||
if a.err != nil {
|
||||
return false
|
||||
}
|
||||
return a.IsSet("json")
|
||||
}
|
||||
|
||||
func (a *args) all() bool {
|
||||
if a.err != nil {
|
||||
return false
|
||||
}
|
||||
return a.Bool("all")
|
||||
}
|
||||
|
||||
var showNode = func(showJSON bool) trie.Visit {
|
||||
return func(prefix trie.Key, val trie.Value) error {
|
||||
if val == nil {
|
||||
fmt.Printf("%-8s:\n", prefix)
|
||||
return nil
|
||||
}
|
||||
if !c.IsSet("json") {
|
||||
if !showJSON {
|
||||
fmt.Printf("%-8s: %v\n", prefix, val)
|
||||
return nil
|
||||
}
|
||||
|
@ -227,22 +238,94 @@ func cmdShow(c *cli.Context) error {
|
|||
fmt.Printf("%-8s: %s\n", prefix, b)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
if !c.IsSet("height") {
|
||||
fmt.Printf("<ClaimTrie Height %d>\n", ct.Height())
|
||||
return ct.Traverse(dump, false, !c.Bool("all"))
|
||||
var recall = func(h claim.Height, visit trie.Visit) trie.Visit {
|
||||
return func(prefix trie.Key, val trie.Value) (err error) {
|
||||
n := val.(*claim.Node)
|
||||
for err == nil && n.Height() > h {
|
||||
err = n.Decrement()
|
||||
}
|
||||
if err == nil {
|
||||
err = visit(prefix, val)
|
||||
}
|
||||
for err == nil && n.Height() < ct.Height() {
|
||||
err = n.Redo()
|
||||
}
|
||||
return err
|
||||
}
|
||||
height := claim.Height(c.Int64("height"))
|
||||
fmt.Printf("NOTE: peeking to the past is broken for now. Try RESET command instead\n")
|
||||
}
|
||||
|
||||
func cmdAddClaim(c *cli.Context) error {
|
||||
a := args{Context: c}
|
||||
amt := a.amount()
|
||||
op := a.outPoint()
|
||||
name := a.name()
|
||||
if a.err != nil {
|
||||
return a.err
|
||||
}
|
||||
return ct.AddClaim(name, op, amt)
|
||||
}
|
||||
|
||||
func cmdAddSupport(c *cli.Context) error {
|
||||
a := args{Context: c}
|
||||
name := a.name()
|
||||
amt := a.amount()
|
||||
op := a.outPoint()
|
||||
id := a.id()
|
||||
if a.err != nil {
|
||||
return a.err
|
||||
}
|
||||
return ct.AddSupport(name, op, amt, id)
|
||||
}
|
||||
|
||||
func cmdSpendClaim(c *cli.Context) error {
|
||||
a := args{Context: c}
|
||||
name := a.name()
|
||||
op := a.outPoint()
|
||||
if a.err != nil {
|
||||
return a.err
|
||||
}
|
||||
return ct.SpendClaim(name, op)
|
||||
}
|
||||
|
||||
func cmdSpendSupport(c *cli.Context) error {
|
||||
a := args{Context: c}
|
||||
name := a.name()
|
||||
op := a.outPoint()
|
||||
if a.err != nil {
|
||||
return a.err
|
||||
}
|
||||
return ct.SpendSupport(name, op)
|
||||
}
|
||||
|
||||
func cmdShow(c *cli.Context) error {
|
||||
a := args{Context: c}
|
||||
h, setHeight := a.height()
|
||||
setJSON := a.json()
|
||||
setAll := a.all()
|
||||
if a.err != nil {
|
||||
return a.err
|
||||
}
|
||||
if h > ct.Height() {
|
||||
return errInvalidHeight
|
||||
}
|
||||
visit := showNode(setJSON)
|
||||
if !setHeight {
|
||||
fmt.Printf("\n<ClaimTrie Height %d (Stage) >\n\n", ct.Height())
|
||||
return ct.Traverse(visit, false, !setAll)
|
||||
}
|
||||
|
||||
visit = recall(h, visit)
|
||||
for commit := ct.Head(); commit != nil; commit = commit.Prev {
|
||||
meta := commit.Meta.(claimtrie.CommitMeta)
|
||||
fmt.Printf("HEAD: %d/%d\n", height, meta.Height)
|
||||
if height == meta.Height {
|
||||
return commit.MerkleTrie.Traverse(dump, false, !c.Bool("all"))
|
||||
if h == meta.Height {
|
||||
fmt.Printf("\n<ClaimTrie Height %d>\n\n", h)
|
||||
return commit.MerkleTrie.Traverse(visit, false, !setAll)
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("commit not found")
|
||||
return errCommitNotFound
|
||||
}
|
||||
|
||||
func cmdMerkle(c *cli.Context) error {
|
||||
|
@ -251,16 +334,16 @@ func cmdMerkle(c *cli.Context) error {
|
|||
}
|
||||
|
||||
func cmdCommit(c *cli.Context) error {
|
||||
height := claim.Height(c.Int64("height"))
|
||||
h := claim.Height(c.Int64("height"))
|
||||
if !c.IsSet("height") {
|
||||
height = ct.Height() + 1
|
||||
h = ct.Height() + 1
|
||||
}
|
||||
return ct.Commit(height)
|
||||
return ct.Commit(h)
|
||||
}
|
||||
|
||||
func cmdReset(c *cli.Context) error {
|
||||
height := claim.Height(c.Int64("height"))
|
||||
return ct.Reset(height)
|
||||
h := claim.Height(c.Int64("height"))
|
||||
return ct.Reset(h)
|
||||
}
|
||||
|
||||
func cmdLog(c *cli.Context) error {
|
||||
|
|
|
@ -1,46 +1,109 @@
|
|||
package memento
|
||||
|
||||
// Command ...
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrCommandStackEmpty is returned when the command stack is empty.
|
||||
ErrCommandStackEmpty = errors.New("command stack empty")
|
||||
)
|
||||
|
||||
// Command specifies the interface of a command for the Memento.
|
||||
type Command interface {
|
||||
Execute()
|
||||
Undo()
|
||||
}
|
||||
|
||||
type commands []Command
|
||||
// CommandList is a list of command.
|
||||
type CommandList []Command
|
||||
|
||||
// Memento ...
|
||||
func (l CommandList) empty() bool { return len(l) == 0 }
|
||||
func (l CommandList) top() Command { return l[len(l)-1] }
|
||||
func (l CommandList) pop() CommandList { return l[:len(l)-1] }
|
||||
func (l CommandList) push(c Command) CommandList { return append(l, c) }
|
||||
|
||||
// CommandStack is stack of command list.
|
||||
type CommandStack []CommandList
|
||||
|
||||
func (s CommandStack) empty() bool { return len(s) == 0 }
|
||||
func (s CommandStack) top() CommandList { return s[len(s)-1] }
|
||||
func (s CommandStack) pop() CommandStack { return s[:len(s)-1] }
|
||||
func (s CommandStack) push(l CommandList) CommandStack { return append(s, l) }
|
||||
|
||||
// Memento implements functionality for state to be undo and redo.
|
||||
type Memento struct {
|
||||
stack []commands
|
||||
cmds commands
|
||||
// Executed but not yet commited command list.
|
||||
executed CommandList
|
||||
|
||||
// A stack of command list that have been commited.
|
||||
commited CommandStack
|
||||
|
||||
// A stack of commited command list that have been undone.
|
||||
undone CommandStack
|
||||
}
|
||||
|
||||
// Execute ...
|
||||
func (m *Memento) Execute(cmd Command) {
|
||||
m.cmds = append(m.cmds, cmd)
|
||||
cmd.Execute()
|
||||
// Executed returns a list of executed command that have not been commited.
|
||||
func (m *Memento) Executed() CommandList { return m.executed }
|
||||
|
||||
// Commited returns the stack of command liist that have been commited.
|
||||
func (m *Memento) Commited() CommandStack { return m.commited }
|
||||
|
||||
// Undone returns the stack of command list that have been undone.
|
||||
func (m *Memento) Undone() CommandStack { return m.undone }
|
||||
|
||||
// Execute executes a command and appends it to the Executed command list.
|
||||
// Any command list on the Undone will discarded, and can no longer be redone.
|
||||
func (m *Memento) Execute(c Command) error {
|
||||
m.executed = m.executed.push(c)
|
||||
c.Execute()
|
||||
m.undone = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// Commit ...
|
||||
// Commit commits the Executed command list to the Commited Stack, and empty the Executed List.
|
||||
func (m *Memento) Commit() {
|
||||
m.stack = append(m.stack, m.cmds)
|
||||
m.cmds = nil
|
||||
m.commited = m.commited.push(m.executed)
|
||||
m.executed = nil
|
||||
m.undone = nil
|
||||
}
|
||||
|
||||
// Rollback ...
|
||||
func (m *Memento) Rollback() {
|
||||
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()
|
||||
// Undo undos the most recent command list on the Commited stack, and moves it to the Undone Stack.
|
||||
func (m *Memento) Undo() error {
|
||||
if m.commited.empty() {
|
||||
return ErrCommandStackEmpty
|
||||
}
|
||||
m.commited, m.undone = process(m.commited, m.undone, true)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RollbackUncommited ...
|
||||
func (m *Memento) RollbackUncommited() {
|
||||
for i := len(m.cmds) - 1; i >= 0; i-- {
|
||||
m.cmds[i].Undo()
|
||||
// Redo redos the most recent command list on the Undone Stack, and moves it back to the Commited Stack.
|
||||
func (m *Memento) Redo() error {
|
||||
if m.undone.empty() {
|
||||
return ErrCommandStackEmpty
|
||||
}
|
||||
m.cmds = nil
|
||||
m.undone, m.commited = process(m.undone, m.commited, false)
|
||||
return nil
|
||||
}
|
||||
|
||||
// RollbackExecuted undos commands on the Executed list, and empty the list.
|
||||
func (m *Memento) RollbackExecuted() {
|
||||
for !m.executed.empty() {
|
||||
m.executed.top().Undo()
|
||||
m.executed = m.executed.pop()
|
||||
}
|
||||
m.executed = nil
|
||||
}
|
||||
|
||||
func process(a, b CommandStack, undo bool) (CommandStack, CommandStack) {
|
||||
processed := CommandList{}
|
||||
for cmds := a.top(); !cmds.empty(); cmds = cmds.pop() {
|
||||
if undo {
|
||||
cmds.top().Undo()
|
||||
} else {
|
||||
cmds.top().Execute()
|
||||
}
|
||||
processed = processed.push(cmds.top())
|
||||
}
|
||||
return a.pop(), b.push(processed)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue