// handleBlockMsg is invoked when a peer receives a block bitcoin message. It // blocks until the bitcoin block has been fully processed. func (p *peer) handleBlockMsg(msg *btcwire.MsgBlock, buf []byte) { // Convert the raw MsgBlock to a btcutil.Block which provides some // convenience methods and things such as hash caching. block := btcutil.NewBlockFromBlockAndBytes(msg, buf) // Add the block to the known inventory for the peer. hash, err := block.Sha() if err != nil { log.Errorf("Unable to get block hash: %v", err) return } iv := btcwire.NewInvVect(btcwire.InvTypeBlock, hash) p.addKnownInventory(iv) // Queue the block up to be handled by the block // manager and intentionally block further receives // until the bitcoin block is fully processed and known // good or bad. This helps prevent a malicious peer // from queueing up a bunch of bad blocks before // disconnecting (or being disconnected) and wasting // memory. Additionally, this behavior is depended on // by at least the block acceptance test tool as the // reference implementation processes blocks in the same // thread and therefore blocks further messages until // the bitcoin block has been fully processed. p.server.blockManager.QueueBlock(block, p) <-p.blockProcessed }
// handleTxMsg is invoked when a peer receives a tx bitcoin message. It blocks // until the bitcoin transaction has been fully processed. Unlock the block // handler this does not serialize all transactions through a single thread // transactions don't rely on the previous one in a linear fashion like blocks. func (p *peer) handleTxMsg(msg *btcwire.MsgTx) { // Add the transaction to the known inventory for the peer. hash, err := msg.TxSha() if err != nil { log.Errorf("Unable to get transaction hash: %v", err) return } iv := btcwire.NewInvVect(btcwire.InvVect_Tx, &hash) p.addKnownInventory(iv) // Process the transaction. err = p.server.txMemPool.ProcessTransaction(msg) if err != nil { // When the error is a rule error, it means the transaction was // simply rejected as opposed to something actually going wrong, // so log it as such. Otherwise, something really did go wrong, // so log it as an actual error. if _, ok := err.(TxRuleError); ok { log.Infof("Rejected transaction %v: %v", hash, err) } else { log.Errorf("Failed to process transaction %v: %v", hash, err) } return } }
// 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 btcwire.MaxInvPerMsg by // the function, so no need to double check it here. gdmsg := btcwire.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 := btcwire.NewInvVect(btcwire.InvTypeBlock, node.sha) if !b.haveInventory(iv) { b.requestedBlocks[*node.sha] = true b.syncPeer.requestedBlocks[*node.sha] = true gdmsg.AddInvVect(iv) numRequested++ } b.startHeader = e.Next() if numRequested >= btcwire.MaxInvPerMsg { break } } if len(gdmsg.InvList) > 0 { b.syncPeer.QueueMessage(gdmsg, nil) } }
// handleMemPoolMsg is invoked when a peer receives a mempool bitcoin message. // It creates and sends an inventory message with the contents of the memory // pool up to the maximum inventory allowed per message. func (p *peer) handleMemPoolMsg(msg *btcwire.MsgMemPool) { // Generate inventory message with the available transactions in the // transaction memory pool. Limit it to the max allowed inventory // per message. The the NewMsgInvSizeHint function automatically limits // the passed hint to the maximum allowed, so it's safe to pass it // without double checking it here. hashes := p.server.txMemPool.TxShas() invMsg := btcwire.NewMsgInvSizeHint(uint(len(hashes))) for i, hash := range hashes { // Another thread might have removed the transaction from the // pool since the initial query. if !p.server.txMemPool.IsTransactionInPool(hash) { continue } iv := btcwire.NewInvVect(btcwire.InvTypeTx, hash) invMsg.AddInvVect(iv) if i+1 >= btcwire.MaxInvPerMsg { break } } // Send the inventory message if there is anything to send. if len(invMsg.InvList) > 0 { p.QueueMessage(invMsg, nil) } }
// TestInv tests the MsgInv API. func TestInv(t *testing.T) { pver := btcwire.ProtocolVersion // Ensure the command is expected value. wantCmd := "inv" msg := btcwire.NewMsgInv() if cmd := msg.Command(); cmd != wantCmd { t.Errorf("NewMsgInv: wrong command - got %v want %v", cmd, wantCmd) } // Ensure max payload is expected value for latest protocol version. // Num inventory vectors (varInt) + max allowed inventory vectors. wantPayload := uint32(1800009) maxPayload := msg.MaxPayloadLength(pver) if maxPayload != wantPayload { t.Errorf("MaxPayloadLength: wrong max payload length for "+ "protocol version %d - got %v, want %v", pver, maxPayload, wantPayload) } // Ensure inventory vectors are added properly. hash := btcwire.ShaHash{} iv := btcwire.NewInvVect(btcwire.InvTypeBlock, &hash) err := msg.AddInvVect(iv) if err != nil { t.Errorf("AddInvVect: %v", err) } if msg.InvList[0] != iv { t.Errorf("AddInvVect: wrong invvect added - got %v, want %v", spew.Sprint(msg.InvList[0]), spew.Sprint(iv)) } // Ensure adding more than the max allowed inventory vectors per // message returns an error. for i := 0; i < btcwire.MaxInvPerMsg; i++ { err = msg.AddInvVect(iv) } if err == nil { t.Errorf("AddInvVect: expected error on too many inventory " + "vectors not received") } // Ensure creating the message with a size hint larger than the max // works as expected. msg = btcwire.NewMsgInvSizeHint(btcwire.MaxInvPerMsg + 1) wantCap := btcwire.MaxInvPerMsg if cap(msg.InvList) != wantCap { t.Errorf("NewMsgInvSizeHint: wrong cap for size hint - "+ "got %v, want %v", cap(msg.InvList), wantCap) } return }
// pushBlockMsg sends a block message for the provided block hash to the // connected peer. An error is returned if the block hash is not known. func (p *peer) pushBlockMsg(sha *btcwire.ShaHash, doneChan chan bool) error { // What should this function do about the rate limiting the // number of blocks queued for this peer? // Current thought is have a counting mutex in the peer // such that if > N Tx/Block requests are currently in // the tx queue, wait until the mutex clears allowing more to be // sent. This prevents 500 1+MB blocks from being loaded into // memory and sit around until the output queue drains. // Actually the outputQueue has a limit of 50 in its queue // but still 50MB to 1.6GB(50 32MB blocks) just setting // in memory waiting to be sent is pointless. // I would recommend a getdata request limit of about 5 // outstanding objects. // Should the tx complete api be a mutex or channel? blk, err := p.server.db.FetchBlockBySha(sha) if err != nil { log.Tracef("PEER: Unable to fetch requested block sha %v: %v", sha, err) return err } // We only send the channel for this message if we aren't sending // an inv straight after. var dc chan bool sendInv := p.continueHash != nil && p.continueHash.IsEqual(sha) if !sendInv { dc = doneChan } p.QueueMessage(blk.MsgBlock(), dc) // When the peer requests the final block that was advertised in // response to a getblocks message which requested more blocks than // would fit into a single message, send it a new inventory message // to trigger it to issue another getblocks message for the next // batch of inventory. if p.continueHash != nil && p.continueHash.IsEqual(sha) { hash, _, err := p.server.db.NewestSha() if err == nil { invMsg := btcwire.NewMsgInv() iv := btcwire.NewInvVect(btcwire.InvTypeBlock, hash) invMsg.AddInvVect(iv) p.QueueMessage(invMsg, doneChan) p.continueHash = nil } else if doneChan != nil { // Avoid deadlock when caller waits on channel. go func() { doneChan <- false }() } } return nil }
// processOrphans determines if there are any orphans which depend on the passed // transaction hash (they are no longer orphans if true) and potentially accepts // them. It repeats the process for the newly accepted transactions (to detect // further orphans which may no longer be orphans) until there are no more. // // This function MUST be called with the mempool lock held (for writes). func (mp *txMemPool) processOrphans(hash *btcwire.ShaHash) error { // Start with processing at least the passed hash. processHashes := list.New() processHashes.PushBack(hash) for processHashes.Len() > 0 { // Pop the first hash to process. firstElement := processHashes.Remove(processHashes.Front()) processHash := firstElement.(*btcwire.ShaHash) // Look up all orphans that are referenced by the transaction we // just accepted. This will typically only be one, but it could // be multiple if the referenced transaction contains multiple // outputs. Skip to the next item on the list of hashes to // process if there are none. orphans, exists := mp.orphansByPrev[*processHash] if !exists || orphans == nil { continue } var enext *list.Element for e := orphans.Front(); e != nil; e = enext { enext = e.Next() tx := e.Value.(*btcutil.Tx) // Remove the orphan from the orphan pool. orphanHash := tx.Sha() mp.removeOrphan(orphanHash) // Potentially accept the transaction into the // transaction pool. var isOrphan bool err := mp.maybeAcceptTransaction(tx, &isOrphan, true, true) if err != nil { return err } if !isOrphan { // Generate the inventory vector and relay it. iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) mp.server.RelayInventory(iv) } else { mp.removeOrphan(orphanHash) } // Add this transaction to the list of transactions to // process so any orphans that depend on this one are // handled too. processHashes.PushBack(orphanHash) } } return nil }
// ProcessTransaction is the main workhorse for handling insertion of new // free-standing transactions into the memory pool. It includes functionality // such as rejecting duplicate transactions, ensuring transactions follow all // rules, orphan transaction handling, and insertion into the memory pool. // // This function is safe for concurrent access. func (mp *txMemPool) ProcessTransaction(tx *btcutil.Tx, allowOrphan, rateLimit bool) error { // Protect concurrent access. mp.Lock() defer mp.Unlock() txmpLog.Tracef("Processing transaction %v", tx.Sha()) // Potentially accept the transaction to the memory pool. var isOrphan bool err := mp.maybeAcceptTransaction(tx, &isOrphan, true, rateLimit) if err != nil { return err } if !isOrphan { // Generate the inventory vector and relay it. iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) mp.server.RelayInventory(iv) // Accept any orphan transactions that depend on this // transaction (they are no longer orphans) and repeat for those // accepted transactions until there are no more. err := mp.processOrphans(tx.Sha()) if err != nil { return err } } else { // The transaction is an orphan (has inputs missing). Reject // it if the flag to allow orphans is not set. if !allowOrphan { // NOTE: RejectDuplicate is really not an accurate // reject code here, but it matches the reference // implementation and there isn't a better choice due // to the limited number of reject codes. Missing // inputs is assumed to mean they are already spent // which is not really always the case. return txRuleError(btcwire.RejectDuplicate, "transaction spends unknown inputs") } // Potentially add the orphan transaction to the orphan pool. err := mp.maybeAddOrphan(tx) if err != nil { return err } } return nil }
// TestInvVect tests the InvVect API. func TestInvVect(t *testing.T) { ivType := btcwire.InvTypeBlock hash := btcwire.ShaHash{} // Ensure we get the same payload and signature back out. iv := btcwire.NewInvVect(ivType, &hash) if iv.Type != ivType { t.Errorf("NewInvVect: wrong type - got %v, want %v", iv.Type, ivType) } if !iv.Hash.IsEqual(&hash) { t.Errorf("NewInvVect: wrong hash - got %v, want %v", spew.Sdump(iv.Hash), spew.Sdump(hash)) } }
// handleTxMsg is invoked when a peer receives a tx bitcoin message. It blocks // until the bitcoin transaction has been fully processed. Unlock the block // handler this does not serialize all transactions through a single thread // transactions don't rely on the previous one in a linear fashion like blocks. func (p *peer) handleTxMsg(msg *btcwire.MsgTx) { // Add the transaction to the known inventory for the peer. // Convert the raw MsgTx to a btcutil.Tx which provides some convenience // methods and things such as hash caching. tx := btcutil.NewTx(msg) iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) p.addKnownInventory(iv) // Queue the transaction up to be handled by the block manager and // intentionally block further receives until the transaction is fully // processed and known good or bad. This helps prevent a malicious peer // from queueing up a bunch of bad transactions before disconnecting (or // being disconnected) and wasting memory. p.server.blockManager.QueueTx(tx, p) <-p.txProcessed }
// handleSendRawTransaction implements the sendrawtransaction command. func handleSendRawTransaction(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { c := cmd.(*btcjson.SendRawTransactionCmd) // Deserialize and send off to tx relay serializedTx, err := hex.DecodeString(c.HexTx) if err != nil { return nil, btcjson.ErrDecodeHexString } msgtx := btcwire.NewMsgTx() err = msgtx.Deserialize(bytes.NewBuffer(serializedTx)) if err != nil { err := btcjson.Error{ Code: btcjson.ErrDeserialization.Code, Message: "TX decode failed", } return nil, err } tx := btcutil.NewTx(msgtx) err = s.server.txMemPool.ProcessTransaction(tx, false) if err != nil { // When the error is a rule error, it means the transaction was // simply rejected as opposed to something actually going wrong, // so log it as such. Otherwise, something really did go wrong, // so log it as an actual error. In both cases, a JSON-RPC // error is returned to the client with the deserialization // error code (to match bitcoind behavior). if _, ok := err.(TxRuleError); ok { rpcsLog.Debugf("Rejected transaction %v: %v", tx.Sha(), err) } else { rpcsLog.Errorf("Failed to process transaction %v: %v", tx.Sha(), err) } err = btcjson.Error{ Code: btcjson.ErrDeserialization.Code, Message: fmt.Sprintf("TX rejected: %v", err), } return nil, err } // We keep track of all the sendrawtransaction request txs so that we // can rebroadcast them if they don't make their way into a block. iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) s.server.AddRebroadcastInventory(iv) return tx.Sha().String(), nil }
// pushBlockMsg sends a block message for the provided block hash to the // connected peer. An error is returned if the block hash is not known. func (p *peer) pushBlockMsg(sha *btcwire.ShaHash, doneChan, waitChan chan bool) error { blk, err := p.server.db.FetchBlockBySha(sha) if err != nil { peerLog.Tracef("Unable to fetch requested block sha %v: %v", sha, err) return err } // Once we have fetched data wait for any previous operation to finish. if waitChan != nil { <-waitChan } // We only send the channel for this message if we aren't sending // an inv straight after. var dc chan bool sendInv := p.continueHash != nil && p.continueHash.IsEqual(sha) if !sendInv { dc = doneChan } p.QueueMessage(blk.MsgBlock(), dc) // When the peer requests the final block that was advertised in // response to a getblocks message which requested more blocks than // would fit into a single message, send it a new inventory message // to trigger it to issue another getblocks message for the next // batch of inventory. if p.continueHash != nil && p.continueHash.IsEqual(sha) { hash, _, err := p.server.db.NewestSha() if err == nil { invMsg := btcwire.NewMsgInv() iv := btcwire.NewInvVect(btcwire.InvTypeBlock, hash) invMsg.AddInvVect(iv) p.QueueMessage(invMsg, doneChan) p.continueHash = nil } else if doneChan != nil { // Avoid deadlock when caller waits on channel. go func() { doneChan <- false }() } } return nil }
// handleMemPoolMsg is invoked when a peer receives a mempool bitcoin message. // It creates and sends an inventory message with the contents of the memory // pool up to the maximum inventory allowed per message. func (p *peer) handleMemPoolMsg(msg *btcwire.MsgMemPool) { // Generate inventory message with the available transactions in the // transaction memory pool. Limit it to the max allowed inventory // per message. invMsg := btcwire.NewMsgInv() hashes := p.server.txMemPool.TxShas() for i, hash := range hashes { iv := btcwire.NewInvVect(btcwire.InvTypeTx, hash) invMsg.AddInvVect(iv) if i+1 >= btcwire.MaxInvPerMsg { break } } // Send the inventory message if there is anything to send. if len(invMsg.InvList) > 0 { p.QueueMessage(invMsg, nil) } }
// ProcessTransaction is the main workhorse for handling insertion of new // free-standing transactions into the memory pool. It includes functionality // such as rejecting duplicate transactions, ensuring transactions follow all // rules, orphan transaction handling, and insertion into the memory pool. // // This function is safe for concurrent access. func (mp *txMemPool) ProcessTransaction(tx *btcutil.Tx, allowOrphan, rateLimit bool) error { // Protect concurrent access. mp.Lock() defer mp.Unlock() txmpLog.Tracef("Processing transaction %v", tx.Sha()) // Potentially accept the transaction to the memory pool. var isOrphan bool err := mp.maybeAcceptTransaction(tx, &isOrphan, true, rateLimit) if err != nil { return err } if !isOrphan { // Generate the inventory vector and relay it. iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) mp.server.RelayInventory(iv) // Accept any orphan transactions that depend on this // transaction (they are no longer orphans) and repeat for those // accepted transactions until there are no more. err := mp.processOrphans(tx.Sha()) if err != nil { return err } } else { // The transaction is an orphan (has inputs missing). Reject // it if the flag to allow orphans is not set. if !allowOrphan { return TxRuleError("transaction spends unknown inputs") } // Potentially add the orphan transaction to the orphan pool. err := mp.maybeAddOrphan(tx) if err != nil { return err } } return nil }
// fetchHeaderBlocks is creates and sends a request to the syncPeer for // the next list of blocks to be downloaded. func (b *blockManager) fetchHeaderBlocks() { gdmsg := btcwire.NewMsgGetDataSizeHint(btcwire.MaxInvPerMsg) numRequested := 0 startBlock := b.startBlock for { if b.startBlock == nil { break } blockHash := b.startBlock firstblock, ok := b.headerPool[*blockHash] if !ok { bmgrLog.Warnf("current fetch block %v missing from headerPool", blockHash) break } iv := btcwire.NewInvVect(btcwire.InvTypeBlock, blockHash) if !b.haveInventory(iv) { b.requestedBlocks[*blockHash] = true b.syncPeer.requestedBlocks[*blockHash] = true gdmsg.AddInvVect(iv) numRequested++ } if b.fetchBlock == nil { b.fetchBlock = b.startBlock } if firstblock.next == nil { b.startBlock = nil break } else { b.startBlock = &firstblock.next.sha } if numRequested >= btcwire.MaxInvPerMsg { break } } if len(gdmsg.InvList) > 0 { bmgrLog.Debugf("requesting block %v len %v\n", startBlock, len(gdmsg.InvList)) b.syncPeer.QueueMessage(gdmsg, 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([]*btcwire.InvVect, 0, numInvVects) for i := 0; i < numInvVects; i++ { hashBytes := make([]byte, btcwire.HashSize) rand.Read(hashBytes) hash, _ := btcwire.NewShaHash(hashBytes) iv := btcwire.NewInvVect(btcwire.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]) } }
// ProcessTransaction is the main workhorse for handling insertion of new // free-standing transactions into the memory pool. It includes functionality // such as rejecting duplicate transactions, ensuring transactions follow all // rules, orphan transaction handling, and insertion into the memory pool. // // This function is safe for concurrent access. func (mp *txMemPool) ProcessTransaction(tx *btcutil.Tx) error { // Protect concurrent access. mp.Lock() defer mp.Unlock() txmpLog.Tracef("Processing transaction %v", tx.Sha()) // Potentially accept the transaction to the memory pool. var isOrphan bool err := mp.maybeAcceptTransaction(tx, &isOrphan) if err != nil { return err } if !isOrphan { // Generate the inventory vector and relay it. iv := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) mp.server.RelayInventory(iv) // Accept any orphan transactions that depend on this // transaction (they are no longer orphans) and repeat for those // accepted transactions until there are no more. err := mp.processOrphans(tx.Sha()) if err != nil { return err } } else { // When the transaction is an orphan (has inputs missing), // potentially add it to the orphan pool. err := mp.maybeAddOrphan(tx) if err != nil { return err } } return nil }
// TestNotFoundWire tests the MsgNotFound wire encode and decode for various // numbers of inventory vectors and protocol versions. func TestNotFoundWire(t *testing.T) { // Block 203707 hash. hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc" blockHash, err := btcwire.NewShaHashFromStr(hashStr) if err != nil { t.Errorf("NewShaHashFromStr: %v", err) } // Transation 1 of Block 203707 hash. hashStr = "d28a3dc7392bf00a9855ee93dd9a81eff82a2c4fe57fbd42cfe71b487accfaf0" txHash, err := btcwire.NewShaHashFromStr(hashStr) if err != nil { t.Errorf("NewShaHashFromStr: %v", err) } iv := btcwire.NewInvVect(btcwire.InvVect_Block, blockHash) iv2 := btcwire.NewInvVect(btcwire.InvVect_Tx, txHash) // Empty notfound message. NoInv := btcwire.NewMsgNotFound() NoInvEncoded := []byte{ 0x00, // Varint for number of inventory vectors } // NotFound message with multiple inventory vectors. MultiInv := btcwire.NewMsgNotFound() MultiInv.AddInvVect(iv) MultiInv.AddInvVect(iv2) MultiInvEncoded := []byte{ 0x02, // Varint for number of inv vectors 0x02, 0x00, 0x00, 0x00, // InvVect_Block 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash 0x01, 0x00, 0x00, 0x00, // InvVect_Tx 0xf0, 0xfa, 0xcc, 0x7a, 0x48, 0x1b, 0xe7, 0xcf, 0x42, 0xbd, 0x7f, 0xe5, 0x4f, 0x2c, 0x2a, 0xf8, 0xef, 0x81, 0x9a, 0xdd, 0x93, 0xee, 0x55, 0x98, 0x0a, 0xf0, 0x2b, 0x39, 0xc7, 0x3d, 0x8a, 0xd2, // Tx 1 of block 203707 hash } tests := []struct { in *btcwire.MsgNotFound // Message to encode out *btcwire.MsgNotFound // Expected decoded message buf []byte // Wire encoding pver uint32 // Protocol version for wire encoding }{ // Latest protocol version with no inv vectors. { NoInv, NoInv, NoInvEncoded, btcwire.ProtocolVersion, }, // Latest protocol version with multiple inv vectors. { MultiInv, MultiInv, MultiInvEncoded, btcwire.ProtocolVersion, }, // Protocol version BIP0035Version no inv vectors. { NoInv, NoInv, NoInvEncoded, btcwire.BIP0035Version, }, // Protocol version BIP0035Version with multiple inv vectors. { MultiInv, MultiInv, MultiInvEncoded, btcwire.BIP0035Version, }, // Protocol version BIP0031Version no inv vectors. { NoInv, NoInv, NoInvEncoded, btcwire.BIP0031Version, }, // Protocol version BIP0031Version with multiple inv vectors. { MultiInv, MultiInv, MultiInvEncoded, btcwire.BIP0031Version, }, // Protocol version NetAddressTimeVersion no inv vectors. { NoInv, NoInv, NoInvEncoded, btcwire.NetAddressTimeVersion, }, // Protocol version NetAddressTimeVersion with multiple inv vectors. { MultiInv, MultiInv, MultiInvEncoded, btcwire.NetAddressTimeVersion, }, // Protocol version MultipleAddressVersion no inv vectors. { NoInv, NoInv, NoInvEncoded, btcwire.MultipleAddressVersion, }, // Protocol version MultipleAddressVersion with multiple inv vectors. { MultiInv, MultiInv, MultiInvEncoded, btcwire.MultipleAddressVersion, }, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Encode the message to wire format. var buf bytes.Buffer err := test.in.BtcEncode(&buf, test.pver) if err != nil { t.Errorf("BtcEncode #%d error %v", i, err) continue } if !bytes.Equal(buf.Bytes(), test.buf) { t.Errorf("BtcEncode #%d\n got: %s want: %s", i, spew.Sdump(buf.Bytes()), spew.Sdump(test.buf)) continue } // Decode the message from wire format. var msg btcwire.MsgNotFound rbuf := bytes.NewBuffer(test.buf) err = msg.BtcDecode(rbuf, test.pver) if err != nil { t.Errorf("BtcDecode #%d error %v", i, err) continue } if !reflect.DeepEqual(&msg, test.out) { t.Errorf("BtcDecode #%d\n got: %s want: %s", i, spew.Sdump(msg), spew.Sdump(test.out)) continue } } }
// TestNotFoundWireErrors performs negative tests against wire encode and decode // of MsgNotFound to confirm error paths work correctly. func TestNotFoundWireErrors(t *testing.T) { pver := btcwire.ProtocolVersion btcwireErr := &btcwire.MessageError{} // Block 203707 hash. hashStr := "3264bc2ac36a60840790ba1d475d01367e7c723da941069e9dc" blockHash, err := btcwire.NewShaHashFromStr(hashStr) if err != nil { t.Errorf("NewShaHashFromStr: %v", err) } iv := btcwire.NewInvVect(btcwire.InvVect_Block, blockHash) // Base message used to induce errors. baseNotFound := btcwire.NewMsgNotFound() baseNotFound.AddInvVect(iv) baseNotFoundEncoded := []byte{ 0x02, // Varint for number of inv vectors 0x02, 0x00, 0x00, 0x00, // InvVect_Block 0xdc, 0xe9, 0x69, 0x10, 0x94, 0xda, 0x23, 0xc7, 0xe7, 0x67, 0x13, 0xd0, 0x75, 0xd4, 0xa1, 0x0b, 0x79, 0x40, 0x08, 0xa6, 0x36, 0xac, 0xc2, 0x4b, 0x26, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Block 203707 hash } // Message that forces an error by having more than the max allowed inv // vectors. maxNotFound := btcwire.NewMsgNotFound() for i := 0; i < btcwire.MaxInvPerMsg; i++ { maxNotFound.AddInvVect(iv) } maxNotFound.InvList = append(maxNotFound.InvList, iv) maxNotFoundEncoded := []byte{ 0xfd, 0x51, 0xc3, // Varint for number of inv vectors (50001) } tests := []struct { in *btcwire.MsgNotFound // Value to encode buf []byte // Wire encoding pver uint32 // Protocol version for wire encoding max int // Max size of fixed buffer to induce errors writeErr error // Expected write error readErr error // Expected read error }{ // Latest protocol version with intentional read/write errors. // Force error in inventory vector count {baseNotFound, baseNotFoundEncoded, pver, 0, io.ErrShortWrite, io.EOF}, // Force error in inventory list. {baseNotFound, baseNotFoundEncoded, pver, 1, io.ErrShortWrite, io.EOF}, // Force error with greater than max inventory vectors. {maxNotFound, maxNotFoundEncoded, pver, 3, btcwireErr, btcwireErr}, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Encode to wire format. w := newFixedWriter(test.max) err := test.in.BtcEncode(w, test.pver) if reflect.TypeOf(err) != reflect.TypeOf(test.writeErr) { t.Errorf("BtcEncode #%d wrong error got: %v, want: %v", i, err, test.writeErr) continue } // For errors which are not of type btcwire.MessageError, check // them for equality. if _, ok := err.(*btcwire.MessageError); !ok { if err != test.writeErr { t.Errorf("BtcEncode #%d wrong error got: %v, "+ "want: %v", i, err, test.writeErr) continue } } // Decode from wire format. var msg btcwire.MsgNotFound r := newFixedReader(test.max, test.buf) err = msg.BtcDecode(r, test.pver) if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) { t.Errorf("BtcDecode #%d wrong error got: %v, want: %v", i, err, test.readErr) continue } // For errors which are not of type btcwire.MessageError, check // them for equality. if _, ok := err.(*btcwire.MessageError); !ok { if err != test.readErr { t.Errorf("BtcDecode #%d wrong error got: %v, "+ "want: %v", i, err, test.readErr) continue } } } }
// handleNotifyMsg handles notifications from btcchain. It does things such // as request orphan block parents and relay accepted blocks to connected peers. func (b *blockManager) handleNotifyMsg(notification *btcchain.Notification) { switch notification.Type { // A block has been accepted into the block chain. Relay it to other // peers. case btcchain.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 } // It's ok to ignore the error here since the notification is // coming from the chain code which has already cached the hash. hash, _ := block.Sha() // Generate the inventory vector and relay it. iv := btcwire.NewInvVect(btcwire.InvTypeBlock, hash) b.server.RelayInventory(iv) // A block has been connected to the main block chain. case btcchain.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. Also, remove any // transactions which are now double spends as a result of these // new transactions. Note that removing a transaction from // pool also removes any transactions which depend on it, // recursively. for _, tx := range block.Transactions()[1:] { b.server.txMemPool.RemoveTransaction(tx) b.server.txMemPool.RemoveDoubleSpends(tx) } 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 := btcwire.NewInvVect(btcwire.InvTypeTx, tx.Sha()) 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 btcchain.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, nil, false, true) 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) } } // Notify registered websocket clients. if r := b.server.rpcServer; r != nil { r.ntfnMgr.NotifyBlockDisconnected(block) } } }
// handleNotifyMsg handles notifications from btcchain. It does things such // as request orphan block parents and relay accepted blocks to connected peers. func (b *blockManager) handleNotifyMsg(notification *btcchain.Notification) { switch notification.Type { // An orphan block has been accepted by the block chain. Request // its parents from the peer that sent it. case btcchain.NTOrphanBlock: orphanHash := notification.Data.(*btcwire.ShaHash) if peer, exists := b.blockPeer[*orphanHash]; exists { orphanRoot := b.blockChain.GetOrphanRoot(orphanHash) locator, err := b.blockChain.LatestBlockLocator() if err != nil { bmgrLog.Errorf("Failed to get block locator "+ "for the latest block: %v", err) break } peer.PushGetBlocksMsg(locator, orphanRoot) delete(b.blockPeer, *orphanRoot) } else { bmgrLog.Warnf("Notification for orphan %v with no peer", orphanHash) } // A block has been accepted into the block chain. Relay it to other // peers. case btcchain.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 } // It's ok to ignore the error here since the notification is // coming from the chain code which has already cached the hash. hash, _ := block.Sha() // Generate the inventory vector and relay it. iv := btcwire.NewInvVect(btcwire.InvTypeBlock, hash) b.server.RelayInventory(iv) // A block has been connected to the main block chain. case btcchain.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. Also, remove any // transactions which are now double spends as a result of these // new transactions. Note that removing a transaction from // pool also removes any transactions which depend on it, // recursively. for _, tx := range block.Transactions()[1:] { b.server.txMemPool.RemoveTransaction(tx) b.server.txMemPool.RemoveDoubleSpends(tx) } // Notify registered websocket clients if r := b.server.rpcServer; r != nil { r.ntfnMgr.NotifyBlockConnected(block) } // A block has been disconnected from the main block chain. case btcchain.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, nil, 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) } } // Notify registered websocket clients if r := b.server.rpcServer; r != nil { r.ntfnMgr.NotifyBlockDisconnected(block) } } }
// maybeAcceptTransaction is the main workhorse for handling insertion of new // free-standing transactions into a memory pool. It includes functionality // such as rejecting duplicate transactions, ensuring transactions follow all // rules, orphan transaction handling, and insertion into the memory pool. func (mp *txMemPool) maybeAcceptTransaction(tx *btcwire.MsgTx, isOrphan *bool) error { *isOrphan = false txHash, err := tx.TxSha() if err != nil { return err } // Don't accept the transaction if it already exists in the pool. This // applies to orphan transactions as well. This check is intended to // be a quick check to weed out duplicates. It is more expensive to // detect a duplicate transaction in the main chain, so that is done // later. if mp.isTransactionInPool(&txHash) { str := fmt.Sprintf("already have transaction %v", txHash) return TxRuleError(str) } // Perform preliminary sanity checks on the transaction. This makes // use of btcchain which contains the invariant rules for what // transactions are allowed into blocks. err = btcchain.CheckTransactionSanity(tx) if err != nil { if _, ok := err.(btcchain.RuleError); ok { return TxRuleError(err.Error()) } return err } // A standalone transaction must not be a coinbase transaction. if btcchain.IsCoinBase(tx) { str := fmt.Sprintf("transaction %v is an individual coinbase", txHash) return TxRuleError(str) } // Don't accept transactions with a lock time after the maximum int32 // value for now. This is an artifact of older bitcoind clients which // treated this field as an int32 and would treat anything larger // incorrectly (as negative). if tx.LockTime > math.MaxInt32 { str := fmt.Sprintf("transaction %v is has a lock time after "+ "2038 which is not accepted yet", txHash) return TxRuleError(str) } // Get the current height of the main chain. A standalone transaction // will be mined into the next block at best, so _, curHeight, err := mp.server.db.NewestSha() if err != nil { return err } nextBlockHeight := curHeight + 1 // Don't allow non-standard transactions on the main network. if activeNetParams.btcnet == btcwire.MainNet { err := checkTransactionStandard(tx, nextBlockHeight) if err != nil { str := fmt.Sprintf("transaction %v is not a standard "+ "transaction: %v", txHash, err) return TxRuleError(str) } } // The transaction may not use any of the same outputs as other // transactions already in the pool as that would ultimately result in a // double spend. This check is intended to be quick and therefore only // detects double spends within the transaction pool itself. The // transaction could still be double spending coins from the main chain // at this point. There is a more in-depth check that happens later // after fetching the referenced transaction inputs from the main chain // which examines the actual spend data and prevents double spends. err = mp.checkPoolDoubleSpend(tx) if err != nil { return err } // Fetch all of the transactions referenced by the inputs to this // transaction. This function also attempts to fetch the transaction // itself to be used for detecting a duplicate transaction without // needing to do a separate lookup. txStore, err := mp.fetchInputTransactions(tx) if err != nil { return err } // Don't allow the transaction if it exists in the main chain and is not // not already fully spent. if txD, exists := txStore[txHash]; exists && txD.Err == nil { for _, isOutputSpent := range txD.Spent { if !isOutputSpent { str := fmt.Sprintf("transaction already exists") return TxRuleError(str) } } } delete(txStore, txHash) // Transaction is an orphan if any of the inputs don't exist. for _, txD := range txStore { if txD.Err == btcdb.TxShaMissing { *isOrphan = true return nil } } // Perform several checks on the transaction inputs using the invariant // rules in btcchain for what transactions are allowed into blocks. // Also returns the fees associated with the transaction which will be // used later. txFee, err := btcchain.CheckTransactionInputs(tx, nextBlockHeight, txStore) if err != nil { return err } // Don't allow transactions with non-standard inputs on the main // network. if activeNetParams.btcnet == btcwire.MainNet { err := checkInputsStandard(tx) if err != nil { str := fmt.Sprintf("transaction %v has a non-standard "+ "input: %v", txHash, err) return TxRuleError(str) } } // Note: if you modify this code to accept non-standard transactions, // you should add code here to check that the transaction does a // reasonable number of ECDSA signature verifications. // TODO(davec): Don't allow the transaction if the transation fee // would be too low to get into an empty block. _ = txFee // Verify crypto signatures for each input and reject the transaction if // any don't verify. err = btcchain.ValidateTransactionScripts(tx, &txHash, time.Now(), txStore) if err != nil { return err } // TODO(davec): Rate-limit free transactions // Add to transaction pool. mp.addTransaction(tx, &txHash) mp.lock.RLock() log.Debugf("[TXMP] Accepted transaction %v (pool size: %v)", txHash, len(mp.pool)) mp.lock.RUnlock() // TODO(davec): Notifications // Generate the inventory vector and relay it. iv := btcwire.NewInvVect(btcwire.InvVect_Tx, &txHash) mp.server.RelayInventory(iv) return nil }
// handleGetBlocksMsg is invoked when a peer receives a getdata bitcoin message. func (p *peer) handleGetBlocksMsg(msg *btcwire.MsgGetBlocks) { // Return all block hashes to the latest one (up to max per message) if // no stop hash was specified. // Attempt to find the ending index of the stop hash if specified. endIdx := btcdb.AllShas if !msg.HashStop.IsEqual(&zeroHash) { block, err := p.server.db.FetchBlockBySha(&msg.HashStop) if err == nil { endIdx = block.Height() + 1 } } // Find the most recent known block based on the block locator. // Use the block after the genesis block if no other blocks in the // provided locator are known. This does mean the client will start // over with the genesis block if unknown block locators are provided. // This mirrors the behavior in the reference implementation. startIdx := int64(1) for _, hash := range msg.BlockLocatorHashes { block, err := p.server.db.FetchBlockBySha(hash) if err == nil { // Start with the next hash since we know this one. startIdx = block.Height() + 1 break } } // Don't attempt to fetch more than we can put into a single message. autoContinue := false if endIdx-startIdx > btcwire.MaxBlocksPerMsg { endIdx = startIdx + btcwire.MaxBlocksPerMsg autoContinue = true } // Generate inventory message. // // The FetchBlockBySha call is limited to a maximum number of hashes // per invocation. Since the maximum number of inventory per message // might be larger, call it multiple times with the appropriate indices // as needed. invMsg := btcwire.NewMsgInv() for start := startIdx; start < endIdx; { // Fetch the inventory from the block database. hashList, err := p.server.db.FetchHeightRange(start, endIdx) if err != nil { log.Warnf("PEER: Block lookup failed: %v", err) return } // The database did not return any further hashes. Break out of // the loop now. if len(hashList) == 0 { break } // Add block inventory to the message. for _, hash := range hashList { hashCopy := hash iv := btcwire.NewInvVect(btcwire.InvTypeBlock, &hashCopy) invMsg.AddInvVect(iv) } start += int64(len(hashList)) } // Send the inventory message if there is anything to send. if len(invMsg.InvList) > 0 { invListLen := len(invMsg.InvList) if autoContinue && invListLen == btcwire.MaxBlocksPerMsg { // Intentionally use a copy of the final hash so there // is not a reference into the inventory slice which // would prevent the entire slice from being eligible // for GC as soon as it's sent. continueHash := invMsg.InvList[invListLen-1].Hash p.continueHash = &continueHash } p.QueueMessage(invMsg, nil) } }
// handleNotifyMsg handles notifications from btcchain. It does things such // as request orphan block parents and relay accepted blocks to connected peers. func (b *blockManager) handleNotifyMsg(notification *btcchain.Notification) { switch notification.Type { // An orphan block has been accepted by the block chain. Request // its parents from the peer that sent it. case btcchain.NTOrphanBlock: orphanHash := notification.Data.(*btcwire.ShaHash) if peer, exists := b.blockPeer[*orphanHash]; exists { orphanRoot := b.blockChain.GetOrphanRoot(orphanHash) locator, err := b.blockChain.LatestBlockLocator() if err != nil { log.Errorf("[BMGR] Failed to get block locator "+ "for the latest block: %v", err) break } peer.PushGetBlocksMsg(locator, orphanRoot) delete(b.blockPeer, *orphanRoot) } else { log.Warnf("Notification for orphan %v with no peer", orphanHash) } // A block has been accepted into the block chain. Relay it to other // peers. case btcchain.NTBlockAccepted: // Don't relay if we are not current. Other peers that are // current should already know about it. // TODO(davec): This should really be over in RelayInventory // in server to stop all relays, but chain is not concurrent // safe at this time, so call it here to single thread access. if !b.blockChain.IsCurrent() { return } block, ok := notification.Data.(*btcutil.Block) if !ok { log.Warnf("[BMGR] Chain accepted notification is not a block.") break } // It's ok to ignore the error here since the notification is // coming from the chain code which has already cached the hash. hash, _ := block.Sha() // Generate the inventory vector and relay it. iv := btcwire.NewInvVect(btcwire.InvVect_Block, hash) b.server.RelayInventory(iv) // A block has been connected to the main block chain. case btcchain.NTBlockConnected: block, ok := notification.Data.(*btcutil.Block) if !ok { log.Warnf("[BMGR] Chain connected notification is not a block.") break } // Remove all of the transactions (except the coinbase) in the // connected block from the transaction pool. for _, tx := range block.MsgBlock().Transactions[1:] { b.server.txMemPool.removeTransaction(tx) } // A block has been disconnected from the main block chain. case btcchain.NTBlockDisconnected: block, ok := notification.Data.(*btcutil.Block) if !ok { log.Warnf("[BMGR] Chain disconnected notification is not a block.") break } // Reinsert all of the transactions (except the coinbase) into // the transaction pool. for _, tx := range block.MsgBlock().Transactions[1:] { err := b.server.txMemPool.ProcessTransaction(tx) 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) } } } }