fix Bidding logics, and port some tests cases from lbrycrd
This commit is contained in:
parent
84c64c1018
commit
fa2e276f3d
10 changed files with 671 additions and 266 deletions
|
@ -72,4 +72,4 @@ Our PGP key is [here](https://keybase.io/lbry/key.asc) if you need it.
|
||||||
|
|
||||||
## Contact
|
## 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)
|
75
claim.go
75
claim.go
|
@ -1,23 +1,17 @@
|
||||||
package claimtrie
|
package claimtrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newClaim ...
|
var dbg bool
|
||||||
func newClaim(op wire.OutPoint, amt Amount, accepted Height) *claim {
|
|
||||||
return &claim{
|
|
||||||
op: op,
|
|
||||||
id: NewClaimID(op),
|
|
||||||
amt: amt,
|
|
||||||
accepted: accepted,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type claim struct {
|
// Claim ...
|
||||||
|
type Claim struct {
|
||||||
op wire.OutPoint
|
op wire.OutPoint
|
||||||
id ClaimID
|
id ClaimID
|
||||||
amt Amount
|
amt Amount
|
||||||
|
@ -26,8 +20,31 @@ type claim struct {
|
||||||
activeAt Height
|
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.
|
// MarshalJSON customizes the representation of JSON.
|
||||||
func (c *claim) MarshalJSON() ([]byte, error) {
|
func (c *Claim) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(&struct {
|
return json.Marshal(&struct {
|
||||||
OutPoint string
|
OutPoint string
|
||||||
ClaimID 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 ...
|
// MarshalJSON ...
|
||||||
func (s *support) MarshalJSON() ([]byte, error) {
|
func (s *Support) MarshalJSON() ([]byte, error) {
|
||||||
return json.Marshal(&struct {
|
return json.Marshal(&struct {
|
||||||
OutPoint string
|
OutPoint string
|
||||||
SupportedClaimID 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, "", " ")
|
b, err := json.MarshalIndent(s, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("can't marshal, err :%s", err)
|
fmt.Printf("can't marshal, err :%s", err)
|
||||||
|
|
|
@ -34,12 +34,3 @@ type ClaimID [20]byte
|
||||||
func (id ClaimID) String() string {
|
func (id ClaimID) String() string {
|
||||||
return hex.EncodeToString(id[:])
|
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
|
|
||||||
}
|
|
||||||
|
|
88
claimtrie.go
88
claimtrie.go
|
@ -1,18 +1,14 @@
|
||||||
package claimtrie
|
package claimtrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
|
||||||
"github.com/lbryio/merkletrie"
|
"github.com/lbryio/merkletrie"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Height ...
|
|
||||||
type Height int64
|
|
||||||
|
|
||||||
// Amount ...
|
|
||||||
type Amount int64
|
|
||||||
|
|
||||||
// ClaimTrie implements a Merkle Trie supporting linear history of commits.
|
// ClaimTrie implements a Merkle Trie supporting linear history of commits.
|
||||||
type ClaimTrie struct {
|
type ClaimTrie struct {
|
||||||
|
|
||||||
|
@ -24,6 +20,9 @@ type ClaimTrie struct {
|
||||||
|
|
||||||
// An overlay supporting Copy-on-Write to the current tip commit.
|
// An overlay supporting Copy-on-Write to the current tip commit.
|
||||||
stg *merkletrie.Stage
|
stg *merkletrie.Stage
|
||||||
|
|
||||||
|
// pending keeps track update for future block height.
|
||||||
|
pending map[Height][]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitMeta implements merkletrie.CommitMeta with commit-specific metadata.
|
// CommitMeta implements merkletrie.CommitMeta with commit-specific metadata.
|
||||||
|
@ -37,19 +36,20 @@ func New() *ClaimTrie {
|
||||||
return &ClaimTrie{
|
return &ClaimTrie{
|
||||||
head: merkletrie.NewCommit(nil, CommitMeta{0}, mt),
|
head: merkletrie.NewCommit(nil, CommitMeta{0}, mt),
|
||||||
stg: merkletrie.NewStage(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))
|
v, err := stg.Get(merkletrie.Key(name))
|
||||||
if err != nil && err != merkletrie.ErrKeyNotFound {
|
if err != nil && err != merkletrie.ErrKeyNotFound {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var n *node
|
var n *Node
|
||||||
if v == nil {
|
if v == nil {
|
||||||
n = newNode()
|
n = NewNode()
|
||||||
} else {
|
} else {
|
||||||
n = v.(*node).clone()
|
n = v.(*Node)
|
||||||
}
|
}
|
||||||
if err = modifier(n); err != nil {
|
if err = modifier(n); err != nil {
|
||||||
return err
|
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.
|
// AddClaim adds a Claim to the Stage of ClaimTrie.
|
||||||
func (ct *ClaimTrie) AddClaim(name string, op wire.OutPoint, amt Amount, accepted Height) error {
|
func (ct *ClaimTrie) AddClaim(name string, op wire.OutPoint, amt Amount, accepted Height) error {
|
||||||
return updateStageNode(ct.stg, name, func(n *node) error {
|
return updateStageNode(ct.stg, name, func(n *Node) error {
|
||||||
return n.addClaim(newClaim(op, amt, accepted))
|
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.
|
// 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 {
|
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 updateStageNode(ct.stg, name, func(n *Node) error {
|
||||||
return n.addSupport(newSupport(op, amt, accepted, supported))
|
_, 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.
|
// SpendClaim removes a Claim in the Stage.
|
||||||
func (ct *ClaimTrie) SpendClaim(name string, op wire.OutPoint) error {
|
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)
|
return n.removeClaim(op)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpendSupport removes a Support in the Stage.
|
// SpendSupport removes a Support in the Stage.
|
||||||
func (ct *ClaimTrie) SpendSupport(name string, op wire.OutPoint) error {
|
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)
|
return n.removeSupport(op)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Traverse visits Nodes in the Stage of the ClaimTrie.
|
// Traverse visits Nodes in the Stage of the ClaimTrie.
|
||||||
func (ct *ClaimTrie) Traverse(visit merkletrie.Visit, update, valueOnly bool) error {
|
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.
|
return ct.stg.Traverse(visit, update, valueOnly)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MerkleHash returns the Merkle Hash of the Stage.
|
// MerkleHash returns the Merkle Hash of the Stage.
|
||||||
func (ct *ClaimTrie) MerkleHash() chainhash.Hash {
|
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()
|
return ct.stg.MerkleHash()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,12 +117,30 @@ func (ct *ClaimTrie) Commit(h Height) error {
|
||||||
if h <= ct.bestBlock {
|
if h <= ct.bestBlock {
|
||||||
return ErrInvalidHeight
|
return ErrInvalidHeight
|
||||||
}
|
}
|
||||||
visit := func(prefix merkletrie.Key, v merkletrie.Value) error {
|
|
||||||
v.(*node).updateBestClaim(h)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
ct.Traverse(visit, true, true)
|
|
||||||
|
|
||||||
|
// 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})
|
commit, err := ct.stg.Commit(ct.head, CommitMeta{Height: h})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -135,6 +152,13 @@ func (ct *ClaimTrie) Commit(h Height) error {
|
||||||
|
|
||||||
// Reset reverts the Stage to a specified commit by height.
|
// Reset reverts the Stage to a specified commit by height.
|
||||||
func (ct *ClaimTrie) Reset(h Height) error {
|
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 {
|
for commit := ct.head; commit != nil; commit = commit.Prev {
|
||||||
meta := commit.Meta.(CommitMeta)
|
meta := commit.Meta.(CommitMeta)
|
||||||
if meta.Height <= h {
|
if meta.Height <= h {
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package claimtrie
|
package claimtrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// pending ...
|
||||||
func TestClaimTrie_Commit(t *testing.T) {
|
func TestClaimTrie_Commit(t *testing.T) {
|
||||||
ct := New()
|
ct := New()
|
||||||
|
|
||||||
|
@ -33,7 +33,7 @@ func TestClaimTrie_Commit(t *testing.T) {
|
||||||
ct.AddClaim("HELLO", *newOutPoint(0), tt.amt, tt.curr)
|
ct.AddClaim("HELLO", *newOutPoint(0), tt.amt, tt.curr)
|
||||||
}
|
}
|
||||||
ct.Commit(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)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -128,6 +128,9 @@ func newOutPoint(s string) (*wire.OutPoint, error) {
|
||||||
return wire.NewOutPoint(&h, uint32(h[0])), nil
|
return wire.NewOutPoint(&h, uint32(h[0])), nil
|
||||||
}
|
}
|
||||||
fields := strings.Split(s, ":")
|
fields := strings.Split(s, ":")
|
||||||
|
if len(fields) != 2 {
|
||||||
|
return nil, fmt.Errorf("invalid outpoint format (HASH:INDEX)")
|
||||||
|
}
|
||||||
h, err := chainhash.NewHashFromStr(fields[0])
|
h, err := chainhash.NewHashFromStr(fields[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -217,11 +220,13 @@ func cmdShow(c *cli.Context) error {
|
||||||
}
|
}
|
||||||
height := claimtrie.Height(c.Int64("height"))
|
height := claimtrie.Height(c.Int64("height"))
|
||||||
if !c.IsSet("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"))
|
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 {
|
for commit := ct.Head(); commit != nil; commit = commit.Prev {
|
||||||
meta := commit.Meta.(claimtrie.CommitMeta)
|
meta := commit.Meta.(claimtrie.CommitMeta)
|
||||||
|
fmt.Printf("HEAD: %d/%d\n", height, meta.Height)
|
||||||
if height == meta.Height {
|
if height == meta.Height {
|
||||||
return commit.MerkleTrie.Traverse(dump, false, !c.Bool("all"))
|
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 {
|
func cmdLog(c *cli.Context) error {
|
||||||
commitVisit := func(c *merkletrie.Commit) {
|
commitVisit := func(c *merkletrie.Commit) {
|
||||||
meta := c.Meta.(claimtrie.CommitMeta)
|
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")
|
fmt.Printf("\n")
|
||||||
|
|
72
memento.go
Normal file
72
memento.go
Normal 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
36
memento/memento.go
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
347
node.go
347
node.go
|
@ -1,112 +1,274 @@
|
||||||
package claimtrie
|
package claimtrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/lbryio/claimtrie/memento"
|
||||||
)
|
)
|
||||||
|
|
||||||
type node struct {
|
// Amount ...
|
||||||
tookover Height
|
type Amount int64
|
||||||
bestClaim *claim
|
|
||||||
|
|
||||||
claims map[string]*claim
|
// Height ...
|
||||||
supports map[string]*support
|
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 {
|
// NewNode ...
|
||||||
return &node{
|
func NewNode() *Node {
|
||||||
claims: map[string]*claim{},
|
return &Node{
|
||||||
supports: map[string]*support{},
|
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 {
|
// BestClaim ...
|
||||||
if _, ok := n.claims[c.op.String()]; ok {
|
func (n *Node) BestClaim() *Claim {
|
||||||
return ErrDuplicate
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) removeClaim(op wire.OutPoint) error {
|
// DecrementBlock ...
|
||||||
c, ok := n.claims[op.String()]
|
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 {
|
if !ok {
|
||||||
return ErrNotFound
|
return ErrNotFound
|
||||||
}
|
}
|
||||||
delete(n.claims, op.String())
|
n.Execute(cmdRemoveClaim{node: n, claim: c})
|
||||||
if n.bestClaim == c {
|
|
||||||
n.bestClaim = nil
|
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(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,
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Is supporting an non-existing Claim aceepted?
|
||||||
|
return nil, ErrNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) removeSupport(op wire.OutPoint) error {
|
||||||
|
s, ok := n.supports[op]
|
||||||
|
if !ok {
|
||||||
|
return ErrNotFound
|
||||||
|
}
|
||||||
|
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 {
|
for _, v := range n.supports {
|
||||||
if c.id == v.supportedID {
|
if old, new := v.activeAt, calActiveHeight(v.accepted, n.height, n.height); old != new {
|
||||||
v.supportedClaim = nil
|
n.Execute(cmdUpdateSupportActiveHeight{support: v, old: old, new: new})
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
}
|
||||||
|
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) addSupport(s *support) error {
|
func (n *Node) findNextUpdateHeights() Height {
|
||||||
if _, ok := n.supports[s.op.String()]; ok {
|
next := Height(math.MaxInt64)
|
||||||
return ErrDuplicate
|
|
||||||
}
|
|
||||||
for _, v := range n.claims {
|
for _, v := range n.claims {
|
||||||
if v.id == s.supportedID {
|
if v.activeAt > n.height && v.activeAt < next {
|
||||||
s.activeAt = calActiveHeight(s.accepted, s.accepted, n.tookover)
|
next = v.activeAt
|
||||||
s.supportedClaim = v
|
|
||||||
n.supports[s.op.String()] = s
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ErrNotFound
|
for _, v := range n.supports {
|
||||||
|
if v.activeAt > n.height && v.activeAt < next {
|
||||||
|
next = v.activeAt
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) removeSupport(op wire.OutPoint) error {
|
|
||||||
if _, ok := n.supports[op.String()]; !ok {
|
|
||||||
return ErrNotFound
|
|
||||||
}
|
}
|
||||||
delete(n.supports, op.String())
|
if next == Height(math.MaxInt64) {
|
||||||
return nil
|
return n.height
|
||||||
|
}
|
||||||
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash calculates the Hash value based on the OutPoint and at which height it tookover.
|
// Hash calculates the Hash value based on the OutPoint and at which height it tookover.
|
||||||
func (n *node) Hash() chainhash.Hash {
|
func (n *Node) Hash() chainhash.Hash {
|
||||||
return calNodeHash(n.bestClaim.op, n.tookover)
|
if n.BestClaim() == nil {
|
||||||
|
return chainhash.Hash{}
|
||||||
|
}
|
||||||
|
return calNodeHash(n.BestClaim().op, n.Tookover())
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON customizes JSON marshaling of the Node.
|
// MarshalJSON customizes JSON marshaling of the Node.
|
||||||
func (n *node) MarshalJSON() ([]byte, error) {
|
func (n *Node) MarshalJSON() ([]byte, error) {
|
||||||
c := make([]*claim, 0, len(n.claims))
|
c := make([]*Claim, 0, len(n.claims))
|
||||||
for _, v := range n.claims {
|
for _, v := range n.claims {
|
||||||
c = append(c, v)
|
c = append(c, v)
|
||||||
}
|
}
|
||||||
s := make([]*support, 0, len(n.supports))
|
s := make([]*Support, 0, len(n.supports))
|
||||||
for _, v := range n.supports {
|
for _, v := range n.supports {
|
||||||
s = append(s, v)
|
s = append(s, v)
|
||||||
}
|
}
|
||||||
return json.Marshal(&struct {
|
return json.Marshal(&struct {
|
||||||
|
Height Height
|
||||||
Hash string
|
Hash string
|
||||||
Tookover Height
|
Tookover Height
|
||||||
BestClaim *claim
|
BestClaim *Claim
|
||||||
Claims []*claim
|
Claims []*Claim
|
||||||
Supports []*support
|
Supports []*Support
|
||||||
}{
|
}{
|
||||||
|
Height: n.height,
|
||||||
Hash: n.Hash().String(),
|
Hash: n.Hash().String(),
|
||||||
Tookover: n.tookover,
|
Tookover: n.Tookover(),
|
||||||
BestClaim: n.bestClaim,
|
BestClaim: n.BestClaim(),
|
||||||
Claims: c,
|
Claims: c,
|
||||||
Supports: s,
|
Supports: s,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// String implements Stringer interface.
|
// 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, "", " ")
|
b, err := json.MarshalIndent(n, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic("can't marshal Node")
|
panic("can't marshal Node")
|
||||||
|
@ -114,23 +276,29 @@ func (n *node) String() string {
|
||||||
return string(b)
|
return string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *node) updateEffectiveAmounts(curr Height) {
|
// func (n *Node) clone() *Node {
|
||||||
for _, v := range n.claims {
|
// clone := NewNode()
|
||||||
v.effAmt = v.amt
|
|
||||||
}
|
|
||||||
for _, v := range n.supports {
|
|
||||||
if v.supportedClaim == n.bestClaim || v.activeAt <= curr {
|
|
||||||
v.supportedClaim.effAmt += v.amt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (n *node) updateBestClaim(curr Height) {
|
// // shallow copy of value fields.
|
||||||
findCandiadte := func() *claim {
|
// *clone = *n
|
||||||
candidate := n.bestClaim
|
|
||||||
|
// // 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 {
|
for _, v := range n.claims {
|
||||||
if v.activeAt > curr {
|
if v.activeAt > n.height {
|
||||||
// Accepted claim, but noy activated yet.
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if candidate == nil || v.effAmt > candidate.effAmt {
|
if candidate == nil || v.effAmt > candidate.effAmt {
|
||||||
|
@ -139,45 +307,6 @@ func (n *node) updateBestClaim(curr Height) {
|
||||||
}
|
}
|
||||||
return candidate
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
func calNodeHash(op wire.OutPoint, tookover Height) chainhash.Hash {
|
func calNodeHash(op wire.OutPoint, tookover Height) chainhash.Hash {
|
||||||
txHash := chainhash.DoubleHashH(op.Hash[:])
|
txHash := chainhash.DoubleHashH(op.Hash[:])
|
||||||
|
@ -196,3 +325,13 @@ func calNodeHash(op wire.OutPoint, tookover Height) chainhash.Hash {
|
||||||
|
|
||||||
return chainhash.DoubleHashH(h)
|
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
|
||||||
|
}
|
||||||
|
|
277
node_test.go
277
node_test.go
|
@ -1,12 +1,13 @@
|
||||||
package claimtrie
|
package claimtrie
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/rand"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newHash(s string) *chainhash.Hash {
|
func newHash(s string) *chainhash.Hash {
|
||||||
|
@ -15,11 +16,12 @@ func newHash(s string) *chainhash.Hash {
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOutPoint(idx int) *wire.OutPoint {
|
func newOutPoint(idx int) *wire.OutPoint {
|
||||||
var h chainhash.Hash
|
// var h chainhash.Hash
|
||||||
if _, err := rand.Read(h[:]); err != nil {
|
// if _, err := rand.Read(h[:]); err != nil {
|
||||||
return nil
|
// return nil
|
||||||
}
|
// }
|
||||||
return wire.NewOutPoint(&h, uint32(idx))
|
// return wire.NewOutPoint(&h, uint32(idx))
|
||||||
|
return wire.NewOutPoint(new(chainhash.Hash), uint32(idx))
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_calNodeHash(t *testing.T) {
|
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
|
func Test_History1(t *testing.T) {
|
||||||
type operation int
|
|
||||||
const (
|
proportionalDelayFactor = 1
|
||||||
opReset = 1 << iota
|
n := NewNode()
|
||||||
opAddClaim
|
|
||||||
opRemoveClaim
|
// no competing bids
|
||||||
opAddSupport
|
test1 := func() {
|
||||||
opRemoveSupport
|
c1, _ = n.addClaim(*newOutPoint(1), 1)
|
||||||
opCheck
|
n.IncrementBlock(1)
|
||||||
)
|
assert.Equal(t, c1, n.BestClaim())
|
||||||
tests := []struct {
|
|
||||||
name string
|
n.DecrementBlock(1)
|
||||||
op operation
|
assert.Nil(t, n.BestClaim())
|
||||||
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.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
tt()
|
||||||
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)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
_ = []func(){test1, test2, test3, test4, test5, test6, test7, test8, test9, test10, test11}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue