func (s *SPVCon) Open(remoteNode string, hfn string, inTs *TxStore) error { // open header file err := s.openHeaderFile(headerFileName) if err != nil { return err } // open TCP connection s.con, err = net.Dial("tcp", remoteNode) if err != nil { return err } s.localVersion = VERSION s.netType = NETVERSION s.TS = inTs myMsgVer, err := wire.NewMsgVersionFromConn(s.con, 0, 0) if err != nil { return err } err = myMsgVer.AddUserAgent("test", "zero") if err != nil { return err } // must set this to enable SPV stuff myMsgVer.AddService(wire.SFNodeBloom) // this actually sends n, err := wire.WriteMessageN(s.con, myMsgVer, s.localVersion, s.netType) if err != nil { return 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.netType) if err != nil { return 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) mva := wire.NewMsgVerAck() n, err = wire.WriteMessageN(s.con, mva, s.localVersion, s.netType) if err != nil { return err } s.WBytes += uint64(n) s.inMsgQueue = make(chan wire.Message) go s.incomingMessageHandler() s.outMsgQueue = make(chan wire.Message) go s.outgoingMessageHandler() return nil }
// crawlIP retrievs a slice of ip addresses from a client func crawlIP(s *dnsseeder, r *result) ([]*wire.NetAddress, *crawlError) { conn, err := net.DialTimeout("tcp", r.node, time.Second*10) if err != nil { if config.debug { log.Printf("%s - debug - Could not connect to %s - %v\n", s.name, r.node, err) } return nil, &crawlError{"", err} } defer conn.Close() if config.debug { log.Printf("%s - debug - Connected to remote address: %s\n", s.name, r.node) } // set a deadline for all comms to be done by. After this all i/o will error conn.SetDeadline(time.Now().Add(time.Second * maxTo)) // First command to remote end needs to be a version command // last parameter is lastblock msgver, err := wire.NewMsgVersionFromConn(conn, nounce, 0) if err != nil { return nil, &crawlError{"Create NewMsgVersionFromConn", err} } err = wire.WriteMessage(conn, msgver, s.pver, s.id) if err != nil { // Log and handle the error return nil, &crawlError{"Write Version Message", err} } // first message received should be version msg, _, err := wire.ReadMessage(conn, s.pver, s.id) if err != nil { // Log and handle the error return nil, &crawlError{"Read message after sending Version", err} } switch msg := msg.(type) { case *wire.MsgVersion: // The message is a pointer to a MsgVersion struct. if config.debug { log.Printf("%s - debug - %s - Remote version: %v\n", s.name, r.node, msg.ProtocolVersion) } // fill the node struct with the remote details r.version = msg.ProtocolVersion r.services = msg.Services r.lastBlock = msg.LastBlock r.strVersion = msg.UserAgent default: return nil, &crawlError{"Did not receive expected Version message from remote client", errors.New("")} } // send verack command msgverack := wire.NewMsgVerAck() err = wire.WriteMessage(conn, msgverack, s.pver, s.id) if err != nil { return nil, &crawlError{"writing message VerAck", err} } // second message received should be verack msg, _, err = wire.ReadMessage(conn, s.pver, s.id) if err != nil { return nil, &crawlError{"reading expected Ver Ack from remote client", err} } switch msg.(type) { case *wire.MsgVerAck: if config.debug { log.Printf("%s - debug - %s - received Version Ack\n", s.name, r.node) } default: return nil, &crawlError{"Did not receive expected Ver Ack message from remote client", errors.New("")} } // if we get this far and if the seeder is full then don't ask for addresses. This will reduce bandwith usage while still // confirming that we can connect to the remote node if len(s.theList) > s.maxSize { return nil, nil } // send getaddr command msgGetAddr := wire.NewMsgGetAddr() err = wire.WriteMessage(conn, msgGetAddr, s.pver, s.id) if err != nil { return nil, &crawlError{"writing Addr message to remote client", err} } c := 0 dowhile := true for dowhile == true { // Using the Bitcoin lib for the some networks means it does not understand some // of the commands and will error. We can ignore these as we are only // interested in the addr message and its content. msgaddr, _, _ := wire.ReadMessage(conn, s.pver, s.id) if msgaddr != nil { switch msg := msgaddr.(type) { case *wire.MsgAddr: // received the addr message so return the result if config.debug { log.Printf("%s - debug - %s - received valid addr message\n", s.name, r.node) } dowhile = false return msg.AddrList, nil default: if config.debug { log.Printf("%s - debug - %s - ignoring message - %v\n", s.name, r.node, msg.Command()) } } } // if we get more than 25 messages before the addr we asked for then give up on this client if c++; c >= 25 { dowhile = false } } // received too many messages before requested Addr return nil, &crawlError{"message loop - did not receive remote addresses in first 25 messages from remote client", errors.New("")} }
// TestVersion tests the MsgVersion API. func TestVersion(t *testing.T) { pver := wire.ProtocolVersion // Create version message data. lastBlock := int32(234234) tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333} me, err := wire.NewNetAddress(tcpAddrMe, wire.SFNodeNetwork) if err != nil { t.Errorf("NewNetAddress: %v", err) } tcpAddrYou := &net.TCPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333} you, err := wire.NewNetAddress(tcpAddrYou, wire.SFNodeNetwork) if err != nil { t.Errorf("NewNetAddress: %v", err) } nonce, err := wire.RandomUint64() if err != nil { t.Errorf("RandomUint64: error generating nonce: %v", err) } // Ensure we get the correct data back out. msg := wire.NewMsgVersion(me, you, nonce, lastBlock) if msg.ProtocolVersion != int32(pver) { t.Errorf("NewMsgVersion: wrong protocol version - got %v, want %v", msg.ProtocolVersion, pver) } if !reflect.DeepEqual(&msg.AddrMe, me) { t.Errorf("NewMsgVersion: wrong me address - got %v, want %v", spew.Sdump(&msg.AddrMe), spew.Sdump(me)) } if !reflect.DeepEqual(&msg.AddrYou, you) { t.Errorf("NewMsgVersion: wrong you address - got %v, want %v", spew.Sdump(&msg.AddrYou), spew.Sdump(you)) } if msg.Nonce != nonce { t.Errorf("NewMsgVersion: wrong nonce - got %v, want %v", msg.Nonce, nonce) } if msg.UserAgent != wire.DefaultUserAgent { t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v", msg.UserAgent, wire.DefaultUserAgent) } if msg.LastBlock != lastBlock { t.Errorf("NewMsgVersion: wrong last block - got %v, want %v", msg.LastBlock, lastBlock) } if msg.DisableRelayTx != false { t.Errorf("NewMsgVersion: disable relay tx is not false by "+ "default - got %v, want %v", msg.DisableRelayTx, false) } msg.AddUserAgent("myclient", "1.2.3", "optional", "comments") customUserAgent := wire.DefaultUserAgent + "myclient:1.2.3(optional; comments)/" if msg.UserAgent != customUserAgent { t.Errorf("AddUserAgent: wrong user agent - got %s, want %s", msg.UserAgent, customUserAgent) } msg.AddUserAgent("mygui", "3.4.5") customUserAgent += "mygui:3.4.5/" if msg.UserAgent != customUserAgent { t.Errorf("AddUserAgent: wrong user agent - got %s, want %s", msg.UserAgent, customUserAgent) } // accounting for ":", "/" err = msg.AddUserAgent(strings.Repeat("t", wire.MaxUserAgentLen-len(customUserAgent)-2+1), "") if _, ok := err.(*wire.MessageError); !ok { t.Errorf("AddUserAgent: expected error not received "+ "- got %v, want %T", err, wire.MessageError{}) } // Version message should not have any services set by default. if msg.Services != 0 { t.Errorf("NewMsgVersion: wrong default services - got %v, want %v", msg.Services, 0) } if msg.HasService(wire.SFNodeNetwork) { t.Errorf("HasService: SFNodeNetwork service is set") } // Ensure the command is expected value. wantCmd := "version" if cmd := msg.Command(); cmd != wantCmd { t.Errorf("NewMsgVersion: wrong command - got %v want %v", cmd, wantCmd) } // Ensure max payload is expected value. // Protocol version 4 bytes + services 8 bytes + timestamp 8 bytes + // remote and local net addresses + nonce 8 bytes + length of user agent // (varInt) + max allowed user agent length + last block 4 bytes + // relay transactions flag 1 byte. wantPayload := uint32(2102) 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 adding the full service node flag works. msg.AddService(wire.SFNodeNetwork) if msg.Services != wire.SFNodeNetwork { t.Errorf("AddService: wrong services - got %v, want %v", msg.Services, wire.SFNodeNetwork) } if !msg.HasService(wire.SFNodeNetwork) { t.Errorf("HasService: SFNodeNetwork service not set") } // Use a fake connection. conn := &fakeConn{localAddr: tcpAddrMe, remoteAddr: tcpAddrYou} msg, err = wire.NewMsgVersionFromConn(conn, nonce, lastBlock) if err != nil { t.Errorf("NewMsgVersionFromConn: %v", err) } // Ensure we get the correct connection data back out. if !msg.AddrMe.IP.Equal(tcpAddrMe.IP) { t.Errorf("NewMsgVersionFromConn: wrong me ip - got %v, want %v", msg.AddrMe.IP, tcpAddrMe.IP) } if !msg.AddrYou.IP.Equal(tcpAddrYou.IP) { t.Errorf("NewMsgVersionFromConn: wrong you ip - got %v, want %v", msg.AddrYou.IP, tcpAddrYou.IP) } // Use a fake connection with local UDP addresses to force a failure. conn = &fakeConn{ localAddr: &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 8333}, remoteAddr: tcpAddrYou, } msg, err = wire.NewMsgVersionFromConn(conn, nonce, lastBlock) if err != wire.ErrInvalidNetAddr { t.Errorf("NewMsgVersionFromConn: expected error not received "+ "- got %v, want %v", err, wire.ErrInvalidNetAddr) } // Use a fake connection with remote UDP addresses to force a failure. conn = &fakeConn{ localAddr: tcpAddrMe, remoteAddr: &net.UDPAddr{IP: net.ParseIP("192.168.0.1"), Port: 8333}, } msg, err = wire.NewMsgVersionFromConn(conn, nonce, lastBlock) if err != wire.ErrInvalidNetAddr { t.Errorf("NewMsgVersionFromConn: expected error not received "+ "- got %v, want %v", err, wire.ErrInvalidNetAddr) } return }
// 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) // 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() return s, nil }