├── 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 |
--------------------------------------------------------------------------------