424235655c
Ideally, network related params should be part of config, which is passed down to the components to avoid references to global instances. This commit is only halfway through as there are a couple of structs that are too small to house the params and are still referencing global variables. We'll rework that later.
313 lines
8.7 KiB
Go
313 lines
8.7 KiB
Go
package node
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"sort"
|
|
|
|
"github.com/btcsuite/btcd/claimtrie/change"
|
|
"github.com/btcsuite/btcd/claimtrie/param"
|
|
)
|
|
|
|
type Node struct {
|
|
BestClaim *Claim // The claim that has most effective amount at the current height.
|
|
TakenOverAt int32 // The height at when the current BestClaim took over.
|
|
Claims ClaimList // List of all Claims.
|
|
Supports ClaimList // List of all Supports, including orphaned ones.
|
|
SupportSums map[string]int64
|
|
}
|
|
|
|
// New returns a new node.
|
|
func New() *Node {
|
|
return &Node{SupportSums: map[string]int64{}}
|
|
}
|
|
|
|
func (n *Node) HasActiveBestClaim() bool {
|
|
return n.BestClaim != nil && n.BestClaim.Status == Activated
|
|
}
|
|
|
|
func (n *Node) ApplyChange(chg change.Change, delay int32) error {
|
|
|
|
visibleAt := chg.VisibleHeight
|
|
if visibleAt <= 0 {
|
|
visibleAt = chg.Height
|
|
}
|
|
|
|
switch chg.Type {
|
|
case change.AddClaim:
|
|
c := &Claim{
|
|
OutPoint: chg.OutPoint,
|
|
Amount: chg.Amount,
|
|
ClaimID: chg.ClaimID,
|
|
// CreatedAt: chg.Height,
|
|
AcceptedAt: chg.Height,
|
|
ActiveAt: chg.Height + delay,
|
|
VisibleAt: visibleAt,
|
|
Sequence: int32(len(n.Claims)),
|
|
}
|
|
// old := n.Claims.find(byOut(chg.OutPoint)) // TODO: remove this after proving ResetHeight works
|
|
// if old != nil {
|
|
// return errors.Errorf("CONFLICT WITH EXISTING TXO! Name: %s, Height: %d", chg.Name, chg.Height)
|
|
// }
|
|
n.Claims = append(n.Claims, c)
|
|
|
|
case change.SpendClaim:
|
|
c := n.Claims.find(byOut(chg.OutPoint))
|
|
if c != nil {
|
|
c.setStatus(Deactivated)
|
|
} else {
|
|
LogOnce(fmt.Sprintf("Spending claim but missing existing claim with TXO %s, "+
|
|
"Name: %s, ID: %s", chg.OutPoint, chg.Name, chg.ClaimID))
|
|
}
|
|
// apparently it's legit to be absent in the map:
|
|
// 'two' at 481100, 36a719a156a1df178531f3c712b8b37f8e7cc3b36eea532df961229d936272a1:0
|
|
|
|
case change.UpdateClaim:
|
|
// Find and remove the claim, which has just been spent.
|
|
c := n.Claims.find(byID(chg.ClaimID))
|
|
if c != nil && c.Status == Deactivated {
|
|
|
|
// Keep its ID, which was generated from the spent claim.
|
|
// And update the rest of properties.
|
|
c.setOutPoint(chg.OutPoint).SetAmt(chg.Amount)
|
|
c.setStatus(Accepted) // it was Deactivated in the spend (but we only activate at the end of the block)
|
|
// that's because the old code would put all insertions into the "queue" that was processed at block's end
|
|
|
|
// This forces us to be newer, which may in an unintentional takeover if there's an older one.
|
|
// TODO: reconsider these updates in future hard forks.
|
|
c.setAccepted(chg.Height)
|
|
c.setActiveAt(chg.Height + delay)
|
|
|
|
} else {
|
|
LogOnce(fmt.Sprintf("Updating claim but missing existing claim with ID %s", chg.ClaimID))
|
|
}
|
|
case change.AddSupport:
|
|
n.Supports = append(n.Supports, &Claim{
|
|
OutPoint: chg.OutPoint,
|
|
Amount: chg.Amount,
|
|
ClaimID: chg.ClaimID,
|
|
AcceptedAt: chg.Height,
|
|
ActiveAt: chg.Height + delay,
|
|
VisibleAt: visibleAt,
|
|
})
|
|
|
|
case change.SpendSupport:
|
|
s := n.Supports.find(byOut(chg.OutPoint))
|
|
if s != nil {
|
|
if s.Status == Activated {
|
|
n.SupportSums[s.ClaimID.Key()] -= s.Amount
|
|
}
|
|
// TODO: we could do without this Deactivated flag if we set expiration instead
|
|
// That would eliminate the above Sum update.
|
|
// We would also need to track the update situation, though, but that could be done locally.
|
|
s.setStatus(Deactivated)
|
|
} else {
|
|
LogOnce(fmt.Sprintf("Spending support but missing existing claim with TXO %s, "+
|
|
"Name: %s, ID: %s", chg.OutPoint, chg.Name, chg.ClaimID))
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AdjustTo activates claims and computes takeovers until it reaches the specified height.
|
|
func (n *Node) AdjustTo(height, maxHeight int32, name []byte) *Node {
|
|
changed := n.handleExpiredAndActivated(height) > 0
|
|
n.updateTakeoverHeight(height, name, changed)
|
|
if maxHeight > height {
|
|
for h := n.NextUpdate(); h <= maxHeight; h = n.NextUpdate() {
|
|
changed = n.handleExpiredAndActivated(h) > 0
|
|
n.updateTakeoverHeight(h, name, changed)
|
|
height = h
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (n *Node) updateTakeoverHeight(height int32, name []byte, refindBest bool) {
|
|
|
|
candidate := n.BestClaim
|
|
if refindBest {
|
|
candidate = n.findBestClaim() // so expensive...
|
|
}
|
|
|
|
hasCandidate := candidate != nil
|
|
hasCurrentWinner := n.HasActiveBestClaim()
|
|
|
|
takeoverHappening := !hasCandidate || !hasCurrentWinner || candidate.ClaimID != n.BestClaim.ClaimID
|
|
|
|
if takeoverHappening {
|
|
if n.activateAllClaims(height) > 0 {
|
|
candidate = n.findBestClaim()
|
|
}
|
|
}
|
|
|
|
if !takeoverHappening && height < param.ActiveParams.MaxRemovalWorkaroundHeight {
|
|
// This is a super ugly hack to work around bug in old code.
|
|
// The bug: un/support a name then update it. This will cause its takeover height to be reset to current.
|
|
// This is because the old code would add to the cache without setting block originals when dealing in supports.
|
|
_, takeoverHappening = param.TakeoverWorkarounds[fmt.Sprintf("%d_%s", height, name)] // TODO: ditch the fmt call
|
|
}
|
|
|
|
if takeoverHappening {
|
|
n.TakenOverAt = height
|
|
n.BestClaim = candidate
|
|
}
|
|
}
|
|
|
|
func (n *Node) handleExpiredAndActivated(height int32) int {
|
|
|
|
changes := 0
|
|
update := func(items ClaimList, sums map[string]int64) ClaimList {
|
|
for i := 0; i < len(items); i++ {
|
|
c := items[i]
|
|
if c.Status == Accepted && c.ActiveAt <= height && c.VisibleAt <= height {
|
|
c.setStatus(Activated)
|
|
changes++
|
|
if sums != nil {
|
|
sums[c.ClaimID.Key()] += c.Amount
|
|
}
|
|
}
|
|
if c.ExpireAt() <= height || c.Status == Deactivated {
|
|
if i < len(items)-1 {
|
|
items[i] = items[len(items)-1]
|
|
i--
|
|
}
|
|
items = items[:len(items)-1]
|
|
changes++
|
|
if sums != nil && c.Status != Deactivated {
|
|
sums[c.ClaimID.Key()] -= c.Amount
|
|
}
|
|
}
|
|
}
|
|
return items
|
|
}
|
|
n.Claims = update(n.Claims, nil)
|
|
n.Supports = update(n.Supports, n.SupportSums)
|
|
return changes
|
|
}
|
|
|
|
// NextUpdate returns the nearest height in the future that the node should
|
|
// be refreshed due to changes of claims or supports.
|
|
func (n Node) NextUpdate() int32 {
|
|
|
|
next := int32(math.MaxInt32)
|
|
|
|
for _, c := range n.Claims {
|
|
if c.ExpireAt() < next {
|
|
next = c.ExpireAt()
|
|
}
|
|
// if we're not active, we need to go to activeAt unless we're still invisible there
|
|
if c.Status == Accepted {
|
|
min := c.ActiveAt
|
|
if c.VisibleAt > min {
|
|
min = c.VisibleAt
|
|
}
|
|
if min < next {
|
|
next = min
|
|
}
|
|
}
|
|
}
|
|
|
|
for _, s := range n.Supports {
|
|
if s.ExpireAt() < next {
|
|
next = s.ExpireAt()
|
|
}
|
|
if s.Status == Accepted {
|
|
min := s.ActiveAt
|
|
if s.VisibleAt > min {
|
|
min = s.VisibleAt
|
|
}
|
|
if min < next {
|
|
next = min
|
|
}
|
|
}
|
|
}
|
|
|
|
return next
|
|
}
|
|
|
|
func (n Node) findBestClaim() *Claim {
|
|
|
|
// WARNING: this method is called billions of times.
|
|
// if we just had some easy way to know that our best claim was the first one in the list...
|
|
// or it may be faster to cache effective amount in the db at some point.
|
|
|
|
var best *Claim
|
|
var bestAmount int64
|
|
for _, candidate := range n.Claims {
|
|
|
|
// not using switch here for performance reasons
|
|
if candidate.Status != Activated {
|
|
continue
|
|
}
|
|
|
|
if best == nil {
|
|
best = candidate
|
|
continue
|
|
}
|
|
|
|
candidateAmount := candidate.Amount + n.SupportSums[candidate.ClaimID.Key()]
|
|
if bestAmount <= 0 {
|
|
bestAmount = best.Amount + n.SupportSums[best.ClaimID.Key()]
|
|
}
|
|
|
|
switch {
|
|
case candidateAmount > bestAmount:
|
|
best = candidate
|
|
bestAmount = candidateAmount
|
|
case candidateAmount < bestAmount:
|
|
continue
|
|
case candidate.AcceptedAt < best.AcceptedAt:
|
|
best = candidate
|
|
bestAmount = candidateAmount
|
|
case candidate.AcceptedAt > best.AcceptedAt:
|
|
continue
|
|
case OutPointLess(candidate.OutPoint, best.OutPoint):
|
|
best = candidate
|
|
bestAmount = candidateAmount
|
|
}
|
|
}
|
|
|
|
return best
|
|
}
|
|
|
|
func (n *Node) activateAllClaims(height int32) int {
|
|
count := 0
|
|
for _, c := range n.Claims {
|
|
if c.Status == Accepted && c.ActiveAt > height && c.VisibleAt <= height {
|
|
c.setActiveAt(height) // don't necessarily need to change this number?
|
|
c.setStatus(Activated)
|
|
count++
|
|
}
|
|
}
|
|
|
|
for _, s := range n.Supports {
|
|
if s.Status == Accepted && s.ActiveAt > height && s.VisibleAt <= height {
|
|
s.setActiveAt(height) // don't necessarily need to change this number?
|
|
s.setStatus(Activated)
|
|
count++
|
|
n.SupportSums[s.ClaimID.Key()] += s.Amount
|
|
}
|
|
}
|
|
return count
|
|
}
|
|
|
|
func (n *Node) SortClaimsByBid() {
|
|
|
|
// purposefully sorting by descent
|
|
sort.Slice(n.Claims, func(j, i int) bool {
|
|
iAmount := n.Claims[i].Amount + n.SupportSums[n.Claims[i].ClaimID.Key()]
|
|
jAmount := n.Claims[j].Amount + n.SupportSums[n.Claims[j].ClaimID.Key()]
|
|
switch {
|
|
case iAmount < jAmount:
|
|
return true
|
|
case iAmount > jAmount:
|
|
return false
|
|
case n.Claims[i].AcceptedAt > n.Claims[j].AcceptedAt:
|
|
return true
|
|
case n.Claims[i].AcceptedAt < n.Claims[j].AcceptedAt:
|
|
return false
|
|
}
|
|
return OutPointLess(n.Claims[j].OutPoint, n.Claims[i].OutPoint)
|
|
})
|
|
}
|