Respect BtcWireProto changes, make another step towards tests
This commit is contained in:
@@ -7,13 +7,6 @@ module EventMachine
|
|||||||
# Implements the TCP protocol that Bitcoin peers speak to each other. This
|
# Implements the TCP protocol that Bitcoin peers speak to each other. This
|
||||||
# module is mixed into both incoming and outgoing connections.
|
# module is mixed into both incoming and outgoing connections.
|
||||||
#
|
#
|
||||||
#
|
|
||||||
#
|
|
||||||
# Here is a list of states:
|
|
||||||
# send_ver, recv_ver, verify_ver
|
|
||||||
# send_verack, recv_verack
|
|
||||||
# wait, finish
|
|
||||||
#
|
|
||||||
# Documentation is here: https://en.bitcoin.it/wiki/Network
|
# Documentation is here: https://en.bitcoin.it/wiki/Network
|
||||||
#
|
#
|
||||||
# @author Nick Thomas <nick@lupine.me.uk>
|
# @author Nick Thomas <nick@lupine.me.uk>
|
||||||
@@ -28,10 +21,6 @@ module EventMachine
|
|||||||
# The actor for this peer
|
# The actor for this peer
|
||||||
attr_reader :actor
|
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.
|
# The list of methods a valid actor will respond to.
|
||||||
ACTOR_METHODS = [
|
ACTOR_METHODS = [
|
||||||
:network_name, # Returns a symbol telling us which network to be on
|
:network_name, # Returns a symbol telling us which network to be on
|
||||||
@@ -39,33 +28,43 @@ module EventMachine
|
|||||||
:sub_version, # String specifying custom version string
|
:sub_version, # String specifying custom version string
|
||||||
:current_height, # Number of the newest block known to the actor
|
:current_height, # Number of the newest block known to the actor
|
||||||
:log, # log(:level, message) - self-evident
|
:log, # log(:level, message) - self-evident
|
||||||
|
:node_nonce, # 32-bit number lets us identify streams to self
|
||||||
:connection=, # Called with +self+ to allow actor interaction
|
:connection=, # Called with +self+ to allow actor interaction
|
||||||
:ready! # Called when the connection is ready to be used
|
:ready! # Called when the connection is ready to be used
|
||||||
]
|
]
|
||||||
|
|
||||||
# receive_version and receive_verack implementations differ in client &
|
# receive_version and receive_verack implementations differ in client &
|
||||||
# server, so are implemented there.
|
# server, so are implemented there.
|
||||||
|
|
||||||
|
# Simple wrapper around +send_data+ that logs usage
|
||||||
|
# @param[BTC::Message] packet Packet to send
|
||||||
|
def send_packet(packet)
|
||||||
|
data = packet.to_binary_s
|
||||||
|
log(:info, "Sending #{packet.cmd_sym} message (#{data.length}b)")
|
||||||
|
log(:debug, data.inspect)
|
||||||
|
log(:debug, packet.inspect)
|
||||||
|
send_data(data)
|
||||||
|
end
|
||||||
|
|
||||||
# Send a 'version' message to the peer.
|
# Send a 'version' message to the peer.
|
||||||
def send_version
|
def send_version
|
||||||
log(:info, "Sending version message")
|
|
||||||
|
|
||||||
m = build_message(:version, {
|
m = build_message(:version, {
|
||||||
|
:version => BTC::CURRENT_VERSION(actor.network_name),
|
||||||
:services => {:node_network => 1},
|
:services => {:node_network => 1},
|
||||||
:timestamp => actor.current_time.to_i,
|
:timestamp => actor.current_time.to_i,
|
||||||
:addr_me => my_netaddr,
|
:addr_me => my_netaddr,
|
||||||
:addr_you => peer_netaddr,
|
:addr_you => peer_netaddr,
|
||||||
:nonce => connection_nonce,
|
:nonce => actor.node_nonce,
|
||||||
:sub_version_num => (actor.sub_version || "em-bitcoin"),
|
:sub_version_num => (actor.sub_version || "em-bitcoin"),
|
||||||
:start_height => actor.current_height
|
:start_height => actor.current_height
|
||||||
})
|
})
|
||||||
send_data(m.to_binary_s)
|
send_packet(m)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Send a 'verack' message to the peer
|
# Send a 'verack' message to the peer
|
||||||
def send_verack
|
def send_verack
|
||||||
log(:info, "Sending verack message")
|
log(:info, "Sending verack message")
|
||||||
send_data(build_message(:verack).to_binary_s)
|
send_packet(build_message(:verack))
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
@@ -75,7 +74,8 @@ module EventMachine
|
|||||||
def build_message(command, payload_opts = {}, header_opts = {})
|
def build_message(command, payload_opts = {}, header_opts = {})
|
||||||
header_opts[:command] = command.to_s
|
header_opts[:command] = command.to_s
|
||||||
header_opts[:magic] ||= BTC::NETWORKS[actor.network_name]
|
header_opts[:magic] ||= BTC::NETWORKS[actor.network_name]
|
||||||
m = BTC::Message.new(:header => header_opts, :payload => payload_opts)
|
header_opts[:payload] = payload_opts
|
||||||
|
m = BTC::Message.new(header_opts)
|
||||||
end
|
end
|
||||||
|
|
||||||
def log(level, data)
|
def log(level, data)
|
||||||
@@ -85,7 +85,6 @@ module EventMachine
|
|||||||
def init_state!
|
def init_state!
|
||||||
@data = ""
|
@data = ""
|
||||||
@ready = nil
|
@ready = nil
|
||||||
@connection_nonce = rand(2**32)
|
|
||||||
actor.connection = self # Tell the actor about the connection
|
actor.connection = self # Tell the actor about the connection
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -122,7 +121,10 @@ module EventMachine
|
|||||||
while !finished
|
while !finished
|
||||||
begin
|
begin
|
||||||
packet = BTC::Message.read(@data)
|
packet = BTC::Message.read(@data)
|
||||||
@data.slice!(0, packet.num_bytes - 1) # Remove data from buffer
|
used = @data.slice!(0, packet.num_bytes) # Remove data from buf
|
||||||
|
log(:info, "Read #{packet.cmd_sym} packet (#{used.length}b)")
|
||||||
|
log(:debug, used.inspect)
|
||||||
|
log(:debug, packet.inspect)
|
||||||
if packet.cmd_sym
|
if packet.cmd_sym
|
||||||
m = "receive_#{packet.cmd_sym}"
|
m = "receive_#{packet.cmd_sym}"
|
||||||
if self.respond_to?(m)
|
if self.respond_to?(m)
|
||||||
@@ -133,13 +135,15 @@ module EventMachine
|
|||||||
end
|
end
|
||||||
else
|
else
|
||||||
log(:warn, "Received packet with no command, discarding it")
|
log(:warn, "Received packet with no command, discarding it")
|
||||||
log(:debug, packet)
|
|
||||||
end
|
end
|
||||||
|
rescue IOError # Not enough data
|
||||||
|
finished = true
|
||||||
rescue EOFError
|
rescue EOFError
|
||||||
finished = true
|
finished = true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
log(:debug, "Leaving receive_data with #{@data.length} bytes in buffer")
|
log(:debug, "Leaving receive_data with #{@data.length} bytes in buffer")
|
||||||
|
log(:debug, @data.inspect) if @data.length > 0
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks whether we can communicate sensibly with a peer of a particular
|
# Checks whether we can communicate sensibly with a peer of a particular
|
||||||
@@ -240,6 +244,10 @@ module EventMachine
|
|||||||
class BitcoinServer < EM::Connection
|
class BitcoinServer < EM::Connection
|
||||||
include BitcoinPeer
|
include BitcoinPeer
|
||||||
|
|
||||||
|
def port
|
||||||
|
my_netaddr.port
|
||||||
|
end
|
||||||
|
|
||||||
# @param[Object] actor See the BitcoinPeer#valid_actor? method
|
# @param[Object] actor See the BitcoinPeer#valid_actor? method
|
||||||
def initialize(actor)
|
def initialize(actor)
|
||||||
@actor = actor
|
@actor = actor
|
||||||
|
@@ -5,15 +5,33 @@ require 'em-bitcoin'
|
|||||||
include ::EM::P
|
include ::EM::P
|
||||||
|
|
||||||
class MockActor
|
class MockActor
|
||||||
attr_accessor :connection
|
attr_accessor :connection, :network_name, :sub_version, :current_height,
|
||||||
|
:node_nonce
|
||||||
|
|
||||||
|
def inspect
|
||||||
|
"#<MockActor: #{name}>"
|
||||||
|
end
|
||||||
|
|
||||||
|
def to_s ; inspect ; end
|
||||||
|
|
||||||
def network_name ; :main ; end
|
def initialize(attributes = {})
|
||||||
def current_time ; Time.now ; end
|
self.network_name = :main # defaults
|
||||||
def sub_version ; nil ; end
|
self.current_height = 0
|
||||||
def current_height ; 0 ; end
|
self.node_nonce = 0
|
||||||
|
|
||||||
|
attributes.each {|k,v| self.send("#{k}=", v) }
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_time=(new_time)
|
||||||
|
@current_time = new_time
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_time
|
||||||
|
@current_time || Time.now
|
||||||
|
end
|
||||||
|
|
||||||
def log(level, msg)
|
def log(level, msg)
|
||||||
STDERR.puts("#{level}: #{msg}") if $DEBUG
|
STDERR.puts([name, level, msg].join(": ")) #if $DEBUG
|
||||||
end
|
end
|
||||||
|
|
||||||
def ready!
|
def ready!
|
||||||
@@ -24,49 +42,57 @@ class MockActor
|
|||||||
@ready == true
|
@ready == true
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
protected
|
||||||
|
|
||||||
class MockSignature
|
attr_accessor :name
|
||||||
attr_reader :lip, :lport, :rip, :rport
|
|
||||||
|
|
||||||
def initialize(lip = "127.0.0.1", lport = 12701, rip="127.0.0.2", rport=12702)
|
|
||||||
@lip, @lport, @rip, @rport = lip, lport, rip, rport
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
module EMPMocks
|
|
||||||
def get_peername
|
|
||||||
Socket::pack_sockaddr_in(@signature.rport, @signature.rip)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_sockname
|
|
||||||
Socket::pack_sockaddr_in(@signature.lport, @signature.lip)
|
|
||||||
end
|
|
||||||
|
|
||||||
def send_data(d)
|
|
||||||
nil # TODO: expectations &c...
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
shared_examples_for BitcoinPeer do
|
shared_examples_for BitcoinPeer do
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
describe BitcoinClient do
|
describe BitcoinClient do
|
||||||
before(:each) do
|
include EM::Spec
|
||||||
@reactor = stub("reactor")
|
|
||||||
@sig = MockSignature.new
|
|
||||||
|
|
||||||
@actor = MockActor.new
|
def host
|
||||||
@client = BitcoinClient.new(@sig, @actor)
|
"127.0.0.1"
|
||||||
@client.extend(EMPMocks)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def port
|
||||||
|
@port || 0
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_server(actor = @server_actor)
|
||||||
|
sig = EM::start_server(host, port, EM::P::BitcoinServer, actor)
|
||||||
|
|
||||||
|
@port = Socket::unpack_sockaddr_in(EM::get_sockname(sig))[0]
|
||||||
|
@server
|
||||||
|
end
|
||||||
|
|
||||||
|
def start_client(actor = @client_actor)
|
||||||
|
@client = EM::connect(host, port, EM::P::BitcoinClient, actor)
|
||||||
|
end
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@client_actor = MockActor.new(:name => "client")
|
||||||
|
@server_actor = MockActor.new(:name => "server")
|
||||||
|
done
|
||||||
|
end
|
||||||
|
|
||||||
describe "connection setup" do
|
describe "connection setup" do
|
||||||
context "successful" do
|
context "successful" do
|
||||||
it "should accept a valid actor" do
|
it "should connect to a Bitcoin server" do
|
||||||
@client.actor.should == @actor
|
start_server
|
||||||
end
|
start_client
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
it "should send a version first, accept a verack + version, then send a verack"
|
# EM::add_periodic_timer(2) do
|
||||||
|
# @client_actor.should_receive(:ready!).and_return(true)
|
||||||
|
# @client_actor.should_receive(:connection=).with(@client).and_return(true)
|
||||||
|
# done
|
||||||
|
# end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@@ -1,4 +1,5 @@
|
|||||||
require 'rspec'
|
require 'rspec'
|
||||||
|
require 'em-spec/rspec'
|
||||||
|
|
||||||
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
||||||
|
|
||||||
@@ -8,3 +9,4 @@ def binary(str_ary)
|
|||||||
d.to_i(16).chr
|
d.to_i(16).chr
|
||||||
end.join("")
|
end.join("")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user