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  }