// TestReorganization loads a set of test blocks which force a chain // reorganization to test the block chain handling code. // The test blocks were originally from a post on the bitcoin talk forums: // https://bitcointalk.org/index.php?topic=46370.msg577556#msg577556 func TestReorganization(t *testing.T) { // Intentionally load the side chain blocks out of order to ensure // orphans are handled properly along with chain reorganization. testFiles := []string{ "blk_0_to_4.dat.bz2", "blk_4A.dat.bz2", "blk_5A.dat.bz2", "blk_3A.dat.bz2", } var blocks []*btcutil.Block for _, file := range testFiles { blockTmp, err := loadBlocks(file) if err != nil { t.Errorf("Error loading file: %v\n", err) } for _, block := range blockTmp { blocks = append(blocks, block) } } t.Logf("Number of blocks: %v\n", len(blocks)) // Create a new database and chain instance to run tests against. chain, teardownFunc, err := chainSetup("reorg") if err != nil { t.Errorf("Failed to setup chain instance: %v", err) return } defer teardownFunc() // Since we're not dealing with the real block chain, disable // checkpoints and set the coinbase maturity to 1. chain.DisableCheckpoints(true) blockchain.TstSetCoinbaseMaturity(1) timeSource := blockchain.NewMedianTime() expectedOrphans := map[int]struct{}{5: struct{}{}, 6: struct{}{}} for i := 1; i < len(blocks); i++ { isOrphan, err := chain.ProcessBlock(blocks[i], timeSource, blockchain.BFNone) if err != nil { t.Errorf("ProcessBlock fail on block %v: %v\n", i, err) return } if _, ok := expectedOrphans[i]; !ok && isOrphan { t.Errorf("ProcessBlock incorrectly returned block %v "+ "is an orphan\n", i) } } return }
// 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, 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 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 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() // 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 := btcutil.NewBlock(btcnet.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 using the underlying database for // the main bitcoin network and ignore notifications. chain := blockchain.New(db, &btcnet.MainNetParams, 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) // 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 := btcnet.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 } } }
// TestHaveBlock tests the HaveBlock API to ensure proper functionality. func TestHaveBlock(t *testing.T) { // Load up blocks such that there is a side chain. // (genesis block) -> 1 -> 2 -> 3 -> 4 // \-> 3a testFiles := []string{ "blk_0_to_4.dat.bz2", "blk_3A.dat.bz2", } var blocks []*btcutil.Block for _, file := range testFiles { blockTmp, err := loadBlocks(file) if err != nil { t.Errorf("Error loading file: %v\n", err) return } for _, block := range blockTmp { blocks = append(blocks, block) } } // Create a new database and chain instance to run tests against. chain, teardownFunc, err := chainSetup("haveblock") if err != nil { t.Errorf("Failed to setup chain instance: %v", err) return } defer teardownFunc() // Since we're not dealing with the real block chain, disable // checkpoints and set the coinbase maturity to 1. chain.DisableCheckpoints(true) blockchain.TstSetCoinbaseMaturity(1) timeSource := blockchain.NewMedianTime() for i := 1; i < len(blocks); i++ { isOrphan, err := chain.ProcessBlock(blocks[i], timeSource, blockchain.BFNone) if err != nil { t.Errorf("ProcessBlock fail on block %v: %v\n", i, err) return } if isOrphan { t.Errorf("ProcessBlock incorrectly returned block %v "+ "is an orphan\n", i) return } } // Insert an orphan block. isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000), timeSource, blockchain.BFNone) if err != nil { t.Errorf("Unable to process block: %v", err) return } if !isOrphan { t.Errorf("ProcessBlock indicated block is an not orphan when " + "it should be\n") return } tests := []struct { hash string want bool }{ // Genesis block should be present (in the main chain). {hash: btcnet.MainNetParams.GenesisHash.String(), want: true}, // Block 3a should be present (on a side chain). {hash: "00000000474284d20067a4d33f6a02284e6ef70764a3a26d6a5b9df52ef663dd", want: true}, // Block 100000 should be present (as an orphan). {hash: "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506", want: true}, // Random hashes should not be availble. {hash: "123", want: false}, } for i, test := range tests { hash, err := btcwire.NewShaHashFromStr(test.hash) if err != nil { t.Errorf("NewShaHashFromStr: %v", err) continue } result, err := chain.HaveBlock(hash) if err != nil { t.Errorf("HaveBlock #%d unexpected error: %v", i, err) return } if result != test.want { t.Errorf("HaveBlock #%d got %v want %v", i, result, test.want) continue } } }
// newServer returns a new pointcoind server configured to listen on addr for the // bitcoin network type specified by netParams. Use start to begin accepting // connections from peers. func newServer(listenAddrs []string, db database.Db, netParams *btcnet.Params) (*server, error) { nonce, err := btcwire.RandomUint64() if err != nil { return nil, err } amgr := addrmgr.New(cfg.DataDir, pointcoindLookup) 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, btcwire.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 := btcwire.NewNetAddressIPPort(ip, uint16(port), btcwire.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, netParams: netParams, 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{}), nat: nat, db: db, timeSource: blockchain.NewMedianTime(), } bm, err := newBlockManager(&s) if err != nil { return nil, err } s.blockManager = bm s.txMemPool = newTxMemPool(&s) if !cfg.DisableRPC { s.rpcServer, err = newRPCServer(cfg.RPCListeners, &s) if err != nil { return nil, err } } return &s, nil }