├── README.md ├── tbp.gif └── tinybitcoinpeer.py /README.md: -------------------------------------------------------------------------------- 1 | [tinybitcoinpeer.py](/tinybitcoinpeer.py) 2 | ================== 3 | 4 | A toy bitcoin peer in Python. Connects to a random testnet 5 | node, shakes hands, reacts to pings, and asks for pongs. 6 | 7 | This file is intended to be useful as a starting point for 8 | building your own Bitcoin network tools. Rather than doing 9 | things one way, it illustrates several alternatives... 10 | feel free to pick and choose. 11 | 12 | Andrew Miller https://soc1024.com/ 13 | 14 | Dependencies 15 | ------------ 16 | - gevent 17 | - https://github.com/petertodd/python-bitcoinlib 18 | 19 |
20 | -------------------------------------------------------------------------------- /tbp.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amiller/tinybitcoinpeer/4d91b19d66cf47cb1e82f909e8e3dffaba623a00/tbp.gif -------------------------------------------------------------------------------- /tinybitcoinpeer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # tinybitcoinpeer.py 3 | # 4 | # A toy bitcoin node in Python. Connects to a random testnet 5 | # node, shakes hands, reacts to pings, and asks for pongs. 6 | # - Andrew Miller https://soc1024.com/ 7 | # 8 | # Thanks to Peter Todd, Jeff Garzik, Ethan Heilman 9 | # 10 | # Dependencies: 11 | # - gevent 12 | # - https://github.com/petertodd/python-bitcoinlib 13 | # 14 | # This file is intended to be useful as a starting point 15 | # for building your own Bitcoin network tools. Rather than 16 | # choosing one way to do things, it illustrates several 17 | # different ways... feel free to pick and choose. 18 | # 19 | # - The msg_stream() function handily turns a stream of raw 20 | # Bitcoin p2p socket data into a stream of parsed messages. 21 | # Parsing is provided by the python-bitcoinlib dependency. 22 | # 23 | # - The handshake is performed with ordinary sequential code. 24 | # You can get a lot done without any concurrency, such as 25 | # connecting immediately to fetching blocks or addrs, 26 | # or sending payloads of data to a node. 27 | 28 | # - The node first attempts to resolve the DNS name of a Bitcoin 29 | # seeder node. Bitcoin seeder speak the DNS protocol, but 30 | # actually respond with IP addresses for random nodes in the 31 | # network. 32 | # 33 | # - After the handshake, a "message handler" is installed as a 34 | # background thread. This handler logs every message 35 | # received, and responds to "Ping" challenges. It is easy 36 | # to add more reactive behaviors too. 37 | # 38 | # - This shows off a versatile way to use gevent threads, in 39 | # multiple ways at once. After forking off the handler 40 | # thread, the main thread also keeps around a tee of the 41 | # stream, making it easy to write sequential schedules. 42 | # This code periodically sends ping messages, sleeping 43 | # in between. Additional threads could be given their 44 | # own tees too. 45 | # 46 | from __future__ import print_function 47 | import gevent.monkey; gevent.monkey.patch_all() # needed for PySocks! 48 | import gevent, gevent.socket as socket 49 | from gevent.queue import Queue 50 | import bitcoin 51 | from bitcoin.messages import * 52 | from bitcoin.net import CAddress 53 | import time, sys, contextlib 54 | from io import BufferedReader 55 | 56 | COLOR_RECV = '\033[95m' 57 | COLOR_SEND = '\033[94m' 58 | COLOR_ENDC = '\033[0m' 59 | 60 | PORT = 18333 61 | bitcoin.SelectParams('testnet') 62 | 63 | # Turn a raw stream of Bitcoin p2p socket data into a stream of 64 | # parsed messages. 65 | def msg_stream(f): 66 | #f = BufferedReader(f) 67 | while True: 68 | yield MsgSerializable.stream_deserialize(f) 69 | 70 | 71 | def send(sock, msg): 72 | print(COLOR_SEND, 'Sent:', COLOR_ENDC, msg.command) 73 | msg.stream_serialize(sock) 74 | 75 | def tee_and_handle(f, msgs): 76 | queue = Queue() # unbounded buffer 77 | def _run(): 78 | for msg in msgs: 79 | print(COLOR_RECV, 'Received:', COLOR_ENDC, msg.command) 80 | if msg.command == b'ping': 81 | send(f, msg_pong(nonce=msg.nonce)) 82 | queue.put(msg) 83 | t = gevent.Greenlet(_run) 84 | t.start() 85 | while True: yield(queue.get()) 86 | 87 | def version_pkt(my_ip, their_ip): 88 | msg = msg_version() 89 | msg.nVersion = 70002 90 | msg.addrTo.ip = their_ip 91 | msg.addrTo.port = PORT 92 | msg.addrFrom.ip = my_ip 93 | msg.addrFrom.port = PORT 94 | msg.strSubVer = b"/tinybitcoinpeer.py/" 95 | return msg 96 | 97 | def addr_pkt( str_addrs ): 98 | msg = msg_addr() 99 | addrs = [] 100 | for i in str_addrs: 101 | addr = CAddress() 102 | addr.port = PORT 103 | addr.nTime = int(time.time()) 104 | addr.ip = i 105 | addrs.append( addr ) 106 | msg.addrs = addrs 107 | return msg 108 | 109 | def main(): 110 | with contextlib.closing(socket.socket()) as s, \ 111 | contextlib.closing(s.makefile('wb',0)) as writer, \ 112 | contextlib.closing(s.makefile('rb', 0)) as reader: 113 | 114 | # This will actually return a random testnet node 115 | their_ip = socket.gethostbyname("testnet-seed.bitcoin.schildbach.de") 116 | print("Connecting to:", their_ip) 117 | 118 | my_ip = "127.0.0.1" 119 | 120 | s.connect( (their_ip,PORT) ) 121 | stream = msg_stream(reader) 122 | 123 | # Send Version packet 124 | send(writer, version_pkt(my_ip, their_ip)) 125 | 126 | # Receive their Version 127 | their_ver = next(stream) 128 | print('Received:', their_ver) 129 | 130 | # Send Version acknolwedgement (Verack) 131 | send(writer, msg_verack()) 132 | 133 | # Fork off a handler, but keep a tee of the stream 134 | stream = tee_and_handle(writer, stream) 135 | 136 | # Get Verack 137 | their_verack = next(stream) 138 | 139 | # Send a ping! 140 | try: 141 | while True: 142 | send(writer, msg_ping()) 143 | send(writer, msg_getaddr()) 144 | gevent.sleep(5) 145 | except KeyboardInterrupt: pass 146 | 147 | try: __IPYTHON__ 148 | except NameError: main() 149 | --------------------------------------------------------------------------------