github.com/lbryio/lbcd@v0.22.119/claimtrie/node/node.go (about) 1 package node 2 3 import ( 4 "fmt" 5 "math" 6 "sort" 7 8 "github.com/lbryio/lbcd/claimtrie/change" 9 "github.com/lbryio/lbcd/claimtrie/param" 10 ) 11 12 type Node struct { 13 BestClaim *Claim // The claim that has most effective amount at the current height. 14 TakenOverAt int32 // The height at when the current BestClaim took over. 15 Claims ClaimList // List of all Claims. 16 Supports ClaimList // List of all Supports, including orphaned ones. 17 SupportSums map[string]int64 18 } 19 20 // New returns a new node. 21 func New() *Node { 22 return &Node{SupportSums: map[string]int64{}} 23 } 24 25 func (n *Node) HasActiveBestClaim() bool { 26 return n.BestClaim != nil && n.BestClaim.Status == Activated 27 } 28 29 func (n *Node) ApplyChange(chg change.Change, delay int32) error { 30 31 visibleAt := chg.VisibleHeight 32 if visibleAt <= 0 { 33 visibleAt = chg.Height 34 } 35 36 switch chg.Type { 37 case change.AddClaim: 38 c := &Claim{ 39 OutPoint: chg.OutPoint, 40 Amount: chg.Amount, 41 ClaimID: chg.ClaimID, 42 // CreatedAt: chg.Height, 43 AcceptedAt: chg.Height, 44 ActiveAt: chg.Height + delay, 45 VisibleAt: visibleAt, 46 Sequence: int32(len(n.Claims)), 47 } 48 // old := n.Claims.find(byOut(chg.OutPoint)) // TODO: remove this after proving ResetHeight works 49 // if old != nil { 50 // return errors.Errorf("CONFLICT WITH EXISTING TXO! Name: %s, Height: %d", chg.Name, chg.Height) 51 // } 52 n.Claims = append(n.Claims, c) 53 54 case change.SpendClaim: 55 c := n.Claims.find(byOut(chg.OutPoint)) 56 if c != nil { 57 c.setStatus(Deactivated) 58 } else { 59 LogOnce(fmt.Sprintf("Spending claim but missing existing claim with TXO %s, "+ 60 "Name: %s, ID: %s", chg.OutPoint, chg.Name, chg.ClaimID)) 61 } 62 // apparently it's legit to be absent in the map: 63 // 'two' at 481100, 36a719a156a1df178531f3c712b8b37f8e7cc3b36eea532df961229d936272a1:0 64 65 case change.UpdateClaim: 66 // Find and remove the claim, which has just been spent. 67 c := n.Claims.find(byID(chg.ClaimID)) 68 if c != nil && c.Status == Deactivated { 69 70 // Keep its ID, which was generated from the spent claim. 71 // And update the rest of properties. 72 c.setOutPoint(chg.OutPoint).SetAmt(chg.Amount) 73 c.setStatus(Accepted) // it was Deactivated in the spend (but we only activate at the end of the block) 74 // that's because the old code would put all insertions into the "queue" that was processed at block's end 75 76 // This forces us to be newer, which may in an unintentional takeover if there's an older one. 77 // TODO: reconsider these updates in future hard forks. 78 c.setAccepted(chg.Height) 79 c.setActiveAt(chg.Height + delay) 80 81 } else { 82 LogOnce(fmt.Sprintf("Updating claim but missing existing claim with ID %s", chg.ClaimID)) 83 } 84 case change.AddSupport: 85 n.Supports = append(n.Supports, &Claim{ 86 OutPoint: chg.OutPoint, 87 Amount: chg.Amount, 88 ClaimID: chg.ClaimID, 89 AcceptedAt: chg.Height, 90 ActiveAt: chg.Height + delay, 91 VisibleAt: visibleAt, 92 }) 93 94 case change.SpendSupport: 95 s := n.Supports.find(byOut(chg.OutPoint)) 96 if s != nil { 97 if s.Status == Activated { 98 n.SupportSums[s.ClaimID.Key()] -= s.Amount 99 } 100 // TODO: we could do without this Deactivated flag if we set expiration instead 101 // That would eliminate the above Sum update. 102 // We would also need to track the update situation, though, but that could be done locally. 103 s.setStatus(Deactivated) 104 } else { 105 LogOnce(fmt.Sprintf("Spending support but missing existing claim with TXO %s, "+ 106 "Name: %s, ID: %s", chg.OutPoint, chg.Name, chg.ClaimID)) 107 } 108 } 109 return nil 110 } 111 112 // AdjustTo activates claims and computes takeovers until it reaches the specified height. 113 func (n *Node) AdjustTo(height, maxHeight int32, name []byte) { 114 changed := n.handleExpiredAndActivated(height) > 0 115 n.updateTakeoverHeight(height, name, changed) 116 if maxHeight > height { 117 for h := n.NextUpdate(); h <= maxHeight; h = n.NextUpdate() { 118 changed = n.handleExpiredAndActivated(h) > 0 119 n.updateTakeoverHeight(h, name, changed) 120 height = h 121 } 122 } 123 } 124 125 func (n *Node) updateTakeoverHeight(height int32, name []byte, refindBest bool) { 126 127 candidate := n.BestClaim 128 if refindBest { 129 candidate = n.findBestClaim() // so expensive... 130 } 131 132 hasCandidate := candidate != nil 133 hasCurrentWinner := n.HasActiveBestClaim() 134 135 takeoverHappening := !hasCandidate || !hasCurrentWinner || candidate.ClaimID != n.BestClaim.ClaimID 136 137 if takeoverHappening { 138 if n.activateAllClaims(height) > 0 { 139 candidate = n.findBestClaim() 140 } 141 } 142 143 if !takeoverHappening && height < param.ActiveParams.MaxRemovalWorkaroundHeight { 144 // This is a super ugly hack to work around bug in old code. 145 // The bug: un/support a name then update it. This will cause its takeover height to be reset to current. 146 // This is because the old code would add to the cache without setting block originals when dealing in supports. 147 _, takeoverHappening = param.TakeoverWorkarounds[fmt.Sprintf("%d_%s", height, name)] // TODO: ditch the fmt call 148 } 149 150 if takeoverHappening { 151 n.TakenOverAt = height 152 n.BestClaim = candidate 153 } 154 } 155 156 func (n *Node) handleExpiredAndActivated(height int32) int { 157 158 ot := param.ActiveParams.OriginalClaimExpirationTime 159 et := param.ActiveParams.ExtendedClaimExpirationTime 160 fk := param.ActiveParams.ExtendedClaimExpirationForkHeight 161 expiresAt := func(c *Claim) int32 { 162 if c.AcceptedAt+ot > fk { 163 return c.AcceptedAt + et 164 } 165 return c.AcceptedAt + ot 166 } 167 168 changes := 0 169 update := func(items ClaimList, sums map[string]int64) ClaimList { 170 for i := 0; i < len(items); i++ { 171 c := items[i] 172 if c.Status == Accepted && c.ActiveAt <= height && c.VisibleAt <= height { 173 c.setStatus(Activated) 174 changes++ 175 if sums != nil { 176 sums[c.ClaimID.Key()] += c.Amount 177 } 178 } 179 if c.Status == Deactivated || expiresAt(c) <= height { 180 if i < len(items)-1 { 181 items[i] = items[len(items)-1] 182 i-- 183 } 184 items = items[:len(items)-1] 185 changes++ 186 if sums != nil && c.Status != Deactivated { 187 sums[c.ClaimID.Key()] -= c.Amount 188 } 189 } 190 } 191 return items 192 } 193 n.Claims = update(n.Claims, nil) 194 n.Supports = update(n.Supports, n.SupportSums) 195 return changes 196 } 197 198 // NextUpdate returns the nearest height in the future that the node should 199 // be refreshed due to changes of claims or supports. 200 func (n Node) NextUpdate() int32 { 201 202 ot := param.ActiveParams.OriginalClaimExpirationTime 203 et := param.ActiveParams.ExtendedClaimExpirationTime 204 fk := param.ActiveParams.ExtendedClaimExpirationForkHeight 205 expiresAt := func(c *Claim) int32 { 206 if c.AcceptedAt+ot > fk { 207 return c.AcceptedAt + et 208 } 209 return c.AcceptedAt + ot 210 } 211 212 next := int32(math.MaxInt32) 213 214 for _, c := range n.Claims { 215 ea := expiresAt(c) 216 if ea < next { 217 next = ea 218 } 219 // if we're not active, we need to go to activeAt unless we're still invisible there 220 if c.Status == Accepted { 221 min := c.ActiveAt 222 if c.VisibleAt > min { 223 min = c.VisibleAt 224 } 225 if min < next { 226 next = min 227 } 228 } 229 } 230 231 for _, s := range n.Supports { 232 es := expiresAt(s) 233 if es < next { 234 next = es 235 } 236 if s.Status == Accepted { 237 min := s.ActiveAt 238 if s.VisibleAt > min { 239 min = s.VisibleAt 240 } 241 if min < next { 242 next = min 243 } 244 } 245 } 246 247 return next 248 } 249 250 func (n Node) findBestClaim() *Claim { 251 252 // WARNING: this method is called billions of times. 253 // if we just had some easy way to know that our best claim was the first one in the list... 254 // or it may be faster to cache effective amount in the db at some point. 255 256 var best *Claim 257 var bestAmount int64 258 for _, candidate := range n.Claims { 259 260 // not using switch here for performance reasons 261 if candidate.Status != Activated { 262 continue 263 } 264 265 if best == nil { 266 best = candidate 267 continue 268 } 269 270 candidateAmount := candidate.Amount + n.SupportSums[candidate.ClaimID.Key()] 271 if bestAmount <= 0 { 272 bestAmount = best.Amount + n.SupportSums[best.ClaimID.Key()] 273 } 274 275 switch { 276 case candidateAmount > bestAmount: 277 best = candidate 278 bestAmount = candidateAmount 279 case candidateAmount < bestAmount: 280 continue 281 case candidate.AcceptedAt < best.AcceptedAt: 282 best = candidate 283 bestAmount = candidateAmount 284 case candidate.AcceptedAt > best.AcceptedAt: 285 continue 286 case OutPointLess(candidate.OutPoint, best.OutPoint): 287 best = candidate 288 bestAmount = candidateAmount 289 } 290 } 291 292 return best 293 } 294 295 func (n *Node) activateAllClaims(height int32) int { 296 count := 0 297 for _, c := range n.Claims { 298 if c.Status == Accepted && c.ActiveAt > height && c.VisibleAt <= height { 299 c.setActiveAt(height) // don't necessarily need to change this number? 300 c.setStatus(Activated) 301 count++ 302 } 303 } 304 305 for _, s := range n.Supports { 306 if s.Status == Accepted && s.ActiveAt > height && s.VisibleAt <= height { 307 s.setActiveAt(height) // don't necessarily need to change this number? 308 s.setStatus(Activated) 309 count++ 310 n.SupportSums[s.ClaimID.Key()] += s.Amount 311 } 312 } 313 return count 314 } 315 316 func (n *Node) SortClaimsByBid() { 317 318 // purposefully sorting by descent via func parameter order: 319 sort.Slice(n.Claims, func(j, i int) bool { 320 // SupportSums only include active values; do the same for amount. No active claim will have a zero amount 321 iAmount := n.SupportSums[n.Claims[i].ClaimID.Key()] 322 if n.Claims[i].Status == Activated { 323 iAmount += n.Claims[i].Amount 324 } 325 jAmount := n.SupportSums[n.Claims[j].ClaimID.Key()] 326 if n.Claims[j].Status == Activated { 327 jAmount += n.Claims[j].Amount 328 } 329 switch { 330 case iAmount < jAmount: 331 return true 332 case iAmount > jAmount: 333 return false 334 case n.Claims[i].AcceptedAt > n.Claims[j].AcceptedAt: 335 return true 336 case n.Claims[i].AcceptedAt < n.Claims[j].AcceptedAt: 337 return false 338 } 339 return OutPointLess(n.Claims[j].OutPoint, n.Claims[i].OutPoint) 340 }) 341 } 342 343 func (n *Node) Clone() *Node { 344 clone := New() 345 if n.SupportSums != nil { 346 clone.SupportSums = map[string]int64{} 347 for key, value := range n.SupportSums { 348 clone.SupportSums[key] = value 349 } 350 } 351 clone.Supports = make(ClaimList, len(n.Supports)) 352 for i, support := range n.Supports { 353 clone.Supports[i] = &Claim{} 354 *clone.Supports[i] = *support 355 } 356 clone.Claims = make(ClaimList, len(n.Claims)) 357 for i, claim := range n.Claims { 358 clone.Claims[i] = &Claim{} 359 *clone.Claims[i] = *claim 360 } 361 clone.TakenOverAt = n.TakenOverAt 362 if n.BestClaim != nil { 363 clone.BestClaim = clone.Claims.find(byID(n.BestClaim.ClaimID)) 364 } 365 return clone 366 }