// 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() }
// OpenPV starts a func OpenSPV(remoteNode string, hfn, dbfn string, inTs *TxStore, hard bool, iron bool, p *chaincfg.Params) (SPVCon, error) { // create new SPVCon var s SPVCon s.HardMode = hard s.Ironman = iron // I should really merge SPVCon and TxStore, they're basically the same inTs.Param = p s.TS = inTs // copy pointer of txstore into spvcon // open header file err := s.openHeaderFile(hfn) if err != nil { return s, err } // open TCP connection s.con, err = net.Dial("tcp", remoteNode) if err != nil { return s, err } // assign version bits for local node s.localVersion = VERSION // transaction store for this SPV connection err = inTs.OpenDB(dbfn) if err != nil { return s, err } myMsgVer, err := wire.NewMsgVersionFromConn(s.con, 0, 0) if err != nil { return s, err } err = myMsgVer.AddUserAgent("test", "zero") if err != nil { return s, err } // must set this to enable SPV stuff myMsgVer.AddService(wire.SFNodeBloom) // set this to enable segWit myMsgVer.AddService(wire.SFNodeWitness) // this actually sends n, err := wire.WriteMessageN(s.con, myMsgVer, s.localVersion, s.TS.Param.Net) if err != nil { return s, err } s.WBytes += uint64(n) log.Printf("wrote %d byte version message to %s\n", n, s.con.RemoteAddr().String()) n, m, b, err := wire.ReadMessageN(s.con, s.localVersion, s.TS.Param.Net) if err != nil { return s, err } s.RBytes += uint64(n) log.Printf("got %d byte response %x\n command: %s\n", n, b, m.Command()) mv, ok := m.(*wire.MsgVersion) if ok { log.Printf("connected to %s", mv.UserAgent) } log.Printf("remote reports version %x (dec %d)\n", mv.ProtocolVersion, mv.ProtocolVersion) // set remote height s.remoteHeight = mv.LastBlock mva := wire.NewMsgVerAck() n, err = wire.WriteMessageN(s.con, mva, s.localVersion, s.TS.Param.Net) if err != nil { return s, err } s.WBytes += uint64(n) s.inMsgQueue = make(chan wire.Message) go s.incomingMessageHandler() s.outMsgQueue = make(chan wire.Message) go s.outgoingMessageHandler() s.blockQueue = make(chan HashAndHeight, 32) // queue depth 32 is a thing s.fPositives = make(chan int32, 4000) // a block full, approx s.inWaitState = make(chan bool, 1) go s.fPositiveHandler() if hard { err = s.TS.Refilter() if err != nil { return s, err } } return s, nil }