// 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 various indexes as 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)) } if !cfg.NoExistsAddrIndex { log.Info("Exists address index is enabled") indexes = append(indexes, indexers.NewExistsAddrIndex(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, activeNetParams) } 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(), startTime: time.Now(), }, nil }
// newBlockImporter returns a new importer for the provided file reader seeker // and database. func newBlockImporter(db database.Db, r io.ReadSeeker) *blockImporter { return &blockImporter{ db: db, r: r, processQueue: make(chan []byte, 2), doneChan: make(chan bool), errChan: make(chan error), quit: make(chan struct{}), chain: blockchain.New(db, nil, activeNetParams, nil), medianTime: blockchain.NewMedianTime(), lastLogTime: time.Now(), } }
// 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 }
// 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 use memdb // which is a memory-only database backend, but we create a new db // here so this is a complete working example. db, err := database.CreateDB("memdb") if err != nil { fmt.Printf("Failed to create database: %v\n", err) return } defer db.Close() var tmdb *stake.TicketDB // Insert the main network genesis block. This is part of the initial // database setup. Like above, this typically would not be needed when // opening an existing database. genesisBlock := dcrutil.NewBlock(chaincfg.MainNetParams.GenesisBlock) _, err = db.InsertBlock(genesisBlock) if err != nil { fmt.Printf("Failed to insert genesis block: %v\n", err) return } // Create a new BlockChain instance without an initialized signature // verification cache, using the underlying database for the main // bitcoin network and ignore notifications. chain := blockchain.New(db, tmdb, &chaincfg.MainNetParams, nil, nil) // Create a new median time source that is required by the upcoming // call to ProcessBlock. Ordinarily this would also add time values // obtained from other peers on the network so the local time is // adjusted to be in agreement with other peers. timeSource := blockchain.NewMedianTime() // Process a block. For this example, we are going to intentionally // cause an error by trying to process the genesis block which already // exists. isOrphan, _, err := chain.ProcessBlock(genesisBlock, timeSource, blockchain.BFNone) if err != nil { fmt.Printf("Failed to process block: %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 }
// 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 } } }
// TestCheckTransactionStandard tests the checkTransactionStandard API. func TestCheckTransactionStandard(t *testing.T) { // Create some dummy, but otherwise standard, data for transactions. prevOutHash, err := chainhash.NewHashFromStr("01") if err != nil { t.Fatalf("NewShaHashFromStr: unexpected error: %v", err) } dummyPrevOut := wire.OutPoint{Hash: *prevOutHash, Index: 1, Tree: 0} dummySigScript := bytes.Repeat([]byte{0x00}, 65) dummyTxIn := wire.TxIn{ PreviousOutPoint: dummyPrevOut, Sequence: wire.MaxTxInSequenceNum, ValueIn: 0, BlockHeight: 0, BlockIndex: 0, SignatureScript: dummySigScript, } addrHash := [20]byte{0x01} addr, err := dcrutil.NewAddressPubKeyHash(addrHash[:], &chaincfg.TestNetParams, chainec.ECTypeSecp256k1) if err != nil { t.Fatalf("NewAddressPubKeyHash: unexpected error: %v", err) } dummyPkScript, err := txscript.PayToAddrScript(addr) if err != nil { t.Fatalf("PayToAddrScript: unexpected error: %v", err) } dummyTxOut := wire.TxOut{ Value: 100000000, // 1 BTC Version: 0, PkScript: dummyPkScript, } tests := []struct { name string tx wire.MsgTx height int64 isStandard bool code wire.RejectCode }{ { name: "Typical pay-to-pubkey-hash transaction", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: true, }, { name: "Transaction version too high", tx: wire.MsgTx{ Version: int32(wire.TxVersion + 1), TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Transaction is not finalized", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, SignatureScript: dummySigScript, Sequence: 0, }}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 300001, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Transaction size is too large", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: bytes.Repeat([]byte{0x00}, maxStandardTxSize+1), }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Signature script size is too large", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, SignatureScript: bytes.Repeat([]byte{0x00}, maxStandardSigScriptSize+1), Sequence: wire.MaxTxInSequenceNum, }}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Signature script that does more than push data", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{{ PreviousOutPoint: dummyPrevOut, SignatureScript: []byte{ txscript.OP_CHECKSIGVERIFY}, Sequence: wire.MaxTxInSequenceNum, }}, TxOut: []*wire.TxOut{&dummyTxOut}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Valid but non standard public key script", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 100000000, PkScript: []byte{txscript.OP_TRUE}, }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "More than four nulldata outputs", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: []byte{txscript.OP_RETURN}, }, { Value: 0, PkScript: []byte{txscript.OP_RETURN}, }, { Value: 0, PkScript: []byte{txscript.OP_RETURN}, }, { Value: 0, PkScript: []byte{txscript.OP_RETURN}, }, { Value: 0, PkScript: []byte{txscript.OP_RETURN}, }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectNonstandard, }, { name: "Dust output", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: dummyPkScript, }}, LockTime: 0, }, height: 300000, isStandard: false, code: wire.RejectDust, }, { name: "One nulldata output with 0 amount (standard)", tx: wire.MsgTx{ Version: 1, TxIn: []*wire.TxIn{&dummyTxIn}, TxOut: []*wire.TxOut{{ Value: 0, PkScript: []byte{txscript.OP_RETURN}, }}, LockTime: 0, }, height: 300000, isStandard: true, }, } timeSource := blockchain.NewMedianTime() for _, test := range tests { // Ensure standardness is as expected. tx := dcrutil.NewTx(&test.tx) err := checkTransactionStandard(tx, stake.DetermineTxType(tx), test.height, timeSource, defaultMinRelayTxFee) if err == nil && test.isStandard { // Test passes since function returned standard for a // transaction which is intended to be standard. continue } if err == nil && !test.isStandard { t.Errorf("checkTransactionStandard (%s): standard when "+ "it should not be", test.name) continue } if err != nil && test.isStandard { t.Errorf("checkTransactionStandard (%s): nonstandard "+ "when it should not be: %v", test.name, err) continue } // Ensure error type is a TxRuleError inside of a RuleError. rerr, ok := err.(RuleError) if !ok { t.Errorf("checkTransactionStandard (%s): unexpected "+ "error type - got %T", test.name, err) continue } txrerr, ok := rerr.Err.(TxRuleError) if !ok { t.Errorf("checkTransactionStandard (%s): unexpected "+ "error type - got %T", test.name, rerr.Err) continue } // Ensure the reject code is the expected one. if txrerr.RejectCode != test.code { t.Errorf("checkTransactionStandard (%s): unexpected "+ "error code - got %v, want %v", test.name, txrerr.RejectCode, test.code) continue } } }
// TestReorganization loads a set of test blocks which force a chain // reorganization to test the block chain handling code. func TestReorganization(t *testing.T) { // Create a new database and chain instance to run tests against. chain, teardownFunc, err := chainSetup("reorgunittest", simNetParams) if err != nil { t.Errorf("Failed to setup chain instance: %v", err) return } defer teardownFunc() err = chain.GenerateInitialIndex() if err != nil { t.Errorf("GenerateInitialIndex: %v", err) } // The genesis block should fail to connect since it's already // inserted. genesisBlock := simNetParams.GenesisBlock err = chain.CheckConnectBlock(dcrutil.NewBlock(genesisBlock)) if err == nil { t.Errorf("CheckConnectBlock: Did not receive expected error") } // Load up the rest of the blocks up to HEAD. filename := filepath.Join("testdata/", "reorgto179.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) blockChain := make(map[int64][]byte) // Decode the blockchain into the map if err := bcDecoder.Decode(&blockChain); err != nil { t.Errorf("error decoding test blockchain: %v", err.Error()) } // Load up the short chain timeSource := blockchain.NewMedianTime() finalIdx1 := 179 for i := 1; i < finalIdx1+1; i++ { bl, err := dcrutil.NewBlockFromBytes(blockChain[int64(i)]) if err != nil { t.Errorf("NewBlockFromBytes error: %v", err.Error()) } bl.SetHeight(int64(i)) _, _, err = chain.ProcessBlock(bl, timeSource, blockchain.BFNone) if err != nil { t.Errorf("ProcessBlock error: %v", err.Error()) } } // Load the long chain and begin loading blocks from that too, // forcing a reorganization // Load up the rest of the blocks up to HEAD. filename = filepath.Join("testdata/", "reorgto180.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) blockChain = make(map[int64][]byte) // Decode the blockchain into the map if err := bcDecoder.Decode(&blockChain); err != nil { t.Errorf("error decoding test blockchain: %v", err.Error()) } forkPoint := 131 finalIdx2 := 180 for i := forkPoint; i < finalIdx2+1; i++ { bl, err := dcrutil.NewBlockFromBytes(blockChain[int64(i)]) if err != nil { t.Errorf("NewBlockFromBytes error: %v", err.Error()) } bl.SetHeight(int64(i)) _, _, err = chain.ProcessBlock(bl, timeSource, blockchain.BFNone) if err != nil { t.Errorf("ProcessBlock error: %v", err.Error()) } } // Ensure our blockchain is at the correct best tip topBlock, _ := chain.GetTopBlock() tipHash := topBlock.Sha() expected, _ := chainhash.NewHashFromStr("5ab969d0afd8295b6cd1506f2a310d" + "259322015c8bd5633f283a163ce0e50594") if *tipHash != *expected { t.Errorf("Failed to correctly reorg; expected tip %v, got tip %v", expected, tipHash) } have, err := chain.HaveBlock(expected) if !have { t.Errorf("missing tip block after reorganization test") } if err != nil { t.Errorf("unexpected error testing for presence of new tip block "+ "after reorg test: %v", err) } return }
// newServer returns a new dcrd server configured to listen on addr for the // decred network type specified by chainParams. Use start to begin accepting // connections from peers. func newServer(listenAddrs []string, database database.Db, tmdb *stake.TicketDB, chainParams *chaincfg.Params) (*server, error) { nonce, err := wire.RandomUint64() if err != nil { return nil, err } amgr := addrmgr.New(cfg.DataDir, dcrdLookup) var listeners []net.Listener var nat NAT if !cfg.DisableListen { ipv4Addrs, ipv6Addrs, wildcard, err := parseListeners(listenAddrs) if err != nil { return nil, err } listeners = make([]net.Listener, 0, len(ipv4Addrs)+len(ipv6Addrs)) discover := true if len(cfg.ExternalIPs) != 0 { discover = false // if this fails we have real issues. port, _ := strconv.ParseUint( activeNetParams.DefaultPort, 10, 16) for _, sip := range cfg.ExternalIPs { eport := uint16(port) host, portstr, err := net.SplitHostPort(sip) if err != nil { // no port, use default. host = sip } else { port, err := strconv.ParseUint( portstr, 10, 16) if err != nil { srvrLog.Warnf("Can not parse "+ "port from %s for "+ "externalip: %v", sip, err) continue } eport = uint16(port) } na, err := amgr.HostToNetAddress(host, eport, wire.SFNodeNetwork) if err != nil { srvrLog.Warnf("Not adding %s as "+ "externalip: %v", sip, err) continue } err = amgr.AddLocalAddress(na, addrmgr.ManualPrio) if err != nil { amgrLog.Warnf("Skipping specified external IP: %v", err) } } } else if discover && cfg.Upnp { nat, err = Discover() if err != nil { srvrLog.Warnf("Can't discover upnp: %v", err) } // nil nat here is fine, just means no upnp on network. } // TODO(oga) nonstandard port... if wildcard { port, err := strconv.ParseUint(activeNetParams.DefaultPort, 10, 16) if err != nil { // I can't think of a cleaner way to do this... goto nowc } addrs, err := net.InterfaceAddrs() for _, a := range addrs { ip, _, err := net.ParseCIDR(a.String()) if err != nil { continue } na := wire.NewNetAddressIPPort(ip, uint16(port), wire.SFNodeNetwork) if discover { err = amgr.AddLocalAddress(na, addrmgr.InterfacePrio) if err != nil { amgrLog.Debugf("Skipping local address: %v", err) } } } } nowc: for _, addr := range ipv4Addrs { listener, err := net.Listen("tcp4", addr) if err != nil { srvrLog.Warnf("Can't listen on %s: %v", addr, err) continue } listeners = append(listeners, listener) if discover { if na, err := amgr.DeserializeNetAddress(addr); err == nil { err = amgr.AddLocalAddress(na, addrmgr.BoundPrio) if err != nil { amgrLog.Warnf("Skipping bound address: %v", err) } } } } for _, addr := range ipv6Addrs { listener, err := net.Listen("tcp6", addr) if err != nil { srvrLog.Warnf("Can't listen on %s: %v", addr, err) continue } listeners = append(listeners, listener) if discover { if na, err := amgr.DeserializeNetAddress(addr); err == nil { err = amgr.AddLocalAddress(na, addrmgr.BoundPrio) if err != nil { amgrLog.Debugf("Skipping bound address: %v", err) } } } } if len(listeners) == 0 { return nil, errors.New("no valid listen address") } } s := server{ nonce: nonce, listeners: listeners, chainParams: chainParams, addrManager: amgr, newPeers: make(chan *peer, cfg.MaxPeers), donePeers: make(chan *peer, cfg.MaxPeers), banPeers: make(chan *peer, cfg.MaxPeers), wakeup: make(chan struct{}), query: make(chan interface{}), relayInv: make(chan relayMsg, cfg.MaxPeers), broadcast: make(chan broadcastMsg, cfg.MaxPeers), quit: make(chan struct{}), modifyRebroadcastInv: make(chan interface{}), peerHeightsUpdate: make(chan updatePeerHeightsMsg), nat: nat, db: database, tmdb: tmdb, timeSource: blockchain.NewMedianTime(), } bm, err := newBlockManager(&s) if err != nil { return nil, err } s.blockManager = bm s.txMemPool = newTxMemPool(&s) s.cpuMiner = newCPUMiner(&s) if !cfg.NoAddrIndex { ai, err := newAddrIndexer(&s) if err != nil { return nil, err } s.addrIndexer = ai } if !cfg.DisableRPC { s.rpcServer, err = newRPCServer(cfg.RPCListeners, &s) if err != nil { return nil, err } } return &s, nil }
// 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 }