claimtrie/node.go
Tzu-Jung Lee 84c64c1018 wip: misc updates
AddClaim / AddSupport is working with minimal testing done so far.
RemoveClaim / RemoveSupport is implemented, but not tested yet.

Some known issues:

Currently, we update the BestClaim for each node in a lazy fashion.
Each node could add/remove claims/supports without recalculating hash
until its being obeserved externally.

However, due to the "Takeover Delay" bidding rule, as the block number
increases, the bestClaim might changes implicitly. The Trie can't detect
this passively, and would need some mechanism for this.
2018-07-03 22:03:55 -07:00

199 lines
4.1 KiB
Go

package claimtrie
import (
"crypto/sha256"
"encoding/binary"
"encoding/json"
"strconv"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
)
type node struct {
tookover Height
bestClaim *claim
claims map[string]*claim
supports map[string]*support
}
func newNode() *node {
return &node{
claims: map[string]*claim{},
supports: map[string]*support{},
}
}
func (n *node) addClaim(c *claim) error {
if _, ok := n.claims[c.op.String()]; ok {
return ErrDuplicate
}
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()]
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
}
}
return nil
}
func (n *node) addSupport(s *support) error {
if _, ok := n.supports[s.op.String()]; ok {
return ErrDuplicate
}
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
}
}
return ErrNotFound
}
func (n *node) removeSupport(op wire.OutPoint) error {
if _, ok := n.supports[op.String()]; !ok {
return ErrNotFound
}
delete(n.supports, op.String())
return nil
}
// 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)
}
// MarshalJSON customizes JSON marshaling of the Node.
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))
for _, v := range n.supports {
s = append(s, v)
}
return json.Marshal(&struct {
Hash string
Tookover Height
BestClaim *claim
Claims []*claim
Supports []*support
}{
Hash: n.Hash().String(),
Tookover: n.tookover,
BestClaim: n.bestClaim,
Claims: c,
Supports: s,
})
}
// String implements Stringer interface.
func (n *node) String() string {
b, err := json.MarshalIndent(n, "", " ")
if err != nil {
panic("can't marshal Node")
}
return string(b)
}
func (n *node) updateEffectiveAmounts(curr Height) {
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
}
}
}
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
}
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)
}