github.com/btcsuite/btcd@v0.24.0/database/ffldb/driver_test.go (about) 1 // Copyright (c) 2015-2016 The btcsuite developers 2 // Use of this source code is governed by an ISC 3 // license that can be found in the LICENSE file. 4 5 package ffldb_test 6 7 import ( 8 "bytes" 9 "fmt" 10 "os" 11 "path/filepath" 12 "reflect" 13 "testing" 14 15 "github.com/btcsuite/btcd/btcutil" 16 "github.com/btcsuite/btcd/chaincfg" 17 "github.com/btcsuite/btcd/chaincfg/chainhash" 18 "github.com/btcsuite/btcd/database" 19 "github.com/btcsuite/btcd/database/ffldb" 20 ) 21 22 // dbType is the database type name for this driver. 23 const dbType = "ffldb" 24 25 // TestCreateOpenFail ensures that errors related to creating and opening a 26 // database are handled properly. 27 func TestCreateOpenFail(t *testing.T) { 28 t.Parallel() 29 30 // Ensure that attempting to open a database that doesn't exist returns 31 // the expected error. 32 wantErrCode := database.ErrDbDoesNotExist 33 _, err := database.Open(dbType, "noexist", blockDataNet) 34 if !checkDbError(t, "Open", err, wantErrCode) { 35 return 36 } 37 38 // Ensure that attempting to open a database with the wrong number of 39 // parameters returns the expected error. 40 wantErr := fmt.Errorf("invalid arguments to %s.Open -- expected "+ 41 "database path and block network", dbType) 42 _, err = database.Open(dbType, 1, 2, 3) 43 if err.Error() != wantErr.Error() { 44 t.Errorf("Open: did not receive expected error - got %v, "+ 45 "want %v", err, wantErr) 46 return 47 } 48 49 // Ensure that attempting to open a database with an invalid type for 50 // the first parameter returns the expected error. 51 wantErr = fmt.Errorf("first argument to %s.Open is invalid -- "+ 52 "expected database path string", dbType) 53 _, err = database.Open(dbType, 1, blockDataNet) 54 if err.Error() != wantErr.Error() { 55 t.Errorf("Open: did not receive expected error - got %v, "+ 56 "want %v", err, wantErr) 57 return 58 } 59 60 // Ensure that attempting to open a database with an invalid type for 61 // the second parameter returns the expected error. 62 wantErr = fmt.Errorf("second argument to %s.Open is invalid -- "+ 63 "expected block network", dbType) 64 _, err = database.Open(dbType, "noexist", "invalid") 65 if err.Error() != wantErr.Error() { 66 t.Errorf("Open: did not receive expected error - got %v, "+ 67 "want %v", err, wantErr) 68 return 69 } 70 71 // Ensure that attempting to create a database with the wrong number of 72 // parameters returns the expected error. 73 wantErr = fmt.Errorf("invalid arguments to %s.Create -- expected "+ 74 "database path and block network", dbType) 75 _, err = database.Create(dbType, 1, 2, 3) 76 if err.Error() != wantErr.Error() { 77 t.Errorf("Create: did not receive expected error - got %v, "+ 78 "want %v", err, wantErr) 79 return 80 } 81 82 // Ensure that attempting to create a database with an invalid type for 83 // the first parameter returns the expected error. 84 wantErr = fmt.Errorf("first argument to %s.Create is invalid -- "+ 85 "expected database path string", dbType) 86 _, err = database.Create(dbType, 1, blockDataNet) 87 if err.Error() != wantErr.Error() { 88 t.Errorf("Create: did not receive expected error - got %v, "+ 89 "want %v", err, wantErr) 90 return 91 } 92 93 // Ensure that attempting to create a database with an invalid type for 94 // the second parameter returns the expected error. 95 wantErr = fmt.Errorf("second argument to %s.Create is invalid -- "+ 96 "expected block network", dbType) 97 _, err = database.Create(dbType, "noexist", "invalid") 98 if err.Error() != wantErr.Error() { 99 t.Errorf("Create: did not receive expected error - got %v, "+ 100 "want %v", err, wantErr) 101 return 102 } 103 104 // Ensure operations against a closed database return the expected 105 // error. 106 dbPath := filepath.Join(os.TempDir(), "ffldb-createfail") 107 _ = os.RemoveAll(dbPath) 108 db, err := database.Create(dbType, dbPath, blockDataNet) 109 if err != nil { 110 t.Errorf("Create: unexpected error: %v", err) 111 return 112 } 113 defer os.RemoveAll(dbPath) 114 db.Close() 115 116 wantErrCode = database.ErrDbNotOpen 117 err = db.View(func(tx database.Tx) error { 118 return nil 119 }) 120 if !checkDbError(t, "View", err, wantErrCode) { 121 return 122 } 123 124 wantErrCode = database.ErrDbNotOpen 125 err = db.Update(func(tx database.Tx) error { 126 return nil 127 }) 128 if !checkDbError(t, "Update", err, wantErrCode) { 129 return 130 } 131 132 wantErrCode = database.ErrDbNotOpen 133 _, err = db.Begin(false) 134 if !checkDbError(t, "Begin(false)", err, wantErrCode) { 135 return 136 } 137 138 wantErrCode = database.ErrDbNotOpen 139 _, err = db.Begin(true) 140 if !checkDbError(t, "Begin(true)", err, wantErrCode) { 141 return 142 } 143 144 wantErrCode = database.ErrDbNotOpen 145 err = db.Close() 146 if !checkDbError(t, "Close", err, wantErrCode) { 147 return 148 } 149 } 150 151 // TestPersistence ensures that values stored are still valid after closing and 152 // reopening the database. 153 func TestPersistence(t *testing.T) { 154 t.Parallel() 155 156 // Create a new database to run tests against. 157 dbPath := filepath.Join(os.TempDir(), "ffldb-persistencetest") 158 _ = os.RemoveAll(dbPath) 159 db, err := database.Create(dbType, dbPath, blockDataNet) 160 if err != nil { 161 t.Errorf("Failed to create test database (%s) %v", dbType, err) 162 return 163 } 164 defer os.RemoveAll(dbPath) 165 defer db.Close() 166 167 // Create a bucket, put some values into it, and store a block so they 168 // can be tested for existence on re-open. 169 bucket1Key := []byte("bucket1") 170 storeValues := map[string]string{ 171 "b1key1": "foo1", 172 "b1key2": "foo2", 173 "b1key3": "foo3", 174 } 175 genesisBlock := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock) 176 genesisHash := chaincfg.MainNetParams.GenesisHash 177 err = db.Update(func(tx database.Tx) error { 178 metadataBucket := tx.Metadata() 179 if metadataBucket == nil { 180 return fmt.Errorf("Metadata: unexpected nil bucket") 181 } 182 183 bucket1, err := metadataBucket.CreateBucket(bucket1Key) 184 if err != nil { 185 return fmt.Errorf("CreateBucket: unexpected error: %v", 186 err) 187 } 188 189 for k, v := range storeValues { 190 err := bucket1.Put([]byte(k), []byte(v)) 191 if err != nil { 192 return fmt.Errorf("Put: unexpected error: %v", 193 err) 194 } 195 } 196 197 if err := tx.StoreBlock(genesisBlock); err != nil { 198 return fmt.Errorf("StoreBlock: unexpected error: %v", 199 err) 200 } 201 202 return nil 203 }) 204 if err != nil { 205 t.Errorf("Update: unexpected error: %v", err) 206 return 207 } 208 209 // Close and reopen the database to ensure the values persist. 210 db.Close() 211 db, err = database.Open(dbType, dbPath, blockDataNet) 212 if err != nil { 213 t.Errorf("Failed to open test database (%s) %v", dbType, err) 214 return 215 } 216 defer db.Close() 217 218 // Ensure the values previously stored in the 3rd namespace still exist 219 // and are correct. 220 err = db.View(func(tx database.Tx) error { 221 metadataBucket := tx.Metadata() 222 if metadataBucket == nil { 223 return fmt.Errorf("Metadata: unexpected nil bucket") 224 } 225 226 bucket1 := metadataBucket.Bucket(bucket1Key) 227 if bucket1 == nil { 228 return fmt.Errorf("Bucket1: unexpected nil bucket") 229 } 230 231 for k, v := range storeValues { 232 gotVal := bucket1.Get([]byte(k)) 233 if !reflect.DeepEqual(gotVal, []byte(v)) { 234 return fmt.Errorf("Get: key '%s' does not "+ 235 "match expected value - got %s, want %s", 236 k, gotVal, v) 237 } 238 } 239 240 genesisBlockBytes, _ := genesisBlock.Bytes() 241 gotBytes, err := tx.FetchBlock(genesisHash) 242 if err != nil { 243 return fmt.Errorf("FetchBlock: unexpected error: %v", 244 err) 245 } 246 if !reflect.DeepEqual(gotBytes, genesisBlockBytes) { 247 return fmt.Errorf("FetchBlock: stored block mismatch") 248 } 249 250 return nil 251 }) 252 if err != nil { 253 t.Errorf("View: unexpected error: %v", err) 254 return 255 } 256 } 257 258 // TestPrune tests that the older .fdb files are deleted with a call to prune. 259 func TestPrune(t *testing.T) { 260 t.Parallel() 261 262 // Create a new database to run tests against. 263 dbPath := t.TempDir() 264 db, err := database.Create(dbType, dbPath, blockDataNet) 265 if err != nil { 266 t.Errorf("Failed to create test database (%s) %v", dbType, err) 267 return 268 } 269 defer db.Close() 270 271 blockFileSize := uint64(2048) 272 273 testfn := func(t *testing.T, db database.DB) { 274 // Load the test blocks and save in the test context for use throughout 275 // the tests. 276 blocks, err := loadBlocks(t, blockDataFile, blockDataNet) 277 if err != nil { 278 t.Errorf("loadBlocks: Unexpected error: %v", err) 279 return 280 } 281 err = db.Update(func(tx database.Tx) error { 282 for i, block := range blocks { 283 err := tx.StoreBlock(block) 284 if err != nil { 285 return fmt.Errorf("StoreBlock #%d: unexpected error: "+ 286 "%v", i, err) 287 } 288 } 289 return nil 290 }) 291 if err != nil { 292 t.Fatal(err) 293 } 294 295 blockHashMap := make(map[chainhash.Hash][]byte, len(blocks)) 296 for _, block := range blocks { 297 bytes, err := block.Bytes() 298 if err != nil { 299 t.Fatal(err) 300 } 301 blockHashMap[*block.Hash()] = bytes 302 } 303 304 err = db.Update(func(tx database.Tx) error { 305 _, err := tx.PruneBlocks(1024) 306 if err == nil { 307 return fmt.Errorf("Expected an error when attempting to prune" + 308 "below the maxFileSize") 309 } 310 311 _, err = tx.PruneBlocks(0) 312 if err == nil { 313 return fmt.Errorf("Expected an error when attempting to prune" + 314 "below the maxFileSize") 315 } 316 317 return nil 318 }) 319 if err != nil { 320 t.Fatal(err) 321 } 322 err = db.View(func(tx database.Tx) error { 323 pruned, err := tx.BeenPruned() 324 if err != nil { 325 return err 326 } 327 328 if pruned { 329 err = fmt.Errorf("The database hasn't been pruned but " + 330 "BeenPruned returned true") 331 } 332 return err 333 }) 334 if err != nil { 335 t.Fatal(err) 336 } 337 338 var deletedBlocks []chainhash.Hash 339 340 // This should leave 3 files on disk. 341 err = db.Update(func(tx database.Tx) error { 342 deletedBlocks, err = tx.PruneBlocks(blockFileSize * 3) 343 if err != nil { 344 return err 345 } 346 347 pruned, err := tx.BeenPruned() 348 if err != nil { 349 return err 350 } 351 352 if pruned { 353 err = fmt.Errorf("The database hasn't been commited yet " + 354 "but files were already deleted") 355 } 356 return err 357 }) 358 if err != nil { 359 t.Fatal(err) 360 } 361 362 // The only error we can get is a bad pattern error. Since we're hardcoding 363 // the pattern, we should not have an error at runtime. 364 files, _ := filepath.Glob(filepath.Join(dbPath, "*.fdb")) 365 if len(files) != 3 { 366 t.Fatalf("Expected to find %d files but got %d", 367 3, len(files)) 368 } 369 370 err = db.View(func(tx database.Tx) error { 371 pruned, err := tx.BeenPruned() 372 if err != nil { 373 return err 374 } 375 376 if !pruned { 377 err = fmt.Errorf("The database has been pruned but " + 378 "BeenPruned returned false") 379 } 380 return err 381 }) 382 if err != nil { 383 t.Fatal(err) 384 } 385 386 // Check that all the blocks that say were deleted are deleted from the 387 // block index bucket as well. 388 err = db.View(func(tx database.Tx) error { 389 for _, deletedBlock := range deletedBlocks { 390 _, err := tx.FetchBlock(&deletedBlock) 391 if dbErr, ok := err.(database.Error); !ok || 392 dbErr.ErrorCode != database.ErrBlockNotFound { 393 394 return fmt.Errorf("Expected ErrBlockNotFound "+ 395 "but got %v", dbErr) 396 } 397 } 398 399 return nil 400 }) 401 if err != nil { 402 t.Fatal(err) 403 } 404 405 // Check that the not deleted blocks are present. 406 for _, deletedBlock := range deletedBlocks { 407 delete(blockHashMap, deletedBlock) 408 } 409 err = db.View(func(tx database.Tx) error { 410 for hash, wantBytes := range blockHashMap { 411 gotBytes, err := tx.FetchBlock(&hash) 412 if err != nil { 413 return err 414 } 415 if !bytes.Equal(gotBytes, wantBytes) { 416 return fmt.Errorf("got bytes %x, want bytes %x", 417 gotBytes, wantBytes) 418 } 419 } 420 return nil 421 }) 422 if err != nil { 423 t.Fatal(err) 424 } 425 } 426 ffldb.TstRunWithMaxBlockFileSize(db, uint32(blockFileSize), func() { 427 testfn(t, db) 428 }) 429 } 430 431 // TestInterface performs all interfaces tests for this database driver. 432 func TestInterface(t *testing.T) { 433 t.Parallel() 434 435 // Create a new database to run tests against. 436 dbPath := filepath.Join(os.TempDir(), "ffldb-interfacetest") 437 _ = os.RemoveAll(dbPath) 438 db, err := database.Create(dbType, dbPath, blockDataNet) 439 if err != nil { 440 t.Errorf("Failed to create test database (%s) %v", dbType, err) 441 return 442 } 443 defer os.RemoveAll(dbPath) 444 defer db.Close() 445 446 // Ensure the driver type is the expected value. 447 gotDbType := db.Type() 448 if gotDbType != dbType { 449 t.Errorf("Type: unepxected driver type - got %v, want %v", 450 gotDbType, dbType) 451 return 452 } 453 454 // Run all of the interface tests against the database. 455 456 // Change the maximum file size to a small value to force multiple flat 457 // files with the test data set. 458 ffldb.TstRunWithMaxBlockFileSize(db, 2048, func() { 459 testInterface(t, db) 460 }) 461 }