1 package blockchain
2 3 import (
4 "fmt"
5 "github.com/p9c/p9/pkg/block"
6 "time"
7 8 "github.com/p9c/p9/pkg/chaincfg"
9 "github.com/p9c/p9/pkg/chainhash"
10 "github.com/p9c/p9/pkg/txscript"
11 "github.com/p9c/p9/pkg/util"
12 )
13 14 // CheckpointConfirmations is the number of blocks before the end of the current best block chain that a good checkpoint
15 // candidate must be. TODO: review this and add it to the fork spec
16 const CheckpointConfirmations = 2016
17 18 // newHashFromStr converts the passed big-endian hex string into a chainhash.Hash.
19 //
20 // It only differs from the one available in chainhash in that it ignores the error since it will only (and must only)
21 // be called with hard-coded, and therefore known good, hashes.
22 func newHashFromStr(hexStr string) *chainhash.Hash {
23 hash, _ := chainhash.NewHashFromStr(hexStr)
24 return hash
25 }
26 27 // Checkpoints returns a slice of checkpoints ( regardless of whether they are already known). When there are no
28 // checkpoints for the chain, it will return nil.
29 //
30 // This function is safe for concurrent access.
31 func (b *BlockChain) Checkpoints() []chaincfg.Checkpoint {
32 return b.checkpoints
33 }
34 35 // HasCheckpoints returns whether this BlockChain has checkpoints defined.
36 //
37 // This function is safe for concurrent access.
38 func (b *BlockChain) HasCheckpoints() bool {
39 return len(b.checkpoints) > 0
40 }
41 42 // LatestCheckpoint returns the most recent checkpoint (regardless of whether it is already known). When there are no
43 // defined checkpoints for the active chain instance, it will return nil.
44 //
45 // This function is safe for concurrent access.
46 func (b *BlockChain) LatestCheckpoint() *chaincfg.Checkpoint {
47 if !b.HasCheckpoints() {
48 return nil
49 }
50 return &b.checkpoints[len(b.checkpoints)-1]
51 }
52 53 // verifyCheckpoint returns whether the passed block height and hash combination match the checkpoint data. It also
54 // returns true if there is no checkpoint data for the passed block height.
55 func (b *BlockChain) verifyCheckpoint(height int32, hash *chainhash.Hash) bool {
56 if !b.HasCheckpoints() {
57 return true
58 }
59 // Nothing to check if there is no checkpoint data for the block height.
60 checkpoint, exists := b.checkpointsByHeight[height]
61 if !exists {
62 return true
63 }
64 if !checkpoint.Hash.IsEqual(hash) {
65 return false
66 }
67 I.F("Verified checkpoint at height %d/block %s", checkpoint.Height,
68 checkpoint.Hash,
69 )
70 return true
71 }
72 73 // findPreviousCheckpoint finds the most recent checkpoint that is already available in the downloaded portion of the
74 // block chain and returns the associated block node. It returns nil if a checkpoint can't be found ( this should really
75 // only happen for blocks before the first checkpoint).
76 //
77 // This function MUST be called with the chain lock held (for reads).
78 func (b *BlockChain) findPreviousCheckpoint() (*BlockNode, error) {
79 if !b.HasCheckpoints() {
80 return nil, nil
81 }
82 // Perform the initial search to find and cache the latest known checkpoint if the best chain is not known yet or we
83 // haven't already previously searched.
84 checkpoints := b.checkpoints
85 numCheckpoints := len(checkpoints)
86 if b.checkpointNode == nil && b.nextCheckpoint == nil {
87 // Loop backwards through the available checkpoints to find one that is already available.
88 for i := numCheckpoints - 1; i >= 0; i-- {
89 node := b.Index.LookupNode(checkpoints[i].Hash)
90 if node == nil || !b.BestChain.Contains(node) {
91 continue
92 }
93 // Checkpoint found. Cache it for future lookups and set the next expected checkpoint accordingly.
94 b.checkpointNode = node
95 if i < numCheckpoints-1 {
96 b.nextCheckpoint = &checkpoints[i+1]
97 }
98 return b.checkpointNode, nil
99 }
100 // No known latest checkpoint. This will only happen on blocks before the first known checkpoint. So, set the
101 // next expected checkpoint to the first checkpoint and return the fact there is no latest known checkpoint
102 // block.
103 b.nextCheckpoint = &checkpoints[0]
104 return nil, nil
105 }
106 // At this point we've already searched for the latest known checkpoint, so when there is no next checkpoint, the
107 // current checkpoint lockin will always be the latest known
108 // checkpoint.
109 if b.nextCheckpoint == nil {
110 return b.checkpointNode, nil
111 }
112 // When there is a next checkpoint and the height of the current best chain does not exceed it, the current
113 // checkpoint lockin is still the latest known checkpoint.
114 if b.BestChain.Tip().height < b.nextCheckpoint.Height {
115 return b.checkpointNode, nil
116 }
117 // We've reached or exceeded the next checkpoint height.
118 //
119 // Note that once a checkpoint lockin has been reached, forks are prevented from any blocks before the checkpoint,
120 // so we don't have to worry about the checkpoint going away out from under us due to a chain reorganize.
121 //
122 // Cache the latest known checkpoint for future lookups.
123 //
124 // Note that if this lookup fails something is very wrong since the chain has already passed the checkpoint which
125 // was verified as accurate before inserting it.
126 checkpointNode := b.Index.LookupNode(b.nextCheckpoint.Hash)
127 if checkpointNode == nil {
128 return nil, AssertError(fmt.Sprintf("findPreviousCheckpoint "+
129 "failed lookup of known good block node %s",
130 b.nextCheckpoint.Hash,
131 ),
132 )
133 }
134 b.checkpointNode = checkpointNode
135 // Set the next expected checkpoint.
136 checkpointIndex := -1
137 for i := numCheckpoints - 1; i >= 0; i-- {
138 if checkpoints[i].Hash.IsEqual(b.nextCheckpoint.Hash) {
139 checkpointIndex = i
140 break
141 }
142 }
143 b.nextCheckpoint = nil
144 if checkpointIndex != -1 && checkpointIndex < numCheckpoints-1 {
145 b.nextCheckpoint = &checkpoints[checkpointIndex+1]
146 }
147 return b.checkpointNode, nil
148 }
149 150 // isNonstandardTransaction determines whether a transaction contains any scripts which are not one of the standard
151 // types.
152 func isNonstandardTransaction(tx *util.Tx) bool {
153 // Chk all of the output public key scripts for non-standard scripts.
154 for _, txOut := range tx.MsgTx().TxOut {
155 scriptClass := txscript.GetScriptClass(txOut.PkScript)
156 if scriptClass == txscript.NonStandardTy {
157 return true
158 }
159 }
160 return false
161 }
162 163 // IsCheckpointCandidate returns whether or not the passed block is a good checkpoint candidate.
164 //
165 // The factors used to determine a good checkpoint are:
166 //
167 // - The block must be in the main chain
168 //
169 // - The block must be at least 'CheckpointConfirmations' blocks prior to the current end of the main chain
170 //
171 // - The timestamps for the blocks before and after the checkpoint must have timestamps which are also before and after
172 // the checkpoint, respectively (due to the median time allowance this is not always the case)
173 //
174 // - The block must not contain any strange transaction such as those with nonstandard scripts
175 //
176 // The intent is that candidates are reviewed by a developer to make the final decision and then manually added to the
177 // list of checkpoints for a network. This function is safe for concurrent access.
178 func (b *BlockChain) IsCheckpointCandidate(block *block.Block) (bool, error) {
179 b.ChainLock.RLock()
180 defer b.ChainLock.RUnlock()
181 // A checkpoint must be in the main chain.
182 node := b.Index.LookupNode(block.Hash())
183 if node == nil || !b.BestChain.Contains(node) {
184 return false, nil
185 }
186 // Ensure the height of the passed block and the entry for the block in the main chain match. This should always be
187 // the case unless the caller provided an invalid block.
188 if node.height != block.Height() {
189 return false, fmt.Errorf("passed block height of %d does not "+
190 "match the main chain height of %d", block.Height(),
191 node.height,
192 )
193 }
194 // A checkpoint must be at least CheckpointConfirmations blocks before the end of the main chain.
195 mainChainHeight := b.BestChain.Tip().height
196 if node.height > (mainChainHeight - CheckpointConfirmations) {
197 return false, nil
198 }
199 // A checkpoint must be have at least one block after it. This should always succeed since the check above already
200 // made sure it is CheckpointConfirmations back, but be safe in case the constant changes.
201 nextNode := b.BestChain.Next(node)
202 if nextNode == nil {
203 return false, nil
204 }
205 // A checkpoint must be have at least one block before it.
206 if node.parent == nil {
207 return false, nil
208 }
209 // A checkpoint must have timestamps for the block and the blocks on either side of it in order (due to the median
210 // time allowance this is not always the case).
211 prevTime := time.Unix(node.parent.timestamp, 0)
212 curTime := block.WireBlock().Header.Timestamp
213 nextTime := time.Unix(nextNode.timestamp, 0)
214 if prevTime.After(curTime) || nextTime.Before(curTime) {
215 return false, nil
216 }
217 // A checkpoint must have transactions that only contain standard scripts.
218 for _, tx := range block.Transactions() {
219 if isNonstandardTransaction(tx) {
220 return false, nil
221 }
222 }
223 // All of the checks passed, so the block is a candidate.
224 return true, nil
225 }
226