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