In theory, version exchange for client and server works now.

Test rig isn't there yet, so untested at the moment.

Added a simple README
This commit is contained in:
Nick Thomas
2011-06-10 23:14:39 +01:00
parent d90b5585ef
commit eb764e4acb
3 changed files with 206 additions and 25 deletions

View File

@@ -23,23 +23,70 @@ module EventMachine
end
PE = ProtocolError
BTC = ::BtcWireProto
# The actor for this peer
attr_reader :actor
# Randomly-generated 32-bit number allowing us to guarantee we're not
# connecting to ourselves.
attr_reader :connection_nonce
# The list of methods a valid actor will respond to.
ACTOR_METHODS = [
:network_name, # Returns a symbol telling us which network to be on
:current_time, # Time instance specifying the current time
:sub_version, # String specifying custom version string
:current_height, # Number of the newest block known to the actor
:log, # log(:level, message) - self-evident
:connection=, # Called with +self+ to allow actor interaction
:ready! # Called when the connection is ready to be used
]
def log(level, data)
@actor.log(level, data)
# receive_version and receive_verack implementations differ in client &
# server, so are implemented there.
# Send a 'version' message to the peer.
def send_version
log(:info, "Sending version message")
m = build_message(:version, {
:services => {:node_network => 1},
:timestamp => actor.current_time.to_i,
:addr_me => my_netaddr,
:addr_you => peer_netaddr,
:nonce => connection_nonce,
:sub_version_num => (actor.sub_version || "em-bitcoin"),
:start_height => actor.current_height
})
send_data(m.to_binary_s)
end
# Send a 'verack' message to the peer
def send_verack
log(:info, "Sending verack message")
send_data(build_message(:verack).to_binary_s)
end
protected
# Puts together a message from the details given. Common fields, like
# magic and checksums, are filled in.
def build_message(command, payload_opts = {}, header_opts = {})
header_opts[:command] = command.to_s
header_opts[:magic] ||= BTC::NETWORKS[actor.network_name]
m = BTC::Message.new(:header => header_opts, :payload => payload_opts)
end
def log(level, data)
actor.log(level, data)
end
def init_state!
@data = ""
@ready = nil
@actor.connection = self # Tell the actor about the connection
@connection_nonce = rand(2**32)
actor.connection = self # Tell the actor about the connection
end
# Checks the current actor object to see if it is valid or not.
@@ -59,7 +106,7 @@ module EventMachine
ACTOR_METHODS.each do |m|
return [false, "Actor doesn't implement all #{m}"] unless
@actor.respond_to?(m)
actor.respond_to?(m)
end
true
@@ -74,7 +121,7 @@ module EventMachine
finished = false
while !finished
begin
packet = BtcWireProto::Message.read(@data)
packet = BTC::Message.read(@data)
@data.slice!(0, packet.num_bytes - 1) # Remove data from buffer
if packet.cmd_sym
m = "receive_#{packet.cmd_sym}"
@@ -94,19 +141,36 @@ module EventMachine
end
log(:debug, "Leaving receive_data with #{@data.length} bytes in buffer")
end
# receive_version and receive_verack implementations differ in client &
# server, so are implemented there.
# Send a 'version' message to the peer.
def send_version
# TODO
# Checks whether we can communicate sensibly with a peer of a particular
# version. At the moment, we're fairly liberal about who we try to talk to
# @param[Fixnum] their_version Version of the peer under question
# @return[Boolean] can we communicate with a peer advertising the version?
def can_talk_version?(their_version)
their_version >= 31402
end
# Send a 'verack' message to the peer
def send_verack
# TODO
# @return[BTC::NetAddr] wire represenation of our own network address
def my_netaddr
sockaddr = get_sockname
raise PE.new("Unable to determine own NetAddr") unless sockaddr
port, ip = Socket::unpack_sockaddr_in(sockaddr)
BTC::NetAddr.new(
:services => {:node_network => 1}, :ip => ip, :port => port
)
end
# FIXME: what does node_network mean here?
# @return[BTC::NetAddr] wire representation of our peer's network address
def peer_netaddr
sockaddr = get_peername
raise PE.new("Unable to determine peer NetAddr") unless sockaddr
port, ip = Socket::unpack_sockaddr_in(sockaddr)
BTC::NetAddr.new(
:services => {:node_network => 1}, :ip => ip, :port => port
)
end
end
# EventMachine protocol class that handles an *outgoing* connection to
@@ -122,7 +186,6 @@ module EventMachine
# @param[Object] actor See the BitcoinPeer#valid_actor?
def initialize(actor)
super
@actor = actor
result, msg = valid_actor?
raise ArgumentError.new("Invalid actor: #{msg}") unless result
@@ -154,10 +217,16 @@ module EventMachine
def receive_version(p)
raise PE.new("Received version inappropriately") unless
@ready == :version_sent
log(:info, "Peer tells us its version is #{p.payload.version}")
send_verack
@ready = true
@actor.ready!
if can_talk_version?(p.payload.version)
log(:info, "Valid peer, version is #{p.payload.version}")
send_verack
@ready = true
actor.ready!
else
log(:info, "Bad peer, can't talk version #{p.payload.version}")
unbind
end
end
end
@@ -173,23 +242,37 @@ module EventMachine
# @param[Object] actor See the BitcoinPeer#valid_actor? method
def initialize(actor)
super
@actor = actor
result, msg = valid_actor?
raise ArgumentError.new("Invalid actor: #{msg}") unless result
init_state!
end
# We should receive this as the very first message on the wire
# We should receive this as the very first message on the wire.
# If we're prepared ot talk to a client with this version, we send a
# verack message followed by our own version and wait for a verack to be
# received.
def receive_version(p)
# TODO
raise PE.new("Received version inappropriately") unless @ready.nil?
if can_talk_version?(p.payload.version)
log(:info, "Peer version #{p.payload.version}")
send_verack
send_version
@ready = :version_sent
else
log(:info, "Bad peer - can't talk version #{p.payload.version}")
unbind
end
end
# We should only receive this after sending our own version
# We should only receive this after sending our own version. If the client
# doesn't like our version, it'll close the connection.
def receive_verack(p)
# TODO
raise PE.new("Received verack inappropriately") unless
@ready == :version_sent
@ready = true
actor.ready!
end
end