misc changes

This commit is contained in:
Tzu-Jung Lee 2018-07-13 22:10:23 -07:00
parent a5627ecfcc
commit 374a75fea6
18 changed files with 1514 additions and 1076 deletions

65
claim/builder.go Normal file
View 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
}

View file

@ -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
}

View file

@ -1,4 +1,4 @@
package claimnode
package claim
import "fmt"

28
claim/hash.go Normal file
View 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
View 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
View 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
View 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
View 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. Its activation height is 1001+min(4032,floor(10011332))=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(10201332))=1020+31=1051
at(1040).AddClaim(cD.SetAmt(300)) // Claim D for 300LBC is accepted. The activation height is 1040+min(4032,floor(10401332))=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(10511051/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
View 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
View 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,
})
}

View file

@ -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)
}
}

View file

@ -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
}

View file

@ -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. Its activation height is 1001+min(4032,floor(10011332))=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(10201332))=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(10401332))=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(10511051/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}
}

View file

@ -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))
}

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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)
}