github.com/btcsuite/btcd@v0.24.0/blockchain/fullblocks_test.go (about) 1 // Copyright (c) 2016 The Decred developers 2 // Copyright (c) 2016-2017 The btcsuite developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 package blockchain_test 7 8 import ( 9 "bytes" 10 "fmt" 11 "os" 12 "path/filepath" 13 "testing" 14 15 "github.com/btcsuite/btcd/blockchain" 16 "github.com/btcsuite/btcd/blockchain/fullblocktests" 17 "github.com/btcsuite/btcd/btcutil" 18 "github.com/btcsuite/btcd/chaincfg" 19 "github.com/btcsuite/btcd/chaincfg/chainhash" 20 "github.com/btcsuite/btcd/database" 21 _ "github.com/btcsuite/btcd/database/ffldb" 22 "github.com/btcsuite/btcd/txscript" 23 "github.com/btcsuite/btcd/wire" 24 ) 25 26 const ( 27 // testDbType is the database backend type to use for the tests. 28 testDbType = "ffldb" 29 30 // testDbRoot is the root directory used to create all test databases. 31 testDbRoot = "testdbs" 32 33 // blockDataNet is the expected network in the test block data. 34 blockDataNet = wire.MainNet 35 ) 36 37 // filesExists returns whether or not the named file or directory exists. 38 func fileExists(name string) bool { 39 if _, err := os.Stat(name); err != nil { 40 if os.IsNotExist(err) { 41 return false 42 } 43 } 44 return true 45 } 46 47 // isSupportedDbType returns whether or not the passed database type is 48 // currently supported. 49 func isSupportedDbType(dbType string) bool { 50 supportedDrivers := database.SupportedDrivers() 51 for _, driver := range supportedDrivers { 52 if dbType == driver { 53 return true 54 } 55 } 56 57 return false 58 } 59 60 // chainSetup is used to create a new db and chain instance with the genesis 61 // block already inserted. In addition to the new chain instance, it returns 62 // a teardown function the caller should invoke when done testing to clean up. 63 func chainSetup(dbName string, params *chaincfg.Params) (*blockchain.BlockChain, func(), error) { 64 if !isSupportedDbType(testDbType) { 65 return nil, nil, fmt.Errorf("unsupported db type %v", testDbType) 66 } 67 68 // Handle memory database specially since it doesn't need the disk 69 // specific handling. 70 var db database.DB 71 var teardown func() 72 if testDbType == "memdb" { 73 ndb, err := database.Create(testDbType) 74 if err != nil { 75 return nil, nil, fmt.Errorf("error creating db: %v", err) 76 } 77 db = ndb 78 79 // Setup a teardown function for cleaning up. This function is 80 // returned to the caller to be invoked when it is done testing. 81 teardown = func() { 82 db.Close() 83 } 84 } else { 85 // Create the root directory for test databases. 86 if !fileExists(testDbRoot) { 87 if err := os.MkdirAll(testDbRoot, 0700); err != nil { 88 err := fmt.Errorf("unable to create test db "+ 89 "root: %v", err) 90 return nil, nil, err 91 } 92 } 93 94 // Create a new database to store the accepted blocks into. 95 dbPath := filepath.Join(testDbRoot, dbName) 96 _ = os.RemoveAll(dbPath) 97 ndb, err := database.Create(testDbType, dbPath, blockDataNet) 98 if err != nil { 99 return nil, nil, fmt.Errorf("error creating db: %v", err) 100 } 101 db = ndb 102 103 // Setup a teardown function for cleaning up. This function is 104 // returned to the caller to be invoked when it is done testing. 105 teardown = func() { 106 db.Close() 107 os.RemoveAll(dbPath) 108 os.RemoveAll(testDbRoot) 109 } 110 } 111 112 // Copy the chain params to ensure any modifications the tests do to 113 // the chain parameters do not affect the global instance. 114 paramsCopy := *params 115 116 // Create the main chain instance. 117 chain, err := blockchain.New(&blockchain.Config{ 118 DB: db, 119 ChainParams: ¶msCopy, 120 Checkpoints: nil, 121 TimeSource: blockchain.NewMedianTime(), 122 SigCache: txscript.NewSigCache(1000), 123 }) 124 if err != nil { 125 teardown() 126 err := fmt.Errorf("failed to create chain instance: %v", err) 127 return nil, nil, err 128 } 129 return chain, teardown, nil 130 } 131 132 // TestFullBlocks ensures all tests generated by the fullblocktests package 133 // have the expected result when processed via ProcessBlock. 134 func TestFullBlocks(t *testing.T) { 135 tests, err := fullblocktests.Generate(false) 136 if err != nil { 137 t.Fatalf("failed to generate tests: %v", err) 138 } 139 140 // Create a new database and chain instance to run tests against. 141 chain, teardownFunc, err := chainSetup("fullblocktest", 142 &chaincfg.RegressionNetParams) 143 if err != nil { 144 t.Errorf("Failed to setup chain instance: %v", err) 145 return 146 } 147 defer teardownFunc() 148 149 // testAcceptedBlock attempts to process the block in the provided test 150 // instance and ensures that it was accepted according to the flags 151 // specified in the test. 152 testAcceptedBlock := func(item fullblocktests.AcceptedBlock) { 153 blockHeight := item.Height 154 block := btcutil.NewBlock(item.Block) 155 block.SetHeight(blockHeight) 156 t.Logf("Testing block %s (hash %s, height %d)", 157 item.Name, block.Hash(), blockHeight) 158 159 isMainChain, isOrphan, err := chain.ProcessBlock(block, 160 blockchain.BFNone) 161 if err != nil { 162 t.Fatalf("block %q (hash %s, height %d) should "+ 163 "have been accepted: %v", item.Name, 164 block.Hash(), blockHeight, err) 165 } 166 167 // Ensure the main chain and orphan flags match the values 168 // specified in the test. 169 if isMainChain != item.IsMainChain { 170 t.Fatalf("block %q (hash %s, height %d) unexpected main "+ 171 "chain flag -- got %v, want %v", item.Name, 172 block.Hash(), blockHeight, isMainChain, 173 item.IsMainChain) 174 } 175 if isOrphan != item.IsOrphan { 176 t.Fatalf("block %q (hash %s, height %d) unexpected "+ 177 "orphan flag -- got %v, want %v", item.Name, 178 block.Hash(), blockHeight, isOrphan, 179 item.IsOrphan) 180 } 181 } 182 183 // testRejectedBlock attempts to process the block in the provided test 184 // instance and ensures that it was rejected with the reject code 185 // specified in the test. 186 testRejectedBlock := func(item fullblocktests.RejectedBlock) { 187 blockHeight := item.Height 188 block := btcutil.NewBlock(item.Block) 189 block.SetHeight(blockHeight) 190 t.Logf("Testing block %s (hash %s, height %d)", 191 item.Name, block.Hash(), blockHeight) 192 193 _, _, err := chain.ProcessBlock(block, blockchain.BFNone) 194 if err == nil { 195 t.Fatalf("block %q (hash %s, height %d) should not "+ 196 "have been accepted", item.Name, block.Hash(), 197 blockHeight) 198 } 199 200 // Ensure the error code is of the expected type and the reject 201 // code matches the value specified in the test instance. 202 rerr, ok := err.(blockchain.RuleError) 203 if !ok { 204 t.Fatalf("block %q (hash %s, height %d) returned "+ 205 "unexpected error type -- got %T, want "+ 206 "blockchain.RuleError", item.Name, block.Hash(), 207 blockHeight, err) 208 } 209 if rerr.ErrorCode != item.RejectCode { 210 t.Fatalf("block %q (hash %s, height %d) does not have "+ 211 "expected reject code -- got %v, want %v", 212 item.Name, block.Hash(), blockHeight, 213 rerr.ErrorCode, item.RejectCode) 214 } 215 } 216 217 // testRejectedNonCanonicalBlock attempts to decode the block in the 218 // provided test instance and ensures that it failed to decode with a 219 // message error. 220 testRejectedNonCanonicalBlock := func(item fullblocktests.RejectedNonCanonicalBlock) { 221 headerLen := len(item.RawBlock) 222 if headerLen > 80 { 223 headerLen = 80 224 } 225 blockHash := chainhash.DoubleHashH(item.RawBlock[0:headerLen]) 226 blockHeight := item.Height 227 t.Logf("Testing block %s (hash %s, height %d)", item.Name, 228 blockHash, blockHeight) 229 230 // Ensure there is an error due to deserializing the block. 231 var msgBlock wire.MsgBlock 232 err := msgBlock.BtcDecode(bytes.NewReader(item.RawBlock), 0, wire.BaseEncoding) 233 if _, ok := err.(*wire.MessageError); !ok { 234 t.Fatalf("block %q (hash %s, height %d) should have "+ 235 "failed to decode", item.Name, blockHash, 236 blockHeight) 237 } 238 } 239 240 // testOrphanOrRejectedBlock attempts to process the block in the 241 // provided test instance and ensures that it was either accepted as an 242 // orphan or rejected with a rule violation. 243 testOrphanOrRejectedBlock := func(item fullblocktests.OrphanOrRejectedBlock) { 244 blockHeight := item.Height 245 block := btcutil.NewBlock(item.Block) 246 block.SetHeight(blockHeight) 247 t.Logf("Testing block %s (hash %s, height %d)", 248 item.Name, block.Hash(), blockHeight) 249 250 _, isOrphan, err := chain.ProcessBlock(block, blockchain.BFNone) 251 if err != nil { 252 // Ensure the error code is of the expected type. 253 if _, ok := err.(blockchain.RuleError); !ok { 254 t.Fatalf("block %q (hash %s, height %d) "+ 255 "returned unexpected error type -- "+ 256 "got %T, want blockchain.RuleError", 257 item.Name, block.Hash(), blockHeight, 258 err) 259 } 260 } 261 262 if !isOrphan { 263 t.Fatalf("block %q (hash %s, height %d) was accepted, "+ 264 "but is not considered an orphan", item.Name, 265 block.Hash(), blockHeight) 266 } 267 } 268 269 // testExpectedTip ensures the current tip of the blockchain is the 270 // block specified in the provided test instance. 271 testExpectedTip := func(item fullblocktests.ExpectedTip) { 272 blockHeight := item.Height 273 block := btcutil.NewBlock(item.Block) 274 block.SetHeight(blockHeight) 275 t.Logf("Testing tip for block %s (hash %s, height %d)", 276 item.Name, block.Hash(), blockHeight) 277 278 // Ensure hash and height match. 279 best := chain.BestSnapshot() 280 if best.Hash != item.Block.BlockHash() || 281 best.Height != blockHeight { 282 283 t.Fatalf("block %q (hash %s, height %d) should be "+ 284 "the current tip -- got (hash %s, height %d)", 285 item.Name, block.Hash(), blockHeight, best.Hash, 286 best.Height) 287 } 288 } 289 290 for testNum, test := range tests { 291 for itemNum, item := range test { 292 switch item := item.(type) { 293 case fullblocktests.AcceptedBlock: 294 testAcceptedBlock(item) 295 case fullblocktests.RejectedBlock: 296 testRejectedBlock(item) 297 case fullblocktests.RejectedNonCanonicalBlock: 298 testRejectedNonCanonicalBlock(item) 299 case fullblocktests.OrphanOrRejectedBlock: 300 testOrphanOrRejectedBlock(item) 301 case fullblocktests.ExpectedTip: 302 testExpectedTip(item) 303 default: 304 t.Fatalf("test #%d, item #%d is not one of "+ 305 "the supported test instance types -- "+ 306 "got type: %T", testNum, itemNum, item) 307 } 308 } 309 } 310 }