// newBlockImporter returns a new importer for the provided file reader seeker // and database. func newBlockImporter(db database.DB, r io.ReadSeeker) (*blockImporter, error) { // Create the transaction and address indexes if needed. // // CAUTION: the txindex needs to be first in the indexes array because // the addrindex uses data from the txindex during catchup. If the // addrindex is run first, it may not have the transactions from the // current block indexed. var indexes []indexers.Indexer if cfg.TxIndex || cfg.AddrIndex { // Enable transaction index if address index is enabled since it // requires it. if !cfg.TxIndex { log.Infof("Transaction index enabled because it is " + "required by the address index") cfg.TxIndex = true } else { log.Info("Transaction index is enabled") } indexes = append(indexes, indexers.NewTxIndex(db)) } if cfg.AddrIndex { log.Info("Address index is enabled") indexes = append(indexes, indexers.NewAddrIndex(db, activeNetParams)) } // Create an index manager if any of the optional indexes are enabled. var indexManager blockchain.IndexManager if len(indexes) > 0 { indexManager = indexers.NewManager(db, indexes) } chain, err := blockchain.New(&blockchain.Config{ DB: db, ChainParams: activeNetParams, TimeSource: blockchain.NewMedianTime(), IndexManager: indexManager, }) if err != nil { return nil, err } return &blockImporter{ db: db, r: r, processQueue: make(chan []byte, 2), doneChan: make(chan bool), errChan: make(chan error), quit: make(chan struct{}), chain: chain, lastLogTime: time.Now(), }, nil }
// 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 Bitcoin 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 := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock) isMainChain, isOrphan, err := chain.ProcessBlock(genesisBlock, blockchain.BFNone) if err != nil { fmt.Printf("Failed to process block: %v\n", err) return } fmt.Printf("Block accepted. Is it on the main chain?: %v", isMainChain) fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan) // Output: // Failed to process block: already have block 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f }
// TestCheckBlockSanity tests the CheckBlockSanity function to ensure it works // as expected. func TestCheckBlockSanity(t *testing.T) { powLimit := chaincfg.MainNetParams.PowLimit block := btcutil.NewBlock(&Block100000) timeSource := blockchain.NewMedianTime() err := blockchain.CheckBlockSanity(block, powLimit, timeSource) if err != nil { t.Errorf("CheckBlockSanity: %v", err) } // Ensure a block that has a timestamp with a precision higher than one // second fails. timestamp := block.MsgBlock().Header.Timestamp block.MsgBlock().Header.Timestamp = timestamp.Add(time.Nanosecond) err = blockchain.CheckBlockSanity(block, powLimit, timeSource) if err == nil { t.Errorf("CheckBlockSanity: error is nil when it shouldn't be") } }
// TestMedianTime tests the medianTime implementation. func TestMedianTime(t *testing.T) { tests := []struct { in []int64 wantOffset int64 useDupID bool }{ // Not enough samples must result in an offset of 0. {in: []int64{1}, wantOffset: 0}, {in: []int64{1, 2}, wantOffset: 0}, {in: []int64{1, 2, 3}, wantOffset: 0}, {in: []int64{1, 2, 3, 4}, wantOffset: 0}, // Various number of entries. The expected offset is only // updated on odd number of elements. {in: []int64{-13, 57, -4, -23, -12}, wantOffset: -12}, {in: []int64{55, -13, 61, -52, 39, 55}, wantOffset: 39}, {in: []int64{-62, -58, -30, -62, 51, -30, 15}, wantOffset: -30}, {in: []int64{29, -47, 39, 54, 42, 41, 8, -33}, wantOffset: 39}, {in: []int64{37, 54, 9, -21, -56, -36, 5, -11, -39}, wantOffset: -11}, {in: []int64{57, -28, 25, -39, 9, 63, -16, 19, -60, 25}, wantOffset: 9}, {in: []int64{-5, -4, -3, -2, -1}, wantOffset: -3, useDupID: true}, // The offset stops being updated once the max number of entries // has been reached. This is actually a bug from Bitcoin Core, // but since the time is ultimately used as a part of the // consensus rules, it must be mirrored. {in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52}, wantOffset: 17}, {in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45}, wantOffset: 17}, {in: []int64{-67, 67, -50, 24, 63, 17, 58, -14, 5, -32, -52, 45, 4}, wantOffset: 17}, // Offsets that are too far away from the local time should // be ignored. {in: []int64{-4201, 4202, -4203, 4204, -4205}, wantOffset: 0}, // Excerise the condition where the median offset is greater // than the max allowed adjustment, but there is at least one // sample that is close enough to the current time to avoid // triggering a warning about an invalid local clock. {in: []int64{4201, 4202, 4203, 4204, -299}, wantOffset: 0}, } // Modify the max number of allowed median time entries for these tests. blockchain.TstSetMaxMedianTimeEntries(10) defer blockchain.TstSetMaxMedianTimeEntries(200) for i, test := range tests { filter := blockchain.NewMedianTime() for j, offset := range test.in { id := strconv.Itoa(j) now := time.Unix(time.Now().Unix(), 0) tOffset := now.Add(time.Duration(offset) * time.Second) filter.AddTimeSample(id, tOffset) // Ensure the duplicate IDs are ignored. if test.useDupID { // Modify the offsets to ensure the final median // would be different if the duplicate is added. tOffset = tOffset.Add(time.Duration(offset) * time.Second) filter.AddTimeSample(id, tOffset) } } // Since it is possible that the time.Now call in AddTimeSample // and the time.Now calls here in the tests will be off by one // second, allow a fudge factor to compensate. gotOffset := filter.Offset() wantOffset := time.Duration(test.wantOffset) * time.Second wantOffset2 := time.Duration(test.wantOffset-1) * time.Second if gotOffset != wantOffset && gotOffset != wantOffset2 { t.Errorf("Offset #%d: unexpected offset -- got %v, "+ "want %v or %v", i, gotOffset, wantOffset, wantOffset2) continue } // Since it is possible that the time.Now call in AdjustedTime // and the time.Now call here in the tests will be off by one // second, allow a fudge factor to compensate. adjustedTime := filter.AdjustedTime() now := time.Unix(time.Now().Unix(), 0) wantTime := now.Add(filter.Offset()) wantTime2 := now.Add(filter.Offset() - time.Second) if !adjustedTime.Equal(wantTime) && !adjustedTime.Equal(wantTime2) { t.Errorf("AdjustedTime #%d: unexpected result -- got %v, "+ "want %v or %v", i, adjustedTime, wantTime, wantTime2) continue } } }
// chainSetup is used to create a new db and chain instance with the genesis // block already inserted. In addition to the new chain instance, 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) } } // Copy the chain params to ensure any modifications the tests do to // the chain parameters do not affect the global instance. paramsCopy := *params // Create the main chain instance. chain, err := blockchain.New(&blockchain.Config{ DB: db, ChainParams: ¶msCopy, TimeSource: blockchain.NewMedianTime(), SigCache: txscript.NewSigCache(1000), }) if err != nil { teardown() err := fmt.Errorf("failed to create chain instance: %v", err) return nil, nil, err } return chain, teardown, nil }