├── .autotest ├── .gemtest ├── .gitignore ├── Gemfile ├── LICENSE ├── README ├── README.md ├── Rakefile ├── bitcoin-protocol.gemspec ├── lib ├── bitcoin.rb └── bitcoin │ ├── autoload_helper.rb │ ├── protocol.rb │ ├── protocol │ ├── binary.rb │ ├── buffer.rb │ ├── configuration.rb │ ├── configurator.rb │ ├── crypto.rb │ ├── message.rb │ ├── serializable.rb │ └── types.rb │ └── utils.rb ├── tasks └── bitcoin.rb └── test ├── fixtures ├── inv.bin ├── msg_bin.rb ├── seeds.rb ├── tools │ ├── bitdump.in.dump │ ├── bitdump.out.dump │ ├── bitdump_2010-02-04_21:18.pcap │ ├── connected_nodes │ ├── dump-via-proxy │ └── seeds └── version.bin ├── helper.rb ├── protocol ├── test_binary.rb ├── test_buffer.rb ├── test_configurator.rb ├── test_message.rb └── test_types.rb └── test_protocol.rb /.autotest: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | require 'autotest/restart' 4 | require 'minitest/pride' 5 | 6 | begin 7 | require 'autotest/fsevent' 8 | rescue LoadError 9 | end 10 | 11 | Autotest.add_hook :initialize do |at| 12 | at.add_exception(/\.git/) 13 | at.add_exception(/bundle$/) 14 | end 15 | 16 | -------------------------------------------------------------------------------- /.gemtest: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altamic/bitcoin-protocol/1fdae46a87cbff285685ac5367862cd2b8f45e3b/.gemtest -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | pkg/* 2 | *.gem 3 | .bundle 4 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "http://rubygems.org" 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Michelangelo Altamore 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 | 20 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | README.md -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bitcoin Protocol 2 | ================ 3 | 4 | An implementation of Bitcoin wire protocol in pure Ruby. 5 | Source code strives for an equilibrium between readability, 6 | flexibility and performance. 7 | 8 | 9 | Requirements 10 | ------------ 11 | 12 | Ruby version 1.8.7 or newer. 13 | 14 | 15 | Install 16 | ------- 17 | 18 | $ gem install bitcoin-protocol 19 | 20 | 21 | 22 | Examples 23 | ======== 24 | 25 | 26 | 27 | A simple monitor Client 28 | ------------------------ 29 | 30 | 31 | 32 | 33 | Usage with Event Machine 34 | ------------------------ 35 | 36 | 37 | 38 | 39 | Configuring a new Message 40 | ------------------------- 41 | 42 | open and modify: 43 | 44 | ~/.bitcoin/protocol/configuration.rb 45 | 46 | 47 | 48 | Source Code 49 | =========== 50 | 51 | Bitcoin Protocol repository is available on GitHub. 52 | You can clone it with: 53 | 54 | git clone .... 55 | 56 | 57 | Development 58 | ----------- 59 | 60 | You will need the following gems to run/test this gem: 61 | 62 | 63 | 64 | Profiling 65 | --------- 66 | 67 | 68 | 69 | 70 | Contributing 71 | ------------ 72 | 73 | If you'd like to hack on, please follow these instructions. 74 | To get all of the dependencies, install the gem first. 75 | 76 | 1. Fork the project and clone down your fork 77 | 2. Create a branch with a descriptive name to contain your change 78 | 4. Hack away 79 | 5. Add tests and make sure everything still passes by running rake 80 | 6. Do not change the version number, I will do that on my end 81 | 7. If necessary, rebase your commits into logical chunks, without errors 82 | 8. Push the branch up to GitHub 83 | 9. Send me (altamic) a pull request for your branch 84 | 85 | 86 | Credits 87 | ======= 88 | 89 | This software is a liberal implementation of the protocol used 90 | in the Bitcoin software by Satoshi Nakamoto. 91 | 92 | Thanks for information and encouragement go to Artforz, davout 93 | MagicalTux from the #bitcoin-dev channel on IRC. 94 | 95 | I am particularly grateful to Ryan Davis and Eric Hodel of SeattleRB 96 | for their unparalleled Ruby software tools. 97 | 98 | 99 | Copyright 100 | ========= 101 | 102 | © Copyright 2011 Michelangelo Altamore. See LICENSE for details. 103 | 104 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- ruby -*- 2 | 3 | # require 'rubygems' 4 | # require 'hoe' 5 | 6 | $LOAD_PATH.unshift('lib') 7 | 8 | # PKG_NAME = 'bitcoin-protocol' 9 | # PKG_VERSION = Bitcoin::Protocol::VERSION 10 | # PKG_DIST = "#{PKG_NAME}-#{PKG_VERSION}" 11 | # PKG_TAR = "pkg/#{PKG_DIST}.tar.gz" 12 | # MANIFEST = `git ls-files`.split("\n") 13 | # MINRUBY = "1.8.7" 14 | 15 | # Hoe.plugin :git 16 | 17 | require 'tasks/bitcoin' 18 | 19 | desc "Open an irb session preloaded with this library" 20 | task :console do 21 | sh "irb -rubygems -r ./lib/bitcoin.rb" 22 | end 23 | 24 | desc "Prepare distribution" 25 | task :distro do 26 | 27 | end 28 | 29 | 30 | task :default => :test 31 | 32 | require 'rake/testtask' 33 | Rake::TestTask.new(:test) do |test| 34 | test.pattern = 'test/**/{helper,test_*}.rb' 35 | test.warning = true 36 | test.verbose = true 37 | end 38 | 39 | 40 | -------------------------------------------------------------------------------- /bitcoin-protocol.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "bitcoin-protocol" 5 | s.version = Bitcoin::Protocol::VERSION 6 | s.platform = Gem::Platform::RUBY 7 | s.authors = ["Michelangelo Altamore"] 8 | s.email = ["michelangelo@altamore.org"] 9 | s.homepage = "" 10 | s.summary = %q{A Bitcoin wire protocol implementation in pure Ruby} 11 | s.description = %q{} 12 | s.rubyforge_project = "bitcoin-protocol" 13 | 14 | s.add_development_dependency('rake', [">= 0.8.7"]) 15 | s.add_development_dependency('minitest', [">= 2.0.0"]) 16 | 17 | s.files = `git ls-files`.split("\n") 18 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") 19 | s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } 20 | s.require_paths = ["lib"] 21 | end 22 | 23 | -------------------------------------------------------------------------------- /lib/bitcoin.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.dirname(__FILE__) 2 | 3 | module Bitcoin 4 | autoload(:Protocol, 'bitcoin/protocol') 5 | autoload(:AutoloadHelper, 'bitcoin/autoload_helper') 6 | end 7 | 8 | # preserve your fingertips 9 | Btc = Bitcoin if not defined?(Btc) 10 | -------------------------------------------------------------------------------- /lib/bitcoin/autoload_helper.rb: -------------------------------------------------------------------------------- 1 | module AutoloadHelper 2 | def register_lookup_modules(mods) 3 | (@lookup_module_index ||= {}).update(mods) 4 | end 5 | 6 | def lookup_module(key) 7 | return if !@lookup_module_index 8 | const_get @lookup_module_index[key] || key 9 | end 10 | 11 | def autoload_all(prefix, options) 12 | options.each do |const_name, path| 13 | autoload const_name, File.join(prefix, path) 14 | end 15 | end 16 | 17 | # Loads each autoloaded constant. 18 | # If thread safety is a concern, wrap 19 | # this in a Mutex. 20 | def load_autoloaded_constants 21 | constants.each do |const| 22 | const_get(const) if autoload?(const) 23 | end 24 | end 25 | 26 | def all_loaded_constants 27 | constants.map { |c| const_get(c) }. 28 | select { |a| a.respond_to?(:loaded?) && a.loaded? } 29 | end 30 | end 31 | 32 | -------------------------------------------------------------------------------- /lib/bitcoin/protocol.rb: -------------------------------------------------------------------------------- 1 | require 'bitcoin/protocol/configurator' 2 | require 'bitcoin/protocol/configuration' 3 | 4 | module Bitcoin 5 | module Protocol 6 | VERSION = '0.0.3' 7 | 8 | extend AutoloadHelper 9 | 10 | autoload_all 'bitcoin/protocol', 11 | :Binary => 'binary', 12 | :Types => 'types', 13 | :Buffer => 'buffer', 14 | :Utils => 'utils', 15 | :Serializable => 'serializable', 16 | :Message => 'message', 17 | :Crypto => 'crypto', 18 | :Configurator => 'configurator' 19 | 20 | 21 | register_lookup_modules \ 22 | :binary => :Binary, 23 | :types => :Types, 24 | :buffer => :Buffer, 25 | :utils => :Utils, 26 | :serializable => :Serializable, 27 | :message => :Message, 28 | :crypto => :Crypto, 29 | :configurator => :Configurator 30 | 31 | end 32 | end 33 | 34 | Bitcoin::Protocol.configure! 35 | 36 | BtcProto = Bitcoin::Protocol 37 | 38 | -------------------------------------------------------------------------------- /lib/bitcoin/protocol/binary.rb: -------------------------------------------------------------------------------- 1 | # Binary mixin handles (de)serialization of: 2 | # 3 | # - Integer numbers ✓ 4 | # - Null terminated strings ✓ 5 | # - Fixed size strings ✓ 6 | # 7 | # It is meant to be used as a fluent interface to 8 | # an IO entity, therefore assumes that read(n) 9 | # and write(str) methods are available where this 10 | # module is mixed in. 11 | # 12 | # In order to be plaftorm agnostic, Binary inspects 13 | # at load time machine's capabilities and adjust its 14 | # own operations accordingly. 15 | # 16 | # Binary performs operations accepted by the following 17 | # grammar: 18 | # 19 | # operation ::= 'read' | 'write' 20 | # 21 | # OP_INT ::= operation '_' integer_type bits ('_' endianness)? 22 | # 23 | # integer_type ::= 'uint' | 'int' 24 | # bits ::= '8' | '16' | '32' | '64' | '128' | '256' 25 | # endianness ::= 'native' | 'little' | 'big' | 'network' 26 | # 27 | # OP_STR ::= operation '_' (str_padding '_')? 28 | # string_flavor '_' str_preposition integer '_' str_size 29 | # 30 | # str_padding ::= 'null_padded' | 'c_' 31 | # string_flavor ::= 'string' | 'fixed_string' | 'binary_string' 32 | # str_preposition ::= '_of_' 33 | # integer ::= [0-9]+ 34 | # str_size ::= 'bytes' 35 | # 36 | module Bitcoin::Protocol 37 | module Binary 38 | OP_RE = /(read|write)_/ 39 | INT_RE = /(uint|int)(8|16|32|64|128|256)_?(native|little|big|network)?/ 40 | STR_RE = /(null_padded|c_)?((binary_|fixed_)?string)(_of_)[0-9]+(bytes)/ 41 | OP_INT_RE = Regexp::compile(OP_RE.source + INT_RE.source) 42 | OP_STR_RE = Regexp::compile(OP_RE.source + STR_RE.source) 43 | 44 | KNOWN_RE = Regexp::compile(OP_INT_RE.source + OP_STR_RE.source) 45 | 46 | NUL = 0.chr 47 | 48 | # Returns the number of bytes used to encode an integer number 49 | INTEGER_SIZE_IN_BYTES = module_eval { 1.size } 50 | def integer_size_in_bytes() INTEGER_SIZE_IN_BYTES end 51 | alias :word_size_in_bytes :integer_size_in_bytes 52 | 53 | def little_endian_byte_order() :little end 54 | def big_endian_byte_order() :big end 55 | alias :network_byte_order :big_endian_byte_order 56 | 57 | # A machine architecture is said to be little endian if puts first the 58 | # LSB. We evaluate the first byte of the number 1 packed as an integer. 59 | # While first_byte is a Fixnum in Ruby 1.8.x, it is a string in 1.9.x; 60 | # in latter case we employ the ord method to obtain the ordinal number 61 | # associated,if is the case. Underlying machine is l.e. if the LSB is 1. 62 | 63 | NATIVE_BYTE_ORDER = module_eval do 64 | first_byte = [1].pack('i')[0] 65 | first_byte = first_byte.ord if RUBY_VERSION =~ /^1\.9/ 66 | first_byte == 1 ? :little : :big 67 | end 68 | 69 | def native_byte_order() NATIVE_BYTE_ORDER end 70 | 71 | def little_endian_platform?() native_byte_order.equal? :little end 72 | def big_endian_platform?() native_byte_order.equal? :big end 73 | alias :network_endian_platform? :big_endian_platform? 74 | 75 | @@pack_mappings = { 76 | 1 => { :uint => { :native => 'C' }, 77 | :int => { :native => 'c' } }, 78 | 2 => { :uint => { :native => 'S', :little => 'v', :big => 'n' }, 79 | :int => { :native => 's', :little => 'v', :big => 'n' } }, 80 | 4 => { :uint => { :native => 'L', :little => 'V', :big => 'N' }, 81 | :int => { :native => 'l', :little => 'V', :big => 'N' } }, 82 | 8 => { :uint => { :native => 'Q' }, 83 | :int => { :native => 'q' } } } 84 | 85 | BIT_MASK = { 4 => 0xF, 8 => 0xFF, 16 => 0xFFFFF, 86 | 32 => 0xFFFFFFFF, 64 => 0xFFFFFFFFFFFFFFFF, 87 | 128 => 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF } 88 | 89 | DEFAULT_BIT_MASK = module_eval { (1.size >> 1) * 8 } 90 | 91 | def msb(num, bits=DEFAULT_BIT_MASK) (num & BIT_MASK[bits]) end 92 | def lsb(num, bits=DEFAULT_BIT_MASK) ((num >> bits) & BIT_MASK[bits]) end 93 | 94 | def split_msb_lsb(num, bits) 95 | [msb(num, bits), lsb(num,bits) ] 96 | end 97 | 98 | def concat(msb, lsb, bits) 99 | lsb + (msb << bits) 100 | end 101 | 102 | def read_uint256_little 103 | lsb = read_uint128_little 104 | msb = read_uint128_little 105 | concat(msb, lsb, 128) 106 | end 107 | 108 | def read_uint256_big 109 | msb = read_uint64_big 110 | lsb = read_uint64_big 111 | concat(msb, lsb, 128) 112 | end 113 | alias :read_uint256_network :read_uint256_big 114 | 115 | def read_uint256_native 116 | little_endian_platform? ? read_uint256_little : read_uint256_big 117 | end 118 | alias :read_uint256 :read_uint256_native 119 | 120 | def write_uint256_little(n) 121 | msb, lsb = split_msb_lsb(n, 128) 122 | write_uint128_little(msb) 123 | write_uint128_little(lsb) 124 | end 125 | 126 | def write_uint256_big(n) 127 | lsb, msb = split_msb_lsb(n, 128) 128 | write_uint128_big(msb) 129 | write_uint128_big(lsb) 130 | end 131 | 132 | def write_uint256_native(n) 133 | little_endian_platform? ? write_uint256_little(n) : write_uint256_big(n) 134 | end 135 | alias :write_uint256 :write_uint256_native 136 | 137 | def read_uint128_little 138 | lsb = read_uint64_little 139 | msb = read_uint64_little 140 | concat(msb, lsb, 64) 141 | end 142 | 143 | def read_uint128_big 144 | msb = read_uint64_big 145 | lsb = read_uint64_big 146 | concat(msb, lsb, 64) 147 | end 148 | alias :read_uint128_network :read_uint128_big 149 | 150 | def read_uint128_native 151 | little_endian_platform? ? read_uint128_little : read_uint128_big 152 | end 153 | alias :read_uint128 :read_uint128_native 154 | 155 | def write_uint128_little(n) 156 | lsb, msb = split_msb_lsb(n, 64) 157 | write_uint64_little(lsb) 158 | write_uint64_little(msb) 159 | end 160 | 161 | def write_uint128_big(n) 162 | lsb, msb = split_msb_lsb(n, 64) 163 | write_uint64_big(msb) 164 | write_uint64_big(lsb) 165 | end 166 | 167 | def write_uint128_native(n) 168 | little_endian_platform? ? write_uint128_little(n) : write_uint128_big(n) 169 | end 170 | alias :write_uint128 :write_uint128_native 171 | 172 | def read_uint64_little 173 | lsb = readn_unpack(4, 'L', :little) 174 | msb = readn_unpack(4, 'L', :little) 175 | concat(msb, lsb, 32) 176 | end 177 | 178 | def read_uint64_big 179 | msb = readn_unpack(4, 'L', :big) 180 | lsb = readn_unpack(4, 'L', :big) 181 | concat(msb, lsb, 32) 182 | end 183 | alias :read_uint64_network :read_uint64_big 184 | 185 | def read_uint64_native 186 | little_endian_platform? ? read_uint64_little : read_uint64_big 187 | end 188 | alias :read_uint64 :read_uint64_native 189 | 190 | def write_uint64_little(n) 191 | lsb, msb = split_msb_lsb(n,32) 192 | write_pack(lsb, 'L', :little) 193 | write_pack(msb, 'L', :little) 194 | end 195 | 196 | def write_uint64_big(n) 197 | lsb, msb = split_msb_lsb(n,32) 198 | write_pack(msb, 'L', :big) 199 | write_pack(lsb, 'L', :big) 200 | end 201 | 202 | def write_uint64_native(n) 203 | little_endian_platform? ? write_uint64_little(n) : write_uint64_big(n) 204 | end 205 | alias :write_uint64 :write_uint64_native 206 | 207 | # obtains the correct pack format for the arguments 208 | def format(byte_size, type, byte_order) 209 | byte_order = :native if byte_order.nil? 210 | byte_order = :big if byte_order.equal?(:network) 211 | @@pack_mappings[byte_size][type][byte_order] 212 | end 213 | 214 | # read n bytes and unpack, swapping bytes as per endianness 215 | def readn_unpack(size, template, byte_order=NATIVE_BYTE_ORDER) 216 | str = readn(size) 217 | str.reverse! if not native_byte_order.equal? byte_order # spotted problem in pack 218 | str.unpack(template).first 219 | end 220 | 221 | # read exactly n characters from the buffer, otherwise raise an exception. 222 | def readn(n) 223 | str = read(n) 224 | raise "couldn't read #{n} characters." if str.nil? or str.size != n 225 | str 226 | end 227 | 228 | # TODO: rewrite this method w/o messing with buffer instance vars: 229 | # call buffer API instead and declare used methods as a dependence 230 | def read_fixed_size_string(size, opt = {:padding => nil}) 231 | str = @content[@position, size] 232 | @position += size 233 | # opt[:padding] ? str.split_msb_lsb(opt[:padding]).first : str 234 | str 235 | end 236 | 237 | # TODO: idem as above 238 | def read_c_string 239 | nul_pos = @content.index(NUL, @position) 240 | raise "no C string found." unless nul_pos 241 | sz = nul_pos - @position 242 | str = @content[@position, sz] 243 | @position += sz + 1 244 | return str 245 | end 246 | 247 | def read_string(opt={:size => nil, :padding => 0.chr}) 248 | if opt[:size] 249 | read_fixed_size_string(opt[:size], opt[:padding]) 250 | else 251 | read_c_string 252 | end 253 | end 254 | 255 | # writes a number and pack it, swapping bytes as per endianness 256 | def write_pack(number, template, byte_order=NATIVE_BYTE_ORDER) 257 | str = [number].pack(template) 258 | str.reverse! if not native_byte_order.equal? byte_order # blame Array#pack not me 259 | write(str) 260 | end 261 | 262 | # writes the string and appends NUL 263 | def write_c_string(str) 264 | #TODO: improve input validation 265 | raise ArgumentError, "Invalid Ruby string" if str.include?(NUL) 266 | write(str) 267 | write(NUL) 268 | end 269 | 270 | def write_string(content, opt = {:padding => nil, :size => nil}) 271 | if (size = opt[:size]) && (opt[:size].kind_of? Integer) 272 | output_string = content[0..size] 273 | # output_string = output_string.ljust(size, opt[:padding]) if opt[:padding] 274 | write(output_string) 275 | else 276 | write_c_string(content) 277 | end 278 | end 279 | 280 | def write_fixed_size_string(content="") 281 | write_string(content, :size => content.size) 282 | end 283 | 284 | def self.recognize?(type) 285 | type.to_s =~ INT_RE || type.to_s =~ STR_RE 286 | end 287 | 288 | def method_missing(method_name, *args, &block) 289 | if method_name.to_s =~ OP_INT_RE 290 | op, type, bits, byte_order = Regexp.last_match[1..4] 291 | # string → sym 292 | op, type, byte_order = [op, type].map!(&:to_sym) 293 | # adjust bits to bytes 294 | byte_size = (bits.to_i / 8) 295 | # normalize endianness 296 | byte_order = byte_order.to_sym unless byte_order.nil? 297 | byte_order = :big if byte_order == 'network' 298 | 299 | fmt = format(byte_size,type,byte_order) 300 | 301 | case op 302 | when :read 303 | self.class.send :define_method, method_name do 304 | readn_unpack(byte_size, fmt, byte_order) 305 | end 306 | self.send method_name 307 | when :write 308 | if (args.first.kind_of? Integer) && (args.size == 1) 309 | self.class.send :define_method, method_name do |value| 310 | write_pack(value, fmt, byte_order) 311 | end 312 | self.send method_name, args.first 313 | end 314 | end 315 | elsif method_name.to_s =~ OP_STR_RE 316 | op, string_flavor = Regexp.last_match[1..2] 317 | case op 318 | when :read 319 | options = args.first.indexes(:size, :padding) 320 | self.send :read_string, options 321 | when :write 322 | str, options = args.shift, args 323 | self.send :write_string, str, options 324 | end 325 | else 326 | super 327 | end 328 | end 329 | 330 | # Tells the byte size required for the method passed as argument. 331 | # When recognized. 332 | def size_of(type, object=nil) 333 | case 334 | when type.to_s =~ INT_RE then Regexp.last_match[2].to_i / 8 335 | when type.to_s =~ STR_RE then Regexp.last_match[-2].to_i 336 | end 337 | end 338 | 339 | def recognize?(type) 340 | (type.to_s =~ Regexp.union(INT_RE,STR_RE)) ? true : false 341 | end 342 | end 343 | end 344 | 345 | -------------------------------------------------------------------------------- /lib/bitcoin/protocol/buffer.rb: -------------------------------------------------------------------------------- 1 | module Bitcoin::Protocol 2 | # A buffer is an IO entity able to 3 | # - hold content 4 | # - perform seeks 5 | # - manage the seek position 6 | # - read/write its content 7 | # - copy from a stream 8 | # - being initialized with a specified size 9 | class Buffer 10 | include BtcProto::Types # should not be namespaced in the future 11 | 12 | class Error < RuntimeError; end 13 | class EOF < Error; end 14 | 15 | def self.of_size(size, &block) 16 | if (not size.kind_of?(Integer)) || (size < 0) 17 | raise ArgumentError, 'positive integer required' 18 | end 19 | new(0.chr * size, &block) 20 | end 21 | 22 | def initialize(content, &block) 23 | raise ArgumentError if not content.kind_of? String 24 | @content = content 25 | @size = content.size 26 | @position = 0 27 | instance_eval(&block) and rewind if block_given? 28 | end 29 | 30 | def size 31 | @size 32 | end 33 | 34 | def position 35 | @position 36 | end 37 | 38 | def position=(new_pos) 39 | raise ArgumentError if new_pos < 0 or new_pos > size 40 | @position = new_pos 41 | end 42 | 43 | def rewind 44 | @position = 0 45 | self 46 | end 47 | 48 | def at_end? 49 | position.equals?(size) 50 | end 51 | 52 | def content 53 | @content 54 | end 55 | 56 | def read(n) 57 | raise EOF, 'cannot read beyond the end of buffer' if position + n > size 58 | str = @content[@position, n] 59 | @position += n 60 | str 61 | end 62 | 63 | def copy_from_stream(stream, n) 64 | raise ArgumentError if n < 0 65 | while n > 0 66 | str = stream.read(n) 67 | write(str) 68 | n -= str.size 69 | end 70 | raise if n < 0 71 | end 72 | 73 | def write(str) 74 | sz = str.size 75 | raise EOF, 'cannot write beyond the end of buffer' if @position + sz > @size 76 | @content[@position, sz] = str 77 | @position += sz 78 | self 79 | end 80 | 81 | # read till the end of the buffer 82 | def read_rest 83 | read(self.size-@position) 84 | end 85 | end 86 | 87 | # needs Binary module 88 | autoload_all 'bitcoin/message', 89 | :Binary => 'binary' 90 | 91 | register_lookup_modules :binary => :Binary 92 | 93 | end 94 | 95 | -------------------------------------------------------------------------------- /lib/bitcoin/protocol/configuration.rb: -------------------------------------------------------------------------------- 1 | # The semantic model of Bitcoin protocol is configured 2 | # here using public APIs exposed in configurator.rb 3 | 4 | Bitcoin::Protocol.configure do 5 | # Constants 6 | register_constant :MESSAGE_SIZE_LIMIT, 50_000 7 | register_constant :VERSION, 318 8 | register_constant :SUB_VERSION, '.1' 9 | 10 | register_constant :DEFAULT_NETWORK, :production 11 | 12 | register_constant :MAGIC, :production => 0xD9B4BEF9, 13 | :test => 0xDAB5BFFA 14 | register_constant :SERVICES, :node_network => 1 15 | 16 | register_constant :UNIX_TIMESTAMP, lambda { Time.now.to_i } 17 | register_constant :RANDOM_64BITS, lambda { rand(2**64) } 18 | 19 | # Types 20 | register_type :address do |t| 21 | t.uint64_little :services, :default => [:SERVICES, :node_network] 22 | t.uint128_network :ip_address, :default => 0xFFFF00000000 23 | t.uint16_network :port, :default => 0 24 | end 25 | 26 | register_type :out_point do |t| 27 | t.uint256_little :hash, :default => 0 28 | t.uint32_little :size, :default => 0 29 | end 30 | 31 | register_type :tx_input do |t| 32 | t.out_point :previous 33 | t.encoded_string :script_signature, :default => "" 34 | t.uint32_little :sequence, :default => 0 35 | end 36 | 37 | register_type :tx_output do |t| 38 | t.int64_little :value, :default => 0 39 | t.encoded_string :script_public_key, :default => "" 40 | end 41 | 42 | register_type :transaction do |t| 43 | t.int32_little :version, :default => :VERSION 44 | t.tx_input_vector :inputs 45 | t.tx_output_vector :outputs 46 | t.uint32_little :lock_time 47 | end 48 | 49 | register_constant :INVENTORY_TYPES, {:error => 0, :tx => 1, :block => 2} 50 | 51 | register_type :inventory do |t| 52 | t.int32_little :type, :default => [:INVENTORY_TYPES, :error] 53 | t.uint256_little :hash, :default => 0 54 | end 55 | 56 | register_type :block_locator do |t| 57 | t.int32_little :version, :default => :VERSION 58 | t.uint256_little_vector :available_hashes 59 | end 60 | 61 | # Messages 62 | register_message :version do |m| 63 | m.uint32_little :version, :default => :VERSION 64 | m.uint64_little :services, :default => [:SERVICES, :node_network] 65 | m.int64_little :time, :default => :UNIX_TIMESTAMP 66 | m.address :origin, :default => :address 67 | m.address :destination, :default => :address 68 | m.int64_little :nonce, :default => :RANDOM_64BITS 69 | m.encoded_string :sub_version, :default => :SUB_VERSION, :size => 4 70 | m.int32_little :starting_height, :default => nil 71 | end 72 | 73 | register_message :verack, :alias => :version_ack 74 | 75 | register_message :addr, :alias => :addresses, :size_limit => 1000 do |m| 76 | m.address_vector :addresses 77 | end 78 | 79 | register_message :inv, :alias => :inventory, :size_limit => true do |m| 80 | m.inventory_vector :inventory 81 | end 82 | 83 | register_message :getdata, :alias => :get_data, :size_limit => true do |m| 84 | m.inventory_vector :inventory 85 | end 86 | 87 | register_message :getblocks, :alias => :get_blocks do |m| 88 | m.block_locator :locator 89 | m.uint256_little :hash_stop 90 | end 91 | 92 | register_message :tx, :alias => :transaction do |m| 93 | m.transaction :transaction 94 | end 95 | 96 | register_message :block do |m| 97 | m.int32_little :version, :default => :VERSION 98 | m.uint256_little :hash_previous_block 99 | m.uint256_little :hash_merkle_root 100 | m.uint32_little :time, :default => 0 101 | m.compact_target :bits, :default => 0 102 | m.uint32_little :nonce, :default => 0 103 | m.transaction_vector :transactions 104 | end 105 | 106 | register_message :getaddr, :alias => :get_addresses 107 | 108 | register_message :checkorder, :alias => :check_order 109 | 110 | register_message :submitorder, :alias => :submit_order 111 | 112 | register_message :reply 113 | 114 | register_message :ping 115 | 116 | register_message :alert 117 | 118 | register_sequence :initialize, :request => :version, :response => :verack 119 | register_sequence :identificate_peer, :request => :getaddr, :response => :addr 120 | register_sequence :bootstrap_blocks, :request => :getblocks, :response => :inv 121 | end 122 | 123 | -------------------------------------------------------------------------------- /lib/bitcoin/protocol/configurator.rb: -------------------------------------------------------------------------------- 1 | require 'bitcoin/utils' 2 | 3 | module Bitcoin::Protocol 4 | # This file is concerned with the responsibility 5 | # of assembling pieces of information for the 6 | # Bitcoin protocol. Such information is separated 7 | # into constants, types and messages. 8 | # 9 | # The protocol configuration itself is expressed in 10 | # the form of an internal DSL in the configuration.rb 11 | # file. 12 | extend self 13 | include Bitcoin::Utils 14 | 15 | def configure(&block) 16 | module_eval(&block) 17 | end 18 | 19 | # hash containing structural information about protocol's 20 | # constants in the following form: 21 | # :CONSTANT_1 => value 22 | # :CONSTANT_2 => 'string' 23 | # :CONSTANT_3 => lambda { Time.now.to_i } 24 | # :CONSTANT_4 => { :key_1 => 'value_1', ... } 25 | # ... 26 | # :CONSTANT_n => ... 27 | def consts 28 | @@constants ||= Hash.new 29 | end 30 | 31 | # hash containing structural information about protocol's 32 | # messages in the following form: 33 | # 34 | # :classes => {:msg_1 => Class_1, ..., :msg_n => Class_N} 35 | # :aliases => {:alias_1 => :msg_1, ..., alias_n => :msg_n} 36 | # :msg_1 => { 37 | # :attributes = %w[attr_1 ... attr_n] 38 | # attr_1 => { :default => nil, :type => nil } 39 | # ... 40 | # attr_n => { :default => nil, :type => nil } 41 | # } 42 | # ... 43 | # :msg_n => { 44 | # ... 45 | # } 46 | def messages 47 | @@messages ||= Hash.new.merge!(:aliases => {}, :classes => {}) 48 | end 49 | 50 | # hash containing structural information about protocol's 51 | # types in the following form: 52 | # 53 | # :classes => {:type_1 => Class_1, ..., :type_n => Class_N} 54 | # :type_1 => { 55 | # :struct => %w[name_1, ..., name_n] 56 | # name_1 => { :type => type_1, :default => value_1 } 57 | # ... 58 | # name_n => { :type => type_n, :default => value_n } 59 | # } 60 | # ... 61 | def types 62 | @@types ||= Hash.new.merge!(:classes => {}) 63 | end 64 | 65 | def warn_if_already_present(name, object) 66 | warn "already registered object #{name} " + 67 | "is going to be modified" if object.has_key? name 68 | end 69 | 70 | def register_constant(name, value) 71 | warn_if_already_present(name, consts) 72 | Definition.new(:constant, name, :value => value) 73 | end 74 | 75 | def register_type(name, options={}, &block) 76 | warn_if_already_present(name, types) 77 | if block_given? 78 | block.call(Definition.new(:type, name, options)) 79 | else 80 | Definition.new(:type, name, options) 81 | end 82 | end 83 | 84 | def register_message(name, options={}, &block) 85 | warn_if_already_present(name, messages) 86 | if block_given? 87 | block.call(Definition.new(:message, name, options)) 88 | else 89 | Definition.new(:message, name, options) 90 | end 91 | end 92 | 93 | def has_const?(name) 94 | consts.keys.include?(name) 95 | end 96 | 97 | def has_type?(name) 98 | types.keys.include?(name) 99 | end 100 | 101 | def has_message?(name) 102 | ((messages.keys - [:aliases, :classes]) + 103 | messages[:aliases].keys).include?(name) 104 | end 105 | 106 | def has_message_alias?(name) 107 | (messages[:aliases].keys).include?(name) 108 | end 109 | 110 | def proper_message_names 111 | messages.keys - [:aliases, :classes] 112 | end 113 | 114 | def proper_type_names 115 | types.keys - [:classes] 116 | end 117 | 118 | def type_classes 119 | types[:classes] 120 | end 121 | 122 | def aliases 123 | messages[:aliases] 124 | end 125 | 126 | def current_network 127 | @@default_network ||= lookup(:DEFAULT_NETWORK) 128 | end 129 | 130 | def current_network=(value) 131 | @@default_network = value if lookup(:MAGIC).keys.include?(value) 132 | end 133 | 134 | def register_sequence(name, options) 135 | end 136 | 137 | def configure! 138 | build_types 139 | build_messages 140 | end 141 | 142 | # Here goes the logic for protocol types. Basically 143 | # involves definition of each class with its own 144 | # attributes types and default values, load and 145 | # dump methods. 146 | # 147 | def build_types 148 | proper_type_names.each do |type| 149 | klass = type.to_s.camelize 150 | struct_attrs = struct_attributes_for(:type, type) 151 | attributes = attributes_for(:type, type) 152 | defaults = defaults_for(:type, type) 153 | types = types_for(:type, type) 154 | 155 | self.module_eval <<-EOS, __FILE__, __LINE__ + 1 156 | class #{klass} < Struct.new('#{klass}'#{struct_attrs}) 157 | def attributes() @@attributes = #{attributes.inspect} end 158 | def defaults() @@defaults = #{defaults.inspect} end 159 | 160 | def initialize 161 | super 162 | attributes.each do |attribute| 163 | send(\"\#{attribute}=", Bitcoin::Protocol.lookup(defaults[attribute])) 164 | end 165 | instance_eval(&block) if block_given? 166 | end 167 | 168 | # evaluated after initialization so it can reflect upon its own values 169 | def types() @@types = #{types.inspect} end 170 | 171 | def bytesize 172 | @bytesize ||= attributes.each.inject(0) do |acc, attr| 173 | acc += BtcProto::Binary.size_of(types[attr], self) 174 | end 175 | end 176 | 177 | def load(buf) 178 | attributes.each do |a| 179 | if BtcProto.proper_type_names & [types[a]] 180 | send("\#{types[a]}=", BtcProto.class_for(:type, types[a]).load(buf)) 181 | else 182 | send("\#{types[a]}=", buf.send("read_\#{types[a]}")) 183 | end 184 | end 185 | end 186 | 187 | def dump(buf) 188 | attributes.each do |a| 189 | if BtcProto.proper_type_names.include?(types[a]) 190 | send(a).dump(buf) 191 | else 192 | buf.send("write_\#{types[a]}", send(a)) 193 | end 194 | end 195 | end 196 | 197 | def self.load() new.load end 198 | def self.dump(buf) new.dump end 199 | end 200 | EOS 201 | 202 | type_classes.merge!(type => Bitcoin::Protocol.const_get(klass)) 203 | end 204 | 205 | Types.mappings = type_classes 206 | end 207 | 208 | # Here goes the logic for protocol messages. Basically 209 | # involves definition of each class with its own 210 | # attributes types and default values, load and 211 | # dump methods. 212 | # 213 | def build_messages 214 | proper_message_names.each do |message| 215 | klass = message.to_s.camelize 216 | attributes = attributes_for(:message, message) 217 | struct_attrs = struct_attributes_for(:message, message) 218 | defaults = defaults_for(:message, message) 219 | types = types_for(:message, message) 220 | 221 | self.module_eval <<-EOS, __FILE__, __LINE__ + 1 222 | class #{klass} < Struct.new('#{klass}'#{struct_attrs}) 223 | def attributes() @@attributes = #{attributes.inspect} end 224 | def defaults() @@defaults = #{defaults.inspect} end 225 | 226 | def initialize 227 | super 228 | attributes.each do |attribute| 229 | send(\"\#{attribute}=", Bitcoin::Protocol.lookup(defaults[attribute])) 230 | end 231 | instance_eval(&block) if block_given? 232 | end 233 | 234 | # evaluated after initialization so it can reflect upon its own values 235 | def types() @@types = #{types.inspect} end 236 | 237 | def load(buf) 238 | attributes.each do |a| 239 | if BtcProto.proper_type_names & [types[a]] 240 | send("\#{types[a]}=", BtcProto.class_for(:type, types[a]).load(buf)) 241 | else 242 | send("\#{types[a]}=", buf.send("read_\#{types[a]}")) 243 | end 244 | end 245 | end 246 | 247 | def dump(buf) 248 | attributes.each do |a| 249 | if BtcProto.proper_type_names.include?(types[a]) 250 | send(a).dump(buf) 251 | else 252 | buf.send("write_\#{types[a]}", send(a)) 253 | end 254 | end 255 | end 256 | end 257 | EOS 258 | 259 | self.messages[:classes]. 260 | merge!(message => Bitcoin::Protocol.const_get(klass)) 261 | end 262 | 263 | # add logic for marshalling i.e. load and dump 264 | # each type should be able to determine its size 265 | 266 | # associate class with names 267 | # new_item = { :class => command } 268 | # properties.assoc(message).push(new_item) 269 | end 270 | 271 | # lookup returns the value of the constant passed as a parameter. 272 | # A constant can also be a Proc object with no parameters. 273 | def lookup(const_key) 274 | case 275 | when const_key.kind_of?(Numeric) then const_key 276 | when const_key.kind_of?(String) then const_key 277 | when const_key.kind_of?(Symbol) then 278 | if has_const?(const_key) 279 | value = consts[const_key] 280 | value.is_a?(Proc) ? value.call : value 281 | elsif proper_type_names.include?(const_key) 282 | class_for(:type, const_key).new 283 | elsif const_key.to_s =~ /vector$/ 284 | Array.new 285 | end 286 | when const_key.kind_of?(Array) then 287 | if has_const?(const_key.first) and const_key.size == 2 288 | key, value =[const_key.first, const_key.last] 289 | consts[key][value] 290 | end 291 | when const_key.kind_of?(Proc) then 292 | const_key.call 293 | end 294 | end 295 | 296 | def attributes_for(object, name) 297 | case object 298 | when :type 299 | types[name][:struct] if has_type?(name) 300 | when :message 301 | if has_message?(name) 302 | if has_message_alias?(name) 303 | aliases[:inventory][name][:attributes] 304 | else 305 | messages[name][:attributes] 306 | end 307 | end 308 | end 309 | end 310 | 311 | def types_for(object, name) 312 | case object 313 | when :type 314 | if has_type?(name) 315 | attributes_for(object, name).inject({}) do |defaults, attribute| 316 | defaults.merge!(attribute => types[name][attribute][:type]) 317 | end 318 | end 319 | when :message 320 | if has_message?(name) 321 | attributes_for(object, name).inject({}) do |defaults, attribute| 322 | defaults.merge!(attribute => messages[name][attribute][:type]) 323 | end 324 | end 325 | end 326 | end 327 | 328 | def defaults_for(object, name) 329 | case object 330 | when :type 331 | attributes_for(object, name).inject({}) do |defaults, attribute| 332 | defaults.merge!(attribute => types[name][attribute][:default]) 333 | end 334 | when :message 335 | attributes_for(object, name).inject({}) do |defaults, attribute| 336 | defaults.merge!(attribute => messages[name][attribute][:default]) 337 | end 338 | end 339 | end 340 | 341 | def struct_attributes_for(object, name) 342 | attributes = attributes_for(object, name) 343 | if attributes.any? 344 | attributes.map{|a| ":#{a}"}.unshift(' ').join(', ') 345 | else 346 | '' 347 | end 348 | end 349 | 350 | def class_for(object, name) 351 | case object 352 | when :type then types[:classes][name] 353 | when :message then messages[:classes][name] 354 | end 355 | end 356 | 357 | def alias_for(message) 358 | aliases[message] if aliases.has_key?(message) 359 | end 360 | 361 | class Definition 362 | PROTOCOL = Bitcoin::Protocol 363 | RESOURCE = { 364 | :integer => '((uint|int)(8|16|32|64|128)_?(big|little|network)?)', 365 | :ipv6 => '(uint128_network|ipv6)', 366 | :bignum => '(uint256_little|bignum)', 367 | :bitcoin => '(string|address|block_locator|inventory|transaction|tx_input|tx_output|out_point|compact_target)' 368 | } 369 | COLLECTION = '(vector|collection)' 370 | 371 | KNOWN = Regexp::compile("((#{RESOURCE.values.join('|')})(_#{COLLECTION})?)") 372 | 373 | def initialize(object, name, options={}) 374 | @object, @name, @options = object, name, options 375 | case @object 376 | when :constant then 377 | # :CONSTANT_1 => value 378 | PROTOCOL.consts[@name] = options[:value] 379 | when :type then 380 | if not PROTOCOL.types.has_key? @name 381 | # :type_m => { 382 | # :struct => %w[name_1, ..., name_n] 383 | # ... 384 | PROTOCOL.types[@name] = { :struct => [] } 385 | end 386 | when :message then 387 | if options[:alias] 388 | # :aliases => {:alias_1 => :msg_1, ..., alias_n => :msg_n} 389 | PROTOCOL.messages[:aliases].merge!(options[:alias] => @name) 390 | end 391 | if not PROTOCOL.messages.has_key? @name 392 | # :msg_1 => { 393 | # :attributes = %w[attr_1 ... attr_n] 394 | # ... 395 | PROTOCOL.messages[@name] = { :attributes => [] } 396 | end 397 | end 398 | end 399 | 400 | def add(field_name, opt={}) 401 | case @object 402 | when :type then 403 | PROTOCOL.types[@name][:struct].push(field_name) 404 | PROTOCOL.types[@name][field_name] = opt 405 | when :message then 406 | PROTOCOL.messages[@name][:attributes].push(field_name) 407 | PROTOCOL.messages[@name][field_name] = opt 408 | end 409 | end 410 | 411 | def method_missing(field_type, field_name, opts={}, &block) 412 | if field_type.to_s =~ KNOWN 413 | resource = Regexp.last_match[0] 414 | add(field_name, {:type => resource.to_sym, :default => opts[:default]}) 415 | elsif field_type.to_s =~ /^((\w)(_\w)+)$/ 416 | resource = Regexp.last_match[1] 417 | add(field_name, {:type => resource.to_sym, :default => opts[:default]}) 418 | else 419 | super 420 | end 421 | end 422 | end 423 | end 424 | 425 | -------------------------------------------------------------------------------- /lib/bitcoin/protocol/crypto.rb: -------------------------------------------------------------------------------- 1 | require 'digest/sha2' 2 | 3 | module Bitcoin::Protocol 4 | # 5 | # 6 | # 7 | module Crypto 8 | def doubleSHA256(string) 9 | Digest::SHA256.digest(Digest::SHA256.digest(string)) 10 | end 11 | 12 | def ripe160_with_sha256(string) 13 | ripemd160 = `echo #{string} | openssl dgst -ripemd160 -binary` 14 | Digest::SHA256.digest(ripemd160).unpack('C*') 15 | end 16 | 17 | def base58encode(string) 18 | raise NotImplementedError #TODO 19 | end 20 | 21 | def base58decode(string) 22 | raise NotImplementedError #TODO 23 | end 24 | 25 | def generate_key_pair 26 | raise NotImplementedError #TODO 27 | end 28 | end 29 | end 30 | 31 | -------------------------------------------------------------------------------- /lib/bitcoin/protocol/message.rb: -------------------------------------------------------------------------------- 1 | class IO 2 | def read_exactly(n) 3 | buf = read(n) 4 | fail EOFError if buf == nil 5 | return buf if buf.size == n 6 | 7 | n -= buf.size 8 | 9 | while n > 0 10 | str = read(n) 11 | fail EOFError if str == nil 12 | buf << str 13 | n -= str.size 14 | end 15 | return buf 16 | end 17 | end 18 | 19 | module Bitcoin::Protocol 20 | module Message 21 | # In this module we define the common behaviour 22 | # for a protocol message 23 | # 24 | # - read a message from a stream 25 | # - returns a particular message when found 26 | # - a sequence of fields as class variables 27 | # - hash of fields containing a serialization 28 | # type and a default value 29 | 30 | extend Crypto 31 | 32 | class LoadError ; end 33 | class DumpError ; end 34 | class ValueNotAllowed < RuntimeError; end 35 | 36 | class BadMagicNumber < ValueNotAllowed; end 37 | class UnknownCommand < ValueNotAllowed; end 38 | class BadPayload < ValueNotAllowed; end 39 | class BadLength < ValueNotAllowed; end 40 | 41 | attr_reader :magic_number, :command, :length, :checksum, :payload 42 | 43 | # adds these to the singleton through configuration 44 | @@attributes = [ :magic_number, :command, :length, :checksum, :payload ] 45 | @@defaults = { :magic_number => [:MAGIC, BtcProto.current_network], 46 | :command => :verack, 47 | :length => 0, 48 | :checksum => 0, 49 | :payload => 0 } 50 | @@types = { :magic_number => :uint32_little, 51 | :command => :null_padded_string_of_12_bytes, 52 | :length => :int32_little, 53 | :checksum => :int32_little, 54 | :payload => [:fixed_string, :length] } 55 | 56 | # it should define the header size 57 | def self.read(stream) 58 | header = Buffer.new(stream.read_exactly(4+12+4+4)) 59 | magic_number = header.read_uint32_little 60 | fail BadMagicNumber if not BtcProto.lookup(:MAGIC).values.include?(magic_number) 61 | network = NETWORKS.index(magic_number) 62 | command = header.read_fixed_size_string(12,:padding => 0.chr).to_sym 63 | fail UnknownCommand if not BtcProto.proper_message_names.include?(command) 64 | length = header.read_int32_little 65 | checksum = header.read_int32_little 66 | payload = Buffer.new(stream.read_exactly(length)).content # possibly avoid Buffer 67 | fail BadPaylod if not valid_payload?(payload, [checksum].pack('V')) 68 | 69 | BtcProto.class_for(command).load(payload) 70 | end 71 | 72 | def self.valid_payload?(content, checksum) 73 | doubleSHA256(content)[0..3] == checksum 74 | end 75 | 76 | def self.compute_checksum_for(content) 77 | doubleSHA256(content)[0..3] 78 | end 79 | 80 | # def magic_number(network) 81 | # return nil if not NETWORKS.keys.include?(network) 82 | # NETWORKS[network] 83 | # end 84 | 85 | # check if buffer size is ok before 86 | def self.load(buffer) 87 | marshal(:read, buffer) 88 | end 89 | 90 | alias :restore :load 91 | 92 | def self.dump(buffer) 93 | # check if buffer size is ok before 94 | marshal(:write, buffer) 95 | end 96 | 97 | def marshal(op, buffer) 98 | case op 99 | when :read 100 | attributes.each do |attribute| 101 | binary_op = "#{read}_#{types[attribute]}".to_sym 102 | self.send("#{attribute}=", buffer.send(binary_op)) 103 | end 104 | when :write 105 | attributes.each do |attribute| 106 | binary_op = "#{write}_#{types[attribute]}".to_sym 107 | self.send("#{attribute}=", buffer.send(binary_op)) 108 | end 109 | end 110 | end 111 | 112 | private :marshal 113 | 114 | def size 115 | end 116 | 117 | # compute_checksum 118 | def compute_checksum 119 | end 120 | end 121 | end 122 | 123 | BtcMsg = Bitcoin::Protocol::Message 124 | 125 | -------------------------------------------------------------------------------- /lib/bitcoin/protocol/serializable.rb: -------------------------------------------------------------------------------- 1 | module Bitcoin 2 | module Protocol 3 | module Serializable 4 | ## who includes me must have the following 5 | # class variables: 6 | # @@fields 7 | # @@types 8 | # @@defaults 9 | 10 | def self.included(receiver) 11 | # receiver.extend self 12 | receiver.send :include, self 13 | end 14 | 15 | 16 | # receives the content 17 | def load(content) 18 | buf = BtcProto::Buffer.new(content) 19 | attributes.each do |a| 20 | puts "buf.read_#{types[a]}" 21 | end 22 | end 23 | 24 | def dump 25 | v = '' 26 | attributes.each do |a| 27 | puts "\":write_#{types[a]}\" (#{a}) #{BtcProto::Type.size(types[a])}" 28 | end 29 | end 30 | 31 | # alias :load :parse 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/bitcoin/protocol/types.rb: -------------------------------------------------------------------------------- 1 | # This module provides methods to handle: 2 | # 3 | # - types (de)serialization 4 | # - types size calculation 5 | # 6 | # Mappings can be configured to provide serialization and 7 | # size calculation functionality for external (vectors of) 8 | # data structures introduced in the original Bitcoin protocol 9 | # implementation. 10 | # 11 | # Types is a mixin and assumes that the following methods: 12 | # 13 | # read(n_bytes) 14 | # write(string) 15 | # 16 | # are available in the class/module where this module 17 | # is mixed in. 18 | # 19 | # The set of recognized names are exposed by OBJECTS and 20 | # COLLECTIONS constants. 21 | # 22 | # By including Binary mixin --and sharing its the very same 23 | # interface, Types provides dynamically common methods 24 | # for reading/writing from/to binary buffer streams. 25 | # Read about Binary mixin for more information. 26 | # 27 | # Types performs operations recognized by the following 28 | # grammars: 29 | # 30 | # operations ::= 'read' | 'write' 31 | # btc_encoding ::= 'encoded' | 'vector' 32 | # numbers ::= 'bignum' | 'uint256_little' 33 | # strings ::= 'string' 34 | # 35 | 36 | module Bitcoin::Protocol 37 | module Types 38 | include Binary 39 | 40 | OP_TYPE_RE = /(read|write)_(\w+)(_(vector))?/ 41 | 42 | # Mapping between a type name and a class is configured externally 43 | # through an hash containing associations. The associated class is 44 | # supposed to provide a load and a dump methods to be employed by, 45 | # respectively, read_encoded_object and write_encoded_object. 46 | class << self 47 | def mappings() 48 | @@mappings 49 | end 50 | 51 | def mappings=(hash={}) 52 | @@mappings = hash 53 | build_mapped_methods! 54 | end 55 | 56 | def valid(mappings, &block) 57 | raise ArgumentError if not mappings.kind_of?(Hash) 58 | validate = lambda { |o| [:load, :dump].each { |m| o.respond_to? m } } 59 | mappings.each_pair do |type, obj| 60 | yield(type, obj) if validate.call(obj) 61 | end 62 | end 63 | 64 | def blank(op, type) 65 | ["#{op}_#{type}", "#{op}_#{type}_vector"].map(&:to_sym).each do |m| 66 | undef_method(m) if defined? m 67 | end 68 | end 69 | 70 | def build_mapped_methods! 71 | valid(@@mappings) do |type, obj| 72 | [:read, :write].each do |op| 73 | # blank(op, type) 74 | case op 75 | when :read then 76 | define_method "read_#{type}".to_sym do 77 | read_encoded_object(type) 78 | end 79 | 80 | define_method "read_#{type}_vector".to_sym do 81 | read_encoded_object_vector(type) 82 | end 83 | when :write then 84 | define_method "write_#{type}".to_sym do |buf| 85 | end 86 | define_method "write_#{type}_vector".to_sym do |buf| 87 | end 88 | end 89 | end 90 | end 91 | end 92 | end 93 | 94 | # NOTE: given the fact that messages above 50KB in size are invalid, 95 | # the standard mechanism for de/encoding size could be reviewed. 96 | # Likely, a uniform 16 bits Pascal string may manage messages up to 65KB; 97 | # compression mechanisms could be discussed whether the need arises. 98 | def read_encoded_size 99 | code = read_uint8 100 | case code 101 | when 253 then size_of(:uint16) + read_uint16_little # upto ~65KB (80%) 102 | when 254 then size_of(:uint32) + read_uint32_little # upto ~4GB (!) 103 | when 255 then sise_of(:uint64) + read_uint64_little # upto ~1.8e9GB (!!) 104 | else 105 | code # size ≤ 252 bytes (happens 20%) 106 | end 107 | end 108 | 109 | # TODO: separate into size and string 110 | def write_encoded_size(size) 111 | raise ArgumentError if not size.kind_of? Integer 112 | case 113 | when (1 < size and size < 252) then 114 | write_uint8(size) 115 | when (253 < size and size < 0xFFFF) then 116 | write_uint8(253) 117 | write_uint16_little(size) 118 | when (0x10000 < size and size < 0xFFFFFFFF) then 119 | write_uint8(254) 120 | write_uint32_little(size) 121 | when (0x100000000 < size and size < 0xFFFFFFFFFFFFFFFF) then 122 | write_uint8(255) 123 | write_uint64_little(size) 124 | end 125 | size 126 | end 127 | 128 | def write_encoded_string(content) 129 | raise ArgumentError, 'string required' if not content.kind_of? String 130 | write_encoded_size(content.size) 131 | write_fixed_size_string(content) # expected from the public interface 132 | end 133 | 134 | def read_encoded_string 135 | # buffer position goes forward by the read_encoded_size 136 | read_string(:size => read_encoded_size) # provided by Binary 137 | end 138 | 139 | alias :read_bignum :read_uint256_little 140 | alias :write_bignum :write_uint256_little 141 | 142 | def read_encoded_bignum_vector 143 | result = [] 144 | read_encoded_size.times do 145 | result.push(read_bignum) 146 | end 147 | result 148 | end 149 | 150 | def write_encoded_bignum_vector(bignum_array) 151 | raise ArgumentError, 'array required' if not bignum_array.respond_to? :each 152 | write_encoded_size(bignum_array.size) 153 | bignum_array.each do |bignum| 154 | raise ArgumentError if not bignum.kind_of? Integer 155 | write_bignum(bignum) 156 | end 157 | end 158 | 159 | # def uint256_from_compact(c): 160 | # nbytes = (c >> 24) & 0xFF 161 | # v = (c & 0xFFFFFFL) << (8 * (nbytes - 3)) 162 | # return vv 163 | def read_compact_target 164 | bits = read_uint32_little 165 | bytes = (bits >> 24) & 0xFF 166 | bignum = (bits & 0xFFFFFF) << (8 * (bytes - 3)) 167 | bignum 168 | end 169 | 170 | def write_compact_target(bignum) 171 | end 172 | 173 | def read_encoded_object(type) 174 | fail "Unknown #{type} type" if not @@mappings.keys.includes?(type) 175 | @@mappings[type].load(self) 176 | end 177 | 178 | def read_encoded_object_vector(type) 179 | fail "Unknown #{type} type" if not @@mappings.keys.includes?(type) 180 | result = [] 181 | read_encoded_size.times do 182 | # read op of the given type 183 | result.push(read_encoded_object(type)) 184 | end 185 | result 186 | end 187 | 188 | private :read_encoded_object, :read_encoded_object_vector 189 | 190 | def write_encoded_object(type = :inventory) 191 | fail "Unknown #{type} type" if not @@mappings.keys.includes?(type) 192 | @@mappings[type].dump(self) 193 | end 194 | 195 | def write_encoded_object_vector(type = :inventory) 196 | fail "Unknown #{type} type" if not @@mappings.keys.includes?(type) 197 | read_encoded_size.times do 198 | write_encoded_object(type) 199 | end 200 | end 201 | 202 | private :write_encoded_object, :write_encoded_object 203 | 204 | def read_uint256_vector 205 | end 206 | end 207 | end 208 | 209 | -------------------------------------------------------------------------------- /lib/bitcoin/utils.rb: -------------------------------------------------------------------------------- 1 | module Bitcoin 2 | module Utils 3 | extend self 4 | 5 | def uint256_from_compact(c) 6 | # nbytes = (c >> 24) & 0xFF 7 | # v = (c & 0xFFFFFFL) << (8 * (nbytes - 3)) 8 | # return v 9 | end 10 | 11 | def string_to_bignum 12 | # s = self.dup.rjust(32,'0') 13 | # r = 0 14 | # t = self[0..32].unpack("> 24) & 0xFF 20 | # v = (c & 0xFFFFFFL) << (8 * (nbytes - 3)) 21 | # return v 22 | # 23 | def compact_target_to_bignum 24 | end 25 | 26 | # alias compact_target_to_bignum compact_target_to_uint256 27 | 28 | # def deser_vector(f, c): 29 | # r = [] 30 | # for i in xrange(nit): 31 | # t = c() 32 | # t.deserialize(f) 33 | # r.append(t) 34 | # return r 35 | # 36 | def read_vector(opt = {:type => :address}) 37 | result = [] 38 | read_encoded_size.times do 39 | result.push(read_uint8) 40 | end 41 | result 42 | end 43 | 44 | def underscore(camel_cased_word) 45 | word = camel_cased_word.to_s.dup 46 | word.gsub!(/::/, '/') 47 | word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') 48 | word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') 49 | word.tr!("-", "_") 50 | word.downcase! 51 | word 52 | end 53 | 54 | def camelize(lower_case_and_underscored_word, first_letter_in_uppercase = true) 55 | if first_letter_in_uppercase 56 | lower_case_and_underscored_word.to_s.gsub(/\/(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase } 57 | else 58 | lower_case_and_underscored_word.to_s[0].chr.downcase + camelize(lower_case_and_underscored_word)[1..-1] 59 | end 60 | end 61 | end 62 | end 63 | 64 | class String 65 | [:underscore, :camelize].each {|method| undef method if self.respond_to? method } 66 | 67 | def underscore 68 | Bitcoin::Utils.underscore(self) 69 | end 70 | 71 | def camelize 72 | Bitcoin::Utils.camelize(self) 73 | end 74 | 75 | def to_bignum 76 | Bitcoin::Utils.string_to_bignum(self) 77 | end 78 | 79 | alias_method(:to_uint256, :to_bignum) 80 | end 81 | 82 | -------------------------------------------------------------------------------- /tasks/bitcoin.rb: -------------------------------------------------------------------------------- 1 | require 'fileutils' 2 | 3 | module BitcoinRunner 4 | module Config 5 | HOST = { 6 | :temp_dir => '~/tmp/bitcoin', 7 | :data_dir => '~/tmp/bitcoin/data', 8 | :config_dir => '~/tmp/bitcoin/bitcoin.conf', 9 | :dtach_socket => '~/tmp/bitcoin.dtach', 10 | :debug_file => '~/Library/Application\ Support/Bitcoin/debug.log' # I hate such dir names 11 | } 12 | end 13 | 14 | def self.debug_file 15 | Config::HOST[:debug_file] 16 | end 17 | 18 | def self.config_dir 19 | Config::HOST[:config_dir] 20 | end 21 | 22 | def self.dtach_socket 23 | Config::HOST[:dtach_socket] 24 | end 25 | 26 | def self.attach 27 | exec "dtach -a #{dtach_socket}" 28 | end 29 | 30 | def self.running? 31 | File.exists? dtach_socket 32 | end 33 | 34 | def self.start 35 | bitcoin_cmd = 'bitcoin -gen=0 -connect=127.0.0.1' 36 | tail_cmd = "tail -f #{debug_file}" 37 | puts 'Detach with Ctrl+\ ' 38 | puts 'Re-attach with rake bitcoin:attach' 39 | sleep 2 40 | exec "dtach -A #{dtach_socket} #{bitcoin_cmd}" 41 | end 42 | 43 | def self.stop 44 | sh 'killall bitcoin' 45 | rm dtach_socket 46 | end 47 | end 48 | 49 | namespace :bitcoin do 50 | desc 'Start Bitcoin' 51 | task :start do 52 | print 'Starting Bitcoin ...' 53 | BitcoinRunner.start 54 | puts 'done' 55 | end 56 | 57 | desc 'Stop Bitcoin' 58 | task :stop do 59 | print 'Stopping Bitcoin ...' 60 | BitcoinRunner.stop 61 | puts 'done' 62 | end 63 | 64 | desc 'Restart Bitcoin' 65 | task :restart => [:stop, :start] 66 | 67 | desc 'Attach to Bitcoin dtach socket' 68 | task :attach do 69 | BitcoinRunner.attach 70 | end 71 | end 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /test/fixtures/inv.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altamic/bitcoin-protocol/1fdae46a87cbff285685ac5367862cd2b8f45e3b/test/fixtures/inv.bin -------------------------------------------------------------------------------- /test/fixtures/msg_bin.rb: -------------------------------------------------------------------------------- 1 | 2 | 3 | [addr].each_with_index do |m, i| 4 | File.open("message_#{i}.bin", 'wb') do |f| 5 | m.each do |byte| 6 | f.write([byte].pack('C')) 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /test/fixtures/seeds.rb: -------------------------------------------------------------------------------- 1 | require 'ipaddr' 2 | 3 | seeds = [ 0x1ddb1032, 0x6242ce40, 0x52d6a445, 0x2dd7a445, 0x8a53cd47, 0x73263750, 0xda23c257, 0xecd4ed57, 4 | 0x0a40ec59, 0x75dce160, 0x7df76791, 0x89370bad, 0xa4f214ad, 0x767700ae, 0x638b0418, 0x868a1018, 5 | 0xcd9f332e, 0x0129653e, 0xcc92dc3e, 0x96671640, 0x56487e40, 0x5b66f440, 0xb1d01f41, 0xf1dc6041, 6 | 0xc1d12b42, 0x86ba1243, 0x6be4df43, 0x6d4cef43, 0xd18e0644, 0x1ab0b344, 0x6584a345, 0xe7c1a445, 7 | 0x58cea445, 0xc5daa445, 0x21dda445, 0x3d3b5346, 0x13e55347, 0x1080d24a, 0x8e611e4b, 0x81518e4b, 8 | 0x6c839e4b, 0xe2ad0a4c, 0xfbbc0a4c, 0x7f5b6e4c, 0x7244224e, 0x1300554e, 0x20690652, 0x5a48b652, 9 | 0x75c5c752, 0x4335cc54, 0x340fd154, 0x87c07455, 0x087b2b56, 0x8a133a57, 0xac23c257, 0x70374959, 10 | 0xfb63d45b, 0xb9a1685c, 0x180d765c, 0x674f645d, 0x04d3495e, 0x1de44b5e, 0x4ee8a362, 0x0ded1b63, 11 | 0xc1b04b6d, 0x8d921581, 0x97b7ea82, 0x1cf83a8e, 0x91490bad, 0x09dc75ae, 0x9a6d79ae, 0xa26d79ae, 12 | 0x0fd08fae, 0x0f3e3fb2, 0x4f944fb2, 0xcca448b8, 0x3ecd6ab8, 0xa9d5a5bc, 0x8d0119c1, 0x045997d5, 13 | 0xca019dd9, 0x0d526c4d, 0xabf1ba44, 0x66b1ab55, 0x1165f462, 0x3ed7cbad, 0xa38fae6e, 0x3bd2cbad, 14 | 0xd36f0547, 0x20df7840, 0x7a337742, 0x549f8e4b, 0x9062365c, 0xd399f562, 0x2b5274a1, 0x8edfa153, 15 | 0x3bffb347, 0x7074bf58, 0xb74fcbad, 0x5b5a795b, 0x02fa29ce, 0x5a6738d4, 0xe8a1d23e, 0xef98c445, 16 | 0x4b0f494c, 0xa2bc1e56, 0x7694ad63, 0xa4a800c3, 0x05fda6cd, 0x9f22175e, 0x364a795b, 0x536285d5, 17 | 0xac44c9d4, 0x0b06254d, 0x150c2fd4, 0x32a50dcc, 0xfd79ce48, 0xf15cfa53, 0x66c01e60, 0x6bc26661, 18 | 0xc03b47ae, 0x4dda1b81, 0x3285a4c1, 0x883ca96d, 0x35d60a4c, 0xdae09744, 0x2e314d61, 0x84e247cf, 19 | 0x6c814552, 0x3a1cc658, 0x98d8f382, 0xe584cb5b, 0x15e86057, 0x7b01504e, 0xd852dd48, 0x56382f56, 20 | 0x0a5df454, 0xa0d18d18, 0x2e89b148, 0xa79c114c, 0xcbdcd054, 0x5523bc43, 0xa9832640, 0x8a066144, 21 | 0x3894c3bc, 0xab76bf58, 0x6a018ac1, 0xfebf4f43, 0x2f26c658, 0x31102f4e, 0x85e929d5, 0x2a1c175e, 22 | 0xfc6c2cd1, 0x27b04b6d, 0xdf024650, 0x161748b8, 0x28be6580, 0x57be6580, 0x1cee677a, 0xaa6bb742, 23 | 0x9a53964b, 0x0a5a2d4d, 0x2434c658, 0x9a494f57, 0x1ebb0e48, 0xf610b85d, 0x077ecf44, 0x085128bc, 24 | 0x5ba17a18, 0x27ca1b42, 0xf8a00b56, 0xfcd4c257, 0xcf2fc15e, 0xd897e052, 0x4cada04f, 0x2f35f6d5, 25 | 0x382ce8c9, 0xe523984b, 0x3f946846, 0x60c8be43, 0x41da6257, 0xde0be142, 0xae8a544b, 0xeff0c254, 26 | 0x1e0f795b, 0xaeb28890, 0xca16acd9, 0x1e47ddd8, 0x8c8c4829, 0xd27dc747, 0xd53b1663, 0x4096b163, 27 | 0x9c8dd958, 0xcb12f860, 0x9e79305c, 0x40c1a445, 0x4a90c2bc, 0x2c3a464d, 0x2727f23c, 0x30b04b6d, 28 | 0x59024cb8, 0xa091e6ad, 0x31b04b6d, 0xc29d46a6, 0x63934fb2, 0xd9224dbe, 0x9f5910d8, 0x7f530a6b, 29 | 0x752e9c95, 0x65453548, 0xa484be46, 0xce5a1b59, 0x710e0718, 0x46a13d18, 0xdaaf5318, 0xc4a8ff53, 30 | 0x87abaa52, 0xb764cf51, 0xb2025d4a, 0x6d351e41, 0xc035c33e, 0xa432c162, 0x61ef34ae, 0xd16fddbc, 31 | 0x0870e8c1, 0x3070e8c1, 0x9c71e8c1, 0xa4992363, 0x85a1f663, 0x4184e559, 0x18d96ed8, 0x17b8dbd5, 32 | 0x60e7cd18, 0xe5ee104c, 0xab17ac62, 0x1e786e1b, 0x5d23b762, 0xf2388fae, 0x88270360, 0x9e5b3d80, 33 | 0x7da518b2, 0xb5613b45, 0x1ad41f3e, 0xd550854a, 0x8617e9a9, 0x925b229c, 0xf2e92542, 0x47af0544, 34 | 0x73b5a843, 0xb9b7a0ad, 0x03a748d0, 0x0a6ff862, 0x6694df62, 0x3bfac948, 0x8e098f4f, 0x746916c3, 35 | 0x02f38e4f, 0x40bb1243, 0x6a54d162, 0x6008414b, 0xa513794c, 0x514aa343, 0x63781747, 0xdbb6795b, 36 | 0xed065058, 0x42d24b46, 0x1518794c, 0x9b271681, 0x73e4ffad, 0x0654784f, 0x438dc945, 0x641846a6, 37 | 0x2d1b0944, 0x94b59148, 0x8d369558, 0xa5a97662, 0x8b705b42, 0xce9204ae, 0x8d584450, 0x2df61555, 38 | 0xeebff943, 0x2e75fb4d, 0x3ef8fc57, 0x9921135e, 0x8e31042e, 0xb5afad43, 0x89ecedd1, 0x9cfcc047, 39 | 0x8fcd0f4c, 0xbe49f5ad, 0x146a8d45, 0x98669ab8, 0x98d9175e, 0xd1a8e46d, 0x839a3ab8, 0x40a0016c, 40 | 0x6d27c257, 0x977fffad, 0x7baa5d5d, 0x1213be43, 0xb167e5a9, 0x640fe8ca, 0xbc9ea655, 0x0f820a4c, 41 | 0x0f097059, 0x69ac957c, 0x366d8453, 0xb1ba2844, 0x8857f081, 0x70b5be63, 0xc545454b, 0xaf36ded1, 42 | 0xb5a4b052, 0x21f062d1, 0x72ab89b2, 0x74a45318, 0x8312e6bc, 0xb916965f, 0x8aa7c858, 0xfe7effad] 43 | 44 | seeds.map {|inet_addr| puts IPAddr.new(inet_addr, Socket::AF_INET).to_s} 45 | 46 | -------------------------------------------------------------------------------- /test/fixtures/tools/bitdump.in.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altamic/bitcoin-protocol/1fdae46a87cbff285685ac5367862cd2b8f45e3b/test/fixtures/tools/bitdump.in.dump -------------------------------------------------------------------------------- /test/fixtures/tools/bitdump.out.dump: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altamic/bitcoin-protocol/1fdae46a87cbff285685ac5367862cd2b8f45e3b/test/fixtures/tools/bitdump.out.dump -------------------------------------------------------------------------------- /test/fixtures/tools/bitdump_2010-02-04_21:18.pcap: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altamic/bitcoin-protocol/1fdae46a87cbff285685ac5367862cd2b8f45e3b/test/fixtures/tools/bitdump_2010-02-04_21:18.pcap -------------------------------------------------------------------------------- /test/fixtures/tools/connected_nodes: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # bitdump.sh 4 | # 5 | # Captures Bitcoin network traffic using netcat 6 | 7 | SELF=`basename $0` 8 | 9 | remote_nodes() { 10 | netstat -an | 11 | awk '/8333/ && /ESTA/ { print $5 }' | 12 | sed 's/[:.]\(8333\)$//' 13 | } 14 | 15 | NODES=($(remote_nodes)) 16 | 17 | for (( i = 0; i < ${#NODES[*]}; i++ )); do 18 | echo ${NODES[i]} 19 | done 20 | -------------------------------------------------------------------------------- /test/fixtures/tools/dump-via-proxy: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # Captures raw Bitcoin network 4 | # traffic using a nc proxy 5 | 6 | SELF=`basename $0` 7 | 8 | remote_nodee() { 9 | netstat -an | 10 | awk '/8333/ && /ESTA/ { print $5 }' | 11 | sed 's/[:.]\(8333\)$/ \1/' 12 | } 13 | 14 | filename() { 15 | node=${1} 16 | local name 17 | # local timestamp 18 | name="netdump" 19 | # timestamp=$(date -u +"%Y-%m-%d_%H-%M") 20 | # echo "${name}_for_${node}" 21 | echo $name 22 | } 23 | 24 | # start a bitcoin instance 25 | # save its pid 26 | # wait 20 seconds 27 | # find connected nodes 28 | # if any kill bitcoin 29 | # set the proxy 30 | # launch proxified bitcoin as a demon 31 | # hostname_port service 32 | 33 | # netcat options 34 | NO_DNS="-n" 35 | # NODES=$(remote_nodes) 36 | NODES=(128.118.58.209) 37 | SOURCE='127.0.0.1' 38 | 39 | PORT=8333 40 | PROXY_PORT=13338 41 | PIPE_FILE='fifo' 42 | 43 | bitcoin -proxy=$SOURCE:$PROXY_PORT & 44 | 45 | # make the pipe 46 | if [[ -p $PIPE_FILE ]]; then 47 | rm -f $PIPE_FILE 48 | else 49 | mkfifo $PIPE_FILE 50 | fi 51 | 52 | if [[ -z "$NODES" ]]; then 53 | echo "$SELF: No peer found, check your internet connection and that Bitcoin is running" 54 | else 55 | for (( i = 0; i < ${#NODES[*]}; i++ )); do 56 | # nc -l $PROXY_PORT < $PIPE_FILE | tee -a $(filename ${NODES[i]}).in.dump | nc ${NODES[i]} $PORT | tee -a $(filename ${NODES[i]}).out.dump 1> $PIPE_FILE & 57 | done 58 | fi 59 | 60 | -------------------------------------------------------------------------------- /test/fixtures/tools/seeds: -------------------------------------------------------------------------------- 1 | 29.219.16.50 2 | 98.66.206.64 3 | 82.214.164.69 4 | 45.215.164.69 5 | 138.83.205.71 6 | 115.38.55.80 7 | 218.35.194.87 8 | 236.212.237.87 9 | 10.64.236.89 10 | 117.220.225.96 11 | 125.247.103.145 12 | 137.55.11.173 13 | 164.242.20.173 14 | 118.119.0.174 15 | 99.139.4.24 16 | 134.138.16.24 17 | 205.159.51.46 18 | 1.41.101.62 19 | 204.146.220.62 20 | 150.103.22.64 21 | 86.72.126.64 22 | 91.102.244.64 23 | 177.208.31.65 24 | 241.220.96.65 25 | 193.209.43.66 26 | 134.186.18.67 27 | 107.228.223.67 28 | 109.76.239.67 29 | 209.142.6.68 30 | 26.176.179.68 31 | 101.132.163.69 32 | 231.193.164.69 33 | 88.206.164.69 34 | 197.218.164.69 35 | 33.221.164.69 36 | 61.59.83.70 37 | 19.229.83.71 38 | 16.128.210.74 39 | 142.97.30.75 40 | 129.81.142.75 41 | 108.131.158.75 42 | 226.173.10.76 43 | 251.188.10.76 44 | 127.91.110.76 45 | 114.68.34.78 46 | 19.0.85.78 47 | 32.105.6.82 48 | 90.72.182.82 49 | 117.197.199.82 50 | 67.53.204.84 51 | 52.15.209.84 52 | 135.192.116.85 53 | 8.123.43.86 54 | 138.19.58.87 55 | 172.35.194.87 56 | 112.55.73.89 57 | 251.99.212.91 58 | 185.161.104.92 59 | 24.13.118.92 60 | 103.79.100.93 61 | 4.211.73.94 62 | 29.228.75.94 63 | 78.232.163.98 64 | 13.237.27.99 65 | 193.176.75.109 66 | 141.146.21.129 67 | 151.183.234.130 68 | 28.248.58.142 69 | 145.73.11.173 70 | 9.220.117.174 71 | 154.109.121.174 72 | 162.109.121.174 73 | 15.208.143.174 74 | 15.62.63.178 75 | 79.148.79.178 76 | 204.164.72.184 77 | 62.205.106.184 78 | 169.213.165.188 79 | 141.1.25.193 80 | 4.89.151.213 81 | 202.1.157.217 82 | 13.82.108.77 83 | 171.241.186.68 84 | 102.177.171.85 85 | 17.101.244.98 86 | 62.215.203.173 87 | 163.143.174.110 88 | 59.210.203.173 89 | 211.111.5.71 90 | 32.223.120.64 91 | 122.51.119.66 92 | 84.159.142.75 93 | 144.98.54.92 94 | 211.153.245.98 95 | 43.82.116.161 96 | 142.223.161.83 97 | 59.255.179.71 98 | 112.116.191.88 99 | 183.79.203.173 100 | 91.90.121.91 101 | 2.250.41.206 102 | 90.103.56.212 103 | 232.161.210.62 104 | 239.152.196.69 105 | 75.15.73.76 106 | 162.188.30.86 107 | 118.148.173.99 108 | 164.168.0.195 109 | 5.253.166.205 110 | 159.34.23.94 111 | 54.74.121.91 112 | 83.98.133.213 113 | 172.68.201.212 114 | 11.6.37.77 115 | 21.12.47.212 116 | 50.165.13.204 117 | 253.121.206.72 118 | 241.92.250.83 119 | 102.192.30.96 120 | 107.194.102.97 121 | 192.59.71.174 122 | 77.218.27.129 123 | 50.133.164.193 124 | 136.60.169.109 125 | 53.214.10.76 126 | 218.224.151.68 127 | 46.49.77.97 128 | 132.226.71.207 129 | 108.129.69.82 130 | 58.28.198.88 131 | 152.216.243.130 132 | 229.132.203.91 133 | 21.232.96.87 134 | 123.1.80.78 135 | 216.82.221.72 136 | 86.56.47.86 137 | 10.93.244.84 138 | 160.209.141.24 139 | 46.137.177.72 140 | 167.156.17.76 141 | 203.220.208.84 142 | 85.35.188.67 143 | 169.131.38.64 144 | 138.6.97.68 145 | 56.148.195.188 146 | 171.118.191.88 147 | 106.1.138.193 148 | 254.191.79.67 149 | 47.38.198.88 150 | 49.16.47.78 151 | 133.233.41.213 152 | 42.28.23.94 153 | 252.108.44.209 154 | 39.176.75.109 155 | 223.2.70.80 156 | 22.23.72.184 157 | 40.190.101.128 158 | 87.190.101.128 159 | 28.238.103.122 160 | 170.107.183.66 161 | 154.83.150.75 162 | 10.90.45.77 163 | 36.52.198.88 164 | 154.73.79.87 165 | 30.187.14.72 166 | 246.16.184.93 167 | 7.126.207.68 168 | 8.81.40.188 169 | 91.161.122.24 170 | 39.202.27.66 171 | 248.160.11.86 172 | 252.212.194.87 173 | 207.47.193.94 174 | 216.151.224.82 175 | 76.173.160.79 176 | 47.53.246.213 177 | 56.44.232.201 178 | 229.35.152.75 179 | 63.148.104.70 180 | 96.200.190.67 181 | 65.218.98.87 182 | 222.11.225.66 183 | 174.138.84.75 184 | 239.240.194.84 185 | 30.15.121.91 186 | 174.178.136.144 187 | 202.22.172.217 188 | 30.71.221.216 189 | 140.140.72.41 190 | 210.125.199.71 191 | 213.59.22.99 192 | 64.150.177.99 193 | 156.141.217.88 194 | 203.18.248.96 195 | 158.121.48.92 196 | 64.193.164.69 197 | 74.144.194.188 198 | 44.58.70.77 199 | 39.39.242.60 200 | 48.176.75.109 201 | 89.2.76.184 202 | 160.145.230.173 203 | 49.176.75.109 204 | 194.157.70.166 205 | 99.147.79.178 206 | 217.34.77.190 207 | 159.89.16.216 208 | 127.83.10.107 209 | 117.46.156.149 210 | 101.69.53.72 211 | 164.132.190.70 212 | 206.90.27.89 213 | 113.14.7.24 214 | 70.161.61.24 215 | 218.175.83.24 216 | 196.168.255.83 217 | 135.171.170.82 218 | 183.100.207.81 219 | 178.2.93.74 220 | 109.53.30.65 221 | 192.53.195.62 222 | 164.50.193.98 223 | 97.239.52.174 224 | 209.111.221.188 225 | 8.112.232.193 226 | 48.112.232.193 227 | 156.113.232.193 228 | 164.153.35.99 229 | 133.161.246.99 230 | 65.132.229.89 231 | 24.217.110.216 232 | 23.184.219.213 233 | 96.231.205.24 234 | 229.238.16.76 235 | 171.23.172.98 236 | 30.120.110.27 237 | 93.35.183.98 238 | 242.56.143.174 239 | 136.39.3.96 240 | 158.91.61.128 241 | 125.165.24.178 242 | 181.97.59.69 243 | 26.212.31.62 244 | 213.80.133.74 245 | 134.23.233.169 246 | 146.91.34.156 247 | 242.233.37.66 248 | 71.175.5.68 249 | 115.181.168.67 250 | 185.183.160.173 251 | 3.167.72.208 252 | 10.111.248.98 253 | 102.148.223.98 254 | 59.250.201.72 255 | 142.9.143.79 256 | 116.105.22.195 257 | 2.243.142.79 258 | 64.187.18.67 259 | 106.84.209.98 260 | 96.8.65.75 261 | 165.19.121.76 262 | 81.74.163.67 263 | 99.120.23.71 264 | 219.182.121.91 265 | 237.6.80.88 266 | 66.210.75.70 267 | 21.24.121.76 268 | 155.39.22.129 269 | 115.228.255.173 270 | 6.84.120.79 271 | 67.141.201.69 272 | 100.24.70.166 273 | 45.27.9.68 274 | 148.181.145.72 275 | 141.54.149.88 276 | 165.169.118.98 277 | 139.112.91.66 278 | 206.146.4.174 279 | 141.88.68.80 280 | 45.246.21.85 281 | 238.191.249.67 282 | 46.117.251.77 283 | 62.248.252.87 284 | 153.33.19.94 285 | 142.49.4.46 286 | 181.175.173.67 287 | 137.236.237.209 288 | 156.252.192.71 289 | 143.205.15.76 290 | 190.73.245.173 291 | 20.106.141.69 292 | 152.102.154.184 293 | 152.217.23.94 294 | 209.168.228.109 295 | 131.154.58.184 296 | 64.160.1.108 297 | 109.39.194.87 298 | 151.127.255.173 299 | 123.170.93.93 300 | 18.19.190.67 301 | 177.103.229.169 302 | 100.15.232.202 303 | 188.158.166.85 304 | 15.130.10.76 305 | 15.9.112.89 306 | 105.172.149.124 307 | 54.109.132.83 308 | 177.186.40.68 309 | 136.87.240.129 310 | 112.181.190.99 311 | 197.69.69.75 312 | 175.54.222.209 313 | 181.164.176.82 314 | 33.240.98.209 315 | 114.171.137.178 316 | 116.164.83.24 317 | 131.18.230.188 318 | 185.22.150.95 319 | 138.167.200.88 320 | 254.126.255.173 321 | -------------------------------------------------------------------------------- /test/fixtures/version.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/altamic/bitcoin-protocol/1fdae46a87cbff285685ac5367862cd2b8f45e3b/test/fixtures/version.bin -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__)) 2 | 3 | # TODO: resort to test_unit where minitest 4 | # is not available and don't require 5 | # anything 6 | begin; gem 'minitest' if RUBY_VERSION =~ /^1\.9/; rescue Gem::LoadError; end 7 | 8 | begin 9 | require 'minitest/autorun' 10 | rescue LoadError 11 | require 'rubygems' 12 | require 'minitest/autorun' 13 | end 14 | 15 | require 'bitcoin' 16 | require 'bitcoin/protocol' 17 | 18 | module Bitcoin 19 | class TestCase < MiniTest::Unit::TestCase 20 | # 21 | # Test::Unit backwards compatibility 22 | # 23 | begin 24 | alias :assert_no_match :refute_match 25 | alias :assert_not_nil :refute_nil 26 | alias :assert_raise :assert_raises 27 | alias :assert_not_equal :refute_equal 28 | end if RUBY_VERSION =~ /^1\.9/ 29 | 30 | def fixture_path 31 | File.join(File.dirname(__FILE__), 'fixtures') 32 | end 33 | 34 | def lib_path 35 | File.join(File.dirname(__FILE__), '../lib/bitcoin') 36 | end 37 | 38 | def random_string(length) 39 | (0...length).inject("") { |m,n| m << (?A + rand(25)).chr } 40 | end 41 | 42 | def random_integer(bits, type=:uint) 43 | (type.equal? :int) ? -1*rand(2**(bits-1)) : rand(2**bits) 44 | end 45 | 46 | def pack_integer(integer, opt={:bytes => 4, :type => :uint, :endian => nil}) 47 | bytes, type, endian = opt[:bytes], opt[:type], opt[:endian] 48 | endian = :big if endian == :network 49 | endian = :native if endian == nil 50 | 51 | pack_mapping = { 52 | 1 => { :uint => { :native => 'C' }, 53 | :int => { :native => 'c' } }, 54 | 2 => { :uint => { :native => 'S', :little => 'v', :big => 'n' }, 55 | :int => { :native => 's', :little => 'v', :big => 'n' } }, 56 | 4 => { :uint => { :native => 'L', :little => 'V', :big => 'N' }, 57 | :int => { :native => 'l', :little => 'V', :big => 'N' } }, 58 | 8 => { :uint => { :native => 'Q' }, 59 | :int => { :native => 'q' } } } 60 | 61 | if bytes == 8 && (endian == :little || endian == :big) 62 | bytes = 4 63 | format = pack_mapping[bytes][type][endian] 64 | msb, lsb = (integer & 0xFFFFFFFF), (integer >> 16 & 0xFFFFFFFF) 65 | array = [msb, lsb] 66 | array.reverse! if [1,"\x01"].include?([1].pack('i')[0]) # little endian 67 | array.pack(format*2) 68 | else 69 | format = pack_mapping[bytes][type][endian] 70 | [integer].pack(format) 71 | end 72 | 73 | end 74 | end 75 | end 76 | -------------------------------------------------------------------------------- /test/protocol/test_binary.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestBinary < Bitcoin::TestCase 4 | def host_endianness 5 | first_byte = [1].pack('i')[0] 6 | [1,"\x01"].include?(first_byte) ? :little : :big 7 | end 8 | 9 | def test_detect_platform_endianness 10 | assert_equal BtcProto::Binary::NATIVE_BYTE_ORDER, host_endianness 11 | end 12 | 13 | def test_detect_platform_integer_size 14 | assert_equal BtcProto::Binary::INTEGER_SIZE_IN_BYTES, 1.size 15 | end 16 | 17 | def test_buffer_includes_binary 18 | assert(BtcProto::Buffer.included_modules & [Bitcoin::Protocol::Binary]) 19 | end 20 | 21 | def test_read_correctly_1_byte 22 | buf = BtcProto::Buffer.of_size(2) { 23 | write_uint8(0x55) 24 | } 25 | assert_equal 0x55, buf.read_uint8 26 | end 27 | 28 | def test_does_not_care_about_overflow 29 | overflow = 0xFF + 1 30 | buf = BtcProto::Buffer.of_size(1) { 31 | write_uint8(overflow) 32 | } 33 | assert_equal 0, buf.read_uint8 34 | end 35 | 36 | def test_read_correctly_2_bytes 37 | buf = BtcProto::Buffer.of_size(2) { 38 | write_uint16_little(0x55) 39 | } 40 | assert_equal 0x55, buf.read_uint16_little 41 | end 42 | 43 | def test_does_not_care_about_overflow_for_uint32 44 | overflow = 0xFFFFFFFF + 1 45 | buf = BtcProto::Buffer.of_size(4) { 46 | write_uint32(overflow) 47 | } 48 | assert_equal 0, buf.read_uint32 49 | end 50 | 51 | def test_read_correctly_4_bytes 52 | v2 = "\324{\000\000" 53 | v2.reverse! if host_endianness.equal? :little 54 | buf = BtcProto::Buffer.new(v2) 55 | value = 0 56 | buf.size.times do |i| 57 | assert_equal v2[i], buf.read_uint8 58 | end 59 | end 60 | 61 | def test_write_fixed_size_string 62 | content = 'new blips are arbitrarily created in the electronic transaction system of the Federal Reserve (known as FedWire), no outside detection is possible whatsoever because there is no outside system that verifies (or even can verify) the total quantity of FedWire deposits' 63 | buf = BtcProto::Buffer.of_size(content.size) { 64 | write_fixed_size_string(content) 65 | } 66 | assert_equal content, buf.content 67 | end 68 | 69 | def test_find_bytesize_for_int16_little 70 | assert_equal 2, BtcProto::Buffer.new('').size_of('read_int16_little') 71 | end 72 | 73 | def test_write_uint64 74 | bytes = [0xB7, 0x0A, 0x46, 0x25, 0xF1, 0xA2, 0x24, 0xCF] 75 | bytes.reverse! if BtcProto::Binary::NATIVE_BYTE_ORDER.equal? :little 76 | buf = BtcProto::Buffer.of_size(8) { write_uint64_big(n) } 77 | bytes.each_with_index do |byte, i| 78 | buf.position = i 79 | assert_equal byte, buf.read_uint8 80 | end 81 | buf.rewind 82 | expected = bytes.pack("C"*bytes.size) 83 | assert_equal n, buf.read_uint64 84 | end 85 | 86 | def test_read_uint128_little 87 | n = rand(2**128) 88 | assert_equal 16, n.size 89 | buf = BtcProto::Buffer.of_size(16) { write_uint128_little(n) } 90 | assert_equal n, buf.read_uint128_little 91 | end 92 | 93 | def test_read_uint256_little 94 | n = rand(2**256) 95 | assert_equal 32, n.size 96 | buf = BtcProto::Buffer.of_size(32) { write_uint256_little(n) } 97 | assert_equal n, buf.read_uint256_little 98 | end 99 | 100 | def test_read_int128_little 101 | skip 102 | n = -rand(2**127) 103 | assert_equal 16, n.size 104 | buf = BtcProto::Buffer.of_size(16) { write_int128_little(n) } 105 | assert_equal n, buf.read_int128_little 106 | end 107 | 108 | def test_read_int256_little 109 | skip 110 | n = -rand(2**255) 111 | assert_equal 32, n.size 112 | buf = BtcProto::Buffer.of_size(32) { write_int256_little(n) } 113 | assert_equal n, buf.read_int256_little 114 | end 115 | end 116 | 117 | # test read 118 | bytes_ary = BtcProto::Binary. 119 | send(:class_variable_get, :@@pack_mappings).keys.sort 120 | 121 | bytes_ary.each do |bytes| 122 | [:int, :uint].each do |type| 123 | [:native, :little, :big, :network].each do |mapped_endian| 124 | next if bytes.equal? 1 125 | TestBinary.class_eval do 126 | bits = bytes * 8 127 | packing = { :bytes => bytes, :type => type, :endian => mapped_endian } 128 | 129 | # read 130 | read_method = "read_#{type}#{bits}_#{mapped_endian}" 131 | define_method "test_#{read_method}" do 132 | number = random_integer(bits, type) 133 | packed_number = pack_integer(number, packing) 134 | buf = BtcProto::Buffer.new(packed_number) 135 | assert number, buf.send(read_method) 136 | end 137 | 138 | # write 139 | write_method = "write_#{type}#{bits}_#{mapped_endian}" 140 | define_method "test_#{write_method}" do 141 | number = random_integer(bits, type) 142 | buf = BtcProto::Buffer.of_size(bytes) { send(write_method, number) } 143 | assert number, buf.send(read_method) 144 | end 145 | 146 | #size 147 | define_method "test_#{type}#{bits}_length_is_#{bytes}_bytes" do 148 | assert bytes, BtcProto::Buffer.new('').size_of(read_method) 149 | assert bytes, BtcProto::Buffer.new('').size_of(write_method) 150 | end 151 | end 152 | end 153 | [nil].each do |no_endian_specified| 154 | bits = bytes * 8 155 | packing = { :bytes => bytes, 156 | :type => type, 157 | :endian => no_endian_specified } 158 | 159 | TestBinary.class_eval do 160 | read_method = "read_#{type}#{bits}" 161 | define_method "test_#{read_method}" do 162 | number = random_integer(bits, type) 163 | packed = pack_integer(number, packing) 164 | buf = BtcProto::Buffer.new(packed) 165 | assert number, buf.send(read_method) 166 | end 167 | 168 | write_method = "write_#{type}#{bits}" 169 | define_method "test_#{write_method}" do 170 | number = random_integer(bits, type) 171 | buf = BtcProto::Buffer.of_size(bytes) { 172 | send(write_method, number) 173 | } 174 | assert number, buf.send(read_method) 175 | end 176 | end 177 | end 178 | end 179 | end 180 | 181 | -------------------------------------------------------------------------------- /test/protocol/test_buffer.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestBuffer < Bitcoin::TestCase 4 | # Spec 5 | # A buffer is an IO entity able to 6 | # - hold content 7 | # - perform seeks 8 | # - manage the seek position 9 | # - read/write its content 10 | # - copy from a stream 11 | # - being initialized with a specified size 12 | 13 | def setup 14 | content = 'crispelle al miele' 15 | @buf = BtcProto::Buffer.new(content) 16 | end 17 | 18 | def test_rewind_buffers_initialized_with_size 19 | content = 'arancino al sugo' 20 | buf = BtcProto::Buffer.of_size(content.size) do 21 | write_fixed_size_string(content) 22 | end 23 | assert_equal 0, buf.position 24 | end 25 | 26 | def test_binary_module_is_included 27 | assert BtcProto::Buffer.included_modules.include?(BtcProto::Binary) 28 | end 29 | 30 | def test_can_be_initialized_with_content 31 | assert_instance_of BtcProto::Buffer, @buf 32 | end 33 | 34 | def test_can_tell_the_current_buffer_position 35 | assert_instance_of Fixnum, @buf.position 36 | end 37 | 38 | def test_can_set_position 39 | assert_respond_to @buf, :position= 40 | end 41 | 42 | def test_can_seek_to_a_positive_value 43 | @buf.position = 2 44 | assert 2, @buf.position 45 | end 46 | 47 | def test_raises_an_error_if_a_negative_index_is_given 48 | assert_raises(ArgumentError) { @buf.position = -1} 49 | end 50 | 51 | def test_raises_an_error_if_an_out_of_bounds_index_is_given 52 | assert_raises(ArgumentError) {@buf.position = (@buf.size + 1)} 53 | end 54 | 55 | def test_can_read_a_null_terminated_string 56 | buf = BtcProto::Buffer.new("home sweet home\000") 57 | assert_equal 'home sweet home', buf.read_string 58 | end 59 | 60 | def test_can_write_a_null_terminated_string 61 | buf = BtcProto::Buffer.of_size(12) do 62 | write_string("o pigghilu!") 63 | end 64 | assert buf.content.each_char.detect {|char| char == "\000" } 65 | end 66 | end 67 | 68 | -------------------------------------------------------------------------------- /test/protocol/test_configurator.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | # it should contain constants as an hash 4 | # it should be able to lookup at constants: differentiate cases 5 | # it should contain messages as an hash 6 | # it should contain types as an hash 7 | 8 | class TestConfigurator < Bitcoin::TestCase 9 | def setup 10 | @definition = BtcProto::Definition 11 | end 12 | 13 | def test_constant_definition 14 | pi = Math::PI 15 | @definition.new(:constant, :pi, :value => pi) 16 | assert BtcProto.consts.keys.include? :pi 17 | assert BtcProto.consts[:pi], pi 18 | end 19 | 20 | def test_type_definition_has_struct 21 | @definition.new(:type, :sample, {}) 22 | assert BtcProto.types[:sample].has_key?(:struct) 23 | end 24 | 25 | # def setup 26 | # @line =' register_message :addr, :alias => :addresses, :size_limit => 1000 do |m|' 27 | # @messages_re = /register_message\s+:(\w+)\s*/ 28 | # @aliases_re = /#{@messages_re}\.*\s*,\s*:alias\s*=>\s*:(\w+[_\w+]*)/ 29 | # @fields_re = /d/ 30 | # @configured_messages = fetch(:messages, :from => 'protocol/configuration.rb') 31 | # @configured_aliases = fetch(:aliases) 32 | # @configured_fields = fetch(:fields) 33 | 34 | # @messages_lines = fetch_lines_matching('register_message') 35 | # @fields_lines = fetch_lines_matching('m.') 36 | # end 37 | 38 | # def test_messages_re 39 | # assert_match @messages_re, @line 40 | # end 41 | 42 | # def test_messages_re_match 43 | # @line =~ @messages_re 44 | # assert_equal Regexp.last_match[1], 'addr' 45 | # end 46 | 47 | # def test_aliases_re 48 | # assert_match @aliases_re, @line 49 | # end 50 | 51 | # def test_aliases_re_match 52 | # @line =~ @aliases_re 53 | # assert_equal Regexp.last_match[2], 'addresses' 54 | # end 55 | 56 | # def test_aliases_re_match_with_underscore 57 | # line = ' register_message :getdata, :alias => :get_data, :size_limit => true' 58 | # line =~ @aliases_re 59 | # assert_equal Regexp.last_match[1], 'getdata' 60 | # assert_equal Regexp.last_match[2], 'get_data' 61 | # end 62 | 63 | # def test_fields_re 64 | # end 65 | 66 | # def test_fields_re_match 67 | # end 68 | 69 | # private 70 | # def fetch_lines_matching(string, opt={:from => 'protocol/configuration.rb'}) 71 | # rel_file_path = File.join(lib_path, opt[:from]) 72 | 73 | # matching_lines = [] 74 | # File.open(File.expand_path(rel_file_path)) do |f| 75 | # f.each_line do |line| 76 | # matching_lines.push(Regexp.last_match[1]) if line =~ /#{string}/ 77 | # end 78 | # end 79 | # matching_lines 80 | # end 81 | 82 | # def fetch(element, opt={:from => 'protocol/configuration.rb'}) 83 | # rel_file_path = File.join(lib_path, opt[:from]) 84 | 85 | # msg_re, match_idx = case element 86 | # when :messages then [@messages_re, 1 ] 87 | # when :aliases then [@aliases_re, 2 ] 88 | # when :fields then [@fields_re, 1..-1] 89 | # else 90 | # return nil 91 | # end 92 | 93 | # configured_elements = [] 94 | # File.open(File.expand_path(rel_file_path)) do |f| 95 | # f.each_line do |line| 96 | # configured_elements.push(Regexp.last_match[match_idx]) if line =~ msg_re 97 | # end 98 | # end 99 | # configured_elements 100 | # end 101 | end 102 | -------------------------------------------------------------------------------- /test/protocol/test_message.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestMessage < Bitcoin::TestCase 4 | def setup 5 | # TODO: collect more binary messages and generalize 6 | files = ['version.bin','inv.bin'] 7 | @io = File.open(File.join(fixture_path, files.last),'rb') 8 | end 9 | 10 | # a message should be able to load itself using the payload 11 | # a command should be able to dump itself 12 | 13 | # def test_read_instantiates_a_command 14 | # @io.seek(0) 15 | # result = BtcMsg.read(@io) 16 | # klazz = BtcProto 17 | # assert_instance_of BtcProto::Message, result 18 | # end 19 | 20 | # def test_read_class_method 21 | # @io.seek(0) 22 | # assert_no_exception BtcProto::Message.read(@io) 23 | # end 24 | 25 | # def test_deserialize_a_la_bitcoin 26 | # string = BtcProto::Message.new 27 | # @io.seek(0x10) 28 | # assert_equal string.size, @io_size 29 | # assert_equal 0x55, string.deserialize(@io).size 30 | # end 31 | end 32 | 33 | -------------------------------------------------------------------------------- /test/protocol/test_types.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestTypes < Bitcoin::TestCase 4 | # a type should know its size 5 | # a type should be able to serialize itself 6 | # a type should be able do deserialize itself 7 | # a type can be a collection of types 8 | # a type can a collection of different types 9 | # mappings should contain classes 10 | 11 | def test_write_encoded_size 12 | content = 'The ‘illusory’ money-energy that is spent by the borrower is accumulated via the depreciated value of the lender’s fractional money' 13 | assert content.size < 252 14 | buf = BtcProto::Buffer.of_size(1 + content.size) do 15 | write_encoded_string(content) 16 | end 17 | assert_equal content.size, buf.read_encoded_size 18 | end 19 | 20 | def test_read_encoded_string_case_one 21 | content = 'There is some remote possibility and flickering indication that new emerging digital currencies could scale “parasite free.”' 22 | buf = BtcProto::Buffer.of_size(1 + content.size) do 23 | write_uint8(content.size) 24 | write_fixed_size_string(content) 25 | end 26 | assert((1..252).include?(buf.size)) 27 | assert_equal content, buf.read_encoded_string 28 | end 29 | 30 | def test_read_encoded_string_case_two 31 | n = rand(0xFFFF - 253 + 1) + 253 32 | rand_str = random_string(n) 33 | buf = BtcProto::Buffer.of_size(1+2+n) do 34 | write_uint8(253) 35 | write_uint16(n) 36 | write_string(rand_str, :size => n) 37 | end 38 | assert((253..0xFFFF).include?(buf.size)) 39 | str = buf.read_encoded_string 40 | assert_equal rand_str, str 41 | end 42 | 43 | def test_read_encoded_string_case_three 44 | n = 0x10000 + 1 45 | rand_str = random_string(n) 46 | buf = BtcProto::Buffer.of_size(1+4+n) do 47 | write_uint8(254) 48 | write_uint32(n) 49 | write_string(rand_str, :size => n) 50 | end 51 | assert((0x10000..0xFFFFFFFF).include?(buf.size)) 52 | str = buf.read_encoded_string 53 | assert_equal rand_str, str 54 | end 55 | 56 | def test_write_encoded_string_case_one 57 | n = rand(252) 58 | rand_str = random_string(n) 59 | buf = BtcProto::Buffer.of_size(1+n) { 60 | write_encoded_string(rand_str) 61 | } 62 | assert_equal rand_str.size, buf.content[0] 63 | assert_equal rand_str, buf.read_encoded_string 64 | end 65 | 66 | def test_write_encoded_string_case_two 67 | n = rand(0xFFFF - 253 + 1) + 253 68 | rand_str = random_string(n) 69 | buf = BtcProto::Buffer.of_size(1+2+n) do 70 | write_encoded_string(rand_str) 71 | end 72 | assert_equal rand_str, buf.read_encoded_string 73 | end 74 | 75 | def test_write_encoded_bignum_vector 76 | bn_v = [] 77 | 5.times { bn_v << rand(2**256) } 78 | buf = BtcProto::Buffer.of_size(256 + 3) do 79 | write_encoded_size(bn_v.size) 80 | bn_v.size.times {|i| write_uint256_little(bn_v[i])} 81 | end 82 | end 83 | 84 | def test_size_encoded_bignum_vector_size 85 | bn_v = [1,2,3,4,5] 86 | buf = BtcProto::Buffer.of_size(312) do 87 | write_encoded_size(bn_v.size) 88 | bn_v.size.times {|i| write_uint256_little(bn_v[i])} 89 | end 90 | assert_equal 5, buf.read_encoded_size 91 | end 92 | 93 | def test_read_encoded_bignum_vector 94 | bn_v = [1,2,3,4,5] 95 | buf = BtcProto::Buffer.of_size(312) do 96 | write_encoded_size(bn_v.size) 97 | bn_v.size.times {|i| write_uint256_little(bn_v[i])} 98 | end 99 | assert_equal bn_v, buf.read_encoded_bignum_vector 100 | end 101 | 102 | end 103 | 104 | BtcProto::Types.mappings.each_pair do |name, klass| 105 | test_name = "test_#{name}_is_mapped_to_a_class" 106 | TestTypes.class_eval do 107 | define_method(test_name) do 108 | assert name.is_a? Symbol 109 | assert_equal Class, klass.class 110 | end 111 | end 112 | end 113 | 114 | -------------------------------------------------------------------------------- /test/test_protocol.rb: -------------------------------------------------------------------------------- 1 | require 'helper' 2 | 3 | class TestProtocol < Bitcoin::TestCase 4 | def test_version 5 | version_match = /\d+\.\d+\.\d+/ 6 | assert_match version_match, BtcProto::VERSION 7 | end 8 | 9 | def has_abbreviated_constant 10 | assert defined?(BtcProto) 11 | end 12 | 13 | def test_has_messages_constant 14 | assert BtcProto.consts 15 | end 16 | 17 | # test lookup? 18 | 19 | def test_responds_to_class_for 20 | assert BtcProto.respond_to? :class_for 21 | end 22 | 23 | def test_method_class_for_has_arity_of_one 24 | assert BtcProto.method(:class_for).arity, 2 25 | end 26 | 27 | def test_has_proper_message_names 28 | assert_kind_of Array, BtcProto.proper_message_names 29 | end 30 | 31 | def test_has_proper_type_name 32 | assert_kind_of Array, BtcProto.proper_type_names 33 | end 34 | end 35 | 36 | BtcProto.proper_message_names.each do |command| 37 | TestProtocol.class_eval do 38 | test_msg_known = "test_protocol_knows_about_#{command}_message" 39 | define_method test_msg_known do 40 | assert BtcProto.messages[:classes].keys.include?(command) 41 | end 42 | 43 | test_msg_handled = "test_there_is_a_class_handling_#{command}_message" 44 | define_method test_msg_handled do 45 | assert BtcProto.class_for(:message, command).kind_of? Class 46 | end 47 | end 48 | end 49 | 50 | 51 | BtcProto.proper_type_names.each do |type| 52 | TestProtocol.class_eval do 53 | test_type_known = "test_protocol_knows_about_#{type}_type" 54 | define_method test_type_known do 55 | assert BtcProto.types[:classes].keys.include?(type) 56 | end 57 | 58 | test_type_handled = "test_there_is_a_class_handling_#{type}_message" 59 | define_method test_type_handled do 60 | assert BtcProto.class_for(:type, type).kind_of? Class 61 | end 62 | end 63 | end 64 | 65 | 66 | --------------------------------------------------------------------------------