github.com/lbryio/lbcd@v0.22.119/claimtrie/node/manager.go (about) 1 package node 2 3 import ( 4 "fmt" 5 "sort" 6 7 "github.com/pkg/errors" 8 9 "github.com/lbryio/lbcd/chaincfg/chainhash" 10 "github.com/lbryio/lbcd/claimtrie/change" 11 "github.com/lbryio/lbcd/claimtrie/param" 12 ) 13 14 type Manager interface { 15 AppendChange(chg change.Change) 16 IncrementHeightTo(height int32, temporary bool) ([][]byte, error) 17 DecrementHeightTo(affectedNames [][]byte, height int32) ([][]byte, error) 18 Height() int32 19 Close() error 20 NodeAt(height int32, name []byte) (*Node, error) 21 IterateNames(predicate func(name []byte) bool) 22 Hash(name []byte) (*chainhash.Hash, int32) 23 Flush() error 24 ClearCache() 25 } 26 27 type BaseManager struct { 28 repo Repo 29 30 height int32 31 changes []change.Change 32 33 tempChanges map[string][]change.Change 34 35 cache *Cache 36 } 37 38 func NewBaseManager(repo Repo) (*BaseManager, error) { 39 40 nm := &BaseManager{ 41 repo: repo, 42 cache: NewCache(10000), // TODO: how many should we cache? 43 } 44 45 return nm, nil 46 } 47 48 func (nm *BaseManager) ClearCache() { 49 nm.cache.clear() 50 } 51 52 func (nm *BaseManager) NodeAt(height int32, name []byte) (*Node, error) { 53 54 n, changes, oldHeight := nm.cache.fetch(name, height) 55 if n == nil { 56 changes, err := nm.repo.LoadChanges(name) 57 if err != nil { 58 return nil, errors.Wrap(err, "in load changes") 59 } 60 61 if nm.tempChanges != nil { // making an assumption that we only ever have tempChanges for a single block 62 changes = append(changes, nm.tempChanges[string(name)]...) 63 } 64 65 n, err = nm.newNodeFromChanges(changes, height) 66 if err != nil { 67 return nil, errors.Wrap(err, "in new node") 68 } 69 // TODO: how can we tell what needs to be cached? 70 if nm.tempChanges == nil && height == nm.height && n != nil && (len(changes) > 4 || len(name) < 12) { 71 nm.cache.insert(name, n, height) 72 } 73 } else { 74 if nm.tempChanges != nil { // making an assumption that we only ever have tempChanges for a single block 75 changes = append(changes, nm.tempChanges[string(name)]...) 76 n = n.Clone() 77 } else if height != nm.height { 78 n = n.Clone() 79 } 80 updated, err := nm.updateFromChanges(n, changes, height) 81 if err != nil { 82 return nil, errors.Wrap(err, "in update from changes") 83 } 84 if !updated { 85 n.AdjustTo(oldHeight, height, name) 86 } 87 if nm.tempChanges == nil && height == nm.height { 88 nm.cache.insert(name, n, height) 89 } 90 } 91 92 return n, nil 93 } 94 95 // Node returns a node at the current height. 96 // The returned node may have pending changes. 97 func (nm *BaseManager) node(name []byte) (*Node, error) { 98 return nm.NodeAt(nm.height, name) 99 } 100 101 func (nm *BaseManager) updateFromChanges(n *Node, changes []change.Change, height int32) (bool, error) { 102 103 count := len(changes) 104 if count == 0 { 105 return false, nil 106 } 107 previous := changes[0].Height 108 109 for i, chg := range changes { 110 if chg.Height < previous { 111 panic("expected the changes to be in order by height") 112 } 113 if chg.Height > height { 114 count = i 115 break 116 } 117 118 if previous < chg.Height { 119 n.AdjustTo(previous, chg.Height-1, chg.Name) // update bids and activation 120 previous = chg.Height 121 } 122 123 delay := nm.getDelayForName(n, chg) 124 err := n.ApplyChange(chg, delay) 125 if err != nil { 126 return false, errors.Wrap(err, "in apply change") 127 } 128 } 129 130 if count <= 0 { 131 // we applied no changes, which means we shouldn't exist if we had all the changes 132 // or might mean nothing significant if we are applying a partial changeset 133 return false, nil 134 } 135 lastChange := changes[count-1] 136 n.AdjustTo(lastChange.Height, height, lastChange.Name) 137 return true, nil 138 } 139 140 // newNodeFromChanges returns a new Node constructed from the changes. 141 // The changes must preserve their order received. 142 func (nm *BaseManager) newNodeFromChanges(changes []change.Change, height int32) (*Node, error) { 143 144 if len(changes) == 0 { 145 return nil, nil 146 } 147 148 n := New() 149 updated, err := nm.updateFromChanges(n, changes, height) 150 if err != nil { 151 return nil, errors.Wrap(err, "in update from changes") 152 } 153 if updated { 154 return n, nil 155 } 156 return nil, nil 157 } 158 159 func (nm *BaseManager) AppendChange(chg change.Change) { 160 161 nm.changes = append(nm.changes, chg) 162 163 // worth putting in this kind of thing pre-emptively? 164 // log.Debugf("CHG: %d, %s, %v, %s, %d", chg.Height, chg.Name, chg.Type, chg.ClaimID, chg.Amount) 165 } 166 167 func collectChildNames(changes []change.Change) { 168 // we need to determine which children (names that start with the same name) go with which change 169 // if we have the names in order then we can avoid iterating through all names in the change list 170 // and we can possibly reuse the previous list. 171 172 // what would happen in the old code: 173 // spending a claim (which happens before every update) could remove a node from the cached trie 174 // in which case we would fall back on the data from the previous block (where it obviously wasn't spent). 175 // It would only delete the node if it had no children, but have even some rare situations 176 // Where all of the children happen to be deleted first. That's what we must detect here. 177 178 // Algorithm: 179 // For each non-spend change 180 // Loop through all the spends before you and add them to your child list if they are your child 181 182 type pair struct { 183 name string 184 order int 185 } 186 187 spends := make([]pair, 0, len(changes)) 188 for i := range changes { 189 t := changes[i].Type 190 if t != change.SpendClaim { 191 continue 192 } 193 spends = append(spends, pair{string(changes[i].Name), i}) 194 } 195 sort.Slice(spends, func(i, j int) bool { 196 return spends[i].name < spends[j].name 197 }) 198 199 for i := range changes { 200 t := changes[i].Type 201 if t == change.SpendClaim || t == change.SpendSupport { 202 continue 203 } 204 a := string(changes[i].Name) 205 sc := map[string]bool{} 206 idx := sort.Search(len(spends), func(k int) bool { 207 return spends[k].name > a 208 }) 209 for idx < len(spends) { 210 b := spends[idx].name 211 if len(b) <= len(a) || a != b[:len(a)] { 212 break // since they're ordered alphabetically, we should be able to break out once we're past matches 213 } 214 if spends[idx].order < i { 215 sc[b] = true 216 } 217 idx++ 218 } 219 changes[i].SpentChildren = sc 220 } 221 } 222 223 // to understand the above function, it may be helpful to refer to the slower implementation: 224 //func collectChildNamesSlow(changes []change.Change) { 225 // for i := range changes { 226 // t := changes[i].Type 227 // if t == change.SpendClaim || t == change.SpendSupport { 228 // continue 229 // } 230 // a := changes[i].Name 231 // sc := map[string]bool{} 232 // for j := 0; j < i; j++ { 233 // t = changes[j].Type 234 // if t != change.SpendClaim { 235 // continue 236 // } 237 // b := changes[j].Name 238 // if len(b) >= len(a) && bytes.Equal(a, b[:len(a)]) { 239 // sc[string(b)] = true 240 // } 241 // } 242 // changes[i].SpentChildren = sc 243 // } 244 //} 245 246 func (nm *BaseManager) IncrementHeightTo(height int32, temporary bool) ([][]byte, error) { 247 248 if height <= nm.height { 249 panic("invalid height") 250 } 251 252 if height >= param.ActiveParams.MaxRemovalWorkaroundHeight { 253 // not technically needed until block 884430, but to be true to the arbitrary rollback length... 254 collectChildNames(nm.changes) 255 } 256 257 if temporary { 258 if nm.tempChanges != nil { 259 return nil, errors.Errorf("expected nil temporary changes") 260 } 261 nm.tempChanges = map[string][]change.Change{} 262 } 263 names := make([][]byte, 0, len(nm.changes)) 264 for i := range nm.changes { 265 names = append(names, nm.changes[i].Name) 266 if temporary { 267 name := string(nm.changes[i].Name) 268 nm.tempChanges[name] = append(nm.tempChanges[name], nm.changes[i]) 269 } 270 } 271 272 if !temporary { 273 nm.cache.addChanges(nm.changes, height) 274 if err := nm.repo.AppendChanges(nm.changes); err != nil { // destroys names 275 return nil, errors.Wrap(err, "in append changes") 276 } 277 } 278 279 // Truncate the buffer size to zero. 280 if len(nm.changes) > 1000 { // TODO: determine a good number here 281 nm.changes = nil // release the RAM 282 } else { 283 nm.changes = nm.changes[:0] 284 } 285 nm.height = height 286 287 return names, nil 288 } 289 290 func (nm *BaseManager) DecrementHeightTo(affectedNames [][]byte, height int32) ([][]byte, error) { 291 if height >= nm.height { 292 return affectedNames, errors.Errorf("invalid height of %d for %d", height, nm.height) 293 } 294 295 if nm.tempChanges != nil { 296 if height != nm.height-1 { 297 return affectedNames, errors.Errorf("invalid temporary rollback at %d to %d", height, nm.height) 298 } 299 for key := range nm.tempChanges { 300 affectedNames = append(affectedNames, []byte(key)) 301 } 302 nm.tempChanges = nil 303 } else { 304 for _, name := range affectedNames { 305 if err := nm.repo.DropChanges(name, height); err != nil { 306 return affectedNames, errors.Wrap(err, "in drop changes") 307 } 308 } 309 310 nm.cache.drop(affectedNames) 311 } 312 nm.height = height 313 314 return affectedNames, nil 315 } 316 317 func (nm *BaseManager) getDelayForName(n *Node, chg change.Change) int32 { 318 // Note: we don't consider the active status of BestClaim here on purpose. 319 // That's because we deactivate and reactivate as part of claim updates. 320 // However, the final status will be accounted for when we compute the takeover heights; 321 // claims may get activated early at that point. 322 323 hasBest := n.BestClaim != nil 324 if hasBest && n.BestClaim.ClaimID == chg.ClaimID { 325 return 0 326 } 327 if chg.ActiveHeight >= chg.Height { // ActiveHeight is usually unset (aka, zero) 328 return chg.ActiveHeight - chg.Height 329 } 330 if !hasBest { 331 return 0 332 } 333 334 delay := calculateDelay(chg.Height, n.TakenOverAt) 335 if delay > 0 && nm.aWorkaroundIsNeeded(n, chg) { 336 if chg.Height >= nm.height { 337 LogOnce(fmt.Sprintf("Delay workaround applies to %s at %d, ClaimID: %s", 338 chg.Name, chg.Height, chg.ClaimID)) 339 } 340 return 0 341 } 342 return delay 343 } 344 345 func hasZeroActiveClaims(n *Node) bool { 346 // this isn't quite the same as having an active best (since that is only updated after all changes are processed) 347 for _, c := range n.Claims { 348 if c.Status == Activated { 349 return false 350 } 351 } 352 return true 353 } 354 355 // aWorkaroundIsNeeded handles bugs that existed in previous versions 356 func (nm *BaseManager) aWorkaroundIsNeeded(n *Node, chg change.Change) bool { 357 358 if chg.Type == change.SpendClaim || chg.Type == change.SpendSupport { 359 return false 360 } 361 362 if chg.Height >= param.ActiveParams.MaxRemovalWorkaroundHeight { 363 // TODO: hard fork this out; it's a bug from previous versions: 364 365 // old 17.3 C++ code we're trying to mimic (where empty means no active claims): 366 // auto it = nodesToAddOrUpdate.find(name); // nodesToAddOrUpdate is the working changes, base is previous block 367 // auto answer = (it || (it = base->find(name))) && !it->empty() ? nNextHeight - it->nHeightOfLastTakeover : 0; 368 369 return hasZeroActiveClaims(n) && nm.hasChildren(chg.Name, chg.Height, chg.SpentChildren, 2) 370 } else if len(n.Claims) > 0 { 371 // NOTE: old code had a bug in it where nodes with no claims but with children would get left in the cache after removal. 372 // This would cause the getNumBlocksOfContinuousOwnership to return zero (causing incorrect takeover height calc). 373 w, ok := param.DelayWorkarounds[string(chg.Name)] 374 if ok { 375 for _, h := range w { 376 if chg.Height == h { 377 return true 378 } 379 } 380 } 381 } 382 return false 383 } 384 385 func calculateDelay(curr, tookOver int32) int32 { 386 387 delay := (curr - tookOver) / param.ActiveParams.ActiveDelayFactor 388 if delay > param.ActiveParams.MaxActiveDelay { 389 return param.ActiveParams.MaxActiveDelay 390 } 391 392 return delay 393 } 394 395 func (nm *BaseManager) Height() int32 { 396 return nm.height 397 } 398 399 func (nm *BaseManager) Close() error { 400 return errors.WithStack(nm.repo.Close()) 401 } 402 403 func (nm *BaseManager) hasChildren(name []byte, height int32, spentChildren map[string]bool, required int) bool { 404 c := map[byte]bool{} 405 if spentChildren == nil { 406 spentChildren = map[string]bool{} 407 } 408 409 err := nm.repo.IterateChildren(name, func(changes []change.Change) bool { 410 // if the key is unseen, generate a node for it to height 411 // if that node is active then increase the count 412 if len(changes) == 0 { 413 return true 414 } 415 if c[changes[0].Name[len(name)]] { // assuming all names here are longer than starter name 416 return true // we already checked a similar name 417 } 418 if spentChildren[string(changes[0].Name)] { 419 return true // children that are spent in the same block cannot count as active children 420 } 421 n, _ := nm.newNodeFromChanges(changes, height) 422 if n != nil && n.HasActiveBestClaim() { 423 c[changes[0].Name[len(name)]] = true 424 if len(c) >= required { 425 return false 426 } 427 } 428 return true 429 }) 430 return err == nil && len(c) >= required 431 } 432 433 func (nm *BaseManager) IterateNames(predicate func(name []byte) bool) { 434 nm.repo.IterateAll(predicate) 435 } 436 437 func (nm *BaseManager) Hash(name []byte) (*chainhash.Hash, int32) { 438 439 n, err := nm.node(name) 440 if err != nil || n == nil { 441 return nil, 0 442 } 443 if len(n.Claims) > 0 { 444 if n.BestClaim != nil && n.BestClaim.Status == Activated { 445 h := calculateNodeHash(n.BestClaim.OutPoint, n.TakenOverAt) 446 return h, n.NextUpdate() 447 } 448 } 449 return nil, n.NextUpdate() 450 } 451 452 func (nm *BaseManager) Flush() error { 453 return nm.repo.Flush() 454 }