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:
20
README
Normal file
20
README
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
For now: See doc/AIMS
|
||||||
|
|
||||||
|
Currently, nothing is interesting.
|
||||||
|
|
||||||
|
Here's a general idea of the order in which i'm going to be implementing things.
|
||||||
|
Note that nothing is set in stone here. Also, parallelism is to be expected as
|
||||||
|
I get bored of one thing and so start on another.
|
||||||
|
|
||||||
|
* Basic em-bitcoin functionality, dragging along btc_wire_proto as I go
|
||||||
|
- Build the full block chain from scratch
|
||||||
|
- Be able to send transactions to others
|
||||||
|
- Wallets
|
||||||
|
* HTTP interface for wallets, users
|
||||||
|
- Client-side transaction signing
|
||||||
|
* Bitcoin RPC interface for interop with existing management tools
|
||||||
|
* Simple market for registered users
|
||||||
|
- Multiple currency support (GBP to start, then euro, then dollar)
|
||||||
|
- Integration for various put-cash-into-the-market services (wire transfer, to start)
|
||||||
|
|
||||||
|
Once those bits are in, polish them 'till they gleam. Then start on the other bits.
|
@@ -23,23 +23,70 @@ module EventMachine
|
|||||||
end
|
end
|
||||||
PE = ProtocolError
|
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.
|
# 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
|
||||||
|
: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
|
:log, # log(:level, message) - self-evident
|
||||||
: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
|
||||||
]
|
]
|
||||||
|
|
||||||
def log(level, data)
|
# receive_version and receive_verack implementations differ in client &
|
||||||
@actor.log(level, data)
|
# 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
|
end
|
||||||
|
|
||||||
protected
|
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!
|
def init_state!
|
||||||
@data = ""
|
@data = ""
|
||||||
@ready = nil
|
@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
|
end
|
||||||
|
|
||||||
# Checks the current actor object to see if it is valid or not.
|
# Checks the current actor object to see if it is valid or not.
|
||||||
@@ -59,7 +106,7 @@ module EventMachine
|
|||||||
|
|
||||||
ACTOR_METHODS.each do |m|
|
ACTOR_METHODS.each do |m|
|
||||||
return [false, "Actor doesn't implement all #{m}"] unless
|
return [false, "Actor doesn't implement all #{m}"] unless
|
||||||
@actor.respond_to?(m)
|
actor.respond_to?(m)
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
true
|
||||||
@@ -74,7 +121,7 @@ module EventMachine
|
|||||||
finished = false
|
finished = false
|
||||||
while !finished
|
while !finished
|
||||||
begin
|
begin
|
||||||
packet = BtcWireProto::Message.read(@data)
|
packet = BTC::Message.read(@data)
|
||||||
@data.slice!(0, packet.num_bytes - 1) # Remove data from buffer
|
@data.slice!(0, packet.num_bytes - 1) # Remove data from buffer
|
||||||
if packet.cmd_sym
|
if packet.cmd_sym
|
||||||
m = "receive_#{packet.cmd_sym}"
|
m = "receive_#{packet.cmd_sym}"
|
||||||
@@ -95,18 +142,35 @@ module EventMachine
|
|||||||
log(:debug, "Leaving receive_data with #{@data.length} bytes in buffer")
|
log(:debug, "Leaving receive_data with #{@data.length} bytes in buffer")
|
||||||
end
|
end
|
||||||
|
|
||||||
# receive_version and receive_verack implementations differ in client &
|
# Checks whether we can communicate sensibly with a peer of a particular
|
||||||
# server, so are implemented there.
|
# 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
|
||||||
# Send a 'version' message to the peer.
|
# @return[Boolean] can we communicate with a peer advertising the version?
|
||||||
def send_version
|
def can_talk_version?(their_version)
|
||||||
# TODO
|
their_version >= 31402
|
||||||
end
|
end
|
||||||
|
|
||||||
# Send a 'verack' message to the peer
|
# @return[BTC::NetAddr] wire represenation of our own network address
|
||||||
def send_verack
|
def my_netaddr
|
||||||
# TODO
|
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
|
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
|
end
|
||||||
|
|
||||||
# EventMachine protocol class that handles an *outgoing* connection to
|
# EventMachine protocol class that handles an *outgoing* connection to
|
||||||
@@ -122,7 +186,6 @@ module EventMachine
|
|||||||
|
|
||||||
# @param[Object] actor See the BitcoinPeer#valid_actor?
|
# @param[Object] actor See the BitcoinPeer#valid_actor?
|
||||||
def initialize(actor)
|
def initialize(actor)
|
||||||
super
|
|
||||||
@actor = actor
|
@actor = actor
|
||||||
result, msg = valid_actor?
|
result, msg = valid_actor?
|
||||||
raise ArgumentError.new("Invalid actor: #{msg}") unless result
|
raise ArgumentError.new("Invalid actor: #{msg}") unless result
|
||||||
@@ -154,10 +217,16 @@ module EventMachine
|
|||||||
def receive_version(p)
|
def receive_version(p)
|
||||||
raise PE.new("Received version inappropriately") unless
|
raise PE.new("Received version inappropriately") unless
|
||||||
@ready == :version_sent
|
@ready == :version_sent
|
||||||
log(:info, "Peer tells us its version is #{p.payload.version}")
|
|
||||||
send_verack
|
if can_talk_version?(p.payload.version)
|
||||||
@ready = true
|
log(:info, "Valid peer, version is #{p.payload.version}")
|
||||||
@actor.ready!
|
send_verack
|
||||||
|
@ready = true
|
||||||
|
actor.ready!
|
||||||
|
else
|
||||||
|
log(:info, "Bad peer, can't talk version #{p.payload.version}")
|
||||||
|
unbind
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -173,7 +242,6 @@ module EventMachine
|
|||||||
|
|
||||||
# @param[Object] actor See the BitcoinPeer#valid_actor? method
|
# @param[Object] actor See the BitcoinPeer#valid_actor? method
|
||||||
def initialize(actor)
|
def initialize(actor)
|
||||||
super
|
|
||||||
@actor = actor
|
@actor = actor
|
||||||
result, msg = valid_actor?
|
result, msg = valid_actor?
|
||||||
raise ArgumentError.new("Invalid actor: #{msg}") unless result
|
raise ArgumentError.new("Invalid actor: #{msg}") unless result
|
||||||
@@ -181,15 +249,30 @@ module EventMachine
|
|||||||
init_state!
|
init_state!
|
||||||
end
|
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)
|
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
|
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)
|
def receive_verack(p)
|
||||||
# TODO
|
raise PE.new("Received verack inappropriately") unless
|
||||||
|
@ready == :version_sent
|
||||||
|
@ready = true
|
||||||
|
actor.ready!
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
78
spec/em-bitcoin_spec.rb
Normal file
78
spec/em-bitcoin_spec.rb
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
require 'em-bitcoin'
|
||||||
|
|
||||||
|
|
||||||
|
include ::EM::P
|
||||||
|
|
||||||
|
class MockActor
|
||||||
|
attr_accessor :connection
|
||||||
|
|
||||||
|
def network_name ; :main ; end
|
||||||
|
def current_time ; Time.now ; end
|
||||||
|
def sub_version ; nil ; end
|
||||||
|
def current_height ; 0 ; end
|
||||||
|
|
||||||
|
def log(level, msg)
|
||||||
|
STDERR.puts("#{level}: #{msg}") if $DEBUG
|
||||||
|
end
|
||||||
|
|
||||||
|
def ready!
|
||||||
|
@ready = true
|
||||||
|
end
|
||||||
|
|
||||||
|
def ready?
|
||||||
|
@ready == true
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
class MockSignature
|
||||||
|
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
|
||||||
|
|
||||||
|
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
|
||||||
|
end
|
||||||
|
|
||||||
|
describe BitcoinClient do
|
||||||
|
before(:each) do
|
||||||
|
@reactor = stub("reactor")
|
||||||
|
@sig = MockSignature.new
|
||||||
|
|
||||||
|
@actor = MockActor.new
|
||||||
|
@client = BitcoinClient.new(@sig, @actor)
|
||||||
|
@client.extend(EMPMocks)
|
||||||
|
end
|
||||||
|
describe "connection setup" do
|
||||||
|
context "successful" do
|
||||||
|
it "should accept a valid actor" do
|
||||||
|
@client.actor.should == @actor
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should send a version first, accept a verack + version, then send a verack"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe BitcoinServer do
|
||||||
|
end
|
||||||
|
|
Reference in New Issue
Block a user