forked from decred/dcrd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
stakenode.go
206 lines (181 loc) · 7.02 KB
/
stakenode.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
// Copyright (c) 2013-2016 The btcsuite developers
// Copyright (c) 2015-2022 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"fmt"
"github.com/decred/dcrd/blockchain/stake/v5"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/database/v3"
)
// maybeFetchNewTickets loads the list of newly maturing tickets for a given
// node by traversing backwards through its parents until it finds the block
// that contains the original tickets to mature if needed.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) maybeFetchNewTickets(node *blockNode) error {
// Nothing to do if the tickets are already loaded. It's important to make
// the distinction here that nil means the value was never looked up, while
// an empty slice means that there are no new tickets at this height.
if node.newTickets != nil {
return nil
}
// No tickets in the live ticket pool are possible before stake enabled
// height.
if node.height < b.chainParams.StakeEnabledHeight {
node.newTickets = []chainhash.Hash{}
return nil
}
// Calculate block number for where new tickets matured from and retrieve
// its block from DB.
matureNode := node.RelativeAncestor(int64(b.chainParams.TicketMaturity))
if matureNode == nil {
return fmt.Errorf("unable to obtain ancestor %d blocks prior to %s "+
"(height %d)", b.chainParams.TicketMaturity, node.hash, node.height)
}
matureBlock, err := b.fetchBlockByNode(matureNode)
if err != nil {
return err
}
// Extract any ticket purchases from the block and cache them.
tickets := []chainhash.Hash{}
for _, stx := range matureBlock.MsgBlock().STransactions {
if stake.IsSStx(stx) {
tickets = append(tickets, stx.TxHash())
}
}
node.newTickets = tickets
return nil
}
// maybeFetchTicketInfo loads and populates prunable ticket information in the
// provided block node if needed.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) maybeFetchTicketInfo(node *blockNode) error {
// Load and populate the tickets maturing in this block when they are not
// already loaded.
if err := b.maybeFetchNewTickets(node); err != nil {
return err
}
// Load and populate the vote and revocation information as needed.
if node.ticketsVoted == nil || node.ticketsRevoked == nil ||
node.votes == nil {
block, err := b.fetchBlockByNode(node)
if err != nil {
return err
}
ticketInfo := stake.FindSpentTicketsInBlock(block.MsgBlock())
b.index.PopulateTicketInfo(node, ticketInfo)
}
return nil
}
// fetchStakeNode returns the stake node associated with the requested node
// while handling the logic to create the stake node if needed. In the majority
// of cases, the stake node either already exists and is simply returned, or it
// can be quickly created when the parent stake node is already available.
// However, it should be noted that, since old stake nodes are pruned, this
// function can be quite expensive if a node deep in history or on a long side
// chain is requested since that requires reconstructing all of the intermediate
// nodes along the path from the existing tip to the requested node that have
// not already been pruned.
//
// This function MUST be called with the chain state lock held (for writes).
func (b *BlockChain) fetchStakeNode(node *blockNode) (*stake.Node, error) {
// Return the cached immutable stake node when it is already loaded.
if node.stakeNode != nil {
return node.stakeNode, nil
}
// Create the requested stake node from the parent stake node if it is
// already loaded as an optimization.
if node.parent != nil && node.parent.stakeNode != nil {
// Populate the prunable ticket information as needed.
if err := b.maybeFetchTicketInfo(node); err != nil {
return nil, err
}
stakeNode, err := node.parent.stakeNode.ConnectNode(node.lotteryIV(),
node.ticketsVoted, node.ticketsRevoked, node.newTickets)
if err != nil {
return nil, err
}
node.stakeNode = stakeNode
return stakeNode, nil
}
// -------------------------------------------------------------------------
// In order to create the stake node, it is necessary to generate a path to
// the stake node from the current tip, which always has the stake node
// loaded, and undo the effects of each block back to, and including, the
// fork point (which might be the requested node itself), and then, in the
// case the target node is on a side chain, replay the effects of each on
// the side chain. In most cases, many of the stake nodes along the path
// will already be loaded, so, they are only regenerated and populated if
// they aren't.
//
// For example, consider the following scenario:
// A -> B -> C -> D
// \-> B' -> C'
//
// Further assume the requested stake node is for C'. The code that follows
// will regenerate and populate (only for those not already loaded) the
// stake nodes for C, B, A, B', and finally, C'.
// -------------------------------------------------------------------------
// Start by undoing the effects from the current tip back to, and including
// the fork point per the above description.
tip := b.bestChain.Tip()
fork := b.bestChain.FindFork(node)
err := b.db.View(func(dbTx database.Tx) error {
for n := tip; n != nil && n != fork; n = n.parent {
// No need to load nodes that are already loaded.
prev := n.parent
if prev == nil || prev.stakeNode != nil {
continue
}
// Generate the previous stake node by starting with the child stake
// node and undoing the modifications caused by the stake details in
// the previous block.
stakeNode, err := n.stakeNode.DisconnectNode(prev.lotteryIV(), nil,
nil, dbTx)
if err != nil {
return err
}
prev.stakeNode = stakeNode
}
return nil
})
if err != nil {
return nil, err
}
// Nothing more to do if the requested node is the fork point itself.
if node == fork {
return node.stakeNode, nil
}
// The requested node is on a side chain, so replay the effects of the
// blocks up to the requested node per the above description.
//
// Note that the blocks between the fork point and the requested node are
// added to the slice from back to front so that they are attached in the
// appropriate order when iterating the slice.
attachNodes := make([]*blockNode, node.height-fork.height)
for n := node; n != nil && n != fork; n = n.parent {
attachNodes[n.height-fork.height-1] = n
}
for _, n := range attachNodes {
// No need to load nodes that are already loaded.
if n.stakeNode != nil {
continue
}
// Populate the prunable ticket information as needed.
if err := b.maybeFetchTicketInfo(n); err != nil {
return nil, err
}
// Generate the stake node by applying the stake details in the current
// block to the previous stake node.
stakeNode, err := n.parent.stakeNode.ConnectNode(n.lotteryIV(),
n.ticketsVoted, n.ticketsRevoked, n.newTickets)
if err != nil {
return nil, err
}
n.stakeNode = stakeNode
}
return node.stakeNode, nil
}