1 package blockchain
2 3 import (
4 "compress/bzip2"
5 "encoding/binary"
6 "fmt"
7 "github.com/p9c/p9/pkg/block"
8 "io"
9 "os"
10 "path/filepath"
11 "strings"
12 "time"
13 14 "github.com/p9c/p9/pkg/chaincfg"
15 "github.com/p9c/p9/pkg/chainhash"
16 "github.com/p9c/p9/pkg/txscript"
17 18 "github.com/p9c/p9/pkg/database"
19 _ "github.com/p9c/p9/pkg/database/ffldb"
20 "github.com/p9c/p9/pkg/wire"
21 )
22 23 const (
24 // testDbType is the database backend type to use for the tests.
25 testDbType = "ffldb"
26 // testDbRoot is the root directory used to create all test databases.
27 testDbRoot = "testdbs"
28 // blockDataNet is the expected network in the test block data.
29 blockDataNet = wire.MainNet
30 )
31 32 // filesExists returns whether or not the named file or directory exists.
33 func fileExists(name string) bool {
34 if _, e := os.Stat(name); E.Chk(e) {
35 if os.IsNotExist(e) {
36 return false
37 }
38 }
39 return true
40 }
41 42 // isSupportedDbType returns whether or not the passed database type is currently supported.
43 func isSupportedDbType(dbType string) bool {
44 supportedDrivers := database.SupportedDrivers()
45 for _, driver := range supportedDrivers {
46 if dbType == driver {
47 return true
48 }
49 }
50 return false
51 }
52 53 // loadBlocks reads files containing bitcoin block data (gzipped but otherwise in the format bitcoind writes) from disk
54 // and returns them as an array of util.Block. This is largely borrowed from the test code in pod.
55 func loadBlocks(filename string) (blocks []*block.Block, e error) {
56 filename = filepath.Join("tstdata/", filename)
57 var network = wire.MainNet
58 var dr io.Reader
59 var fi io.ReadCloser
60 fi, e = os.Open(filename)
61 if e != nil {
62 return
63 }
64 if strings.HasSuffix(filename, ".bz2") {
65 dr = bzip2.NewReader(fi)
66 } else {
67 dr = fi
68 }
69 defer func() {
70 if e = fi.Close(); E.Chk(e) {
71 }
72 }()
73 var blk *block.Block
74 height := int64(1)
75 for ; ; height++ {
76 var rintbuf uint32
77 e = binary.Read(dr, binary.LittleEndian, &rintbuf)
78 if e == io.EOF {
79 // hit end of file at expected offset: no warning
80 // height--
81 e = nil
82 }
83 if rintbuf != uint32(network) {
84 break
85 }
86 e = binary.Read(dr, binary.LittleEndian, &rintbuf)
87 blocklen := rintbuf
88 rbytes := make([]byte, blocklen)
89 // read block
90 _, e = dr.Read(rbytes)
91 if e != nil {
92 fmt.Println(e)
93 }
94 blk, e = block.NewFromBytes(rbytes)
95 if e != nil {
96 return
97 }
98 blocks = append(blocks, blk)
99 }
100 return
101 }
102 103 // chainSetup is used to create a new db and chain instance with the genesis block already inserted. In addition to the
104 // new chain instance, it returns a teardown function the caller should invoke when done testing to clean up.
105 func chainSetup(dbName string, netparams *chaincfg.Params) (chain *BlockChain, teardown func(), e error) {
106 if !isSupportedDbType(testDbType) {
107 return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
108 }
109 // Handle memory database specially since it doesn't need the disk specific handling.
110 var db database.DB
111 if testDbType == "memdb" {
112 var ndb database.DB
113 ndb, e = database.Create(testDbType)
114 if e != nil {
115 return nil, nil, fmt.Errorf("error creating db: %v", e)
116 }
117 db = ndb
118 // Setup a teardown function for cleaning up. This function is returned to the caller to be invoked when it is
119 // done testing.
120 teardown = func() {
121 if e = db.Close(); E.Chk(e) {
122 }
123 }
124 } else {
125 // Create the root directory for test databases.
126 if !fileExists(testDbRoot) {
127 if e = os.MkdirAll(testDbRoot, 0700); E.Chk(e) {
128 e = fmt.Errorf(
129 "unable to create test db "+
130 "root: %v", e,
131 )
132 return nil, nil, e
133 }
134 }
135 // Create a new database to store the accepted blocks into.
136 dbPath := filepath.Join(testDbRoot, dbName)
137 _ = os.RemoveAll(dbPath)
138 var ndb database.DB
139 ndb, e = database.Create(testDbType, dbPath, blockDataNet)
140 if e != nil {
141 return nil, nil, fmt.Errorf("error creating db: %v", e)
142 }
143 db = ndb
144 // Setup a teardown function for cleaning up. This function is returned to the caller to be invoked when it is
145 // done testing.
146 teardown = func() {
147 if e = db.Close(); E.Chk(e) {
148 }
149 if e = os.RemoveAll(dbPath); E.Chk(e) {
150 }
151 if e = os.RemoveAll(testDbRoot); E.Chk(e) {
152 }
153 }
154 }
155 // Copy the chain netparams to ensure any modifications the tests do to the chain parameters do not affect the
156 // global instance.
157 paramsCopy := *netparams
158 // Create the main chain instance.
159 chain, e = New(
160 &Config{
161 DB: db,
162 ChainParams: ¶msCopy,
163 Checkpoints: nil,
164 TimeSource: NewMedianTime(),
165 SigCache: txscript.NewSigCache(1000),
166 },
167 )
168 if e != nil {
169 teardown()
170 e = fmt.Errorf("failed to create chain instance: %v", e)
171 return nil, nil, e
172 }
173 return chain, teardown, nil
174 }
175 176 // loadUtxoView returns a utxo view loaded from a file.
177 func loadUtxoView(filename string) (*UtxoViewpoint, error) {
178 // The utxostore file format is:
179 //
180 // <tx hash><output index><serialized utxo len><serialized utxo>
181 //
182 // The output index and serialized utxo len are little endian uint32s and the serialized utxo uses the format
183 // described in chainio.go.
184 filename = filepath.Join("tstdata", filename)
185 fi, e := os.Open(filename)
186 if e != nil {
187 return nil, e
188 }
189 // Choose read based on whether the file is compressed or not.
190 var r io.Reader
191 if strings.HasSuffix(filename, ".bz2") {
192 r = bzip2.NewReader(fi)
193 } else {
194 r = fi
195 }
196 defer func() {
197 if e := fi.Close(); E.Chk(e) {
198 }
199 }()
200 view := NewUtxoViewpoint()
201 for {
202 // Hash of the utxo entry.
203 var hash chainhash.Hash
204 _, e := io.ReadAtLeast(r, hash[:], len(hash[:]))
205 if e != nil {
206 // Expected EOF at the right offset.
207 if e == io.EOF {
208 break
209 }
210 return nil, e
211 }
212 // Output index of the utxo entry.
213 var index uint32
214 e = binary.Read(r, binary.LittleEndian, &index)
215 if e != nil {
216 return nil, e
217 }
218 // Num of serialized utxo entry bytes.
219 var numBytes uint32
220 e = binary.Read(r, binary.LittleEndian, &numBytes)
221 if e != nil {
222 return nil, e
223 }
224 // Serialized utxo entry.
225 serialized := make([]byte, numBytes)
226 _, e = io.ReadAtLeast(r, serialized, int(numBytes))
227 if e != nil {
228 return nil, e
229 }
230 // Deserialize it and add it to the view.
231 entry, e := deserializeUtxoEntry(serialized)
232 if e != nil {
233 return nil, e
234 }
235 view.Entries()[wire.OutPoint{Hash: hash, Index: index}] = entry
236 }
237 return view, nil
238 }
239 240 // convertUtxoStore reads a utxostore from the legacy format and writes it back out using the latest format. It is only
241 // useful for converting utxostore data used in the tests, which has already been done. However, the code is left
242 // available for future reference.
243 func convertUtxoStore(r io.Reader, w io.Writer) (e error) {
244 // The old utxostore file format was:
245 //
246 // <tx hash><serialized utxo len><serialized utxo>
247 //
248 // The serialized utxo len was a little endian uint32 and the serialized utxo uses the format described in
249 // upgrade.go.
250 littleEndian := binary.LittleEndian
251 for {
252 // Hash of the utxo entry.
253 var hash chainhash.Hash
254 _, e := io.ReadAtLeast(r, hash[:], len(hash[:]))
255 if e != nil {
256 // Expected EOF at the right offset.
257 if e == io.EOF {
258 break
259 }
260 return e
261 }
262 // Num of serialized utxo entry bytes.
263 var numBytes uint32
264 e = binary.Read(r, littleEndian, &numBytes)
265 if e != nil {
266 return e
267 }
268 // Serialized utxo entry.
269 serialized := make([]byte, numBytes)
270 _, e = io.ReadAtLeast(r, serialized, int(numBytes))
271 if e != nil {
272 return e
273 }
274 // Deserialize the entry.
275 entries, e := deserializeUtxoEntryV0(serialized)
276 if e != nil {
277 return e
278 }
279 // Loop through all of the utxos and write them out in the new format.
280 for outputIdx, entry := range entries {
281 // Reserialize the entries using the new format.
282 serialized, e := serializeUtxoEntry(entry)
283 if e != nil {
284 return e
285 }
286 // Write the hash of the utxo entry.
287 _, e = w.Write(hash[:])
288 if e != nil {
289 return e
290 }
291 // Write the output index of the utxo entry.
292 e = binary.Write(w, littleEndian, outputIdx)
293 if e != nil {
294 return e
295 }
296 // Write num of serialized utxo entry bytes.
297 e = binary.Write(w, littleEndian, uint32(len(serialized)))
298 if e != nil {
299 return e
300 }
301 // Write the serialized utxo.
302 _, e = w.Write(serialized)
303 if e != nil {
304 return e
305 }
306 }
307 }
308 return nil
309 }
310 311 // TstSetCoinbaseMaturity makes the ability to set the coinbase maturity available when running tests.
312 func (b *BlockChain) TstSetCoinbaseMaturity(maturity uint16) {
313 b.params.CoinbaseMaturity = maturity
314 }
315 316 // newFakeChain returns a chain that is usable for syntetic tests. It is important to note that this chain has no
317 // database associated with it, so it is not usable with all functions and the tests must take care when making use of
318 // it.
319 func newFakeChain(params *chaincfg.Params) *BlockChain {
320 // Create a genesis block node and block index index populated with it for use when creating the fake chain below.
321 node := NewBlockNode(¶ms.GenesisBlock.Header, nil)
322 index := newBlockIndex(nil, params)
323 index.AddNode(node)
324 targetTimespan := params.TargetTimespan
325 targetTimePerBlock := params.TargetTimePerBlock
326 adjustmentFactor := params.RetargetAdjustmentFactor
327 return &BlockChain{
328 params: params,
329 timeSource: NewMedianTime(),
330 minRetargetTimespan: targetTimespan / adjustmentFactor,
331 maxRetargetTimespan: targetTimespan * adjustmentFactor,
332 blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
333 Index: index,
334 BestChain: newChainView(node),
335 }
336 }
337 338 // newFakeNode creates a block node connected to the passed parent with the provided fields populated and fake values
339 // for the other fields.
340 func newFakeNode(parent *BlockNode, blockVersion int32, bits uint32, timestamp time.Time) *BlockNode {
341 // Make up a header and create a block node from it.
342 header := &wire.BlockHeader{
343 Version: blockVersion,
344 PrevBlock: parent.hash,
345 Bits: bits,
346 Timestamp: timestamp,
347 }
348 return NewBlockNode(header, parent)
349 }
350