Exemple #1
0
// TestInterface performs all interfaces tests for this database driver.
func TestInterface(t *testing.T) {
	t.Parallel()

	// Create a new database to run tests against.
	dbPath := filepath.Join(os.TempDir(), "ffldb-interfacetest")
	_ = os.RemoveAll(dbPath)
	db, err := database.Create(dbType, dbPath, blockDataNet)
	if err != nil {
		t.Errorf("Failed to create test database (%s) %v", dbType, err)
		return
	}
	defer os.RemoveAll(dbPath)
	defer db.Close()

	// Ensure the driver type is the expected value.
	gotDbType := db.Type()
	if gotDbType != dbType {
		t.Errorf("Type: unepxected driver type - got %v, want %v",
			gotDbType, dbType)
		return
	}

	// Run all of the interface tests against the database.
	runtime.GOMAXPROCS(runtime.NumCPU())

	// Change the maximum file size to a small value to force multiple flat
	// files with the test data set.
	ffldb.TstRunWithMaxBlockFileSize(db, 2048, func() {
		testInterface(t, db)
	})
}
Exemple #2
0
// loadBlockDB opens the block database and returns a handle to it.
func loadBlockDB() (database.DB, error) {
	// The database name is based on the database type.
	dbName := blockDbNamePrefix + "_" + cfg.DbType
	dbPath := filepath.Join(cfg.DataDir, dbName)

	log.Infof("Loading block database from '%s'", dbPath)
	db, err := database.Open(cfg.DbType, dbPath, activeNetParams.Net)
	if err != nil {
		// Return the error if it's not because the database doesn't
		// exist.
		if dbErr, ok := err.(database.Error); !ok || dbErr.ErrorCode !=
			database.ErrDbDoesNotExist {

			return nil, err
		}

		// Create the db if it does not exist.
		err = os.MkdirAll(cfg.DataDir, 0700)
		if err != nil {
			return nil, err
		}
		db, err = database.Create(cfg.DbType, dbPath, activeNetParams.Net)
		if err != nil {
			return nil, err
		}
	}

	log.Info("Block database loaded")
	return db, nil
}
Exemple #3
0
// This example demonstrates how to create a new chain instance and use
// ProcessBlock to attempt to attempt add a block to the chain.  As the package
// overview documentation describes, this includes all of the Decred consensus
// rules.  This example intentionally attempts to insert a duplicate genesis
// block to illustrate how an invalid block is handled.
func ExampleBlockChain_ProcessBlock() {
	// Create a new database to store the accepted blocks into.  Typically
	// this would be opening an existing database and would not be deleting
	// and creating a new database like this, but it is done here so this is
	// a complete working example and does not leave temporary files laying
	// around.
	dbPath := filepath.Join(os.TempDir(), "exampleprocessblock")
	_ = os.RemoveAll(dbPath)
	db, err := database.Create("ffldb", dbPath, chaincfg.MainNetParams.Net)
	if err != nil {
		fmt.Printf("Failed to create database: %v\n", err)
		return
	}
	defer os.RemoveAll(dbPath)
	defer db.Close()

	// Create a new BlockChain instance using the underlying database for
	// the main bitcoin network.  This example does not demonstrate some
	// of the other available configuration options such as specifying a
	// notification callback and signature cache.  Also, the caller would
	// ordinarily keep a reference to the median time source and add time
	// values obtained from other peers on the network so the local time is
	// adjusted to be in agreement with other peers.
	chain, err := blockchain.New(&blockchain.Config{
		DB:          db,
		ChainParams: &chaincfg.MainNetParams,
		TimeSource:  blockchain.NewMedianTime(),
	})
	if err != nil {
		fmt.Printf("Failed to create chain instance: %v\n", err)
		return
	}

	// Process a block.  For this example, we are going to intentionally
	// cause an error by trying to process the genesis block which already
	// exists.
	genesisBlock := dcrutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
	_, isOrphan, err := chain.ProcessBlock(genesisBlock, blockchain.BFNone)
	if err != nil {
		fmt.Printf("Failed to create chain instance: %v\n", err)
		return
	}
	fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan)

	// This output is dependent on the genesis block, and needs to be
	// updated if the mainnet genesis block is updated.
	// Output:
	// Failed to process block: already have block 267a53b5ee86c24a48ec37aee4f4e7c0c4004892b7259e695e9f5b321f1ab9d2
}
Exemple #4
0
// TestCreateOpenUnsupported ensures that attempting to create or open an
// unsupported database type is handled properly.
func TestCreateOpenUnsupported(t *testing.T) {
	// Ensure creating a database with an unsupported type fails with the
	// expected error.
	testName := "create with unsupported database type"
	dbType := "unsupported"
	_, err := database.Create(dbType)
	if !checkDbError(t, testName, err, database.ErrDbUnknownType) {
		return
	}

	// Ensure opening a database with the an unsupported type fails with the
	// expected error.
	testName = "open with unsupported database type"
	_, err = database.Open(dbType)
	if !checkDbError(t, testName, err, database.ErrDbUnknownType) {
		return
	}
}
Exemple #5
0
// BenchmarkBlockHeader benchmarks how long it takes to load the mainnet genesis
// block.
func BenchmarkBlock(b *testing.B) {
	// Start by creating a new database and populating it with the mainnet
	// genesis block.
	dbPath := filepath.Join(os.TempDir(), "ffldb-benchblk")
	_ = os.RemoveAll(dbPath)
	db, err := database.Create("ffldb", dbPath, blockDataNet)
	if err != nil {
		b.Fatal(err)
	}
	defer os.RemoveAll(dbPath)
	defer db.Close()
	err = db.Update(func(tx database.Tx) error {
		block := dcrutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
		if err := tx.StoreBlock(block); err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		b.Fatal(err)
	}

	b.ReportAllocs()
	b.ResetTimer()
	err = db.View(func(tx database.Tx) error {
		blockHash := chaincfg.MainNetParams.GenesisHash
		for i := 0; i < b.N; i++ {
			_, err := tx.FetchBlock(blockHash)
			if err != nil {
				return err
			}
		}
		return nil
	})
	if err != nil {
		b.Fatal(err)
	}

	// Don't benchmark teardown.
	b.StopTimer()
}
Exemple #6
0
// TestCreateOpenFail ensures that errors which occur while opening or closing
// a database are handled properly.
func TestCreateOpenFail(t *testing.T) {
	// bogusCreateDB is a function which acts as a bogus create and open
	// driver function that intentionally returns a failure which can be
	// detected.
	dbType := "createopenfail"
	openError := fmt.Errorf("failed to create or open database for "+
		"database type [%v]", dbType)
	bogusCreateDB := func(args ...interface{}) (database.DB, error) {
		return nil, openError
	}

	// Create and add driver that intentionally fails when created or opened
	// to ensure errors on database open and create are handled properly.
	driver := database.Driver{
		DbType: dbType,
		Create: bogusCreateDB,
		Open:   bogusCreateDB,
	}
	database.RegisterDriver(driver)

	// Ensure creating a database with the new type fails with the expected
	// error.
	_, err := database.Create(dbType)
	if err != openError {
		t.Errorf("expected error not received - got: %v, want %v", err,
			openError)
		return
	}

	// Ensure opening a database with the new type fails with the expected
	// error.
	_, err = database.Open(dbType)
	if err != openError {
		t.Errorf("expected error not received - got: %v, want %v", err,
			openError)
		return
	}
}
Exemple #7
0
// This example demonstrates creating a new database.
func ExampleCreate() {
	// This example assumes the ffldb driver is imported.
	//
	// import (
	// 	"github.com/decred/dcrd/database2"
	// 	_ "github.com/decred/dcrd/database/ffldb"
	// )

	// Create a database and schedule it to be closed and removed on exit.
	// Typically you wouldn't want to remove the database right away like
	// this, nor put it in the temp directory, but it's done here to ensure
	// the example cleans up after itself.
	dbPath := filepath.Join(os.TempDir(), "examplecreate")
	db, err := database.Create("ffldb", dbPath, wire.MainNet)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer os.RemoveAll(dbPath)
	defer db.Close()

	// Output:
}
Exemple #8
0
// TestCreateOpenFail ensures that errors related to creating and opening a
// database are handled properly.
func TestCreateOpenFail(t *testing.T) {
	t.Parallel()

	// Ensure that attempting to open a database that doesn't exist returns
	// the expected error.
	wantErrCode := database.ErrDbDoesNotExist
	_, err := database.Open(dbType, "noexist", blockDataNet)
	if !checkDbError(t, "Open", err, wantErrCode) {
		return
	}

	// Ensure that attempting to open a database with the wrong number of
	// parameters returns the expected error.
	wantErr := fmt.Errorf("invalid arguments to %s.Open -- expected "+
		"database path and block network", dbType)
	_, err = database.Open(dbType, 1, 2, 3)
	if err.Error() != wantErr.Error() {
		t.Errorf("Open: did not receive expected error - got %v, "+
			"want %v", err, wantErr)
		return
	}

	// Ensure that attempting to open a database with an invalid type for
	// the first parameter returns the expected error.
	wantErr = fmt.Errorf("first argument to %s.Open is invalid -- "+
		"expected database path string", dbType)
	_, err = database.Open(dbType, 1, blockDataNet)
	if err.Error() != wantErr.Error() {
		t.Errorf("Open: did not receive expected error - got %v, "+
			"want %v", err, wantErr)
		return
	}

	// Ensure that attempting to open a database with an invalid type for
	// the second parameter returns the expected error.
	wantErr = fmt.Errorf("second argument to %s.Open is invalid -- "+
		"expected block network", dbType)
	_, err = database.Open(dbType, "noexist", "invalid")
	if err.Error() != wantErr.Error() {
		t.Errorf("Open: did not receive expected error - got %v, "+
			"want %v", err, wantErr)
		return
	}

	// Ensure that attempting to create a database with the wrong number of
	// parameters returns the expected error.
	wantErr = fmt.Errorf("invalid arguments to %s.Create -- expected "+
		"database path and block network", dbType)
	_, err = database.Create(dbType, 1, 2, 3)
	if err.Error() != wantErr.Error() {
		t.Errorf("Create: did not receive expected error - got %v, "+
			"want %v", err, wantErr)
		return
	}

	// Ensure that attempting to create a database with an invalid type for
	// the first parameter returns the expected error.
	wantErr = fmt.Errorf("first argument to %s.Create is invalid -- "+
		"expected database path string", dbType)
	_, err = database.Create(dbType, 1, blockDataNet)
	if err.Error() != wantErr.Error() {
		t.Errorf("Create: did not receive expected error - got %v, "+
			"want %v", err, wantErr)
		return
	}

	// Ensure that attempting to create a database with an invalid type for
	// the second parameter returns the expected error.
	wantErr = fmt.Errorf("second argument to %s.Create is invalid -- "+
		"expected block network", dbType)
	_, err = database.Create(dbType, "noexist", "invalid")
	if err.Error() != wantErr.Error() {
		t.Errorf("Create: did not receive expected error - got %v, "+
			"want %v", err, wantErr)
		return
	}

	// Ensure operations against a closed database return the expected
	// error.
	dbPath := filepath.Join(os.TempDir(), "ffldb-createfail")
	_ = os.RemoveAll(dbPath)
	db, err := database.Create(dbType, dbPath, blockDataNet)
	if err != nil {
		t.Errorf("Create: unexpected error: %v", err)
		return
	}
	defer os.RemoveAll(dbPath)
	db.Close()

	wantErrCode = database.ErrDbNotOpen
	err = db.View(func(tx database.Tx) error {
		return nil
	})
	if !checkDbError(t, "View", err, wantErrCode) {
		return
	}

	wantErrCode = database.ErrDbNotOpen
	err = db.Update(func(tx database.Tx) error {
		return nil
	})
	if !checkDbError(t, "Update", err, wantErrCode) {
		return
	}

	wantErrCode = database.ErrDbNotOpen
	_, err = db.Begin(false)
	if !checkDbError(t, "Begin(false)", err, wantErrCode) {
		return
	}

	wantErrCode = database.ErrDbNotOpen
	_, err = db.Begin(true)
	if !checkDbError(t, "Begin(true)", err, wantErrCode) {
		return
	}

	wantErrCode = database.ErrDbNotOpen
	err = db.Close()
	if !checkDbError(t, "Close", err, wantErrCode) {
		return
	}
}
Exemple #9
0
// TestPersistence ensures that values stored are still valid after closing and
// reopening the database.
func TestPersistence(t *testing.T) {
	t.Parallel()

	// Create a new database to run tests against.
	dbPath := filepath.Join(os.TempDir(), "ffldb-persistencetest")
	_ = os.RemoveAll(dbPath)
	db, err := database.Create(dbType, dbPath, blockDataNet)
	if err != nil {
		t.Errorf("Failed to create test database (%s) %v", dbType, err)
		return
	}
	defer os.RemoveAll(dbPath)
	defer db.Close()

	// Create a bucket, put some values into it, and store a block so they
	// can be tested for existence on re-open.
	bucket1Key := []byte("bucket1")
	storeValues := map[string]string{
		"b1key1": "foo1",
		"b1key2": "foo2",
		"b1key3": "foo3",
	}
	genesisBlock := dcrutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
	genesisHash := chaincfg.MainNetParams.GenesisHash
	err = db.Update(func(tx database.Tx) error {
		metadataBucket := tx.Metadata()
		if metadataBucket == nil {
			return fmt.Errorf("Metadata: unexpected nil bucket")
		}

		bucket1, err := metadataBucket.CreateBucket(bucket1Key)
		if err != nil {
			return fmt.Errorf("CreateBucket: unexpected error: %v",
				err)
		}

		for k, v := range storeValues {
			err := bucket1.Put([]byte(k), []byte(v))
			if err != nil {
				return fmt.Errorf("Put: unexpected error: %v",
					err)
			}
		}

		if err := tx.StoreBlock(genesisBlock); err != nil {
			return fmt.Errorf("StoreBlock: unexpected error: %v",
				err)
		}

		return nil
	})
	if err != nil {
		t.Errorf("Update: unexpected error: %v", err)
		return
	}

	// Close and reopen the database to ensure the values persist.
	db.Close()
	db, err = database.Open(dbType, dbPath, blockDataNet)
	if err != nil {
		t.Errorf("Failed to open test database (%s) %v", dbType, err)
		return
	}
	defer db.Close()

	// Ensure the values previously stored in the 3rd namespace still exist
	// and are correct.
	err = db.View(func(tx database.Tx) error {
		metadataBucket := tx.Metadata()
		if metadataBucket == nil {
			return fmt.Errorf("Metadata: unexpected nil bucket")
		}

		bucket1 := metadataBucket.Bucket(bucket1Key)
		if bucket1 == nil {
			return fmt.Errorf("Bucket1: unexpected nil bucket")
		}

		for k, v := range storeValues {
			gotVal := bucket1.Get([]byte(k))
			if !reflect.DeepEqual(gotVal, []byte(v)) {
				return fmt.Errorf("Get: key '%s' does not "+
					"match expected value - got %s, want %s",
					k, gotVal, v)
			}
		}

		genesisBlockBytes, _ := genesisBlock.Bytes()
		gotBytes, err := tx.FetchBlock(genesisHash)
		if err != nil {
			return fmt.Errorf("FetchBlock: unexpected error: %v",
				err)
		}
		if !reflect.DeepEqual(gotBytes, genesisBlockBytes) {
			return fmt.Errorf("FetchBlock: stored block mismatch")
		}

		return nil
	})
	if err != nil {
		t.Errorf("View: unexpected error: %v", err)
		return
	}
}
Exemple #10
0
// TestLiveDatabase tests various functions that require a live database.
func TestLiveDatabase(t *testing.T) {
	// Create a new database to store the accepted stake node data into.
	dbName := "ffldb_ticketdb_test"
	dbPath := filepath.Join(testDbRoot, dbName)
	_ = os.RemoveAll(dbPath)
	testDb, err := database.Create(testDbType, dbPath, chaincfg.SimNetParams.Net)
	if err != nil {
		t.Fatalf("error creating db: %v", err)
	}

	// Setup a teardown.
	defer os.RemoveAll(dbPath)
	defer os.RemoveAll(testDbRoot)
	defer testDb.Close()

	// Initialize the database, then try to read the version.
	err = testDb.Update(func(dbTx database.Tx) error {
		return DbCreate(dbTx)
	})
	if err != nil {
		t.Fatalf("%v", err.Error())
	}

	var dbi *DatabaseInfo
	err = testDb.View(func(dbTx database.Tx) error {
		dbi, err = DbFetchDatabaseInfo(dbTx)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		t.Fatalf("%v", err.Error())
	}
	if dbi.Version != currentDatabaseVersion {
		t.Fatalf("bad version after reading from DB; want %v, got %v",
			currentDatabaseVersion, dbi.Version)
	}

	// Test storing arbitrary ticket treaps.
	ticketMap := make(map[tickettreap.Key]*tickettreap.Value)
	tickets := make([]chainhash.Hash, 5)
	for i := 0; i < 4; i++ {
		h := chainhash.HashFuncH(bytes.Repeat([]byte{0x01}, i))
		ticketMap[tickettreap.Key(h)] = &tickettreap.Value{
			Height:  12345 + uint32(i),
			Missed:  i%2 == 0,
			Revoked: i%2 != 0,
			Spent:   i%2 == 0,
			Expired: i%2 != 0,
		}
		tickets[i] = h
	}

	err = testDb.Update(func(dbTx database.Tx) error {
		for k, v := range ticketMap {
			h := chainhash.Hash(k)
			err = DbPutTicket(dbTx, dbnamespace.LiveTicketsBucketName, &h,
				v.Height, v.Missed, v.Revoked, v.Spent, v.Expired)
			if err != nil {
				return err
			}
		}

		return nil
	})
	if err != nil {
		t.Fatalf("%v", err.Error())
	}

	var treap *tickettreap.Immutable
	ticketMap2 := make(map[tickettreap.Key]*tickettreap.Value)
	err = testDb.View(func(dbTx database.Tx) error {
		treap, err = DbLoadAllTickets(dbTx, dbnamespace.LiveTicketsBucketName)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		t.Fatalf("%v", err.Error())
	}
	treap.ForEach(func(k tickettreap.Key, v *tickettreap.Value) bool {
		ticketMap2[k] = v

		return true
	})

	if !reflect.DeepEqual(ticketMap, ticketMap2) {
		t.Fatalf("not same ticket maps")
	}
}
Exemple #11
0
// This example demonstrates creating a new database and using a managed
// read-write transaction to store and retrieve metadata.
func Example_basicUsage() {
	// This example assumes the ffldb driver is imported.
	//
	// import (
	// 	"github.com/decred/dcrd/database2"
	// 	_ "github.com/decred/dcrd/database/ffldb"
	// )

	// Create a database and schedule it to be closed and removed on exit.
	// Typically you wouldn't want to remove the database right away like
	// this, nor put it in the temp directory, but it's done here to ensure
	// the example cleans up after itself.
	dbPath := filepath.Join(os.TempDir(), "exampleusage")
	db, err := database.Create("ffldb", dbPath, wire.MainNet)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer os.RemoveAll(dbPath)
	defer db.Close()

	// Use the Update function of the database to perform a managed
	// read-write transaction.  The transaction will automatically be rolled
	// back if the supplied inner function returns a non-nil error.
	err = db.Update(func(tx database.Tx) error {
		// Store a key/value pair directly in the metadata bucket.
		// Typically a nested bucket would be used for a given feature,
		// but this example is using the metadata bucket directly for
		// simplicity.
		key := []byte("mykey")
		value := []byte("myvalue")
		if err := tx.Metadata().Put(key, value); err != nil {
			return err
		}

		// Read the key back and ensure it matches.
		if !bytes.Equal(tx.Metadata().Get(key), value) {
			return fmt.Errorf("unexpected value for key '%s'", key)
		}

		// Create a new nested bucket under the metadata bucket.
		nestedBucketKey := []byte("mybucket")
		nestedBucket, err := tx.Metadata().CreateBucket(nestedBucketKey)
		if err != nil {
			return err
		}

		// The key from above that was set in the metadata bucket does
		// not exist in this new nested bucket.
		if nestedBucket.Get(key) != nil {
			return fmt.Errorf("key '%s' is not expected nil", key)
		}

		return nil
	})
	if err != nil {
		fmt.Println(err)
		return
	}

	// Output:
}
Exemple #12
0
// This example demonstrates creating a new database, using a managed read-write
// transaction to store a block, and using a managed read-only transaction to
// fetch the block.
func Example_blockStorageAndRetrieval() {
	// This example assumes the ffldb driver is imported.
	//
	// import (
	// 	"github.com/decred/dcrd/database2"
	// 	_ "github.com/decred/dcrd/database/ffldb"
	// )

	// Create a database and schedule it to be closed and removed on exit.
	// Typically you wouldn't want to remove the database right away like
	// this, nor put it in the temp directory, but it's done here to ensure
	// the example cleans up after itself.
	dbPath := filepath.Join(os.TempDir(), "exampleblkstorage")
	db, err := database.Create("ffldb", dbPath, wire.MainNet)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer os.RemoveAll(dbPath)
	defer db.Close()

	// Use the Update function of the database to perform a managed
	// read-write transaction and store a genesis block in the database as
	// and example.
	err = db.Update(func(tx database.Tx) error {
		genesisBlock := chaincfg.MainNetParams.GenesisBlock
		return tx.StoreBlock(dcrutil.NewBlock(genesisBlock))
	})
	if err != nil {
		fmt.Println(err)
		return
	}

	// Use the View function of the database to perform a managed read-only
	// transaction and fetch the block stored above.
	var loadedBlockBytes []byte
	err = db.Update(func(tx database.Tx) error {
		genesisHash := chaincfg.MainNetParams.GenesisHash
		blockBytes, err := tx.FetchBlock(genesisHash)
		if err != nil {
			return err
		}

		// As documented, all data fetched from the database is only
		// valid during a database transaction in order to support
		// zero-copy backends.  Thus, make a copy of the data so it
		// can be used outside of the transaction.
		loadedBlockBytes = make([]byte, len(blockBytes))
		copy(loadedBlockBytes, blockBytes)
		return nil
	})
	if err != nil {
		fmt.Println(err)
		return
	}

	// Typically at this point, the block could be deserialized via the
	// wire.MsgBlock.Deserialize function or used in its serialized form
	// depending on need.  However, for this example, just display the
	// number of serialized bytes to show it was loaded as expected.
	fmt.Printf("Serialized block size: %d bytes\n", len(loadedBlockBytes))

	// Output:
	// Serialized block size: 300 bytes
}
Exemple #13
0
func TestTicketDBGeneral(t *testing.T) {
	// Declare some useful variables.
	testBCHeight := int64(168)
	filename := filepath.Join("..", "/../blockchain/testdata", "blocks0to168.bz2")
	fi, err := os.Open(filename)
	bcStream := bzip2.NewReader(fi)
	defer fi.Close()

	// Create a buffer of the read file.
	bcBuf := new(bytes.Buffer)
	bcBuf.ReadFrom(bcStream)

	// Create decoder from the buffer and a map to store the data.
	bcDecoder := gob.NewDecoder(bcBuf)
	testBlockchainBytes := make(map[int64][]byte)

	// Decode the blockchain into the map.
	if err := bcDecoder.Decode(&testBlockchainBytes); err != nil {
		t.Errorf("error decoding test blockchain")
	}
	testBlockchain := make(map[int64]*dcrutil.Block, len(testBlockchainBytes))
	for k, v := range testBlockchainBytes {
		bl, err := dcrutil.NewBlockFromBytes(v)
		if err != nil {
			t.Fatalf("couldn't decode block")
		}

		testBlockchain[k] = bl
	}

	// Create a new database to store the accepted stake node data into.
	dbName := "ffldb_staketest"
	dbPath := filepath.Join(testDbRoot, dbName)
	_ = os.RemoveAll(dbPath)
	testDb, err := database.Create(testDbType, dbPath, simNetParams.Net)
	if err != nil {
		t.Fatalf("error creating db: %v", err)
	}

	// Setup a teardown.
	defer os.RemoveAll(dbPath)
	defer os.RemoveAll(testDbRoot)
	defer testDb.Close()

	// Load the genesis block and begin testing exported functions.
	var bestNode *Node
	err = testDb.Update(func(dbTx database.Tx) error {
		var errLocal error
		bestNode, errLocal = InitDatabaseState(dbTx, simNetParams)
		if errLocal != nil {
			return errLocal
		}

		return nil
	})
	if err != nil {
		t.Fatalf(err.Error())
	}

	// Cache all of our nodes so that we can check them when we start
	// disconnecting and going backwards through the blockchain.
	nodesForward := make([]*Node, testBCHeight+1)
	loadedNodesForward := make([]*Node, testBCHeight+1)
	nodesForward[0] = bestNode
	loadedNodesForward[0] = bestNode
	err = testDb.Update(func(dbTx database.Tx) error {
		for i := int64(1); i <= testBCHeight; i++ {
			block := testBlockchain[i]
			ticketsToAdd := make([]chainhash.Hash, 0)
			if i >= simNetParams.StakeEnabledHeight {
				matureHeight := (i - int64(simNetParams.TicketMaturity))
				ticketsToAdd = ticketsInBlock(testBlockchain[matureHeight])
			}
			header := block.MsgBlock().Header
			if int(header.PoolSize) != len(bestNode.LiveTickets()) {
				t.Errorf("bad number of live tickets: want %v, got %v",
					header.PoolSize, len(bestNode.LiveTickets()))
			}
			if header.FinalState != bestNode.FinalState() {
				t.Errorf("bad final state: want %x, got %x",
					header.FinalState, bestNode.FinalState())
			}

			// In memory addition test.
			bestNode, err = bestNode.ConnectNode(header,
				ticketsSpentInBlock(block), revokedTicketsInBlock(block),
				ticketsToAdd)
			if err != nil {
				return fmt.Errorf("couldn't connect node: %v", err.Error())
			}

			// Write the new node to db.
			nodesForward[i] = bestNode
			blockSha := block.Sha()
			err := WriteConnectedBestNode(dbTx, bestNode, *blockSha)
			if err != nil {
				return fmt.Errorf("failure writing the best node: %v",
					err.Error())
			}

			// Reload the node from DB and make sure it's the same.
			blockHash := block.Sha()
			loadedNode, err := LoadBestNode(dbTx, bestNode.Height(),
				*blockHash, header, simNetParams)
			if err != nil {
				return fmt.Errorf("failed to load the best node: %v",
					err.Error())
			}
			err = nodesEqual(loadedNode, bestNode)
			if err != nil {
				return fmt.Errorf("loaded best node was not same as "+
					"in memory best node: %v", err.Error())
			}
			loadedNodesForward[i] = loadedNode
		}

		return nil
	})
	if err != nil {
		t.Fatalf(err.Error())
	}

	nodesBackward := make([]*Node, testBCHeight+1)
	nodesBackward[testBCHeight] = bestNode
	for i := testBCHeight; i >= int64(1); i-- {
		parentBlock := testBlockchain[i-1]
		ticketsToAdd := make([]chainhash.Hash, 0)
		if i >= simNetParams.StakeEnabledHeight {
			matureHeight := (i - 1 - int64(simNetParams.TicketMaturity))
			ticketsToAdd = ticketsInBlock(testBlockchain[matureHeight])
		}
		header := parentBlock.MsgBlock().Header
		blockUndoData := nodesForward[i-1].UndoData()
		formerBestNode := bestNode

		// In memory disconnection test.
		bestNode, err = bestNode.DisconnectNode(header, blockUndoData,
			ticketsToAdd, nil)
		if err != nil {
			t.Fatalf(err.Error())
		}

		err = nodesEqual(bestNode, nodesForward[i-1])
		if err != nil {
			t.Errorf("non-equiv stake nodes at height %v: %v", i-1, err.Error())
		}

		// Try again using the database instead of the in memory
		// data to disconnect the node, too.
		var bestNodeUsingDB *Node
		err = testDb.View(func(dbTx database.Tx) error {
			// Negative test.
			bestNodeUsingDB, err = formerBestNode.DisconnectNode(header, nil,
				nil, nil)
			if err == nil && formerBestNode.height > 1 {
				return fmt.Errorf("expected error when no in memory data " +
					"or dbtx is passed")
			}

			bestNodeUsingDB, err = formerBestNode.DisconnectNode(header, nil,
				nil, dbTx)
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			t.Errorf("couldn't disconnect using the database: %v",
				err.Error())
		}
		err = nodesEqual(bestNode, bestNodeUsingDB)
		if err != nil {
			t.Errorf("non-equiv stake nodes using db when disconnecting: %v",
				err.Error())
		}

		// Write the new best node to the database.
		nodesBackward[i-1] = bestNode
		err = testDb.Update(func(dbTx database.Tx) error {
			nodesForward[i] = bestNode
			parentBlockSha := parentBlock.Sha()
			err := WriteDisconnectedBestNode(dbTx, bestNode,
				*parentBlockSha, formerBestNode.UndoData())
			if err != nil {
				return fmt.Errorf("failure writing the best node: %v",
					err.Error())
			}

			return nil
		})
		if err != nil {
			t.Errorf("%s", err.Error())
		}

		// Check the best node against the loaded best node from
		// the database after.
		err = testDb.View(func(dbTx database.Tx) error {
			parentBlockHash := parentBlock.Sha()
			loadedNode, err := LoadBestNode(dbTx, bestNode.Height(),
				*parentBlockHash, header, simNetParams)
			if err != nil {
				return fmt.Errorf("failed to load the best node: %v",
					err.Error())
			}
			err = nodesEqual(loadedNode, bestNode)
			if err != nil {
				return fmt.Errorf("loaded best node was not same as "+
					"in memory best node: %v", err.Error())
			}
			err = nodesEqual(loadedNode, loadedNodesForward[i-1])
			if err != nil {
				return fmt.Errorf("loaded best node was not same as "+
					"previously cached node: %v", err.Error())
			}

			return nil
		})
		if err != nil {
			t.Errorf("%s", err.Error())
		}
	}

	// Unit testing the in-memory implementation negatively.
	b161 := testBlockchain[161]
	b162 := testBlockchain[162]
	n162Test := copyNode(nodesForward[162])

	// No node.
	_, err = connectNode(nil, b162.MsgBlock().Header,
		n162Test.SpentByBlock(), revokedTicketsInBlock(b162),
		n162Test.NewTickets())
	if err == nil {
		t.Errorf("expect error for no node")
	}

	// Best node missing ticket in live ticket bucket to spend.
	n161Copy := copyNode(nodesForward[161])
	n161Copy.liveTickets.Delete(tickettreap.Key(n162Test.SpentByBlock()[0]))
	_, err = n161Copy.ConnectNode(b162.MsgBlock().Header,
		n162Test.SpentByBlock(), revokedTicketsInBlock(b162),
		n162Test.NewTickets())
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Best node missing ticket in live ticket bucket to spend: %v", err)
	}

	// Duplicate best winners.
	n161Copy = copyNode(nodesForward[161])
	n162Copy := copyNode(nodesForward[162])
	n161Copy.nextWinners[0] = n161Copy.nextWinners[1]
	spentInBlock := n162Copy.SpentByBlock()
	spentInBlock[0] = spentInBlock[1]
	_, err = n161Copy.ConnectNode(b162.MsgBlock().Header,
		spentInBlock, revokedTicketsInBlock(b162),
		n162Test.NewTickets())
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Best node missing ticket in live ticket bucket to spend: %v", err)
	}

	// Test for corrupted spentInBlock.
	someHash := chainhash.HashFuncH([]byte{0x00})
	spentInBlock = n162Test.SpentByBlock()
	spentInBlock[4] = someHash
	_, err = nodesForward[161].ConnectNode(b162.MsgBlock().Header,
		spentInBlock, revokedTicketsInBlock(b162), n162Test.NewTickets())
	if err == nil || err.(RuleError).GetCode() != ErrUnknownTicketSpent {
		t.Errorf("unexpected wrong or no error for "+
			"Test for corrupted spentInBlock: %v", err)
	}

	// Corrupt winners.
	n161Copy = copyNode(nodesForward[161])
	n161Copy.nextWinners[4] = someHash
	_, err = n161Copy.ConnectNode(b162.MsgBlock().Header,
		spentInBlock, revokedTicketsInBlock(b162), n162Test.NewTickets())
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Corrupt winners: %v", err)
	}

	// Unknown missed ticket.
	n162Copy = copyNode(nodesForward[162])
	spentInBlock = n162Copy.SpentByBlock()
	_, err = nodesForward[161].ConnectNode(b162.MsgBlock().Header,
		spentInBlock, append(revokedTicketsInBlock(b162), someHash),
		n162Copy.NewTickets())
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Unknown missed ticket: %v", err)
	}

	// Insert a duplicate new ticket.
	spentInBlock = n162Test.SpentByBlock()
	newTicketsDup := []chainhash.Hash{someHash, someHash}
	_, err = nodesForward[161].ConnectNode(b162.MsgBlock().Header,
		spentInBlock, revokedTicketsInBlock(b162), newTicketsDup)
	if err == nil || err.(RuleError).GetCode() != ErrDuplicateTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Insert a duplicate new ticket: %v", err)
	}

	// Impossible undo data for disconnecting.
	n161Copy = copyNode(nodesForward[161])
	n162Copy = copyNode(nodesForward[162])
	n162Copy.databaseUndoUpdate[0].Expired = false
	n162Copy.databaseUndoUpdate[0].Missed = false
	n162Copy.databaseUndoUpdate[0].Spent = false
	n162Copy.databaseUndoUpdate[0].Revoked = true
	_, err = n162Copy.DisconnectNode(b161.MsgBlock().Header,
		n161Copy.UndoData(), n161Copy.NewTickets(), nil)
	if err == nil {
		t.Errorf("unexpected wrong or no error for "+
			"Impossible undo data for disconnecting: %v", err)
	}

	// Missing undo data for disconnecting.
	n161Copy = copyNode(nodesForward[161])
	n162Copy = copyNode(nodesForward[162])
	n162Copy.databaseUndoUpdate = n162Copy.databaseUndoUpdate[0:3]
	_, err = n162Copy.DisconnectNode(b161.MsgBlock().Header,
		n161Copy.UndoData(), n161Copy.NewTickets(), nil)
	if err == nil {
		t.Errorf("unexpected wrong or no error for "+
			"Missing undo data for disconnecting: %v", err)
	}

	// Unknown undo data hash when disconnecting (missing).
	n161Copy = copyNode(nodesForward[161])
	n162Copy = copyNode(nodesForward[162])
	n162Copy.databaseUndoUpdate[0].TicketHash = someHash
	n162Copy.databaseUndoUpdate[0].Expired = false
	n162Copy.databaseUndoUpdate[0].Missed = true
	n162Copy.databaseUndoUpdate[0].Spent = false
	n162Copy.databaseUndoUpdate[0].Revoked = false
	_, err = n162Copy.DisconnectNode(b161.MsgBlock().Header,
		n161Copy.UndoData(), n161Copy.NewTickets(), nil)
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Unknown undo data for disconnecting (missing): %v", err)
	}

	// Unknown undo data hash when disconnecting (revoked).
	n161Copy = copyNode(nodesForward[161])
	n162Copy = copyNode(nodesForward[162])
	n162Copy.databaseUndoUpdate[0].TicketHash = someHash
	n162Copy.databaseUndoUpdate[0].Expired = false
	n162Copy.databaseUndoUpdate[0].Missed = true
	n162Copy.databaseUndoUpdate[0].Spent = false
	n162Copy.databaseUndoUpdate[0].Revoked = true
	_, err = n162Copy.DisconnectNode(b161.MsgBlock().Header,
		n161Copy.UndoData(), n161Copy.NewTickets(), nil)
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Unknown undo data for disconnecting (revoked): %v", err)
	}
}
Exemple #14
0
// TestFailureScenarios ensures several failure scenarios such as database
// corruption, block file write failures, and rollback failures are handled
// correctly.
func TestFailureScenarios(t *testing.T) {
	// Create a new database to run tests against.
	dbPath := filepath.Join(os.TempDir(), "ffldb-failurescenarios")
	_ = os.RemoveAll(dbPath)
	idb, err := database.Create(dbType, dbPath, blockDataNet)
	if err != nil {
		t.Errorf("Failed to create test database (%s) %v", dbType, err)
		return
	}
	defer os.RemoveAll(dbPath)
	defer idb.Close()

	// Create a test context to pass around.
	tc := &testContext{
		t:            t,
		db:           idb,
		files:        make(map[uint32]*lockableFile),
		maxFileSizes: make(map[uint32]int64),
	}

	// Change the maximum file size to a small value to force multiple flat
	// files with the test data set and replace the file-related functions
	// to make use of mock files in memory.  This allows injection of
	// various file-related errors.
	store := idb.(*db).store
	store.maxBlockFileSize = 1024 // 1KiB
	store.openWriteFileFunc = func(fileNum uint32) (filer, error) {
		if file, ok := tc.files[fileNum]; ok {
			// "Reopen" the file.
			file.Lock()
			mock := file.file.(*mockFile)
			mock.Lock()
			mock.closed = false
			mock.Unlock()
			file.Unlock()
			return mock, nil
		}

		// Limit the max size of the mock file as specified in the test
		// context.
		maxSize := int64(-1)
		if maxFileSize, ok := tc.maxFileSizes[fileNum]; ok {
			maxSize = int64(maxFileSize)
		}
		file := &mockFile{maxSize: int64(maxSize)}
		tc.files[fileNum] = &lockableFile{file: file}
		return file, nil
	}
	store.openFileFunc = func(fileNum uint32) (*lockableFile, error) {
		// Force error when trying to open max file num.
		if fileNum == ^uint32(0) {
			return nil, makeDbErr(database.ErrDriverSpecific,
				"test", nil)
		}
		if file, ok := tc.files[fileNum]; ok {
			// "Reopen" the file.
			file.Lock()
			mock := file.file.(*mockFile)
			mock.Lock()
			mock.closed = false
			mock.Unlock()
			file.Unlock()
			return file, nil
		}
		file := &lockableFile{file: &mockFile{}}
		tc.files[fileNum] = file
		return file, nil
	}
	store.deleteFileFunc = func(fileNum uint32) error {
		if file, ok := tc.files[fileNum]; ok {
			file.Lock()
			file.file.Close()
			file.Unlock()
			delete(tc.files, fileNum)
			return nil
		}

		str := fmt.Sprintf("file %d does not exist", fileNum)
		return makeDbErr(database.ErrDriverSpecific, str, nil)
	}

	// Load the test blocks and save in the test context for use throughout
	// the tests.
	blocks, err := loadBlocks(t, blockDataFile, blockDataNet)
	if err != nil {
		t.Errorf("loadBlocks: Unexpected error: %v", err)
		return
	}
	tc.blocks = blocks

	// Test various failures paths when writing to the block files.
	if !testWriteFailures(tc) {
		return
	}

	// Test various file-related issues such as closed and missing files.
	if !testBlockFileErrors(tc) {
		return
	}

	// Test various corruption scenarios.
	testCorruption(tc)
}
Exemple #15
0
// chainSetup is used to create a new db and chain instance with the genesis
// block already inserted.  In addition to the new chain instnce, it returns
// a teardown function the caller should invoke when done testing to clean up.
func chainSetup(dbName string, params *chaincfg.Params) (*blockchain.BlockChain, func(), error) {
	if !isSupportedDbType(testDbType) {
		return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
	}

	// Handle memory database specially since it doesn't need the disk
	// specific handling.
	var db database.DB
	var teardown func()
	if testDbType == "memdb" {
		ndb, err := database.Create(testDbType)
		if err != nil {
			return nil, nil, fmt.Errorf("error creating db: %v", err)
		}
		db = ndb

		// Setup a teardown function for cleaning up.  This function is
		// returned to the caller to be invoked when it is done testing.
		teardown = func() {
			db.Close()
		}
	} else {
		// Create the root directory for test databases.
		if !fileExists(testDbRoot) {
			if err := os.MkdirAll(testDbRoot, 0700); err != nil {
				err := fmt.Errorf("unable to create test db "+
					"root: %v", err)
				return nil, nil, err
			}
		}

		// Create a new database to store the accepted blocks into.
		dbPath := filepath.Join(testDbRoot, dbName)
		_ = os.RemoveAll(dbPath)
		ndb, err := database.Create(testDbType, dbPath, blockDataNet)
		if err != nil {
			return nil, nil, fmt.Errorf("error creating db: %v", err)
		}
		db = ndb

		// Setup a teardown function for cleaning up.  This function is
		// returned to the caller to be invoked when it is done testing.
		teardown = func() {
			db.Close()
			os.RemoveAll(dbPath)
			os.RemoveAll(testDbRoot)
		}
	}

	// Create the main chain instance.
	chain, err := blockchain.New(&blockchain.Config{
		DB:          db,
		ChainParams: params,
		TimeSource:  blockchain.NewMedianTime(),
	})

	if err != nil {
		teardown()
		err := fmt.Errorf("failed to create chain instance: %v", err)
		return nil, nil, err
	}

	return chain, teardown, nil
}