// TestPingCrossProtocol tests the MsgPing API when encoding with the latest // protocol version and decoding with BIP0031Version. func TestPingCrossProtocol(t *testing.T) { nonce, err := btcwire.RandomUint64() if err != nil { t.Errorf("RandomUint64: Error generating nonce: %v", err) } msg := btcwire.NewMsgPing(nonce) if msg.Nonce != nonce { t.Errorf("NewMsgPing: wrong nonce - got %v, want %v", msg.Nonce, nonce) } // Encode with latest protocol version. var buf bytes.Buffer err = msg.BtcEncode(&buf, btcwire.ProtocolVersion) if err != nil { t.Errorf("encode of MsgPing failed %v err <%v>", msg, err) } // Decode with old protocol version. readmsg := btcwire.NewMsgPing(0) err = readmsg.BtcDecode(&buf, btcwire.BIP0031Version) if err != nil { t.Errorf("decode of MsgPing failed [%v] err <%v>", buf, err) } // Since one of the protocol versions doesn't support the nonce, make // sure it didn't get encoded and decoded back out. if msg.Nonce == readmsg.Nonce { t.Error("Should not get same nonce for cross protocol") } }
// TestPing tests the MsgPing API against the latest protocol version. func TestPing(t *testing.T) { pver := btcwire.ProtocolVersion // Ensure we get the same nonce back out. nonce, err := btcwire.RandomUint64() if err != nil { t.Errorf("RandomUint64: Error generating nonce: %v", err) } msg := btcwire.NewMsgPing(nonce) if msg.Nonce != nonce { t.Errorf("NewMsgPing: wrong nonce - got %v, want %v", msg.Nonce, nonce) } // Ensure the command is expected value. wantCmd := "ping" if cmd := msg.Command(); cmd != wantCmd { t.Errorf("NewMsgPing: wrong command - got %v want %v", cmd, wantCmd) } // Ensure max payload is expected value for latest protocol version. wantPayload := uint32(8) 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) } return }
// TestPingBIP0031 tests the MsgPing API against the protocol version // BIP0031Version. func TestPingBIP0031(t *testing.T) { // Use the protocol version just prior to BIP0031Version changes. pver := btcwire.BIP0031Version nonce, err := btcwire.RandomUint64() if err != nil { t.Errorf("RandomUint64: Error generating nonce: %v", err) } msg := btcwire.NewMsgPing(nonce) if msg.Nonce != nonce { t.Errorf("NewMsgPing: wrong nonce - got %v, want %v", msg.Nonce, nonce) } // Ensure max payload is expected value for old protocol version. wantPayload := uint32(0) 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) } // Test encode with old protocol version. var buf bytes.Buffer err = msg.BtcEncode(&buf, pver) if err != nil { t.Errorf("encode of MsgPing failed %v err <%v>", msg, err) } // Test decode with old protocol version. readmsg := btcwire.NewMsgPing(0) err = readmsg.BtcDecode(&buf, pver) if err != nil { t.Errorf("decode of MsgPing failed [%v] err <%v>", buf, err) } // Since this protocol version doesn't support the nonce, make sure // it didn't get encoded and decoded back out. if msg.Nonce == readmsg.Nonce { t.Errorf("Should not get same nonce for protocol version %d", pver) } return }
// handlePing implements the ping command. func handlePing(s *rpcServer, cmd btcjson.Cmd) (interface{}, error) { // Ask server to ping \o_ nonce, err := btcwire.RandomUint64() if err != nil { return nil, fmt.Errorf("Not sending ping - can not generate "+ "nonce: %v", err) } s.server.BroadcastMessage(btcwire.NewMsgPing(nonce)) return nil, nil }
// outHandler handles all outgoing messages for the peer. It must be run as a // goroutine. It uses a buffered channel to serialize output messages while // allowing the sender to continue running asynchronously. func (p *peer) outHandler() { trickleTicker := time.NewTicker(time.Second * 10) pingTimer := time.AfterFunc(pingTimeoutMinutes*time.Minute, func() { nonce, err := btcwire.RandomUint64() if err != nil { log.Errorf("Not sending ping on timeout to %s: %v", p, err) return } p.QueueMessage(btcwire.NewMsgPing(nonce), nil) }) out: for { select { case msg := <-p.outputQueue: // If the message is one we should get a reply for // then reset the timer, we only want to send pings // when otherwise we would not recieve a reply from // the peer. We specifically do not count block or inv // messages here since they are not sure of a reply if // the inv is of no interest explicitly solicited invs // should elicit a reply but we don't track them // specially. reset := true switch msg.msg.(type) { case *btcwire.MsgVersion: // should get an ack case *btcwire.MsgGetAddr: // should get addresses case *btcwire.MsgPing: // expects pong case *btcwire.MsgMemPool: // Should return an inv. case *btcwire.MsgGetData: // Should get us block, tx, or not found. case *btcwire.MsgGetHeaders: // Should get us headers back. default: // Not one of the above, no sure reply. // We want to ping if nothing else // interesting happens. reset = false } if reset { pingTimer.Reset(pingTimeoutMinutes * time.Minute) } p.writeMessage(msg.msg) p.lastSend = time.Now() if msg.doneChan != nil { msg.doneChan <- true } case iv := <-p.outputInvChan: // No handshake? They'll find out soon enough. if p.versionKnown { p.invSendQueue.PushBack(iv) } case <-trickleTicker.C: // Don't send anything if we're disconnecting or there // is no queued inventory. if atomic.LoadInt32(&p.disconnect) != 0 || p.invSendQueue.Len() == 0 || !p.versionKnown { continue } // Create and send as many inv messages as needed to // drain the inventory send queue. invMsg := btcwire.NewMsgInv() for e := p.invSendQueue.Front(); e != nil; e = p.invSendQueue.Front() { iv := p.invSendQueue.Remove(e).(*btcwire.InvVect) // Don't send inventory that became known after // the initial check. if p.isKnownInventory(iv) { continue } invMsg.AddInvVect(iv) if len(invMsg.InvList) >= maxInvTrickleSize { p.writeMessage(invMsg) invMsg = btcwire.NewMsgInv() } // Add the inventory that is being relayed to // the known inventory for the peer. p.addKnownInventory(iv) } if len(invMsg.InvList) > 0 { p.writeMessage(invMsg) } case <-p.quit: break out } } pingTimer.Stop() // Drain any wait channels before we go away so we don't leave something // waiting for us. cleanup: for { select { case msg := <-p.outputQueue: if msg.doneChan != nil { msg.doneChan <- false } default: break cleanup } } log.Tracef("PEER: Peer output handler done for %s", p.addr) }
// TestMessage tests the Read/WriteMessage API. func TestMessage(t *testing.T) { pver := btcwire.ProtocolVersion // Create the various types of messages to test. // MsgVersion. addrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333} you, err := btcwire.NewNetAddress(addrYou, btcwire.SFNodeNetwork) if err != nil { t.Errorf("NewNetAddress: %v", err) } you.Timestamp = time.Time{} // Version message has zero value timestamp. addrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333} me, err := btcwire.NewNetAddress(addrMe, btcwire.SFNodeNetwork) if err != nil { t.Errorf("NewNetAddress: %v", err) } me.Timestamp = time.Time{} // Version message has zero value timestamp. msgVersion := btcwire.NewMsgVersion(me, you, 123123, "/test:0.0.1/", 0) msgVerack := btcwire.NewMsgVerAck() msgGetAddr := btcwire.NewMsgGetAddr() msgAddr := btcwire.NewMsgAddr() msgGetBlocks := btcwire.NewMsgGetBlocks(&btcwire.ShaHash{}) msgBlock := &blockOne msgInv := btcwire.NewMsgInv() msgGetData := btcwire.NewMsgGetData() msgNotFound := btcwire.NewMsgNotFound() msgTx := btcwire.NewMsgTx() msgPing := btcwire.NewMsgPing(123123) msgPong := btcwire.NewMsgPong(123123) msgGetHeaders := btcwire.NewMsgGetHeaders() msgHeaders := btcwire.NewMsgHeaders() msgAlert := btcwire.NewMsgAlert("payload", "signature") msgMemPool := btcwire.NewMsgMemPool() tests := []struct { in btcwire.Message // Value to encode out btcwire.Message // Expected decoded value pver uint32 // Protocol version for wire encoding btcnet btcwire.BitcoinNet // Network to use for wire encoding }{ {msgVersion, msgVersion, pver, btcwire.MainNet}, {msgVerack, msgVerack, pver, btcwire.MainNet}, {msgGetAddr, msgGetAddr, pver, btcwire.MainNet}, {msgAddr, msgAddr, pver, btcwire.MainNet}, {msgGetBlocks, msgGetBlocks, pver, btcwire.MainNet}, {msgBlock, msgBlock, pver, btcwire.MainNet}, {msgInv, msgInv, pver, btcwire.MainNet}, {msgGetData, msgGetData, pver, btcwire.MainNet}, {msgNotFound, msgNotFound, pver, btcwire.MainNet}, {msgTx, msgTx, pver, btcwire.MainNet}, {msgPing, msgPing, pver, btcwire.MainNet}, {msgPong, msgPong, pver, btcwire.MainNet}, {msgGetHeaders, msgGetHeaders, pver, btcwire.MainNet}, {msgHeaders, msgHeaders, pver, btcwire.MainNet}, {msgAlert, msgAlert, pver, btcwire.MainNet}, {msgMemPool, msgMemPool, pver, btcwire.MainNet}, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { // Encode to wire format. var buf bytes.Buffer err := btcwire.WriteMessage(&buf, test.in, test.pver, test.btcnet) if err != nil { t.Errorf("WriteMessage #%d error %v", i, err) continue } // Decode from wire format. rbuf := bytes.NewBuffer(buf.Bytes()) msg, _, err := btcwire.ReadMessage(rbuf, test.pver, test.btcnet) if err != nil { t.Errorf("ReadMessage #%d error %v, msg %v", i, err, spew.Sdump(msg)) continue } if !reflect.DeepEqual(msg, test.out) { t.Errorf("ReadMessage #%d\n got: %v want: %v", i, spew.Sdump(msg), spew.Sdump(test.out)) continue } } }
// outHandler handles all outgoing messages for the peer. It must be run as a // goroutine. It uses a buffered channel to serialize output messages while // allowing the sender to continue running asynchronously. func (p *peer) outHandler() { pingTimer := time.AfterFunc(pingTimeoutMinutes*time.Minute, func() { nonce, err := btcwire.RandomUint64() if err != nil { peerLog.Errorf("Not sending ping on timeout to %s: %v", p, err) return } p.QueueMessage(btcwire.NewMsgPing(nonce), nil) }) out: for { select { case msg := <-p.sendQueue: // If the message is one we should get a reply for // then reset the timer, we only want to send pings // when otherwise we would not recieve a reply from // the peer. We specifically do not count block or inv // messages here since they are not sure of a reply if // the inv is of no interest explicitly solicited invs // should elicit a reply but we don't track them // specially. peerLog.Tracef("%s: recieved from queuehandler", p) reset := true switch msg.msg.(type) { case *btcwire.MsgVersion: // should get an ack case *btcwire.MsgGetAddr: // should get addresses case *btcwire.MsgPing: // expects pong case *btcwire.MsgMemPool: // Should return an inv. case *btcwire.MsgGetData: // Should get us block, tx, or not found. case *btcwire.MsgGetHeaders: // Should get us headers back. default: // Not one of the above, no sure reply. // We want to ping if nothing else // interesting happens. reset = false } if reset { pingTimer.Reset(pingTimeoutMinutes * time.Minute) } p.writeMessage(msg.msg) p.lastSend = time.Now() if msg.doneChan != nil { msg.doneChan <- true } peerLog.Tracef("%s: acking queuehandler", p) p.sendDoneQueue <- true peerLog.Tracef("%s: acked queuehandler", p) case <-p.quit: break out } } pingTimer.Stop() p.queueWg.Wait() // Drain any wait channels before we go away so we don't leave something // waiting for us. We have waited on queueWg and thus we can be sure // that we will not miss anything sent on sendQueue. cleanup: for { select { case msg := <-p.sendQueue: if msg.doneChan != nil { msg.doneChan <- false } // no need to send on sendDoneQueue since queueHandler // has been waited on and already exited. default: break cleanup } } peerLog.Tracef("Peer output handler done for %s", p.addr) }