Files
sharp-coin/lib/em-bitcoin.rb

155 lines
4.4 KiB
Ruby
Raw Normal View History

require 'eventmachine'
2011-05-19 23:13:34 +01:00
require 'stringio'
require 'btc_wire_proto'
module EventMachine
module Protocols
# Implements the TCP protocol that Bitcoin peers speak to each other. This
# module is mixed into both incoming and outgoing connections.
#
# We implement the protocol as a simple(ish!) state machine. When we want
# something doing, we call state(sym, data) to append that to the
# list of things to do. If something is urgent, we can call state! to
# put it at the beginning of the list.
#
# 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
#
# @author Nick Thomas <nick@lupine.me.uk>
module BitcoinPeer
protected
# Sets up the variables required to manage the state machine. Should be
# called before you try to push a state - in post_init, say.
def init_state!
2011-05-19 23:13:34 +01:00
@stream = StringIO.new("")
@state_m = Mutex.new # Synchronize around @states and @working
@state_m.synchronize do
@states = []
2011-05-19 23:13:34 +01:00
@current_state = nil
end
end
# Checks the current configuration object to see if we have a valid config
# or not.
# @return[Array[true|false, msg]] Whether the config is valid, and an
# optional message specifying why it's invalid, if it is.
def valid_config?
[false, "configuration check not implemented yet"]
end
2011-05-19 23:13:34 +01:00
# Grabs the state that we're currently working on.
# @return[Array[Symbol,Object]] state symbol + data
def get_state
@state_m.synchronize do
@current_state ||= @states.shift
@current_state
end
end
# Call this when we've completed the actions required by the current state
def finished_state
@state_m.synchronize { @current_state = nil }
end
# Push a state to the end of the state queue.
def state(new_state, data = nil)
@state_m.synchronize { @states.push(new_state, data) }
end
# Add a state to the start of the state queue.
def state!(new_state, data = nil)
@state_m.synchronize { @states.unshift(new_state, data) }
end
2011-05-19 23:13:34 +01:00
# Do some work on the state machine
def state_tick
state, data = get_state
return unless state
if respond_to?(state)
send(state, data)
else
raise NotImplementedError.new("Unknown state: #{state} with #{data}")
end
end
# Receiving data mostly drives the state machine. Where something wants
# to send data, it gets queued straight onto the I/O/
def receive_data(io)
end
# State machine behaviours now.
# Send a 'version' message to the peer.
# Next
def send_ver
end
end
# EventMachine protocol class that handles an *outgoing* connection to
# another bitcoin peer. Common functionality (p2p!) is held in BitcoinPeer.
#
# State machine flow:
# send_ver, recv_verack
# recv_ver, verify_ver, send_verack
#
# @author Nick Thomas <nick@lupine.me.uk>
class BitcoinClient < EM::Connection
include BitcoinPeer
# @param[Object] config See the BitcoinPeer#valid_config?
def initialize(config)
super
@config = config
result, msg = valid_config?
raise ArgumentError.new("Invalid configuration: #{msg}") unless result
init_state!
end
def post_init
state(:send_ver)
end
2011-05-19 23:13:34 +01:00
def connection_completed
advance_state
end
end
# EventMachine protocol class that handles an *incoming* connection from
# another bitcoin peer. Common functionality (p2p!) is held in BitcoinPeer
#
# State machine flow:
# recv_ver, verify_ver, send_verack
# send_ver, recv_verack
#
# @author Nick Thomas <nick@lupine.me.uk>
class BitcoinServer < EM::Connection
include BitcoinPeer
# @param[Object] config See the BitcoinPeer#valid_config?
def initialize(config)
super
@config = config
result, msg = valid_config?
raise ArgumentError.new("Invalid configuration: #{msg}") unless result
init_state!
end
def post_init
state(:recv_ver)
end
end
end
end