fix Bidding logics, and port some tests cases from lbrycrd

This commit is contained in:
Tzu-Jung Lee 2018-07-05 13:17:40 -07:00
parent 84c64c1018
commit fa2e276f3d
10 changed files with 671 additions and 266 deletions

View file

@ -72,4 +72,4 @@ Our PGP key is [here](https://keybase.io/lbry/key.asc) if you need it.
## Contact
The primary contact for this project is [@roylee17](https://github.com/roylee) (roylee@lbry.io)
The primary contact for this project is [@roylee17](https://github.com/roylee17) (roylee@lbry.io)

View file

@ -1,23 +1,17 @@
package claimtrie
import (
"bytes"
"encoding/json"
"fmt"
"github.com/btcsuite/btcd/wire"
)
// newClaim ...
func newClaim(op wire.OutPoint, amt Amount, accepted Height) *claim {
return &claim{
op: op,
id: NewClaimID(op),
amt: amt,
accepted: accepted,
}
}
var dbg bool
type claim struct {
// Claim ...
type Claim struct {
op wire.OutPoint
id ClaimID
amt Amount
@ -26,8 +20,31 @@ type claim struct {
activeAt Height
}
func (c *Claim) String() string {
if dbg {
w := bytes.NewBuffer(nil)
fmt.Fprintf(w, "C%-3d amt: %2d, effamt: %v, accepted: %2d, active: %2d, id: %s", c.op.Index, c.amt, c.effAmt, c.accepted, c.activeAt, c.id)
return w.String()
}
b, err := json.MarshalIndent(c, "", " ")
if err != nil {
fmt.Printf("can't marshal, err :%s", err)
}
return string(b)
}
// Support ...
type Support struct {
op wire.OutPoint
amt Amount
accepted Height
activeAt Height
supportedID ClaimID
}
// MarshalJSON customizes the representation of JSON.
func (c *claim) MarshalJSON() ([]byte, error) {
func (c *Claim) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
OutPoint string
ClaimID string
@ -45,35 +62,8 @@ func (c *claim) MarshalJSON() ([]byte, error) {
})
}
func (c *claim) String() string {
b, err := json.MarshalIndent(c, "", " ")
if err != nil {
fmt.Printf("can't marshal, err :%s", err)
}
return string(b)
}
func newSupport(op wire.OutPoint, amt Amount, accepted Height, supported ClaimID) *support {
return &support{
op: op,
amt: amt,
accepted: accepted,
supportedID: supported,
}
}
type support struct {
op wire.OutPoint
amt Amount
accepted Height
activeAt Height
supportedID ClaimID
supportedClaim *claim
}
// MarshalJSON ...
func (s *support) MarshalJSON() ([]byte, error) {
func (s *Support) MarshalJSON() ([]byte, error) {
return json.Marshal(&struct {
OutPoint string
SupportedClaimID string
@ -89,7 +79,12 @@ func (s *support) MarshalJSON() ([]byte, error) {
})
}
func (s *support) String() string {
func (s *Support) String() string {
if dbg {
w := bytes.NewBuffer(nil)
fmt.Fprintf(w, "S%-3d amt: %2d, accepted: %2d, active: %2d, id: %s", s.op.Index, s.amt, s.accepted, s.activeAt, s.supportedID)
return w.String()
}
b, err := json.MarshalIndent(s, "", " ")
if err != nil {
fmt.Printf("can't marshal, err :%s", err)

View file

@ -34,12 +34,3 @@ type ClaimID [20]byte
func (id ClaimID) String() string {
return hex.EncodeToString(id[:])
}
func calActiveHeight(accepted, curr, tookover Height) Height {
factor := Height(32)
delay := (curr - tookover) / factor
if delay > 4032 {
delay = 4032
}
return accepted + delay
}

View file

@ -1,18 +1,14 @@
package claimtrie
import (
"fmt"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lbryio/merkletrie"
)
// Height ...
type Height int64
// Amount ...
type Amount int64
// ClaimTrie implements a Merkle Trie supporting linear history of commits.
type ClaimTrie struct {
@ -24,6 +20,9 @@ type ClaimTrie struct {
// An overlay supporting Copy-on-Write to the current tip commit.
stg *merkletrie.Stage
// pending keeps track update for future block height.
pending map[Height][]string
}
// CommitMeta implements merkletrie.CommitMeta with commit-specific metadata.
@ -35,21 +34,22 @@ type CommitMeta struct {
func New() *ClaimTrie {
mt := merkletrie.New()
return &ClaimTrie{
head: merkletrie.NewCommit(nil, CommitMeta{0}, mt),
stg: merkletrie.NewStage(mt),
head: merkletrie.NewCommit(nil, CommitMeta{0}, mt),
stg: merkletrie.NewStage(mt),
pending: map[Height][]string{},
}
}
func updateStageNode(stg *merkletrie.Stage, name string, modifier func(n *node) error) error {
func updateStageNode(stg *merkletrie.Stage, name string, modifier func(n *Node) error) error {
v, err := stg.Get(merkletrie.Key(name))
if err != nil && err != merkletrie.ErrKeyNotFound {
return err
}
var n *node
var n *Node
if v == nil {
n = newNode()
n = NewNode()
} else {
n = v.(*node).clone()
n = v.(*Node)
}
if err = modifier(n); err != nil {
return err
@ -59,51 +59,50 @@ func updateStageNode(stg *merkletrie.Stage, name string, modifier func(n *node)
// AddClaim adds a Claim to the Stage of ClaimTrie.
func (ct *ClaimTrie) AddClaim(name string, op wire.OutPoint, amt Amount, accepted Height) error {
return updateStageNode(ct.stg, name, func(n *node) error {
return n.addClaim(newClaim(op, amt, accepted))
return updateStageNode(ct.stg, name, func(n *Node) error {
n.IncrementBlock(ct.bestBlock - n.height)
_, err := n.addClaim(op, amt)
next := ct.bestBlock + 1
ct.pending[next] = append(ct.pending[next], name)
return err
})
}
// AddSupport adds a Support to the Stage of ClaimTrie.
func (ct *ClaimTrie) AddSupport(name string, op wire.OutPoint, amt Amount, accepted Height, supported ClaimID) error {
return updateStageNode(ct.stg, name, func(n *node) error {
return n.addSupport(newSupport(op, amt, accepted, supported))
return updateStageNode(ct.stg, name, func(n *Node) error {
_, err := n.addSupport(op, amt, supported)
next := ct.bestBlock + 1
ct.pending[next] = append(ct.pending[next], name)
return err
})
}
// SpendClaim removes a Claim in the Stage.
func (ct *ClaimTrie) SpendClaim(name string, op wire.OutPoint) error {
return updateStageNode(ct.stg, name, func(n *node) error {
return updateStageNode(ct.stg, name, func(n *Node) error {
next := ct.bestBlock + 1
ct.pending[next] = append(ct.pending[next], name)
return n.removeClaim(op)
})
}
// SpendSupport removes a Support in the Stage.
func (ct *ClaimTrie) SpendSupport(name string, op wire.OutPoint) error {
return updateStageNode(ct.stg, name, func(n *node) error {
return updateStageNode(ct.stg, name, func(n *Node) error {
next := ct.bestBlock + 1
ct.pending[next] = append(ct.pending[next], name)
return n.removeSupport(op)
})
}
// Traverse visits Nodes in the Stage of the ClaimTrie.
func (ct *ClaimTrie) Traverse(visit merkletrie.Visit, update, valueOnly bool) error {
// wrapper function to make sure the node is updated before it's observed externally.
fn := func(prefix merkletrie.Key, v merkletrie.Value) error {
if v != nil {
v.(*node).updateBestClaim(ct.bestBlock)
}
return visit(prefix, v)
}
return ct.stg.Traverse(fn, update, valueOnly)
return ct.stg.Traverse(visit, update, valueOnly)
}
// MerkleHash returns the Merkle Hash of the Stage.
func (ct *ClaimTrie) MerkleHash() chainhash.Hash {
visit := func(prefix merkletrie.Key, v merkletrie.Value) error {
v.(*node).updateBestClaim(ct.bestBlock)
return nil
}
ct.Traverse(visit, true, true)
return ct.stg.MerkleHash()
}
@ -118,12 +117,30 @@ func (ct *ClaimTrie) Commit(h Height) error {
if h <= ct.bestBlock {
return ErrInvalidHeight
}
visit := func(prefix merkletrie.Key, v merkletrie.Value) error {
v.(*node).updateBestClaim(h)
return nil
}
ct.Traverse(visit, true, true)
for i := ct.bestBlock + 1; i <= h; i++ {
for _, prefix := range ct.pending[i] {
// Brings the value node to date.
catchup := func(n *Node) error {
if err := n.IncrementBlock(i - n.height); err != nil {
return err
}
// After the update, the node may subscribe to another pending update.
if next := n.findNextUpdateHeights(); next > i {
fmt.Printf("Subscribe pendings for %v to future Height at %d\n", prefix, next)
ct.pending[next] = append(ct.pending[next], prefix)
}
return nil
}
// Update the node with the catchup modifier, and clear the Merkle Hash along the way.
if err := updateStageNode(ct.stg, prefix, catchup); err != nil {
return err
}
}
delete(ct.pending, i)
}
commit, err := ct.stg.Commit(ct.head, CommitMeta{Height: h})
if err != nil {
return err
@ -135,6 +152,13 @@ func (ct *ClaimTrie) Commit(h Height) error {
// Reset reverts the Stage to a specified commit by height.
func (ct *ClaimTrie) Reset(h Height) error {
visit := func(prefix merkletrie.Key, value merkletrie.Value) error {
n := value.(*Node)
return n.DecrementBlock(n.height - h)
}
if err := ct.stg.Traverse(visit, true, true); err != nil {
return err
}
for commit := ct.head; commit != nil; commit = commit.Prev {
meta := commit.Meta.(CommitMeta)
if meta.Height <= h {

View file

@ -1,12 +1,12 @@
package claimtrie
import (
"fmt"
"testing"
"github.com/btcsuite/btcd/chaincfg/chainhash"
)
// pending ...
func TestClaimTrie_Commit(t *testing.T) {
ct := New()
@ -33,7 +33,7 @@ func TestClaimTrie_Commit(t *testing.T) {
ct.AddClaim("HELLO", *newOutPoint(0), tt.amt, tt.curr)
}
ct.Commit(tt.curr)
fmt.Printf("ct.Merkle[%2d]: %s, amt: %d\n", ct.BestBlock(), ct.MerkleHash(), tt.amt)
// fmt.Printf("ct.Merkle[%2d]: %s, amt: %d\n", ct.BestBlock(), ct.MerkleHash(), tt.amt)
})
}
}

View file

@ -128,6 +128,9 @@ func newOutPoint(s string) (*wire.OutPoint, error) {
return wire.NewOutPoint(&h, uint32(h[0])), nil
}
fields := strings.Split(s, ":")
if len(fields) != 2 {
return nil, fmt.Errorf("invalid outpoint format (HASH:INDEX)")
}
h, err := chainhash.NewHashFromStr(fields[0])
if err != nil {
return nil, err
@ -217,11 +220,13 @@ func cmdShow(c *cli.Context) error {
}
height := claimtrie.Height(c.Int64("height"))
if !c.IsSet("height") {
fmt.Printf("<BestBlock: %d>\n", ct.BestBlock())
fmt.Printf("<ClaimTrie Height %d>\n", ct.BestBlock())
return ct.Traverse(dump, false, !c.Bool("all"))
}
fmt.Printf("NOTE: peeking to the past is broken for now. Try RESET command instead\n")
for commit := ct.Head(); commit != nil; commit = commit.Prev {
meta := commit.Meta.(claimtrie.CommitMeta)
fmt.Printf("HEAD: %d/%d\n", height, meta.Height)
if height == meta.Height {
return commit.MerkleTrie.Traverse(dump, false, !c.Bool("all"))
}
@ -251,7 +256,7 @@ func cmdReset(c *cli.Context) error {
func cmdLog(c *cli.Context) error {
commitVisit := func(c *merkletrie.Commit) {
meta := c.Meta.(claimtrie.CommitMeta)
fmt.Printf("height: %d, commit %s\n\n", meta.Height, c.MerkleTrie.MerkleHash())
fmt.Printf("height: %d, commit %s\n", meta.Height, c.MerkleTrie.MerkleHash())
}
fmt.Printf("\n")

72
memento.go Normal file
View file

@ -0,0 +1,72 @@
package claimtrie
type cmdAddClaim struct {
node *Node
claim *Claim
}
func (c cmdAddClaim) Execute() { c.node.claims[c.claim.op] = c.claim }
func (c cmdAddClaim) Undo() { delete(c.node.claims, c.claim.op) }
type cmdRemoveClaim struct {
node *Node
claim *Claim
}
func (c cmdRemoveClaim) Execute() { delete(c.node.claims, c.claim.op) }
func (c cmdRemoveClaim) Undo() { c.node.claims[c.claim.op] = c.claim }
type cmdAddSupport struct {
node *Node
support *Support
}
func (c cmdAddSupport) Execute() { c.node.supports[c.support.op] = c.support }
func (c cmdAddSupport) Undo() { delete(c.node.supports, c.support.op) }
type cmdRemoveSupport struct {
node *Node
support *Support
}
func (c cmdRemoveSupport) Execute() { delete(c.node.supports, c.support.op) }
func (c cmdRemoveSupport) Undo() { c.node.supports[c.support.op] = 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)
}
}

36
memento/memento.go Normal file
View file

@ -0,0 +1,36 @@
package memento
// Command ...
type Command interface {
Execute()
Undo()
}
type commands []Command
// Memento ...
type Memento struct {
stack []commands
cmds commands
}
// Execute ...
func (m *Memento) Execute(cmd Command) {
m.cmds = append(m.cmds, cmd)
cmd.Execute()
}
// Commit ...
func (m *Memento) Commit() {
m.stack = append(m.stack, m.cmds)
m.cmds = nil
}
// Rollback ...
func (m *Memento) Rollback() {
cmds := m.stack[len(m.stack)-1]
m.stack = m.stack[:len(m.stack)-1]
for i := len(cmds) - 1; i >= 0; i-- {
cmds[i].Undo()
}
}

357
node.go
View file

@ -1,112 +1,274 @@
package claimtrie
import (
"bytes"
"crypto/sha256"
"encoding/binary"
"encoding/json"
"fmt"
"math"
"strconv"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/lbryio/claimtrie/memento"
)
type node struct {
tookover Height
bestClaim *claim
// Amount ...
type Amount int64
claims map[string]*claim
supports map[string]*support
// Height ...
type Height int64
// Node ...
type Node struct {
memento.Memento
height Height
bestClaims map[Height]*Claim
claims map[wire.OutPoint]*Claim
supports map[wire.OutPoint]*Support
}
func newNode() *node {
return &node{
claims: map[string]*claim{},
supports: map[string]*support{},
// NewNode ...
func NewNode() *Node {
return &Node{
Memento: memento.Memento{},
bestClaims: map[Height]*Claim{0: nil},
claims: map[wire.OutPoint]*Claim{},
supports: map[wire.OutPoint]*Support{},
}
}
func (n *node) addClaim(c *claim) error {
if _, ok := n.claims[c.op.String()]; ok {
return ErrDuplicate
// BestClaim ...
func (n *Node) BestClaim() *Claim {
var latest Height
for k := range n.bestClaims {
if k > latest {
latest = k
}
}
return n.bestClaims[latest]
}
// Tookover ...
func (n *Node) Tookover() Height {
var latest Height
for k := range n.bestClaims {
if k > latest {
latest = k
}
}
return latest
}
// IncrementBlock ...
func (n *Node) IncrementBlock(h Height) error {
if h < 0 {
return ErrInvalidHeight
}
for i := Height(0); i < h; i++ {
n.height++
n.processBlock()
n.Commit()
}
c.activeAt = calActiveHeight(c.accepted, c.accepted, n.tookover)
n.claims[c.op.String()] = c
return nil
}
func (n *node) removeClaim(op wire.OutPoint) error {
c, ok := n.claims[op.String()]
// DecrementBlock ...
func (n *Node) DecrementBlock(h Height) error {
if h < 0 {
return ErrInvalidHeight
}
for i := Height(0); i < h; i++ {
n.height--
n.Rollback()
}
return nil
}
func (n *Node) addClaim(op wire.OutPoint, amt Amount) (*Claim, error) {
c := &Claim{
op: op,
id: NewClaimID(op),
amt: amt,
accepted: n.height + 1,
activeAt: n.height + 1,
}
if n.BestClaim() != nil {
c.activeAt = calActiveHeight(c.accepted, c.accepted, n.Tookover())
}
n.Execute(cmdAddClaim{node: n, claim: c})
return c, nil
}
func (n *Node) removeClaim(op wire.OutPoint) error {
c, ok := n.claims[op]
if !ok {
return ErrNotFound
}
delete(n.claims, op.String())
if n.bestClaim == c {
n.bestClaim = nil
}
for _, v := range n.supports {
if c.id == v.supportedID {
v.supportedClaim = nil
return nil
}
n.Execute(cmdRemoveClaim{node: n, claim: c})
if n.BestClaim() != c {
return nil
}
n.Execute(updateNodeBestClaim{node: n, height: n.Tookover(), old: c, new: nil})
n.updateActiveHeights()
return nil
}
func (n *node) addSupport(s *support) error {
if _, ok := n.supports[s.op.String()]; ok {
return ErrDuplicate
func (n *Node) addSupport(op wire.OutPoint, amt Amount, supported ClaimID) (*Support, error) {
s := &Support{
op: op,
amt: amt,
supportedID: supported,
accepted: n.height + 1,
activeAt: n.height + 1,
}
for _, v := range n.claims {
if v.id == s.supportedID {
s.activeAt = calActiveHeight(s.accepted, s.accepted, n.tookover)
s.supportedClaim = v
n.supports[s.op.String()] = s
return nil
if n.BestClaim() == nil || n.BestClaim().op != op {
s.activeAt = calActiveHeight(s.accepted, s.accepted, n.Tookover())
}
for _, c := range n.claims {
if c.id != supported {
continue
}
n.Execute(cmdAddSupport{node: n, support: s})
return s, nil
}
return ErrNotFound
// Is supporting an non-existing Claim aceepted?
return nil, ErrNotFound
}
func (n *node) removeSupport(op wire.OutPoint) error {
if _, ok := n.supports[op.String()]; !ok {
func (n *Node) removeSupport(op wire.OutPoint) error {
s, ok := n.supports[op]
if !ok {
return ErrNotFound
}
delete(n.supports, op.String())
n.Execute(cmdRemoveSupport{node: n, support: s})
return nil
}
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.supportedID != 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.Execute(cmdUpdateClaimActiveHeight{claim: v, old: old, new: new})
}
}
for _, v := range n.supports {
if old, new := v.activeAt, calActiveHeight(v.accepted, n.height, n.height); old != new {
n.Execute(cmdUpdateSupportActiveHeight{support: v, old: old, new: new})
}
}
}
func (n *Node) processBlock() {
for {
candidate := findCandiadte(n)
if n.BestClaim() == candidate {
return
}
n.Execute(updateNodeBestClaim{node: n, height: n.height, old: n.bestClaims[n.height], new: candidate})
n.updateActiveHeights()
}
}
func (n *Node) findNextUpdateHeights() Height {
next := 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 == 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 {
return calNodeHash(n.bestClaim.op, n.tookover)
func (n *Node) Hash() chainhash.Hash {
if n.BestClaim() == nil {
return chainhash.Hash{}
}
return calNodeHash(n.BestClaim().op, n.Tookover())
}
// MarshalJSON customizes JSON marshaling of the Node.
func (n *node) MarshalJSON() ([]byte, error) {
c := make([]*claim, 0, len(n.claims))
func (n *Node) MarshalJSON() ([]byte, error) {
c := make([]*Claim, 0, len(n.claims))
for _, v := range n.claims {
c = append(c, v)
}
s := make([]*support, 0, len(n.supports))
s := make([]*Support, 0, len(n.supports))
for _, v := range n.supports {
s = append(s, v)
}
return json.Marshal(&struct {
Height Height
Hash string
Tookover Height
BestClaim *claim
Claims []*claim
Supports []*support
BestClaim *Claim
Claims []*Claim
Supports []*Support
}{
Height: n.height,
Hash: n.Hash().String(),
Tookover: n.tookover,
BestClaim: n.bestClaim,
Tookover: n.Tookover(),
BestClaim: n.BestClaim(),
Claims: c,
Supports: s,
})
}
// String implements Stringer interface.
func (n *node) String() string {
func (n *Node) String() string {
if dbg {
w := bytes.NewBuffer(nil)
fmt.Fprintf(w, "H: %2d BestClaims: ", n.height)
for k, v := range n.bestClaims {
if v == nil {
fmt.Fprintf(w, "{%d, nil}, ", k)
continue
}
fmt.Fprintf(w, "{%d, %d}, ", k, v.op.Index)
}
fmt.Fprintf(w, "\n")
for _, v := range n.claims {
fmt.Fprintf(w, "\n %v", v)
if v == n.BestClaim() {
fmt.Fprintf(w, " <B> ")
}
}
for _, v := range n.supports {
fmt.Fprintf(w, "\n %v", v)
}
fmt.Fprintf(w, "\n")
return w.String()
}
b, err := json.MarshalIndent(n, "", " ")
if err != nil {
panic("can't marshal Node")
@ -114,69 +276,36 @@ func (n *node) String() string {
return string(b)
}
func (n *node) updateEffectiveAmounts(curr Height) {
// func (n *Node) clone() *Node {
// clone := NewNode()
// // shallow copy of value fields.
// *clone = *n
// // deep copy of reference and pointer fields.
// clone.claims = map[wire.OutPoint]*Claim{}
// for k, v := range n.claims {
// clone.claims[k] = v
// }
// clone.supports = map[wire.OutPoint]*Support{}
// for k, v := range n.supports {
// clone.supports[k] = v
// }
// return clone
// }
func findCandiadte(n *Node) *Claim {
n.updateEffectiveAmounts()
var candidate *Claim
for _, v := range n.claims {
v.effAmt = v.amt
}
for _, v := range n.supports {
if v.supportedClaim == n.bestClaim || v.activeAt <= curr {
v.supportedClaim.effAmt += v.amt
if v.activeAt > n.height {
continue
}
if candidate == nil || v.effAmt > candidate.effAmt {
candidate = v
}
}
}
func (n *node) updateBestClaim(curr Height) {
findCandiadte := func() *claim {
candidate := n.bestClaim
for _, v := range n.claims {
if v.activeAt > curr {
// Accepted claim, but noy activated yet.
continue
}
if candidate == nil || v.effAmt > candidate.effAmt {
candidate = v
}
}
return candidate
}
takeover := func(candidate *claim) {
n.bestClaim = candidate
n.tookover = curr
for _, v := range n.claims {
v.activeAt = calActiveHeight(v.accepted, curr, curr)
}
}
for {
n.updateEffectiveAmounts(curr)
candidate := findCandiadte()
if n.bestClaim == nil {
takeover(candidate)
return
}
if n.bestClaim == candidate {
return
}
takeover(candidate)
}
}
func (n *node) clone() *node {
clone := newNode()
// shallow copy of value fields.
*clone = *n
// deep copy of reference and pointer fields.
clone.claims = map[string]*claim{}
for k, v := range n.claims {
clone.claims[k] = v
}
clone.supports = map[string]*support{}
for k, v := range n.supports {
clone.supports[k] = v
}
return clone
return candidate
}
func calNodeHash(op wire.OutPoint, tookover Height) chainhash.Hash {
@ -196,3 +325,13 @@ func calNodeHash(op wire.OutPoint, tookover Height) chainhash.Hash {
return chainhash.DoubleHashH(h)
}
var proportionalDelayFactor = Height(32)
func calActiveHeight(accepted, curr, tookover Height) Height {
delay := (curr - tookover) / proportionalDelayFactor
if delay > 4032 {
delay = 4032
}
return accepted + delay
}

View file

@ -1,12 +1,13 @@
package claimtrie
import (
"crypto/rand"
"reflect"
"testing"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/stretchr/testify/assert"
)
func newHash(s string) *chainhash.Hash {
@ -15,11 +16,12 @@ func newHash(s string) *chainhash.Hash {
}
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))
// 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) {
@ -61,71 +63,212 @@ func Test_calNodeHash(t *testing.T) {
})
}
}
func Test_BestClaim(t *testing.T) {
cA := newClaim(*newOutPoint(1), 0, 0)
cB := newClaim(*newOutPoint(2), 0, 0)
cC := newClaim(*newOutPoint(3), 0, 0)
cD := newClaim(*newOutPoint(4), 0, 0)
s1 := newSupport(*newOutPoint(91), 0, 0, cA.id)
var c1, c2, c3, c4, c5, c6, c7, c8, c9, c10 *Claim
var s1, s2, s3, s4, s5, s6, s7, s8, s9, s10 *Support
var n *node
type operation int
const (
opReset = 1 << iota
opAddClaim
opRemoveClaim
opAddSupport
opRemoveSupport
opCheck
)
tests := []struct {
name string
op operation
claim *claim
support *support
amount Amount
curr Height
want *claim
}{
{name: "0-0", op: opReset},
{name: "0-1", op: opAddClaim, claim: cA, amount: 10, curr: 13, want: cA}, // A(10) is controlling
{name: "0-2", op: opAddClaim, claim: cB, amount: 20, curr: 1001, want: cA}, // A(10) is controlling, B(20) is accepted. Act(B) = 1001 + (1001-13)/32 = 1031
{name: "0-3", op: opAddSupport, claim: cA, support: s1, amount: 14, curr: 1010, want: cA}, // A(10+14) is controlling, B(20) is accepted.
{name: "0-4", op: opAddClaim, claim: cC, amount: 50, curr: 1020, want: cA}, // A(10+14) is controlling, B(20) is accepted, C(50) is accepted. Act(C) = 1020 + (1020-13)/32 = 1051
{name: "0-5", op: opCheck, curr: 1031, want: cA}, // A(10+14) is controlling, B(20) is active, C(50) is accepted.
{name: "0-6", op: opAddClaim, claim: cD, amount: 300, curr: 1040, want: cA}, // A(10+14) is controlling, B(20) is active, C(50) is accepted, D(300) is accepted. Act(C) = 1040 + (1040-13)/32 = 1072
{name: "0-7", op: opCheck, curr: 1051, want: cD}, // A(10+14) is active, B(20) is active, C(50) is active, D(300) is controlling.
func Test_History1(t *testing.T) {
proportionalDelayFactor = 1
n := NewNode()
// no competing bids
test1 := func() {
c1, _ = n.addClaim(*newOutPoint(1), 1)
n.IncrementBlock(1)
assert.Equal(t, c1, n.BestClaim())
n.DecrementBlock(1)
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.IncrementBlock(1)
assert.Equal(t, c3, n.BestClaim())
n.DecrementBlock(1)
assert.Nil(t, n.BestClaim())
}
// make two claims , one older
test3 := func() {
c4, _ = n.addClaim(*newOutPoint(4), 1)
n.IncrementBlock(1)
assert.Equal(t, c4, n.BestClaim())
n.addClaim(*newOutPoint(5), 1)
n.IncrementBlock(1)
assert.Equal(t, c4, n.BestClaim())
n.IncrementBlock(1)
assert.Equal(t, c4, n.BestClaim())
n.DecrementBlock(1)
assert.Equal(t, c4, n.BestClaim())
n.DecrementBlock(1)
assert.Equal(t, c4, n.BestClaim())
n.DecrementBlock(1)
assert.Nil(t, n.BestClaim())
}
// check claim takeover
test4 := func() {
c6, _ = n.addClaim(*newOutPoint(6), 1)
n.IncrementBlock(10)
assert.Equal(t, c6, n.BestClaim())
c7, _ = n.addClaim(*newOutPoint(7), 2)
n.IncrementBlock(1)
assert.Equal(t, c6, n.BestClaim())
n.IncrementBlock(10)
assert.Equal(t, c7, n.BestClaim())
n.DecrementBlock(10)
assert.Equal(t, c6, n.BestClaim())
n.DecrementBlock(10)
assert.Equal(t, c6, n.BestClaim())
n.DecrementBlock(1)
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.IncrementBlock(1)
assert.Equal(t, c1, n.BestClaim())
n.removeClaim(c1.op)
n.IncrementBlock(1)
assert.Equal(t, c2, n.BestClaim())
n.DecrementBlock(1)
assert.Equal(t, c1, n.BestClaim())
n.DecrementBlock(1)
assert.Nil(t, n.BestClaim())
}
// spending winning claim will make inactive claim winner
test6 := func() {
c3, _ = n.addClaim(*newOutPoint(3), 2)
n.IncrementBlock(10)
assert.Equal(t, c3, n.BestClaim())
c4, _ = n.addClaim(*newOutPoint(4), 2)
n.IncrementBlock(1)
assert.Equal(t, c3, n.BestClaim())
n.removeClaim(c3.op)
n.IncrementBlock(1)
assert.Equal(t, c4, n.BestClaim())
n.DecrementBlock(1)
assert.Equal(t, c3, n.BestClaim())
n.DecrementBlock(1)
assert.Equal(t, c3, n.BestClaim())
n.DecrementBlock(10)
assert.Nil(t, n.BestClaim())
}
// spending winning claim will empty out claim trie
test7 := func() {
c5, _ = n.addClaim(*newOutPoint(5), 2)
n.IncrementBlock(1)
assert.Equal(t, c5, n.BestClaim())
n.removeClaim(c5.op)
n.IncrementBlock(1)
assert.NotEqual(t, c5, n.BestClaim())
n.DecrementBlock(1)
assert.Equal(t, c5, n.BestClaim())
n.DecrementBlock(1)
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.IncrementBlock(1)
assert.Equal(t, c2, n.BestClaim())
assert.Equal(t, Amount(11), n.BestClaim().effAmt)
n.DecrementBlock(1)
assert.Nil(t, n.BestClaim())
}
// check support delay
test9 := func() {
c3, _ = n.addClaim(*newOutPoint(3), 1)
c4, _ = n.addClaim(*newOutPoint(4), 2)
n.IncrementBlock(10)
assert.Equal(t, c4, n.BestClaim())
assert.Equal(t, Amount(2), n.BestClaim().effAmt)
s4, _ = n.addSupport(*newOutPoint(14), 10, c3.id)
n.IncrementBlock(10)
assert.Equal(t, c4, n.BestClaim())
assert.Equal(t, Amount(2), n.BestClaim().effAmt)
n.IncrementBlock(1)
assert.Equal(t, c3, n.BestClaim())
assert.Equal(t, Amount(11), n.BestClaim().effAmt)
n.DecrementBlock(1)
assert.Equal(t, c4, n.BestClaim())
assert.Equal(t, Amount(2), n.BestClaim().effAmt)
n.DecrementBlock(10)
assert.Equal(t, c4, n.BestClaim())
assert.Equal(t, Amount(2), n.BestClaim().effAmt)
n.DecrementBlock(10)
assert.Nil(t, n.BestClaim())
}
// supporting and abandoing on the same block will cause it to crash
test10 := func() {
c1, _ = n.addClaim(*newOutPoint(1), 2)
n.IncrementBlock(1)
s1, _ = n.addSupport(*newOutPoint(11), 1, c1.id)
n.removeClaim(c1.op)
n.IncrementBlock(1)
assert.NotEqual(t, c1, n.BestClaim())
n.DecrementBlock(1)
assert.Equal(t, c1, n.BestClaim())
n.DecrementBlock(1)
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.IncrementBlock(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.op)
n.removeSupport(s1.op)
n.IncrementBlock(1)
assert.Nil(t, n.BestClaim())
n.DecrementBlock(1)
assert.Equal(t, c1, n.BestClaim())
n.DecrementBlock(1)
assert.Nil(t, n.BestClaim())
}
tests := []func(){
test1,
test2,
test3,
test4,
test5,
test6,
test7,
test8,
test9,
test10,
test11,
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var err error
switch tt.op {
case opReset:
n = newNode()
case opAddClaim:
tt.claim.amt = tt.amount
tt.claim.accepted = tt.curr
err = n.addClaim(tt.claim)
case opRemoveClaim:
err = n.removeClaim(tt.claim.op)
case opAddSupport:
tt.support.accepted = tt.curr
tt.support.amt = tt.amount
tt.support.supportedID = tt.claim.id
err = n.addSupport(tt.support)
case opRemoveSupport:
}
if err != nil {
t.Errorf("BestClaim() failed, err: %s", err)
}
n.updateBestClaim(tt.curr)
got := n.bestClaim
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("BestClaim() = %d, want %d", got.op.Index, tt.want.op.Index)
}
})
tt()
}
_ = []func(){test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11}
}