Module: Bitcoin::Protocol

Defined in:
lib/bitcoin/protocol.rb,
lib/bitcoin/protocol/tx.rb,
lib/bitcoin/protocol/txin.rb,
lib/bitcoin/protocol/block.rb,
lib/bitcoin/protocol/txout.rb,
lib/bitcoin/protocol/parser.rb,
lib/bitcoin/protocol/reject.rb,
lib/bitcoin/protocol/address.rb,
lib/bitcoin/protocol/aux_pow.rb,
lib/bitcoin/protocol/handler.rb,
lib/bitcoin/protocol/version.rb,
lib/bitcoin/protocol/script_witness.rb,
lib/bitcoin/protocol/partial_merkle_tree.rb

Defined Under Namespace

Classes: Addr, AuxPow, Block, Handler, Parser, PartialMerkleTree, Reject, ScriptWitness, Tx, TxIn, TxOut, Version

Constant Summary collapse

MAX_INV_SZ =

bitcoin/src/main.h

50000
BIP0031_VERSION =

BIP 0031, pong message, is enabled for all versions AFTER this one

60000
Uniq =
rand(0xffffffffffffffff)
BINARY =
Encoding.find('ASCII-8BIT')
TypeLookup =
Hash[:tx, 1, :block, 2, :filtered_block, 3, nil, 0]
DEFAULT_STOP_HASH =
"00"*32

Class Method Summary collapse

Class Method Details

.getblocks_pkt(version, locator_hashes, stop_hash = DEFAULT_STOP_HASH) ⇒ Object


174
175
176
# File 'lib/bitcoin/protocol.rb', line 174

def self.getblocks_pkt(version, locator_hashes, stop_hash=DEFAULT_STOP_HASH)
  pkt "getblocks",  locator_payload(version, locator_hashes, stop_hash)
end

.getdata_pkt(type, hashes) ⇒ Object


151
152
153
154
155
# File 'lib/bitcoin/protocol.rb', line 151

def self.getdata_pkt(type, hashes)
  return if hashes.size > MAX_INV_SZ
  t = [ TypeLookup[type] ].pack("V")
  pkt("getdata", pack_var_int(hashes.size) + hashes.map{|hash| t + hash[0..32].reverse }.join)
end

.getheaders_pkt(version, locator_hashes, stop_hash = DEFAULT_STOP_HASH) ⇒ Object


178
179
180
# File 'lib/bitcoin/protocol.rb', line 178

def self.getheaders_pkt(version, locator_hashes, stop_hash=DEFAULT_STOP_HASH)
  pkt "getheaders", locator_payload(version, locator_hashes, stop_hash)
end

.headers_pkt(version, blocks) ⇒ Object


182
183
184
# File 'lib/bitcoin/protocol.rb', line 182

def self.headers_pkt(version, blocks)
  pkt "headers", [pack_var_int(blocks.size), blocks.map{|block| block.block_header}.join].join
end

.inv_pkt(type, hashes) ⇒ Object


157
158
159
160
161
# File 'lib/bitcoin/protocol.rb', line 157

def self.inv_pkt(type, hashes)
  return if hashes.size > MAX_INV_SZ
  t = [ TypeLookup[type] ].pack("V")
  pkt("inv", pack_var_int(hashes.size) + hashes.map{|hash| t + hash[0..32].reverse }.join)
end

.locator_payload(version, locator_hashes, stop_hash) ⇒ Object


165
166
167
168
169
170
171
172
# File 'lib/bitcoin/protocol.rb', line 165

def self.locator_payload(version, locator_hashes, stop_hash)
  [
    [version].pack("V"),
    pack_var_int(locator_hashes.size),
    locator_hashes.map{|l| l.htb_reverse }.join,
    stop_hash.htb_reverse
  ].join
end

.pack_boolean(b) ⇒ Object


105
106
107
# File 'lib/bitcoin/protocol.rb', line 105

def self.pack_boolean(b)
  (b == true) ? [0xFF].pack("C") : [0x00].pack("C")
end

.pack_var_int(i) ⇒ Object


53
54
55
56
57
58
59
60
# File 'lib/bitcoin/protocol.rb', line 53

def self.pack_var_int(i)
  if    i <  0xfd;                [      i].pack("C")
  elsif i <= 0xffff;              [0xfd, i].pack("Cv")
  elsif i <= 0xffffffff;          [0xfe, i].pack("CV")
  elsif i <= 0xffffffffffffffff;  [0xff, i].pack("CQ")
  else raise "int(#{i}) too large!"
  end
end

.pack_var_string(payload) ⇒ Object


72
73
74
# File 'lib/bitcoin/protocol.rb', line 72

def self.pack_var_string(payload)
  pack_var_int(payload.bytesize) + payload
end

.ping_pkt(nonce = rand(0xffffffff)) ⇒ Object


137
138
139
# File 'lib/bitcoin/protocol.rb', line 137

def self.ping_pkt(nonce = rand(0xffffffff))
  pkt("ping", [nonce].pack("Q"))
end

.pkt(command, payload) ⇒ Object


