One more in-place commit. Moved BtcWireProto to its own file, added some features
This commit is contained in:
391
lib/btc_wire_proto.rb
Normal file
391
lib/btc_wire_proto.rb
Normal file
@@ -0,0 +1,391 @@
|
||||
require 'bindata'
|
||||
|
||||
# Implementation of the BitCoin wire protocol, written using bindata.
|
||||
# Reference: https://en.bitcoin.it/wiki/Protocol_specification
|
||||
#
|
||||
# @author Nick Thomas <nick@lupine.me.uk
|
||||
module BtcWireProto
|
||||
CURRENT_VERSION = 32100
|
||||
# Comprehensive list of known networks. The hex values are what you see in
|
||||
# MessageHdr#magic and the symbols are their known friendly names.
|
||||
NETWORKS = {
|
||||
:main => 0xF9BEB4D9,
|
||||
0xF9BEB4D9 => :main,
|
||||
|
||||
:testnet => 0xFABFB5DA,
|
||||
0xFABFB5DA => :testnet
|
||||
}
|
||||
|
||||
# Comprehensive list of known inventory vector types.
|
||||
INV_VEC_TYPES = {
|
||||
0 => :error,
|
||||
:error => 0,
|
||||
|
||||
1 => :msg_tx,
|
||||
:msg_tx => 1,
|
||||
|
||||
2 => :msg_block,
|
||||
:msg_block => 2
|
||||
}
|
||||
|
||||
# Used in Reply messages
|
||||
REPLY_CODES = {
|
||||
0 => :success,
|
||||
:success => 0,
|
||||
|
||||
1 => :wallet_error,
|
||||
:wallet_error => 1,
|
||||
|
||||
2 => :denied,
|
||||
:denied => 2
|
||||
}
|
||||
|
||||
# Only Alert messages signed by this key are valid.
|
||||
# This is an ECDSA public key (FIXME: in what format?)
|
||||
ALERT_PUBKEY = "04fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b" +
|
||||
"28a49e0ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7" +
|
||||
"c5ecbcd68284"
|
||||
|
||||
## Components of payloads ##
|
||||
end
|
||||
|
||||
# Bitmask advertising various capabilities of the node.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::ServicesMask < BinData::Record
|
||||
endian :little
|
||||
|
||||
bit62 :undefined_top
|
||||
bit1 :node_network
|
||||
bit1 :undefined_bottom
|
||||
end
|
||||
|
||||
# Structure holding an IP address and port in a slightly unusual format.
|
||||
# This one is big-endian - everything else is little-endian.
|
||||
#
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::NetAddr < BinData::Record
|
||||
endian :big
|
||||
services_mask :services
|
||||
uint128 :ip # IPv6 address. IPv4 addresses given as IPv6-mapped IPv4
|
||||
uint16 :port
|
||||
end
|
||||
|
||||
# Like a NetAddr but with a timestamp to boot.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::TimestampedNetAddr < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :timestamp
|
||||
net_addr :net_addr
|
||||
end
|
||||
|
||||
# Variable-length integer. This is slightly scary.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::VarInt < BinData::BasePrimitive
|
||||
|
||||
def value_to_binary_string(val)
|
||||
val = val.to_i
|
||||
|
||||
if val < -0xffffffffffffffff # unrepresentable
|
||||
""
|
||||
elsif val < 0 # 64-bit negative integer
|
||||
top_32 = (val & 0xffffffff00000000) >> 32
|
||||
btm_32 = val & 0x00000000ffffffff
|
||||
[0xff, top_32, btm_32].pack("CVV")
|
||||
elsif val <= 0xfc # 8-bit (almost) positive integer
|
||||
[val].pack("C")
|
||||
elsif val <= 0xffff # 16-bit positive integer
|
||||
[0xfd, val].pack("Cv")
|
||||
elsif val <= 0xffffffff # 32-bit positive integer
|
||||
[0xfe, val].pack("CV")
|
||||
else # We can't represent this, whatever it is
|
||||
""
|
||||
end
|
||||
end
|
||||
BinData::RegisteredClasses.register("var_int", BtcWireProto::VarInt)
|
||||
|
||||
def read_and_return_value(io)
|
||||
return nil if io.length < 1
|
||||
|
||||
magic = read_uint8(io)
|
||||
if magic <= 0xfc # 8-bit (almost) positive integer
|
||||
magic
|
||||
elsif magic == 0xfd # 16-bit positive integer
|
||||
read_uint16(io)
|
||||
elsif magic == 0xfe # 32-bit positive integer
|
||||
read_uint32(io)
|
||||
elsif magic == 0xff # 64-bit negative integer
|
||||
-(read_uint64(io))
|
||||
end
|
||||
end
|
||||
|
||||
def sensible_default
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
# Variable-length pascal string with a variable-length int specifying the
|
||||
# length. I kid you not.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::VarStr < BinData::Primitive
|
||||
endian :little
|
||||
|
||||
var_int :len, :value => lambda { data.length }
|
||||
string :data, :read_length => :len
|
||||
|
||||
def get ; self.data ; end
|
||||
def set(v) ; self.data = v ; end
|
||||
end
|
||||
|
||||
class BtcWireProto::InventoryVector < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :type # For values, see INV_VEC_TYPES
|
||||
string :hash, :length => 32
|
||||
end
|
||||
|
||||
# Simple class wrapping raw SHA256 data. Might have utility methods later.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::Sha256 < BinData::Record
|
||||
uint256 :data # Raw SHA256 data
|
||||
end
|
||||
BtcWireProto::SHA256 = BtcWireProto::Sha256
|
||||
|
||||
class BtcWireProto::TransactionIn < BinData::Record
|
||||
struct :previous_output do
|
||||
sha256 :hash
|
||||
uint32 :index
|
||||
end
|
||||
var_str :signature_script # Script for confirming transaction authorisation
|
||||
uint32 :sequence # Version of this record.
|
||||
end
|
||||
|
||||
class BtcWireProto::TranactionOut < BinData::Record
|
||||
uint64 :value
|
||||
var_str :pk_script # Script containing conditions to claim to transaction
|
||||
end
|
||||
|
||||
# Header for a block.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::BlockHdr < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :version
|
||||
sha256 :prev_block
|
||||
sha256 :merkle_root
|
||||
uint32 :timestamp
|
||||
uint32 :difficulty
|
||||
uint32 :nonce
|
||||
var_int :txn_count
|
||||
end
|
||||
|
||||
## Payloads ##
|
||||
|
||||
# Payload for a version message
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::Version < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :version
|
||||
services_mask :services
|
||||
uint64 :timestamp
|
||||
net_addr :addr_me
|
||||
net_addr :addr_you, :only_if => lambda { version >= 106 }
|
||||
uint64 :nonce, :only_if => lambda { version >= 106 }
|
||||
var_str :sub_version, :only_if => lambda { version >= 106 }
|
||||
uint32 :start_height, :only_if => lambda { version >= 209 }
|
||||
end
|
||||
|
||||
# Payload for an addr message in versions earlier than 31402. These are
|
||||
# used to get a list of peers to interact with.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::AddrPre31402 < BinData::Record
|
||||
endian :little
|
||||
|
||||
var_int :count
|
||||
array :addrs, :type => :net_addr,
|
||||
:read_until => lambda { index == count - 1 }
|
||||
|
||||
end
|
||||
|
||||
# Payload for an addr message in versions later than 31402. A timestamp was
|
||||
# added to the list of addresses, but otherwise it's the same as AddrPre31402
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::AddrFrom31402 < BinData::Record
|
||||
endian :little
|
||||
|
||||
var_int :count
|
||||
array :timestamped_addrs, :type => :timestamped_net_addr,
|
||||
:read_until => lambda { index == count - 1 }
|
||||
end
|
||||
|
||||
# Payload for a getdata or inv message. This lets the peer advertise the
|
||||
# various objects it has knowledge of.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::Inventory < BinData::Record
|
||||
endian :little
|
||||
|
||||
var_int :count
|
||||
array :items, :type => :inventory_vector,
|
||||
:read_until => lambda { index == count - 1 }
|
||||
end
|
||||
|
||||
# Payload for a getblocks or getheaders message. Specifies a set of blocks
|
||||
# that the sender wants details of.
|
||||
# @author Nick thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::BlockSpec < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :version
|
||||
var_int :start_count
|
||||
array :hash_start, :type => :sha256,
|
||||
:read_until => lambda { index == start_count - 1 }
|
||||
# Hash of the last desired block, or 0 to get as many as possible (max: 500)
|
||||
sha256 :hash_stop, :length => 32
|
||||
end
|
||||
|
||||
# A transaction. This contains a number of transactions 'in', and 'out'.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::Transaction < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :version
|
||||
var_int :tx_in_count
|
||||
array :transactions_in, :type => :transaction_in,
|
||||
:read_until => lambda { index == tx_in_count - 1 }
|
||||
var_int :tx_out_count
|
||||
array :transactions_out, :type => :transaction_out,
|
||||
:read_until => lambda { index == tx_out_count - 1 }
|
||||
uint32 :lock_time
|
||||
end
|
||||
|
||||
# Details about a particular block. Returned in response to a block request
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::Block < BinData::Record
|
||||
endian :little
|
||||
|
||||
block_hdr :header
|
||||
array :txns, :type => :transaction,
|
||||
:read_until => lambda { index == header.txn_count - 1 }
|
||||
end
|
||||
|
||||
# Headers payloads are returned in response to a getheaders request.
|
||||
# Limit of 2,000 entries per message.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::Headers < BinData::Record
|
||||
endian :little
|
||||
|
||||
var_int :count
|
||||
array :block_hdrs, :type => :block_hdr,
|
||||
:read_until => lambda { index == count - 1 }
|
||||
|
||||
end
|
||||
|
||||
# For now, we don't support CheckOrder requests at all. Protocol documentation
|
||||
# is lacking! FIXME
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::CheckOrder < BinData::Record
|
||||
endian :little
|
||||
end
|
||||
|
||||
# We don't support SubmitOrder requests either. Receiving either of these will
|
||||
# actually break the stream, since we don't even know how long they are. FIXME
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::SubmitOrder < BinData::Record
|
||||
endian :little
|
||||
end
|
||||
|
||||
# Used as a response to a CheckOrder request.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::Reply < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :reply # See REPLYCODES for possible values
|
||||
end
|
||||
|
||||
# A message sent using the p2p network. Signed by a key so you can tell who
|
||||
# sent it - if it's signed by a particular key, then we should apparently
|
||||
# show the message to the user and cease operation until further notice. Fun!
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::Alert < BinData::Record
|
||||
endian :little
|
||||
|
||||
var_str :message
|
||||
var_str :signature
|
||||
end
|
||||
|
||||
## Top-level message format ##
|
||||
|
||||
# Found at the start of all Bitcoin messages.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::MessageHdr < BinData::Record
|
||||
endian :little
|
||||
uint32 :magic
|
||||
string :command, :length => 12
|
||||
uint32 :payload_len
|
||||
uint32 :checksum, :onlyif => :has_checksum?
|
||||
|
||||
protected
|
||||
|
||||
# version and verack messages don't have a checksum. The rest do.
|
||||
# @return[Boolean] does this message header have a checksum field or not?
|
||||
def has_checksum?
|
||||
command != "version" && command != "verack"
|
||||
end
|
||||
end
|
||||
|
||||
# Everything on the wire is a Message.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class BtcWireProto::Message < BinData::Record
|
||||
|
||||
# @param[Fixnum,nil] version The protocol version. Setting this affects
|
||||
# the layout of various fields.
|
||||
def initialize(version = nil)
|
||||
@version = version || BtcWireProto::CURRENT_VERSION
|
||||
end
|
||||
|
||||
message_hdr :header
|
||||
|
||||
choice :payload, :selection => :payload_choice do
|
||||
version "version"
|
||||
addr_pre_31402 "addr_pre_31402"
|
||||
addr_from_31402 "addr_from_31402"
|
||||
inventory "inv"
|
||||
inventory "getdata"
|
||||
block_spec "getblocks"
|
||||
block_spec "getheaders"
|
||||
transaction "tx"
|
||||
block "block"
|
||||
headers "headers"
|
||||
checkorder "checkorder"
|
||||
submitorder "submitorder"
|
||||
alert "alert"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Works out what the payload looks like based on the MessageHdr struct
|
||||
# and (potentially) the version
|
||||
def payload_choice
|
||||
return header.command if %w{
|
||||
version inv getdata getblocks getheaders tx block headers alert
|
||||
}.include?(header.command)
|
||||
|
||||
# We can't parse these yet, and so we don't know where in the stream the
|
||||
# next message starts. So all we can do is throw an error
|
||||
raise NotImplementedError.new(
|
||||
"Received unsupported command #{header.command}"
|
||||
) if %w|checkorder submitorder|.include?(header.command)
|
||||
|
||||
# These commands don't have any payloads
|
||||
return nil if %w|verack getaddr ping|.include?(header.command)
|
||||
|
||||
# Payload has two forms, depending on protocol version. Ugh.
|
||||
return (@version < 31402 ? "addr_pre_31402" : "addr_from_31402") if
|
||||
header.command == "addr"
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
#end
|
||||
|
@@ -1,288 +1,5 @@
|
||||
require 'eventmachine'
|
||||
|
||||
|
||||
# TODO: Break this out into its own little library, since it's general-purpose.
|
||||
require 'bindata'
|
||||
# Implementation of the BitCoin wire protocol, written using bindata.
|
||||
# Reference: https://en.bitcoin.it/wiki/Protocol_specification
|
||||
#
|
||||
# @author Nick Thomas <nick@lupine.me.uk
|
||||
module BtcWireProto
|
||||
CURRENT_VERSION = 32100
|
||||
# Comprehensive list of known networks. The hex values are what you see in
|
||||
# MessageHdr#magic and the symbols are their known friendly names.
|
||||
NETWORKS = {
|
||||
:main => 0xF9BEB4D9,
|
||||
0xF9BEB4D9 => :main,
|
||||
|
||||
:testnet => 0xFABFB5DA,
|
||||
0xFABFB5DA => :testnet
|
||||
}
|
||||
|
||||
# Comprehensive list of known inventory vector types.
|
||||
INV_VEC_TYPES = {
|
||||
0 => :error,
|
||||
:error => 0,
|
||||
|
||||
1 => :msg_tx,
|
||||
:msg_tx => 1,
|
||||
|
||||
2 => :msg_block,
|
||||
:msg_block => 2
|
||||
}
|
||||
|
||||
## Components of payloads ##
|
||||
|
||||
# Bitmask advertising various capabilities of the node.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class ServicesMask < BinData::Record
|
||||
endian :little
|
||||
|
||||
bit62 :undefined
|
||||
bit1 :node_network
|
||||
bit1 :undefined
|
||||
end
|
||||
|
||||
# Structure holding an IP address and port in a slightly unusual format.
|
||||
# This one is big-endian - everything else is little-endian.
|
||||
#
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class NetAddr < BinData::Record
|
||||
endian :big
|
||||
services_mask :services
|
||||
uint128 :ip # IPv6 address. IPv4 addresses given as IPv6-mapped IPv4
|
||||
uint16 :port
|
||||
end
|
||||
|
||||
# Like a NetAddr but with a timestamp to boot.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
def TimestampedNetAddr < BinData::Record
|
||||
uint32 :timestamp, :endian => :little
|
||||
net_addr :net_addr
|
||||
end
|
||||
|
||||
# Variable-length integer. This is slightly scary.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class VarInt < BinData::BasePrimitive
|
||||
def value_to_binary_string(val)
|
||||
val = val.to_i
|
||||
|
||||
case val
|
||||
if val < -0xffffffffffffffff # unrepresentable
|
||||
""
|
||||
elsif val < 0 # 64-bit negative integer
|
||||
top_32 = (val & 0xffffffff00000000) >> 32
|
||||
btm_32 = val & 0x00000000ffffffff
|
||||
[0xff, top_32, btm_32].pack("CVV")
|
||||
elsif val <= 0xfc # 8-bit (almost) positive integer
|
||||
[val].pack("C")
|
||||
elsif val <= 0xffff # 16-bit positive integer
|
||||
[0xfd, val].pack("Cv")
|
||||
elsif val <= 0xffffffff # 32-bit positive integer
|
||||
[0xfe, val].pack("CV")
|
||||
else # We can't represent this, whatever it is
|
||||
""
|
||||
end
|
||||
end
|
||||
|
||||
def read_and_return_value(io)
|
||||
return nil if str.size < 1
|
||||
magic = read_uint8(io)
|
||||
if magic <= 0xfc # 8-bit (almost) positive integer
|
||||
magic
|
||||
elsif magic == 0xfd # 16-bit positive integer
|
||||
read_uint16(io)
|
||||
elsif magic == 0xfe # 32-bit positive integer
|
||||
read_uint32(io)
|
||||
elsif magic == 0xff # 64-bit negative integer
|
||||
-(read_uint64(io)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def sensible_default
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
# Variable-length pascal string with a variable-length int specifying the
|
||||
# length. I kid you not.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class VarStr < BinData::Primitive
|
||||
endian :little
|
||||
|
||||
var_int :len, :value => lambda { data.length }
|
||||
string :data, :read_length => :len
|
||||
|
||||
def get ; self.data ; end
|
||||
def set(v) ; self.data = v ; end
|
||||
end
|
||||
|
||||
class InventoryVector < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :type # For values, see INV_VEC_TYPES
|
||||
string :hash, :length => 32
|
||||
end
|
||||
|
||||
# Simple class wrapping raw SHA256 data. Might have utility methods later.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class Sha256 < BinData::Record
|
||||
uint256 :data # Raw SHA256 data
|
||||
end
|
||||
SHA256 = Sha256
|
||||
|
||||
class TransactionIn < BinData::Record
|
||||
struct :previous_output do
|
||||
sha256 :hash
|
||||
uint32 :index
|
||||
end
|
||||
var_str :signature_script # Script for confirming transaction authorisation
|
||||
uint32 :sequence # Version of this record.
|
||||
end
|
||||
|
||||
class TranactionOut < BinData::Record
|
||||
uint64 :value
|
||||
var_str :pk_script # Script containing conditions to claim to transaction
|
||||
end
|
||||
|
||||
|
||||
## Payloads ##
|
||||
|
||||
# Payload for a version message
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class Version < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :version
|
||||
services_mask :services
|
||||
uint64 :timestamp
|
||||
net_addr :addr_me
|
||||
net_addr :addr_you, :only_if => lambda { version >= 106 }
|
||||
uint64 :nonce, :only_if => lambda { version >= 106 }
|
||||
var_str :sub_version, :only_if => lambda { version >= 106 }
|
||||
uint32 :start_height, :only_if => lambda { version >= 209 }
|
||||
end
|
||||
|
||||
# Payload for an addr message in versions earlier than 31402. These are
|
||||
# used to get a list of peers to interact with.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class AddrPre31402 < BinData::Record
|
||||
endian :little
|
||||
|
||||
var_int :count
|
||||
array :addrs, :type => :net_addr,
|
||||
:read_until => lambda { index == count - 1 }
|
||||
|
||||
end
|
||||
|
||||
# Payload for an addr message in versions later than 31402. A timestamp was
|
||||
# added to the list of addresses, but otherwise it's the same as AddrPre31402
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class AddrFrom31402 < BinData::Record
|
||||
endian :little
|
||||
|
||||
var_int :count
|
||||
array :timestamped_addrs, :type => :timestamped_net_addr,
|
||||
:read_until => lambda { index == count - 1 }
|
||||
end
|
||||
|
||||
# Payload for a getdata or inv message. This lets the peer advertise the
|
||||
# various objects it has knowledge of.
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
class Inventory < BinData::Record
|
||||
endian :little
|
||||
|
||||
var_int :count
|
||||
array :items, :type => :inventory_vector,
|
||||
:read_until => lambda { index == count - 1 }
|
||||
end
|
||||
|
||||
# Payload for a getblocks or getheaders message. Specifies a set of blocks
|
||||
# that the sender wants details of.
|
||||
# @author Nick thomas <nick@lupine.me.uk>
|
||||
class BlockSet < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :version
|
||||
var_int :start_count
|
||||
array :hash_start, :type => :sha256,
|
||||
:read_until => lambda { index == start_count - 1 }
|
||||
# Hash of the last desired block, or 0 to get as many as possible (max: 500)
|
||||
sha256 :hash_stop, :length => 32
|
||||
end
|
||||
|
||||
class Transaction < BinData::Record
|
||||
endian :little
|
||||
|
||||
uint32 :version
|
||||
var_int :tx_in_count
|
||||
array :transactions_in, :type => :transaction_in,
|
||||
:read_until => lambda { index == tx_in_count - 1 }
|
||||
var_int :tx_out_count
|
||||
array :transactions_out, :type => :transaction_out,
|
||||
:read_until => lambda { index == tx_in_count - 1 }
|
||||
uint32 :lock_time
|
||||
end
|
||||
|
||||
## Top-level message format ##
|
||||
|
||||
class MessageHdr < BinData::Record
|
||||
endian :little
|
||||
uint32 :magic
|
||||
string :command, :length => 12
|
||||
uint32 :payload_len
|
||||
uint32 :checksum, :onlyif => :has_checksum?
|
||||
|
||||
protected
|
||||
|
||||
# version and verack messages don't have a checksum. The rest do.
|
||||
# @return[Boolean] does this message header have a checksum field or not?
|
||||
def has_checksum?
|
||||
command != "version" && command != "verack"
|
||||
end
|
||||
end
|
||||
|
||||
# Everything on the wire is a Message.
|
||||
class Message < BinData::Record
|
||||
|
||||
# @param[Fixnum,nil] version The protocol version. Setting this affects
|
||||
# the layout of various fields.
|
||||
def initialize(version = nil)
|
||||
@version = version || BtcWireProto::CURRENT_VERSION
|
||||
end
|
||||
|
||||
message_hdr :header
|
||||
|
||||
choice :payload, :selection => :payload_choice do
|
||||
version "version"
|
||||
addr_pre_31402 "addr_pre_31402"
|
||||
addr_from_31402 "addr_from_31402"
|
||||
inventory "inv"
|
||||
inventory "getdata"
|
||||
block_set "getblocks"
|
||||
block_set "getheaders"
|
||||
transaction "tx"
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def payload_choice
|
||||
return header.command if %w{
|
||||
version inv getdata getblocks getheaders tx
|
||||
}.include?(header.command)
|
||||
|
||||
case header.command
|
||||
when "verack" then nil # No payload for a verack message
|
||||
when "addr" # two forms, depending on protocol version
|
||||
@version < 31402 ? "addr_pre_31402" : "addr_from_31402"
|
||||
else
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
require 'btc_wire_proto'
|
||||
|
||||
module EventMachine
|
||||
module Protocols
|
||||
@@ -297,10 +14,9 @@ module EventMachine
|
||||
# Here is a list of states:
|
||||
# send_ver, recv_ver, verify_ver
|
||||
# send_verack, recv_verack
|
||||
# wait
|
||||
# wait, finish
|
||||
#
|
||||
# We must receive a configuration object before we can do much of interest -
|
||||
# this is received
|
||||
# Documentation is here: https://en.bitcoin.it/wiki/Network
|
||||
#
|
||||
# @author Nick Thomas <nick@lupine.me.uk>
|
||||
module BitcoinPeer
|
||||
|
83
spec/btc_wire_proto_spec.rb
Normal file
83
spec/btc_wire_proto_spec.rb
Normal file
@@ -0,0 +1,83 @@
|
||||
require 'spec_helper'
|
||||
require 'btc_wire_proto'
|
||||
|
||||
# Fixtures data. Taken from: https://en.bitcoin.it/wiki/Protocol_specification
|
||||
|
||||
SERVICE_MASK = "\x00\x00\x00\x01" # Sets NODE_NETWORK
|
||||
|
||||
|
||||
describe BtcWireProto do
|
||||
# Payload fragments
|
||||
describe ServicesMask do
|
||||
it "should have node_network set to false when its bit is 0" do
|
||||
s = BtcWireProto::ServicesMask::read("\x00\x00\x00\x00")
|
||||
s.node_network.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe NetAddr do
|
||||
end
|
||||
describe TimestampedNetAddr do
|
||||
end
|
||||
describe VarInt do
|
||||
end
|
||||
describe VarStr do
|
||||
end
|
||||
describe InventoryVector do
|
||||
end
|
||||
describe Sha256 do
|
||||
end
|
||||
describe TransactionIn do
|
||||
end
|
||||
describe TransactionOut do
|
||||
end
|
||||
describe BlockHeader do
|
||||
end
|
||||
|
||||
# Payloads
|
||||
|
||||
describe Version do
|
||||
end
|
||||
|
||||
describe AddrPre31402 do
|
||||
end
|
||||
|
||||
describe AddrFrom31402 do
|
||||
end
|
||||
|
||||
describe Inventory do
|
||||
end
|
||||
|
||||
describe BlockSpec do
|
||||
end
|
||||
|
||||
describe Transaction do
|
||||
end
|
||||
|
||||
describe Block do
|
||||
end
|
||||
|
||||
describe Headers do
|
||||
end
|
||||
|
||||
describe CheckOrder do
|
||||
end
|
||||
|
||||
describe SubmitOrder do
|
||||
end
|
||||
|
||||
describe Reply do
|
||||
end
|
||||
|
||||
describe Alert do
|
||||
end
|
||||
|
||||
|
||||
# Messages
|
||||
describe MessageHdr do
|
||||
end
|
||||
|
||||
describe Message do
|
||||
end
|
||||
|
||||
end
|
3
spec/spec_helper.rb
Normal file
3
spec/spec_helper.rb
Normal file
@@ -0,0 +1,3 @@
|
||||
require 'rspec'
|
||||
|
||||
$: << File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
Reference in New Issue
Block a user