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.
6 package blockchain_test
15 "github.com/btcsuite/btcd/blockchain"
16 "github.com/btcsuite/btcd/blockchain/fullblocktests"
17 "github.com/btcsuite/btcd/chaincfg"
18 "github.com/btcsuite/btcd/chaincfg/chainhash"
19 "github.com/btcsuite/btcd/database"
20 _ "github.com/btcsuite/btcd/database/ffldb"
21 "github.com/btcsuite/btcd/txscript"
22 "github.com/btcsuite/btcd/wire"
23 "github.com/btcsuite/btcutil"
27 // testDbType is the database backend type to use for the tests.
30 // testDbRoot is the root directory used to create all test databases.
31 testDbRoot = "testdbs"
33 // blockDataNet is the expected network in the test block data.
34 blockDataNet = wire.MainNet
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) {
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 {
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)
68 // Handle memory database specially since it doesn't need the disk
72 if testDbType == "memdb" {
73 ndb, err := database.Create(testDbType)
75 return nil, nil, fmt.Errorf("error creating db: %v", err)
79 // Setup a teardown function for cleaning up. This function is
80 // returned to the caller to be invoked when it is done testing.
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 "+
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)
99 return nil, nil, fmt.Errorf("error creating db: %v", err)
103 // Setup a teardown function for cleaning up. This function is
104 // returned to the caller to be invoked when it is done testing.
108 os.RemoveAll(testDbRoot)
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
116 // Create the main chain instance.
117 chain, err := blockchain.New(&blockchain.Config{
119 ChainParams: ¶msCopy,
121 TimeSource: blockchain.NewMedianTime(),
122 SigCache: txscript.NewSigCache(1000),
126 err := fmt.Errorf("failed to create chain instance: %v", err)
129 return chain, teardown, nil
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)
137 t.Fatalf("failed to generate tests: %v", err)
140 // Create a new database and chain instance to run tests against.
141 chain, teardownFunc, err := chainSetup("fullblocktest",
142 &chaincfg.RegressionNetParams)
144 t.Errorf("Failed to setup chain instance: %v", err)
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)
159 isMainChain, isOrphan, err := chain.ProcessBlock(block,
162 t.Fatalf("block %q (hash %s, height %d) should "+
163 "have been accepted: %v", item.Name,
164 block.Hash(), blockHeight, err)
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,
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,
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)
193 _, _, err := chain.ProcessBlock(block, blockchain.BFNone)
195 t.Fatalf("block %q (hash %s, height %d) should not "+
196 "have been accepted", item.Name, block.Hash(),
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)
204 t.Fatalf("block %q (hash %s, height %d) returned "+
205 "unexpected error type -- got %T, want "+
206 "blockchain.RuleError", item.Name, block.Hash(),
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)
217 // testRejectedNonCanonicalBlock attempts to decode the block in the
218 // provided test instance and ensures that it failed to decode with a
220 testRejectedNonCanonicalBlock := func(item fullblocktests.RejectedNonCanonicalBlock) {
221 headerLen := len(item.RawBlock)
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)
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,
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)
250 _, isOrphan, err := chain.ProcessBlock(block, blockchain.BFNone)
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,
263 t.Fatalf("block %q (hash %s, height %d) was accepted, "+
264 "but is not considered an orphan", item.Name,
265 block.Hash(), blockHeight)
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)
278 // Ensure hash and height match.
279 best := chain.BestSnapshot()
280 if best.Hash != item.Block.BlockHash() ||
281 best.Height != blockHeight {
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,
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)
304 t.Fatalf("test #%d, item #%d is not one of "+
305 "the supported test instance types -- "+
306 "got type: %T", testNum, itemNum, item)