111
112
113
114
115
116
117
118
119
120
121
# File 'lib/bitcoin/protocol.rb', line 111

def self.pkt(command, payload)
  cmd      = command.ljust(12, "\x00")[0...12]
  length   = [payload.bytesize].pack("V")
  checksum = Digest::SHA256.digest(Digest::SHA256.digest(payload))[0...4]
  pkt      = "".force_encoding(BINARY)
  pkt << Bitcoin.network[:magic_head].force_encoding(BINARY)
  pkt << cmd.force_encoding(BINARY)
  pkt << length
  pkt << checksum
  pkt << payload.dup.force_encoding(BINARY)
end

.pong_pkt(nonce) ⇒ Object


141
142
143
# File 'lib/bitcoin/protocol.rb', line 141

def self.pong_pkt(nonce)
  pkt("pong", [nonce].pack("Q"))
end

.read_binary_file(path) ⇒ Object


186
187
188
# File 'lib/bitcoin/protocol.rb', line 186

def self.read_binary_file(path)
  File.open(path, 'rb'){|f| f.read }
end

.unpack_boolean(payload) ⇒ Object


100
101
102
103
# File 'lib/bitcoin/protocol.rb', line 100

def self.unpack_boolean(payload)
  bdata, payload = payload.unpack("Ca*")
  [ (bdata == 0 ? false : true), payload ]
end

.unpack_var_int(payload) ⇒ Object

var_int refers to en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer and is what Satoshi called “CompactSize” BitcoinQT has later added even more compact format called CVarInt to use in its local block storage. CVarInt is not implemented here.


34
35
36
37
38
39
40
41
# File 'lib/bitcoin/protocol.rb', line 34

def self.unpack_var_int(payload)
  case payload.unpack("C")[0] # TODO add test cases
  when 0xfd; payload.unpack("xva*")
  when 0xfe; payload.unpack("xVa*")
  when 0xff; payload.unpack("xQa*") # TODO add little-endian version of Q
  else;      payload.unpack("Ca*")
  end
end

.unpack_var_int_array(payload) ⇒ Object

unpacks set<int>


88
89
90
91
92
93
94
95
96
97
98
# File 'lib/bitcoin/protocol.rb', line 88

def self.unpack_var_int_array(payload) # unpacks set<int>
  buf = StringIO.new(payload)
  size =  unpack_var_int_from_io(buf)
  return [nil, buf.read] if size == 0
  ints = []
  size.times{
    break if buf.eof?
    ints << unpack_var_int_from_io(buf)
  }
  [ints, buf.read]
end

.unpack_var_int_from_io(io) ⇒ Object


43
44
45
46
47
48
49
50
51
# File 'lib/bitcoin/protocol.rb', line 43

def self.unpack_var_int_from_io(io)
  uchar = io.read(1).unpack("C")[0]
  case uchar
  when 0xfd; io.read(2).unpack("v")[0]
  when 0xfe; io.read(4).unpack("V")[0]
  when 0xff; io.read(8).unpack("Q")[0]
  else;      uchar
  end
end

.unpack_var_string(payload) ⇒ Object


62
63
64
65
# File 'lib/bitcoin/protocol.rb', line 62

def self.unpack_var_string(payload)
  size, payload = unpack_var_int(payload)
  size > 0 ? (_, payload = payload.unpack("a#{size}a*")) : [nil, payload]
end

.unpack_var_string_array(payload) ⇒ Object

unpacks set<string>


76
77
78
79
80
81
82
83
84
85
86
# File 'lib/bitcoin/protocol.rb', line 76

def self.unpack_var_string_array(payload) # unpacks set<string>
  buf = StringIO.new(payload)
  size = unpack_var_int_from_io(buf)
  return [nil, buf.read] if size == 0
  strings = []
  size.times{
    break if buf.eof?
    strings << unpack_var_string_from_io(buf)
  }
  [strings, buf.read]
end

.unpack_var_string_from_io(buf) ⇒ Object


67
68
69
70
# File 'lib/bitcoin/protocol.rb', line 67

def self.unpack_var_string_from_io(buf)
  size = unpack_var_int_from_io(buf)
  size > 0 ? buf.read(size) : nil
end

.verack_pktObject


145
146
147
# File 'lib/bitcoin/protocol.rb', line 145

def self.verack_pkt
  pkt("verack", "")
end

.version_pkt(from_id, from = nil, to = nil, last_block = nil, time = nil, user_agent = nil, version = nil) ⇒ Object


123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/bitcoin/protocol.rb', line 123

def self.version_pkt(from_id, from=nil, to=nil, last_block=nil, time=nil, user_agent=nil, version=nil)
  opts = if from_id.is_a?(Hash)
    from_id
  else
    STDERR.puts "Bitcoin::Protocol.version_pkt - API deprecated. please change it soon.."
    {
      :nonce => from_id, :from => from, :to => to, :last_block => last_block,
      :time => time, :user_agent => user_agent, :version => version
    }
  end
  version = Protocol::Version.new(opts)
  version.to_pkt
end