Skip to content

Instantly share code, notes, and snippets.

@sappelt
Created June 1, 2018 16:31
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save sappelt/9e60af207219bfb6c6d07c6dab38bcaa to your computer and use it in GitHub Desktop.
Save sappelt/9e60af207219bfb6c6d07c6dab38bcaa to your computer and use it in GitHub Desktop.
How to establish a connection to the bitcoin manually. Peer discovery + connecting and sending version message
# Import requests and regex library
import requests
import re
def get_external_ip():
# Make a request to checkip.dyndns.org as proposed
# in https://en.bitcoin.it/wiki/Satoshi_Client_Node_Discovery#DNS_Addresses
response = requests.get('http://checkip.dyndns.org').text
# Filter the response with a regex for an IPv4 address
ip = re.search("(?:[0-9]{1,3}\.){3}[0-9]{1,3}", response).group()
return ip
external_ip = get_external_ip()
print(external_ip)
# Import socket and time library
import socket
import time
def get_node_addresses():
# The list of seeds as hardcoded in a Bitcoin client
# view https://en.bitcoin.it/wiki/Satoshi_Client_Node_Discovery#DNS_Addresses
dns_seeds = [
("seed.bitcoin.sipa.be", 8333),
("dnsseed.bluematt.me", 8333),
("dnsseed.bitcoin.dashjr.org", 8333),
("seed.bitcoinstats.com", 8333),
("seed.bitnodes.io", 8333),
("bitseed.xf2.org", 8333),
]
# The list where we store our found peers
found_peers = []
try:
# Loop our seed list
for (ip_address, port) in dns_seeds:
index = 0
# Connect to a dns address and get the A records
for info in socket.getaddrinfo(ip_address, port,
socket.AF_INET, socket.SOCK_STREAM,
socket.IPPROTO_TCP):
# The IP address and port is at index [4][0]
# for example: ('13.250.46.106', 8333)
found_peers.append((info[4][0], info[4][1]))
except Exception:
return found_peers
peers = get_node_addresses()
print(peers)
import struct
import random
import hashlib
# Create a streaming socket
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# Connect to the first responding peer from our dns list
def connect(peer_index):
try:
print("Trying to connect to ", peers[peer_index])
err = sock.connect(peers[peer_index])
return peer_index
except Exception:
# Sidenote: Recursive call to test the next peer
# You would it not do like this in a real world, but it is for educational purposes only
return connect(peer_index+1)
peer_index = connect(0)
def create_version_message():
# Encode all values to the right binary representation on https://bitcoin.org/en/developer-reference#version
# And https://docs.python.org/3/library/struct.html#format-characters
# The current protocol version, look it up under https://bitcoin.org/en/developer-reference#protocol-versions
version = struct.pack("i", 70015)
# Services that we support, can be either full-node (1) or not full-node (0)
services = struct.pack("Q", 0)
# The current timestamp
timestamp = struct.pack("q", int(time.time()))
# Services that receiver supports
add_recv_services = struct.pack("Q", 0)
# The receiver's IP, we got it from the DNS example above
add_recv_ip = struct.pack(">16s", bytes(peers[peer_index][0], 'utf-8'))
# The receiver's port (Bitcoin default is 8333)
add_recv_port = struct.pack(">H", 8333)
# Should be identical to services, was added later by the protocol
add_trans_services = struct.pack("Q", 0)
# Our ip or 127.0.0.1
add_trans_ip = struct.pack(">16s", bytes("127.0.0.1", 'utf-8'))
# Our port
add_trans_port = struct.pack(">H", 8333)
# A nonce to detect connections to ourself
# If we receive the same nonce that we sent, we want to connect to oursel
nonce = struct.pack("Q", random.getrandbits(64))
# Can be a user agent like Satoshi:0.15.1, we leave it empty
user_agent_bytes = struct.pack("B", 0)
# The block starting height, you can find the latest on http://blockchain.info/
starting_height = struct.pack("i", 525453)
# We do not relay data and thus want to prevent to get tx messages
relay = struct.pack("?", False)
# Let's combine everything to our payload
payload = version + services + timestamp + add_recv_services + add_recv_ip + add_recv_port + \
add_trans_services + add_trans_ip + add_trans_port + nonce + user_agent_bytes + starting_height + relay
# To meet the protocol specifications, we also have to create a header
# The general header format is described here https://en.bitcoin.it/wiki/Protocol_documentation#Message_structure
# The magic bytes, indicate the initiating network (Mainnet or Testned)
# The known values can be found here https://en.bitcoin.it/wiki/Protocol_documentation#Common_structures
magic = bytes.fromhex("F9BEB4D9")
# The command we want to send e.g. version message
# This must be null padded to reach 12 bytes in total (version = 7 Bytes + 5 zero bytes)
command = b"version" + 5 * b"\00"
# The payload length
length = struct.pack("I", len(payload))
# The checksum, combuted as described in https://en.bitcoin.it/wiki/Protocol_documentation#Message_structure
checksum = hashlib.sha256(hashlib.sha256(payload).digest()).digest()[:4]
# Build up the message
return magic + command + length + checksum + payload
# Send out our version message
sock.send(create_version_message())
def encode_received_message(recv_message):
# Encode the magic number
recv_magic = recv_message[:4].hex()
# Encode the command (should be version)
recv_command = recv_message[4:16]
# Encode the payload length
recv_length = struct.unpack("I", recv_message[16:20])
# Encode the checksum
recv_checksum = recv_message[20:24]
# Encode the payload (the rest)
recv_payload = recv_message[24:]
# Encode the version of the other peer
recv_version = struct.unpack("i", recv_payload[:4])
return (recv_magic, recv_command, recv_length, recv_checksum, recv_payload, recv_version)
time.sleep(1)
# Receive the message
encoded_values = encode_received_message(sock.recv(8192))
print("Version: ", encoded_values[-1])
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment