diff --git a/blockchain/claimtrie.go b/blockchain/claimtrie.go index 0bf89978..790c7e76 100644 --- a/blockchain/claimtrie.go +++ b/blockchain/claimtrie.go @@ -171,6 +171,9 @@ func (b *BlockChain) GetClaimsForName(height int32, name string) (string, *node. n, err := b.claimTrie.NodeAt(height, normalizedName) if err != nil { + if n != nil { + n.Close() + } return string(normalizedName), nil, err } diff --git a/claimtrie/claimtrie_test.go b/claimtrie/claimtrie_test.go index 61194f1a..ebd62f03 100644 --- a/claimtrie/claimtrie_test.go +++ b/claimtrie/claimtrie_test.go @@ -133,6 +133,7 @@ func TestNormalizationFork(t *testing.T) { r.NoError(err) r.NotNil(n.BestClaim) r.Equal(int32(1), n.TakenOverAt) + n.Close() o8 := wire.OutPoint{Hash: hash, Index: 8} err = ct.AddClaim([]byte("aNĖƒEJO"), o8, change.NewClaimID(o8), 8) @@ -150,6 +151,7 @@ func TestNormalizationFork(t *testing.T) { n, err = ct.nodeManager.NodeAt(ct.nodeManager.Height(), []byte("test")) r.NoError(err) r.Equal(int64(18), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()]) + n.Close() } func TestActivationsOnNormalizationFork(t *testing.T) { @@ -229,6 +231,7 @@ func verifyBestIndex(t *testing.T, ct *ClaimTrie, name string, idx uint32, claim if claims > 0 { r.Equal(idx, n.BestClaim.OutPoint.Index) } + n.Close() } func TestRebuild(t *testing.T) { diff --git a/claimtrie/cmd/cmd/node.go b/claimtrie/cmd/cmd/node.go index 4ae82e6b..008340d2 100644 --- a/claimtrie/cmd/cmd/node.go +++ b/claimtrie/cmd/cmd/node.go @@ -108,6 +108,7 @@ func NewNodeReplayCommand() *cobra.Command { } showNode(n) + n.Close() return nil }, } diff --git a/claimtrie/node/manager.go b/claimtrie/node/manager.go index 912940e1..a0667b45 100644 --- a/claimtrie/node/manager.go +++ b/claimtrie/node/manager.go @@ -65,6 +65,9 @@ func (nm *BaseManager) NodeAt(height int32, name []byte) (*Node, error) { n, err = nm.newNodeFromChanges(changes, height) if err != nil { + if n != nil { + n.Close() + } return nil, errors.Wrap(err, "in new node") } // TODO: how can we tell what needs to be cached? @@ -78,6 +81,9 @@ func (nm *BaseManager) NodeAt(height int32, name []byte) (*Node, error) { n = n.Clone() updated, err := nm.updateFromChanges(n, changes, height) if err != nil { + if n != nil { + n.Close() + } return nil, errors.Wrap(err, "in update from changes") } if !updated { @@ -122,6 +128,7 @@ func (nm *BaseManager) updateFromChanges(n *Node, changes []change.Change, heigh delay := nm.getDelayForName(n, chg) err := n.ApplyChange(chg, delay) if err != nil { + n.Close() return false, errors.Wrap(err, "in apply change") } } @@ -129,6 +136,7 @@ func (nm *BaseManager) updateFromChanges(n *Node, changes []change.Change, heigh if count <= 0 { // we applied no changes, which means we shouldn't exist if we had all the changes // or might mean nothing significant if we are applying a partial changeset + n.Close() return false, nil } lastChange := changes[count-1] @@ -418,10 +426,13 @@ func (nm *BaseManager) hasChildren(name []byte, height int32, spentChildren map[ return true // children that are spent in the same block cannot count as active children } n, _ := nm.newNodeFromChanges(changes, height) - if n != nil && n.HasActiveBestClaim() { - c[changes[0].Name[len(name)]] = true - if len(c) >= required { - return false + if n != nil { + defer n.Close() + if n.HasActiveBestClaim() { + c[changes[0].Name[len(name)]] = true + if len(c) >= required { + return false + } } } return true diff --git a/claimtrie/node/manager_test.go b/claimtrie/node/manager_test.go index c907bb4c..bb13296c 100644 --- a/claimtrie/node/manager_test.go +++ b/claimtrie/node/manager_test.go @@ -147,6 +147,7 @@ func TestNodeSort(t *testing.T) { r.True(OutPointLess(*out1, *out3)) n := New() + defer n.Close() n.Claims = append(n.Claims, &Claim{OutPoint: *out1, AcceptedAt: 3, Amount: 3, ClaimID: change.ClaimID{1}}) n.Claims = append(n.Claims, &Claim{OutPoint: *out2, AcceptedAt: 3, Amount: 3, ClaimID: change.ClaimID{2}}) n.handleExpiredAndActivated(3) @@ -167,6 +168,7 @@ func TestClaimSort(t *testing.T) { param.ActiveParams.ExtendedClaimExpirationTime = 1000 n := New() + defer n.Close() n.Claims = append(n.Claims, &Claim{OutPoint: *out2, AcceptedAt: 3, Amount: 3, ClaimID: change.ClaimID{2}, Status: Activated}) n.Claims = append(n.Claims, &Claim{OutPoint: *out3, AcceptedAt: 3, Amount: 2, ClaimID: change.ClaimID{3}, Status: Activated}) n.Claims = append(n.Claims, &Claim{OutPoint: *out3, AcceptedAt: 4, Amount: 2, ClaimID: change.ClaimID{4}, Status: Activated}) diff --git a/claimtrie/node/node.go b/claimtrie/node/node.go index 2e072d27..1eaccbe9 100644 --- a/claimtrie/node/node.go +++ b/claimtrie/node/node.go @@ -4,6 +4,7 @@ import ( "fmt" "math" "sort" + "sync" "github.com/lbryio/lbcd/claimtrie/change" "github.com/lbryio/lbcd/claimtrie/param" @@ -26,6 +27,27 @@ func (n *Node) HasActiveBestClaim() bool { return n.BestClaim != nil && n.BestClaim.Status == Activated } +var claimPool = sync.Pool{ + New: func() interface{} { + return &Claim{} + }, +} + +func (n *Node) Close() { + n.BestClaim = nil + n.SupportSums = nil + + for i := range n.Claims { + claimPool.Put(n.Claims[i]) + } + n.Claims = nil + + for i := range n.Supports { + claimPool.Put(n.Supports[i]) + } + n.Supports = nil +} + func (n *Node) ApplyChange(chg change.Change, delay int32) error { visibleAt := chg.VisibleHeight @@ -35,17 +57,19 @@ func (n *Node) ApplyChange(chg change.Change, delay int32) error { 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 + c := claimPool.Get().(*Claim) + // set all 8 fields on c as they aren't initialized to 0: + c.Status = Accepted + c.OutPoint = chg.OutPoint + c.Amount = chg.Amount + c.ClaimID = chg.ClaimID + // CreatedAt: chg.Height, + c.AcceptedAt = chg.Height + c.ActiveAt = chg.Height + delay + c.VisibleAt = visibleAt + c.Sequence = int32(len(n.Claims)) + // removed this after proving ResetHeight works: + // old := n.Claims.find(byOut(chg.OutPoint)) // if old != nil { // return errors.Errorf("CONFLICT WITH EXISTING TXO! Name: %s, Height: %d", chg.Name, chg.Height) // } @@ -63,7 +87,6 @@ func (n *Node) ApplyChange(chg change.Change, delay int32) error { // '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 { @@ -82,14 +105,18 @@ func (n *Node) ApplyChange(chg change.Change, delay int32) error { 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, - }) + s := claimPool.Get().(*Claim) + // set all 8 fields on s: + s.Status = Accepted + s.OutPoint = chg.OutPoint + s.Amount = chg.Amount + s.ClaimID = chg.ClaimID + s.AcceptedAt = chg.Height + s.ActiveAt = chg.Height + delay + s.VisibleAt = visibleAt + s.Sequence = int32(len(n.Supports)) + + n.Supports = append(n.Supports, s) case change.SpendSupport: s := n.Supports.find(byOut(chg.OutPoint)) diff --git a/claimtrie/node/normalizing_manager.go b/claimtrie/node/normalizing_manager.go index 604fa34d..ba2ae922 100644 --- a/claimtrie/node/normalizing_manager.go +++ b/claimtrie/node/normalizing_manager.go @@ -72,6 +72,7 @@ func (nm *NormalizingManager) addNormalizationForkChangesIfNecessary(height int3 if err != nil || n == nil { return true } + defer n.Close() for _, c := range n.Claims { nm.Manager.AppendChange(change.Change{ Type: change.AddClaim, diff --git a/rpcclaimtrie.go b/rpcclaimtrie.go index 11706920..6999f8cd 100644 --- a/rpcclaimtrie.go +++ b/rpcclaimtrie.go @@ -106,6 +106,7 @@ func handleGetClaimsForName(s *rpcServer, cmd interface{}, _ <-chan struct{}) (i Message: "Message: " + err.Error(), } } + defer n.Close() var results []btcjson.ClaimResult for i := range n.Claims { @@ -140,6 +141,7 @@ func handleGetClaimsForNameByID(s *rpcServer, cmd interface{}, _ <-chan struct{} Message: "Message: " + err.Error(), } } + defer n.Close() var results []btcjson.ClaimResult for i := 0; i < len(n.Claims); i++ { @@ -179,6 +181,7 @@ func handleGetClaimsForNameByBid(s *rpcServer, cmd interface{}, _ <-chan struct{ Message: "Message: " + err.Error(), } } + defer n.Close() var results []btcjson.ClaimResult for _, b := range c.Bids { // claims are already sorted in bid order @@ -215,6 +218,7 @@ func handleGetClaimsForNameBySeq(s *rpcServer, cmd interface{}, _ <-chan struct{ Message: "Message: " + err.Error(), } } + defer n.Close() sm := map[int32]bool{} for _, seq := range c.Sequences {