// AskForOneBlock is for testing only, so you can ask for a specific block height // and see what goes wrong func (s *SPVCon) AskForOneBlock(h int32) error { var hdr wire.BlockHeader var err error dbTip := int32(h) s.headerMutex.Lock() // seek to header we need _, err = s.headerFile.Seek(int64((dbTip)*80), os.SEEK_SET) if err != nil { return err } err = hdr.Deserialize(s.headerFile) // read header, done w/ file for now s.headerMutex.Unlock() // unlock after reading 1 header if err != nil { log.Printf("header deserialize error!\n") return err } bHash := hdr.BlockSha() // create inventory we're asking for iv1 := wire.NewInvVect(wire.InvTypeWitnessBlock, &bHash) gdataMsg := wire.NewMsgGetData() // add inventory err = gdataMsg.AddInvVect(iv1) if err != nil { return err } hah := NewRootAndHeight(bHash, h) s.outMsgQueue <- gdataMsg s.blockQueue <- hah // push height and mroot of requested block on queue return nil }
// fetchHeaderBlocks creates and sends a request to the syncPeer for the next // list of blocks to be downloaded based on the current list of headers. func (b *blockManager) fetchHeaderBlocks() { // Nothing to do if there is no start header. if b.startHeader == nil { bmgrLog.Warnf("fetchHeaderBlocks called with no start header") return } // Build up a getdata request for the list of blocks the headers // describe. The size hint will be limited to wire.MaxInvPerMsg by // the function, so no need to double check it here. gdmsg := wire.NewMsgGetDataSizeHint(uint(b.headerList.Len())) numRequested := 0 for e := b.startHeader; e != nil; e = e.Next() { node, ok := e.Value.(*headerNode) if !ok { bmgrLog.Warn("Header list node type is not a headerNode") continue } iv := wire.NewInvVect(wire.InvTypeBlock, node.hash) haveInv, err := b.haveInventory(iv) if err != nil { bmgrLog.Warnf("Unexpected failure when checking for "+ "existing inventory during header block "+ "fetch: %v", err) } if !haveInv { b.requestedBlocks[*node.hash] = struct{}{} b.syncPeer.requestedBlocks[*node.hash] = struct{}{} // If we're fetching from a witness enabled peer // post-fork, then ensure that we receive all the // witness data in the blocks. b.syncPeer.witnessMtx.Lock() if b.syncPeer.witnessEnabled { iv.Type = wire.InvTypeWitnessBlock } b.syncPeer.witnessMtx.Unlock() gdmsg.AddInvVect(iv) numRequested++ } b.startHeader = e.Next() if numRequested >= wire.MaxInvPerMsg { break } } if len(gdmsg.InvList) > 0 { b.syncPeer.QueueMessage(gdmsg, nil) } }
// TestMruInventoryMapStringer tests the stringized output for the // MruInventoryMap type. func TestMruInventoryMapStringer(t *testing.T) { // Create a couple of fake inventory vectors to use in testing the mru // inventory stringer code. hash1 := &chainhash.Hash{0x01} hash2 := &chainhash.Hash{0x02} iv1 := wire.NewInvVect(wire.InvTypeBlock, hash1) iv2 := wire.NewInvVect(wire.InvTypeBlock, hash2) // Create new mru inventory map and add the inventory vectors. mruInvMap := newMruInventoryMap(uint(2)) mruInvMap.Add(iv1) mruInvMap.Add(iv2) // Ensure the stringer gives the expected result. Since map iteration // is not ordered, either entry could be first, so account for both // cases. wantStr1 := fmt.Sprintf("<%d>[%s, %s]", 2, *iv1, *iv2) wantStr2 := fmt.Sprintf("<%d>[%s, %s]", 2, *iv2, *iv1) gotStr := mruInvMap.String() if gotStr != wantStr1 && gotStr != wantStr2 { t.Fatalf("unexpected string representation - got %q, want %q "+ "or %q", gotStr, wantStr1, wantStr2) } }
func (s *SPVCon) NewOutgoingTx(tx *wire.MsgTx) error { txid := tx.TxSha() // assign height of zero for txs we create err := s.TS.AddTxid(&txid, 0) if err != nil { return err } _, err = s.TS.Ingest(tx, 0) // our own tx; don't keep track of false positives if err != nil { return err } // make an inv message instead of a tx message to be polite iv1 := wire.NewInvVect(wire.InvTypeWitnessTx, &txid) invMsg := wire.NewMsgInv() err = invMsg.AddInvVect(iv1) if err != nil { return err } s.outMsgQueue <- invMsg return nil }
// GetPendingInv returns an inv message containing all txs known to the // db which are at height 0 (not known to be confirmed). // This can be useful on startup or to rebroadcast unconfirmed txs. func (ts *TxStore) GetPendingInv() (*wire.MsgInv, error) { // use a map (really a set) do avoid dupes txidMap := make(map[wire.ShaHash]struct{}) utxos, err := ts.GetAllUtxos() // get utxos from db if err != nil { return nil, err } stxos, err := ts.GetAllStxos() // get stxos from db if err != nil { return nil, err } // iterate through utxos, adding txids of anything with height 0 for _, utxo := range utxos { if utxo.AtHeight == 0 { txidMap[utxo.Op.Hash] = struct{}{} // adds to map } } // do the same with stxos based on height at which spent for _, stxo := range stxos { if stxo.SpendHeight == 0 { txidMap[stxo.SpendTxid] = struct{}{} } } invMsg := wire.NewMsgInv() for txid := range txidMap { item := wire.NewInvVect(wire.InvTypeTx, &txid) err = invMsg.AddInvVect(item) if err != nil { if err != nil { return nil, err } } } // return inv message with all txids (maybe none) return invMsg, nil }
// BenchmarkMruInventoryList performs basic benchmarks on the most recently // used inventory handling. func BenchmarkMruInventoryList(b *testing.B) { // Create a bunch of fake inventory vectors to use in benchmarking // the mru inventory code. b.StopTimer() numInvVects := 100000 invVects := make([]*wire.InvVect, 0, numInvVects) for i := 0; i < numInvVects; i++ { hashBytes := make([]byte, chainhash.HashSize) rand.Read(hashBytes) hash, _ := chainhash.NewHash(hashBytes) iv := wire.NewInvVect(wire.InvTypeBlock, hash) invVects = append(invVects, iv) } b.StartTimer() // Benchmark the add plus evicition code. limit := 20000 mruInvMap := newMruInventoryMap(uint(limit)) for i := 0; i < b.N; i++ { mruInvMap.Add(invVects[i%numInvVects]) } }
// handleNotifyMsg handles notifications from blockchain. It does things such // as request orphan block parents and relay accepted blocks to connected peers. func (b *blockManager) handleNotifyMsg(notification *blockchain.Notification) { switch notification.Type { // A block has been accepted into the block chain. Relay it to other // peers. case blockchain.NTBlockAccepted: // Don't relay if we are not current. Other peers that are // current should already know about it. if !b.current() { return } block, ok := notification.Data.(*btcutil.Block) if !ok { bmgrLog.Warnf("Chain accepted notification is not a block.") break } // Generate the inventory vector and relay it. iv := wire.NewInvVect(wire.InvTypeBlock, block.Hash()) b.server.RelayInventory(iv, block.MsgBlock().Header) // A block has been connected to the main block chain. case blockchain.NTBlockConnected: block, ok := notification.Data.(*btcutil.Block) if !ok { bmgrLog.Warnf("Chain connected notification is not a block.") break } // Remove all of the transactions (except the coinbase) in the // connected block from the transaction pool. Secondly, remove any // transactions which are now double spends as a result of these // new transactions. Finally, remove any transaction that is // no longer an orphan. Transactions which depend on a confirmed // transaction are NOT removed recursively because they are still // valid. for _, tx := range block.Transactions()[1:] { b.server.txMemPool.RemoveTransaction(tx, false) b.server.txMemPool.RemoveDoubleSpends(tx) b.server.txMemPool.RemoveOrphan(tx) acceptedTxs := b.server.txMemPool.ProcessOrphans(tx) b.server.AnnounceNewTransactions(acceptedTxs) } if r := b.server.rpcServer; r != nil { // Now that this block is in the blockchain we can mark // all the transactions (except the coinbase) as no // longer needing rebroadcasting. for _, tx := range block.Transactions()[1:] { iv := wire.NewInvVect(wire.InvTypeTx, tx.Hash()) b.server.RemoveRebroadcastInventory(iv) } // Notify registered websocket clients of incoming block. r.ntfnMgr.NotifyBlockConnected(block) } // A block has been disconnected from the main block chain. case blockchain.NTBlockDisconnected: block, ok := notification.Data.(*btcutil.Block) if !ok { bmgrLog.Warnf("Chain disconnected notification is not a block.") break } // Reinsert all of the transactions (except the coinbase) into // the transaction pool. for _, tx := range block.Transactions()[1:] { _, _, err := b.server.txMemPool.MaybeAcceptTransaction(tx, false, false) if err != nil { // Remove the transaction and all transactions // that depend on it if it wasn't accepted into // the transaction pool. b.server.txMemPool.RemoveTransaction(tx, true) } } // Notify registered websocket clients. if r := b.server.rpcServer; r != nil { r.ntfnMgr.NotifyBlockDisconnected(block) } } }
// TestOutboundPeer tests that the outbound peer works as expected. func TestOutboundPeer(t *testing.T) { peerCfg := &peer.Config{ NewestBlock: func() (*chainhash.Hash, int32, error) { return nil, 0, errors.New("newest block not found") }, UserAgentName: "peer", UserAgentVersion: "1.0", ChainParams: &chaincfg.MainNetParams, Services: 0, } r, w := io.Pipe() c := &conn{raddr: "10.0.0.1:8333", Writer: w, Reader: r} p, err := peer.NewOutboundPeer(peerCfg, "10.0.0.1:8333") if err != nil { t.Errorf("NewOutboundPeer: unexpected err - %v\n", err) return } // Test trying to connect twice. p.AssociateConnection(c) p.AssociateConnection(c) disconnected := make(chan struct{}) go func() { p.WaitForDisconnect() disconnected <- struct{}{} }() select { case <-disconnected: close(disconnected) case <-time.After(time.Second): t.Fatal("Peer did not automatically disconnect.") } if p.Connected() { t.Fatalf("Should not be connected as NewestBlock produces error.") } // Test Queue Inv fakeBlockHash := &chainhash.Hash{0: 0x00, 1: 0x01} fakeInv := wire.NewInvVect(wire.InvTypeBlock, fakeBlockHash) // Should be noops as the peer could not connect. p.QueueInventory(fakeInv) p.AddKnownInventory(fakeInv) p.QueueInventory(fakeInv) fakeMsg := wire.NewMsgVerAck() p.QueueMessage(fakeMsg, nil) done := make(chan struct{}) p.QueueMessage(fakeMsg, done) <-done p.Disconnect() // Test NewestBlock var newestBlock = func() (*chainhash.Hash, int32, error) { hashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef" hash, err := chainhash.NewHashFromStr(hashStr) if err != nil { return nil, 0, err } return hash, 234439, nil } peerCfg.NewestBlock = newestBlock r1, w1 := io.Pipe() c1 := &conn{raddr: "10.0.0.1:8333", Writer: w1, Reader: r1} p1, err := peer.NewOutboundPeer(peerCfg, "10.0.0.1:8333") if err != nil { t.Errorf("NewOutboundPeer: unexpected err - %v\n", err) return } p1.AssociateConnection(c1) // Test update latest block latestBlockHash, err := chainhash.NewHashFromStr("1a63f9cdff1752e6375c8c76e543a71d239e1a2e5c6db1aa679") if err != nil { t.Errorf("NewHashFromStr: unexpected err %v\n", err) return } p1.UpdateLastAnnouncedBlock(latestBlockHash) p1.UpdateLastBlockHeight(234440) if p1.LastAnnouncedBlock() != latestBlockHash { t.Errorf("LastAnnouncedBlock: wrong block - got %v, want %v", p1.LastAnnouncedBlock(), latestBlockHash) return } // Test Queue Inv after connection p1.QueueInventory(fakeInv) p1.Disconnect() // Test regression peerCfg.ChainParams = &chaincfg.RegressionNetParams peerCfg.Services = wire.SFNodeBloom r2, w2 := io.Pipe() c2 := &conn{raddr: "10.0.0.1:8333", Writer: w2, Reader: r2} p2, err := peer.NewOutboundPeer(peerCfg, "10.0.0.1:8333") if err != nil { t.Errorf("NewOutboundPeer: unexpected err - %v\n", err) return } p2.AssociateConnection(c2) // Test PushXXX var addrs []*wire.NetAddress for i := 0; i < 5; i++ { na := wire.NetAddress{} addrs = append(addrs, &na) } if _, err := p2.PushAddrMsg(addrs); err != nil { t.Errorf("PushAddrMsg: unexpected err %v\n", err) return } if err := p2.PushGetBlocksMsg(nil, &chainhash.Hash{}); err != nil { t.Errorf("PushGetBlocksMsg: unexpected err %v\n", err) return } if err := p2.PushGetHeadersMsg(nil, &chainhash.Hash{}); err != nil { t.Errorf("PushGetHeadersMsg: unexpected err %v\n", err) return } p2.PushRejectMsg("block", wire.RejectMalformed, "malformed", nil, false) p2.PushRejectMsg("block", wire.RejectInvalid, "invalid", nil, false) // Test Queue Messages p2.QueueMessage(wire.NewMsgGetAddr(), nil) p2.QueueMessage(wire.NewMsgPing(1), nil) p2.QueueMessage(wire.NewMsgMemPool(), nil) p2.QueueMessage(wire.NewMsgGetData(), nil) p2.QueueMessage(wire.NewMsgGetHeaders(), nil) p2.QueueMessage(wire.NewMsgFeeFilter(20000), nil) p2.Disconnect() }
// AskForTx requests a tx we heard about from an inv message. // It's one at a time but should be fast enough. // I don't like this function because SPV shouldn't even ask... func (s *SPVCon) AskForTx(txid wire.ShaHash) { gdata := wire.NewMsgGetData() inv := wire.NewInvVect(wire.InvTypeTx, &txid) gdata.AddInvVect(inv) s.outMsgQueue <- gdata }
// AskForMerkBlocks requests blocks from current to last // right now this asks for 1 block per getData message. // Maybe it's faster to ask for many in a each message? func (s *SPVCon) AskForBlocks() error { var hdr wire.BlockHeader s.headerMutex.Lock() // lock just to check filesize stat, err := os.Stat(headerFileName) s.headerMutex.Unlock() // checked, unlock endPos := stat.Size() headerTip := int32(endPos/80) - 1 // move back 1 header length to read dbTip, err := s.TS.GetDBSyncHeight() if err != nil { return err } fmt.Printf("dbTip %d headerTip %d\n", dbTip, headerTip) if dbTip > headerTip { return fmt.Errorf("error- db longer than headers! shouldn't happen.") } if dbTip == headerTip { // nothing to ask for; set wait state and return fmt.Printf("no blocks to request, entering wait state\n") fmt.Printf("%d bytes received\n", s.RBytes) s.inWaitState <- true // also advertise any unconfirmed txs here s.Rebroadcast() return nil } fmt.Printf("will request blocks %d to %d\n", dbTip+1, headerTip) if !s.HardMode { // don't send this in hardmode! that's the whole point // create initial filter filt, err := s.TS.GimmeFilter() if err != nil { return err } // send filter s.SendFilter(filt) fmt.Printf("sent filter %x\n", filt.MsgFilterLoad().Filter) } // loop through all heights where we want merkleblocks. for dbTip < headerTip { dbTip++ // we're requesting the next header // load header from file s.headerMutex.Lock() // seek to header we need _, err = s.headerFile.Seek(int64((dbTip)*80), os.SEEK_SET) if err != nil { return err } err = hdr.Deserialize(s.headerFile) // read header, done w/ file for now s.headerMutex.Unlock() // unlock after reading 1 header if err != nil { log.Printf("header deserialize error!\n") return err } bHash := hdr.BlockSha() // create inventory we're asking for iv1 := new(wire.InvVect) // if hardmode, ask for legit blocks, none of this ralphy stuff if s.HardMode { iv1 = wire.NewInvVect(wire.InvTypeWitnessBlock, &bHash) } else { // ah well iv1 = wire.NewInvVect(wire.InvTypeFilteredWitnessBlock, &bHash) } gdataMsg := wire.NewMsgGetData() // add inventory err = gdataMsg.AddInvVect(iv1) if err != nil { return err } hah := NewRootAndHeight(hdr.BlockSha(), dbTip) if dbTip == headerTip { // if this is the last block, indicate finality hah.final = true } // waits here most of the time for the queue to empty out s.blockQueue <- hah // push height and mroot of requested block on queue s.outMsgQueue <- gdataMsg } return nil }
// TestMruInventoryMap ensures the MruInventoryMap behaves as expected including // limiting, eviction of least-recently used entries, specific entry removal, // and existence tests. func TestMruInventoryMap(t *testing.T) { // Create a bunch of fake inventory vectors to use in testing the mru // inventory code. numInvVects := 10 invVects := make([]*wire.InvVect, 0, numInvVects) for i := 0; i < numInvVects; i++ { hash := &chainhash.Hash{byte(i)} iv := wire.NewInvVect(wire.InvTypeBlock, hash) invVects = append(invVects, iv) } tests := []struct { name string limit int }{ {name: "limit 0", limit: 0}, {name: "limit 1", limit: 1}, {name: "limit 5", limit: 5}, {name: "limit 7", limit: 7}, {name: "limit one less than available", limit: numInvVects - 1}, {name: "limit all available", limit: numInvVects}, } testLoop: for i, test := range tests { // Create a new mru inventory map limited by the specified test // limit and add all of the test inventory vectors. This will // cause evicition since there are more test inventory vectors // than the limits. mruInvMap := newMruInventoryMap(uint(test.limit)) for j := 0; j < numInvVects; j++ { mruInvMap.Add(invVects[j]) } // Ensure the limited number of most recent entries in the // inventory vector list exist. for j := numInvVects - test.limit; j < numInvVects; j++ { if !mruInvMap.Exists(invVects[j]) { t.Errorf("Exists #%d (%s) entry %s does not "+ "exist", i, test.name, *invVects[j]) continue testLoop } } // Ensure the entries before the limited number of most recent // entries in the inventory vector list do not exist. for j := 0; j < numInvVects-test.limit; j++ { if mruInvMap.Exists(invVects[j]) { t.Errorf("Exists #%d (%s) entry %s exists", i, test.name, *invVects[j]) continue testLoop } } // Readd the entry that should currently be the least-recently // used entry so it becomes the most-recently used entry, then // force an eviction by adding an entry that doesn't exist and // ensure the evicted entry is the new least-recently used // entry. // // This check needs at least 2 entries. if test.limit > 1 { origLruIndex := numInvVects - test.limit mruInvMap.Add(invVects[origLruIndex]) iv := wire.NewInvVect(wire.InvTypeBlock, &chainhash.Hash{0x00, 0x01}) mruInvMap.Add(iv) // Ensure the original lru entry still exists since it // was updated and should've have become the mru entry. if !mruInvMap.Exists(invVects[origLruIndex]) { t.Errorf("MRU #%d (%s) entry %s does not exist", i, test.name, *invVects[origLruIndex]) continue testLoop } // Ensure the entry that should've become the new lru // entry was evicted. newLruIndex := origLruIndex + 1 if mruInvMap.Exists(invVects[newLruIndex]) { t.Errorf("MRU #%d (%s) entry %s exists", i, test.name, *invVects[newLruIndex]) continue testLoop } } // Delete all of the entries in the inventory vector list, // including those that don't exist in the map, and ensure they // no longer exist. for j := 0; j < numInvVects; j++ { mruInvMap.Delete(invVects[j]) if mruInvMap.Exists(invVects[j]) { t.Errorf("Delete #%d (%s) entry %s exists", i, test.name, *invVects[j]) continue testLoop } } } }