// This example demonstrates the basic process for initializing and creating an // outbound peer. Peers negotiate by exchanging version and verack messages. // For demonstration, a simple handler for version message is attached to the // peer. func Example_newOutboundPeer() { // Ordinarily this will not be needed since the outbound peer will be // connecting to a remote peer, however, since this example is executed // and tested, a mock remote peer is needed to listen for the outbound // peer. if err := mockRemotePeer(); err != nil { fmt.Printf("mockRemotePeer: unexpected error %v\n", err) return } // Create an outbound peer that is configured to act as a simnet node // that offers no services and has listeners for the version and verack // messages. The verack listener is used here to signal the code below // when the handshake has been finished by signalling a channel. verack := make(chan struct{}) peerCfg := &peer.Config{ UserAgentName: "peer", // User agent name to advertise. UserAgentVersion: "1.0.0", // User agent version to advertise. ChainParams: &chaincfg.SimNetParams, Services: 0, Listeners: peer.MessageListeners{ OnVersion: func(p *peer.Peer, msg *wire.MsgVersion) { fmt.Println("outbound: received version") }, OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { verack <- struct{}{} }, }, } p, err := peer.NewOutboundPeer(peerCfg, "127.0.0.1:18555") if err != nil { fmt.Printf("NewOutboundPeer: error %v\n", err) return } // Establish the connection to the peer address and mark it connected. conn, err := net.Dial("tcp", p.Addr()) if err != nil { fmt.Printf("net.Dial: error %v\n", err) return } if err := p.Connect(conn); err != nil { fmt.Printf("Connect: error %v\n", err) return } // Wait for the verack message or timeout in case of failure. select { case <-verack: case <-time.After(time.Second * 1): fmt.Printf("Example_peerConnection: verack timeout") } // Disconnect the peer. p.Disconnect() p.WaitForDisconnect() // Output: // outbound: received version }
// TestOutboundPeer tests that the outbound peer works as expected. func TestOutboundPeer(t *testing.T) { // Use a mock NewestBlock func to test errs var errBlockNotFound = errors.New("newest block not found") var mockNewestSha = func() (*wire.ShaHash, int32, error) { return nil, 0, errBlockNotFound } peerCfg := &peer.Config{ NewestBlock: mockNewestSha, 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 } wantErr := errBlockNotFound if err := p.Connect(c); err != wantErr { t.Errorf("Connect: expected err %v, got %v\n", wantErr, err) return } // Test already connected. if err := p.Connect(c); err != nil { t.Errorf("Connect: unexpected err %v\n", err) return } // Test Queue Inv fakeBlockHash := &wire.ShaHash{0: 0x00, 1: 0x01} fakeInv := wire.NewInvVect(wire.InvTypeBlock, fakeBlockHash) p.QueueInventory(fakeInv) p.AddKnownInventory(fakeInv) p.QueueInventory(fakeInv) // Test Queue Message fakeMsg := wire.NewMsgVerAck() p.QueueMessage(fakeMsg, nil) done := make(chan struct{}) p.QueueMessage(fakeMsg, done) <-done p.Disconnect() // Test NewestBlock var newestBlock = func() (*wire.ShaHash, int32, error) { hashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef" hash, err := wire.NewShaHashFromStr(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 } if err := p1.Connect(c1); err != nil { t.Errorf("Connect: unexpected err %v\n", err) return } // Test update latest block latestBlockSha, err := wire.NewShaHashFromStr("1a63f9cdff1752e6375c8c76e543a71d239e1a2e5c6db1aa679") if err != nil { t.Errorf("NewShaHashFromStr: unexpected err %v\n", err) return } p1.UpdateLastAnnouncedBlock(latestBlockSha) p1.UpdateLastBlockHeight(234440) if p1.LastAnnouncedBlock() != latestBlockSha { t.Errorf("LastAnnouncedBlock: wrong block - got %v, want %v", p1.LastAnnouncedBlock(), latestBlockSha) 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 } if err := p2.Connect(c2); err != nil { t.Errorf("Connect: unexpected err %v\n", err) return } // 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, &wire.ShaHash{}); err != nil { t.Errorf("PushGetBlocksMsg: unexpected err %v\n", err) return } if err := p2.PushGetHeadersMsg(nil, &wire.ShaHash{}); err != nil { t.Errorf("PushGetHeadersMsg: unexpected err %v\n", err) return } p2.PushRejectMsg("block", wire.RejectMalformed, "malformed", nil, true) 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.Disconnect() }
// TestPeerListeners tests that the peer listeners are called as expected. func TestPeerListeners(t *testing.T) { verack := make(chan struct{}, 1) ok := make(chan wire.Message, 20) peerCfg := &peer.Config{ Listeners: peer.MessageListeners{ OnGetAddr: func(p *peer.Peer, msg *wire.MsgGetAddr) { ok <- msg }, OnAddr: func(p *peer.Peer, msg *wire.MsgAddr) { ok <- msg }, OnPing: func(p *peer.Peer, msg *wire.MsgPing) { ok <- msg }, OnPong: func(p *peer.Peer, msg *wire.MsgPong) { ok <- msg }, OnAlert: func(p *peer.Peer, msg *wire.MsgAlert) { ok <- msg }, OnMemPool: func(p *peer.Peer, msg *wire.MsgMemPool) { ok <- msg }, OnTx: func(p *peer.Peer, msg *wire.MsgTx) { ok <- msg }, OnBlock: func(p *peer.Peer, msg *wire.MsgBlock, buf []byte) { ok <- msg }, OnInv: func(p *peer.Peer, msg *wire.MsgInv) { ok <- msg }, OnHeaders: func(p *peer.Peer, msg *wire.MsgHeaders) { ok <- msg }, OnNotFound: func(p *peer.Peer, msg *wire.MsgNotFound) { ok <- msg }, OnGetData: func(p *peer.Peer, msg *wire.MsgGetData) { ok <- msg }, OnGetBlocks: func(p *peer.Peer, msg *wire.MsgGetBlocks) { ok <- msg }, OnGetHeaders: func(p *peer.Peer, msg *wire.MsgGetHeaders) { ok <- msg }, OnFilterAdd: func(p *peer.Peer, msg *wire.MsgFilterAdd) { ok <- msg }, OnFilterClear: func(p *peer.Peer, msg *wire.MsgFilterClear) { ok <- msg }, OnFilterLoad: func(p *peer.Peer, msg *wire.MsgFilterLoad) { ok <- msg }, OnMerkleBlock: func(p *peer.Peer, msg *wire.MsgMerkleBlock) { ok <- msg }, OnVersion: func(p *peer.Peer, msg *wire.MsgVersion) { ok <- msg }, OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { verack <- struct{}{} }, OnReject: func(p *peer.Peer, msg *wire.MsgReject) { ok <- msg }, }, UserAgentName: "peer", UserAgentVersion: "1.0", ChainParams: &chaincfg.MainNetParams, Services: wire.SFNodeBloom, } inConn, outConn := pipe( &conn{raddr: "10.0.0.1:8333"}, &conn{raddr: "10.0.0.2:8333"}, ) inPeer := peer.NewInboundPeer(peerCfg) if err := inPeer.Connect(inConn); err != nil { t.Errorf("TestPeerListeners: unexpected err %v\n", err) return } peerCfg.Listeners = peer.MessageListeners{ OnVerAck: func(p *peer.Peer, msg *wire.MsgVerAck) { verack <- struct{}{} }, } outPeer, err := peer.NewOutboundPeer(peerCfg, "10.0.0.1:8333") if err != nil { t.Errorf("NewOutboundPeer: unexpected err %v\n", err) return } if err := outPeer.Connect(outConn); err != nil { t.Errorf("TestPeerListeners: unexpected err %v\n", err) return } for i := 0; i < 2; i++ { select { case <-verack: case <-time.After(time.Second * 1): t.Errorf("TestPeerListeners: verack timeout\n") return } } tests := []struct { listener string msg wire.Message }{ { "OnGetAddr", wire.NewMsgGetAddr(), }, { "OnAddr", wire.NewMsgAddr(), }, { "OnPing", wire.NewMsgPing(42), }, { "OnPong", wire.NewMsgPong(42), }, { "OnAlert", wire.NewMsgAlert([]byte("payload"), []byte("signature")), }, { "OnMemPool", wire.NewMsgMemPool(), }, { "OnTx", wire.NewMsgTx(), }, { "OnBlock", wire.NewMsgBlock(wire.NewBlockHeader(&wire.ShaHash{}, &wire.ShaHash{}, 1, 1)), }, { "OnInv", wire.NewMsgInv(), }, { "OnHeaders", wire.NewMsgHeaders(), }, { "OnNotFound", wire.NewMsgNotFound(), }, { "OnGetData", wire.NewMsgGetData(), }, { "OnGetBlocks", wire.NewMsgGetBlocks(&wire.ShaHash{}), }, { "OnGetHeaders", wire.NewMsgGetHeaders(), }, { "OnFilterAdd", wire.NewMsgFilterAdd([]byte{0x01}), }, { "OnFilterClear", wire.NewMsgFilterClear(), }, { "OnFilterLoad", wire.NewMsgFilterLoad([]byte{0x01}, 10, 0, wire.BloomUpdateNone), }, { "OnMerkleBlock", wire.NewMsgMerkleBlock(wire.NewBlockHeader(&wire.ShaHash{}, &wire.ShaHash{}, 1, 1)), }, // only one version message is allowed // only one verack message is allowed { "OnMsgReject", wire.NewMsgReject("block", wire.RejectDuplicate, "dupe block"), }, } t.Logf("Running %d tests", len(tests)) for _, test := range tests { // Queue the test message outPeer.QueueMessage(test.msg, nil) select { case <-ok: case <-time.After(time.Second * 1): t.Errorf("TestPeerListeners: %s timeout", test.listener) return } } inPeer.Disconnect() outPeer.Disconnect() }
// TestPeerConnection tests connection between inbound and outbound peers. func TestPeerConnection(t *testing.T) { verack := make(chan struct{}, 1) peerCfg := &peer.Config{ Listeners: peer.MessageListeners{ OnWrite: func(p *peer.Peer, bytesWritten int, msg wire.Message, err error) { switch msg.(type) { case *wire.MsgVerAck: verack <- struct{}{} } }, }, UserAgentName: "peer", UserAgentVersion: "1.0", ChainParams: &chaincfg.MainNetParams, Services: 0, } wantStats := peerStats{ wantUserAgent: wire.DefaultUserAgent + "peer:1.0/", wantServices: 0, wantProtocolVersion: peer.MaxProtocolVersion, wantConnected: true, wantVersionKnown: true, wantVerAckReceived: true, wantLastPingTime: time.Time{}, wantLastPingNonce: uint64(0), wantLastPingMicros: int64(0), wantTimeOffset: int64(0), wantBytesSent: 158, // 134 version + 24 verack wantBytesReceived: 158, } tests := []struct { name string setup func() (*peer.Peer, *peer.Peer, error) }{ { "basic handshake", func() (*peer.Peer, *peer.Peer, error) { inConn, outConn := pipe( &conn{raddr: "10.0.0.1:8333"}, &conn{raddr: "10.0.0.2:8333"}, ) inPeer := peer.NewInboundPeer(peerCfg) if err := inPeer.Connect(inConn); err != nil { return nil, nil, err } outPeer, err := peer.NewOutboundPeer(peerCfg, "10.0.0.2:8333") if err != nil { return nil, nil, err } if err := outPeer.Connect(outConn); err != nil { return nil, nil, err } for i := 0; i < 2; i++ { select { case <-verack: case <-time.After(time.Second * 1): return nil, nil, errors.New("verack timeout") } } return inPeer, outPeer, nil }, }, { "socks proxy", func() (*peer.Peer, *peer.Peer, error) { inConn, outConn := pipe( &conn{raddr: "10.0.0.1:8333", proxy: true}, &conn{raddr: "10.0.0.2:8333"}, ) inPeer := peer.NewInboundPeer(peerCfg) if err := inPeer.Connect(inConn); err != nil { return nil, nil, err } outPeer, err := peer.NewOutboundPeer(peerCfg, "10.0.0.2:8333") if err != nil { return nil, nil, err } if err := outPeer.Connect(outConn); err != nil { return nil, nil, err } for i := 0; i < 2; i++ { select { case <-verack: case <-time.After(time.Second * 1): return nil, nil, errors.New("verack timeout") } } return inPeer, outPeer, nil }, }, } t.Logf("Running %d tests", len(tests)) for i, test := range tests { inPeer, outPeer, err := test.setup() if err != nil { t.Errorf("TestPeerConnection setup #%d: unexpected err %v\n", i, err) return } testPeer(t, inPeer, wantStats) testPeer(t, outPeer, wantStats) inPeer.Disconnect() outPeer.Disconnect() } }
// TestOutboundPeer tests that the outbound peer works as expected. func TestOutboundPeer(t *testing.T) { peerCfg := &peer.Config{ NewestBlock: func() (*wire.ShaHash, 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.Connect(c) p.Connect(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 := &wire.ShaHash{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() (*wire.ShaHash, int32, error) { hashStr := "14a0810ac680a3eb3f82edc878cea25ec41d6b790744e5daeef" hash, err := wire.NewShaHashFromStr(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.Connect(c1) // Test update latest block latestBlockSha, err := wire.NewShaHashFromStr("1a63f9cdff1752e6375c8c76e543a71d239e1a2e5c6db1aa679") if err != nil { t.Errorf("NewShaHashFromStr: unexpected err %v\n", err) return } p1.UpdateLastAnnouncedBlock(latestBlockSha) p1.UpdateLastBlockHeight(234440) if p1.LastAnnouncedBlock() != latestBlockSha { t.Errorf("LastAnnouncedBlock: wrong block - got %v, want %v", p1.LastAnnouncedBlock(), latestBlockSha) 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.Connect(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, &wire.ShaHash{}); err != nil { t.Errorf("PushGetBlocksMsg: unexpected err %v\n", err) return } if err := p2.PushGetHeadersMsg(nil, &wire.ShaHash{}); 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.Disconnect() }