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:
@@ -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
|
||||
|
Reference in New Issue
Block a user