Commit 6061d92f8f348e4396995883c4322f01ecbbfcd4

Authored by Brian Armstrong

Merge branch 'master' of git://github.com/lian/bitcoin-ruby

Showing 32 changed files Side-by-side Diff

... ... @@ -16,6 +16,7 @@
16 16 autoload :Logger, 'bitcoin/logger'
17 17 autoload :Key, 'bitcoin/key'
18 18 autoload :Config, 'bitcoin/config'
  19 + autoload :Builder, 'bitcoin/builder'
19 20  
20 21 module Network
21 22 autoload :ConnectionHandler, 'bitcoin/network/connection_handler'
lib/bitcoin/builder.rb
  1 +module Bitcoin
  2 + class BlockBuilder
  3 +
  4 + def initialize
  5 + @block = Bitcoin::P::Block.new(nil)
  6 + end
  7 +
  8 + def version v
  9 + @version = v
  10 + end
  11 +
  12 + def prev_block hash
  13 + @prev_block = hash
  14 + end
  15 +
  16 + def tx &block
  17 + c = TxBuilder.new
  18 + c.instance_eval &block
  19 + @block.tx << c.tx
  20 + end
  21 +
  22 + def block target
  23 + @block.ver = @version || 1
  24 + @block.prev_block = [@prev_block].pack("H*").reverse
  25 + @block.mrkl_root = @mrkl_root
  26 + @block.time = Time.now.to_i
  27 + @block.nonce = 0
  28 + @block.mrkl_root = [Bitcoin.hash_mrkl_tree(@block.tx.map {|t|
  29 + t.hash }).last].pack("H*")
  30 + find_hash(target)
  31 + Bitcoin::P::Block.new(@block.to_payload)
  32 + end
  33 +
  34 + def find_hash target
  35 + @block.bits = Bitcoin.encode_compact_bits(target)
  36 + t = Time.now
  37 + @block.recalc_block_hash
  38 + until @block.hash < target
  39 + @block.nonce += 1
  40 + @block.recalc_block_hash
  41 + if @block.nonce == 100000
  42 + if t
  43 + tt = 1 / ((Time.now - t) / 100000) / 1000
  44 + print "\r%.2f khash/s" % tt
  45 + end
  46 + t = Time.now
  47 + @block.time = Time.now.to_i
  48 + @block.nonce = 0
  49 + $stdout.flush
  50 + end
  51 + end
  52 + end
  53 +
  54 + end
  55 +
  56 + class TxBuilder
  57 +
  58 + def initialize
  59 + @tx = Bitcoin::P::Tx.new(nil)
  60 + @tx.ver, @tx.lock_time = 1, 0
  61 + @ins, @outs = [], []
  62 + end
  63 +
  64 + def version n
  65 + @tx.ver = n
  66 + end
  67 +
  68 + def lock_time n
  69 + @tx.lock_time = n
  70 + end
  71 +
  72 + def input &block
  73 + c = TxInBuilder.new
  74 + c.instance_eval &block
  75 + @ins << c
  76 + end
  77 +
  78 + def output &block
  79 + c = TxOutBuilder.new
  80 + c.instance_eval &block
  81 + @outs << c
  82 + end
  83 +
  84 + def tx
  85 + @ins.each {|i| @tx.add_in(i.txin) }
  86 + @outs.each {|o| @tx.add_out(o.txout) }
  87 + @ins.each_with_index do |inc, i|
  88 + if @tx.in[i].coinbase?
  89 + script_sig = [inc.coinbase_data].pack("H*")
  90 + @tx.in[i].script_sig_length = script_sig.bytesize
  91 + @tx.in[i].script_sig = script_sig
  92 + next
  93 + end
  94 + prev_tx = inc.instance_variable_get(:@prev_out)
  95 + sig_hash = @tx.signature_hash_for_input(i, prev_tx)
  96 + sig = inc.key.sign(sig_hash)
  97 + script_sig = Bitcoin::Script.to_signature_pubkey_script(sig, [inc.key.pub].pack("H*"))
  98 + @tx.in[i].script_sig_length = script_sig.bytesize
  99 + @tx.in[i].script_sig = script_sig
  100 + raise "Signature error" unless @tx.verify_input_signature(i, prev_tx)
  101 + end
  102 + Bitcoin::P::Tx.new(@tx.to_payload)
  103 + end
  104 + end
  105 +
  106 + class TxInBuilder
  107 + attr_reader :key, :coinbase_data
  108 +
  109 + def initialize
  110 + @txin = Bitcoin::P::TxIn.new
  111 + end
  112 +
  113 + def prev_out tx
  114 + @prev_out = tx
  115 + end
  116 +
  117 + def prev_out_index i
  118 + @prev_out_index = i
  119 + end
  120 +
  121 + def sequence s
  122 + @sequence = s
  123 + end
  124 +
  125 + def signature_key key
  126 + @key = key
  127 + end
  128 +
  129 + def coinbase data = nil
  130 + @coinbase_data = data || OpenSSL::Random.random_bytes(32)
  131 + @prev_out = nil
  132 + @prev_out_index = 4294967295
  133 + end
  134 +
  135 + def txin
  136 + @txin.prev_out = (@prev_out ? [@prev_out.hash].pack("H*").reverse : "\x00"*32)
  137 + @txin.prev_out_index = @prev_out_index
  138 + @txin.sequence = @sequence || "\xff\xff\xff\xff"
  139 + @txin
  140 + end
  141 + end
  142 +
  143 + class ScriptBuilder
  144 + attr_reader :script
  145 +
  146 + def initialize
  147 + @type = nil
  148 + @script = nil
  149 + end
  150 +
  151 + def type type
  152 + @type = type.to_sym
  153 + end
  154 +
  155 + def recipient data
  156 + @script = Bitcoin::Script.send("to_#{@type}_script", data)
  157 + end
  158 + end
  159 +
  160 + class TxOutBuilder
  161 + attr_reader :txout
  162 +
  163 + def initialize
  164 + @txout = Bitcoin::P::TxOut.new
  165 + end
  166 +
  167 + def value value
  168 + @txout.value = value
  169 + end
  170 +
  171 + def script &block
  172 + c = ScriptBuilder.new
  173 + c.instance_eval &block
  174 + @txout.pk_script = c.script
  175 + end
  176 +
  177 + end
  178 +
  179 + module Builder
  180 +
  181 + def blk(target = "00".ljust(32, 'f'), &block)
  182 + c = BlockBuilder.new
  183 + c.instance_eval &block
  184 + c.block(target)
  185 + end
  186 +
  187 + def tx &block
  188 + c = TxBuilder.new
  189 + c.instance_eval &block
  190 + c.tx
  191 + end
  192 +
  193 + end
  194 +end
lib/bitcoin/network/command_handler.rb
... ... @@ -62,12 +62,12 @@
62 62 end
63 63  
64 64 def handle_connections
65   - @node.connections.sort{|x,y| x.host <=> y.host}.map{|c|
  65 + @node.connections.sort{|x,y| y.uptime <=> x.uptime}.map{|c|
66 66 "#{c.host.rjust(15)}:#{c.port} [state: #{c.state}, " +
67 67 "version: #{c.version.version rescue '?'}, " +
68   - "client: #{c.version.user_agent rescue '?'}, " +
69 68 "block: #{c.version.block rescue '?'}, " +
70   - "uptime: #{format_uptime(c.uptime) rescue 0}]" }
  69 + "uptime: #{format_uptime(c.uptime) rescue 0}, " +
  70 + "client: #{c.version.user_agent rescue '?'}]" }
71 71 end
72 72  
73 73 def handle_connect *args
lib/bitcoin/network/connection_handler.rb
... ... @@ -17,7 +17,7 @@
17 17 end
18 18  
19 19 def uptime
20   - @started ? (Time.now - @started).to_i : nil
  20 + @started ? (Time.now - @started).to_i : 0
21 21 end
22 22  
23 23 def initialize node, host, port
... ... @@ -32,7 +32,9 @@
32 32 end
33 33  
34 34 def post_init
35   - return close_connection if @node.connections.size >= @node.config[:max][:connections]
  35 + if @node.connections.size >= @node.config[:max][:connections]
  36 + return close_connection unless @node.config[:connect].include?([@host, @port.to_s])
  37 + end
36 38 log.info { "Connected to #{@host}:#{@port}" }
37 39 @state = :established
38 40 @node.connections << self
... ... @@ -51,8 +53,6 @@
51 53 log.info { "Disconnected #{@host}:#{@port}" }
52 54 @state = :disconnected
53 55 @node.connections.delete(self)
54   - addr = @node.addrs.find{|a|a.ip == @host && a.port == @port}
55   - @node.addrs.delete(addr)
56 56 end
57 57  
58 58  
lib/bitcoin/network/node.rb
1 1 require 'eventmachine'
  2 +require 'json'
  3 +require 'fileutils'
2 4  
3 5 module Bitcoin::Network
4 6  
... ... @@ -18,6 +20,7 @@
18 20 :epoll => false,
19 21 :epoll_limit => 10000,
20 22 :epoll_user => nil,
  23 + :addr_file => "#{ENV['HOME']}/.bitcoin-ruby/addrs.json",
21 24 :log => {
22 25 :network => :info,
23 26 :storage => :info,
... ... @@ -48,7 +51,7 @@
48 51 @inv_queue = []
49 52 @inv_queue_thread = nil
50 53 set_store
51   - @addrs = []
  54 + load_addrs
52 55 @timers = {}
53 56 @inv_cache = []
54 57 @notify = EM::Channel.new
... ... @@ -60,6 +63,30 @@
60 63 @store.log.level = @config[:log][:storage]
61 64 end
62 65  
  66 + def load_addrs
  67 + @addrs = JSON.load(File.read(@config[:addr_file])).map do |a|
  68 + addr = Bitcoin::P::Addr.new
  69 + addr.time, addr.service, addr.ip, addr.port =
  70 + a['time'], a['service'], a['ip'], a['port']
  71 + addr
  72 + end
  73 + log.info { "Initialized #{@addrs.size} addrs from #{@config[:addr_file]}." }
  74 + end
  75 +
  76 + def store_addrs
  77 + return if !@addrs || !@addrs.any?
  78 + file = @config[:addr_file]
  79 + FileUtils.mkdir_p(File.dirname(file))
  80 + File.open(file, 'w') do |f|
  81 + addrs = @addrs.map {|a|
  82 + Hash[[:time, :service, :ip, :port].zip(a.entries)] rescue nil }.compact
  83 + f.write(JSON.pretty_generate(addrs))
  84 + end
  85 + log.info { "Stored #{@addrs.size} addrs to #{file}" }
  86 + rescue
  87 + log.warn { "Error storing addrs to #{file}." }
  88 + end
  89 +
63 90 def stop
64 91 log.info { "Shutting down..." }
65 92 EM.stop
... ... @@ -73,6 +100,7 @@
73 100 @started = Time.now
74 101  
75 102 EM.add_shutdown_hook do
  103 + store_addrs
76 104 log.info { "Bye" }
77 105 end
78 106  
... ... @@ -101,6 +129,7 @@
101 129 @config[:connect].each{|host| connect_peer(*host) }
102 130 end
103 131  
  132 + work_connect if @addrs.any?
104 133 connect_dns if @config[:dns]
105 134 work_inv_queue
106 135 work_queue
... ... @@ -113,7 +142,8 @@
113 142 log.info { "Attempting to connect to #{host}:#{port}" }
114 143 EM.connect(host, port.to_i, ConnectionHandler, self, host, port.to_i)
115 144 rescue
116   - p $!; puts $@; exit
  145 + log.warn { "Error connecting to #{host}:#{port}" }
  146 + log.debug { $!.inspect }
117 147 end
118 148  
119 149 # query addrs from dns seed and connect
lib/bitcoin/protocol.rb
... ... @@ -48,6 +48,18 @@
48 48 pack_var_int(payload.bytesize) + payload
49 49 end
50 50  
  51 + def self.unpack_var_string_array(payload) # unpacks set<string>
  52 + size, payload = unpack_var_int(payload)
  53 + return [nil, payload] if size == 0
  54 + [(0...size).map{ s, payload = unpack_var_string(payload); s }, payload]
  55 + end
  56 +
  57 + def self.unpack_var_int_array(payload) # unpacks set<int>
  58 + size, payload = unpack_var_int(payload)
  59 + return [nil, payload] if size == 0
  60 + [(0...size).map{ i, payload = unpack_var_int(payload); i }, payload]
  61 + end
  62 +
51 63 def self.pkt(command, payload)
52 64 cmd = command.ljust(12, "\x00")[0...12]
53 65 length = [payload.bytesize].pack("I")
lib/bitcoin/protocol/alert.rb
... ... @@ -29,9 +29,9 @@
29 29  
30 30 version, relay_until, expiration, id, cancel, payload = alert_payload.unpack("VQQVVa*")
31 31  
32   - set_cancel, payload = Bitcoin::Protocol.unpack_var_string(payload)
  32 + set_cancel, payload = Bitcoin::Protocol.unpack_var_int_array(payload)
33 33 min_ver, max_ver, payload = payload.unpack("VVa*")
34   - set_sub_ver, payload = Bitcoin::Protocol.unpack_var_string(payload)
  34 + set_sub_ver, payload = Bitcoin::Protocol.unpack_var_string_array(payload)
35 35 priority, payload = payload.unpack("Va*")
36 36 comment, payload = Bitcoin::Protocol.unpack_var_string(payload)
37 37 status_bar, payload = Bitcoin::Protocol.unpack_var_string(payload)
lib/bitcoin/protocol/block.rb
... ... @@ -2,7 +2,7 @@
2 2 module Protocol
3 3  
4 4 class Block
5   - attr_reader :hash, :payload, :tx, :ver, :prev_block, :mrkl_root, :time, :bits, :nonce
  5 + attr_accessor :hash, :payload, :tx, :ver, :prev_block, :mrkl_root, :time, :bits, :nonce
6 6  
7 7 # compare to another block
8 8 def ==(other)
lib/bitcoin/protocol/parser.rb
... ... @@ -39,7 +39,11 @@
39 39 def parse_addr(payload)
40 40 count, payload = Protocol.unpack_var_int(payload)
41 41 payload.each_byte.each_slice(30){|i|
42   - @h.on_addr( Addr.new(i.pack("C*")) )
  42 + begin
  43 + @h.on_addr( Addr.new(i.pack("C*")) )
  44 + rescue
  45 + puts "Error parsing addr: #{i.pack("C*")}"
  46 + end
43 47 }
44 48 end
45 49  
lib/bitcoin/protocol/tx.rb
... ... @@ -19,7 +19,7 @@
19 19  
20 20 # create tx from raw binary +data+
21 21 def initialize(data=nil)
22   - @ver, @lock_time = 1, 0
  22 + @ver, @lock_time, @in, @out = 1, 0, [], []
23 23  
24 24 parse_data(data) if data
25 25 end
... ... @@ -137,6 +137,7 @@
137 137  
138 138 # convert to ruby hash (see also #from_hash)
139 139 def to_hash
  140 + @hash ||= hash_from_payload(to_payload)
140 141 {
141 142 'hash' => @hash, 'ver' => @ver,
142 143 'vin_sz' => @in.size, 'vout_sz' => @out.size,
... ... @@ -178,6 +179,7 @@
178 179 coinbase_data = [ input['coinbase'] ].pack("H*")
179 180 txin.script_sig_length = coinbase_data.bytesize
180 181 txin.script_sig = coinbase_data
  182 + txin.sequence = "\xff\xff\xff\xff"
181 183 else
182 184 script_data = Script.binary_from_string(input['scriptSig'])
183 185 txin.script_sig_length = script_data.bytesize
lib/bitcoin/protocol/txout.rb
... ... @@ -6,7 +6,11 @@
6 6 attr_accessor :value, :pk_script_length, :pk_script
7 7  
8 8 def initialize *args
9   - @value, @pk_script_length, @pk_script = *args
  9 + if args.size == 2
  10 + @value, @pk_script_length, @pk_script = args[0], args[1].bytesize, args[1]
  11 + else
  12 + @value, @pk_script_length, @pk_script = *args
  13 + end
10 14 end
11 15  
12 16 # compare to another txout
13 17  
... ... @@ -40,9 +44,13 @@
40 44 idx
41 45 end
42 46  
  47 + def pk_script=(script)
  48 + @pk_script_length, @pk_script = script.bytesize, script
  49 + end
  50 +
43 51 def self.value_to_address(value, address)
44 52 pk_script = Bitcoin::Script.to_address_script(address)
45   - new(value, pk_script.bytesize, pk_script)
  53 + new(value, pk_script)
46 54 end
47 55  
48 56 end
lib/bitcoin/script.rb
... ... @@ -239,6 +239,18 @@
239 239 @chunks[-1] == OP_CHECKMULTISIG
240 240 end
241 241  
  242 + def type
  243 + if is_hash160?
  244 + return :hash160
  245 + elsif is_pubkey?
  246 + return :pubkey
  247 + elsif is_multisig?
  248 + return :multisig
  249 + else
  250 + return :unknown
  251 + end
  252 + end
  253 +
242 254 # get the public key for this pubkey script
243 255 def get_pubkey
244 256 return @chunks[0].unpack("H*")[0] if @chunks.size == 1
lib/bitcoin/storage/activerecord.rb
1   -require 'active_record'
2   -
3   -$:.unshift( File.dirname(__FILE__) )
4   -
5   -module Bitcoin::Storage::Backends
6   -
7   - class ActiverecordStore < StoreBase
8   -
9   - require_relative 'activerecord_store/base'
10   - require_relative 'activerecord_store/block'
11   - require_relative 'activerecord_store/transactions_parent'
12   - require_relative 'activerecord_store/transaction'
13   - require_relative 'activerecord_store/input'
14   - require_relative 'activerecord_store/output'
15   -
16   - include Bitcoin::Storage::Backends::ActiverecordStore
17   -
18   - def initialize config
19   - @config = config
20   - connect
21   - super @config
22   - end
23   -
24   - def connect
25   - # TODO: load schema if not already there
26   - ActiveRecord::Base.establish_connection @config
27   - if defined?(Log4r)
28   - ActiveRecord::Base.logger = Bitcoin::Logger.create(:database)
29   - ActiveRecord::Base.logger.level = 2
30   - end
31   - end
32   -
33   - def reset
34   - [:blocks, :transactions_parents, :transactions, :inputs, :outputs, :chains].each do |t|
35   - ActiveRecord::Base.connection.query("DELETE from #{t};")
36   - end
37   - end
38   -
39   - def get_depth
40   - Block.order("depth DESC").limit(1).first.depth rescue -1
41   - end
42   -
43   - def get_head
44   - Bitcoin::hth(Block.order("depth DESC").limit(1).first.block_hash)
45   - rescue
46   - Bitcoin::network[:genesis_hash]
47   - end
48   -
49   - # TODO
50   - def get_balance
51   - s = "41" + pubkey_hash + "ac"
52   - Output.where("script = decode('#{s}', 'hex')")
53   - end
54   -
55   - def store_block(blk)
56   - return nil unless blk
57   -
58   - if block = get_block(blk.hash)
59   - log.debug { "Block #{blk.hash} already stored" }
60   - end
61   -
62   - block = Block.from_protocol(blk)
63   -
64   - return nil unless block
65   -
66   - begin
67   - if block.save
68   - log.info { "NEW HEAD: #{blk.hash} (#{blk.payload.size} bytes) - DEPTH: #{block.depth}" }
69   - return block.depth
70   - end
71   - rescue
72   - log.error { "ERROR SAVING BLOCK: #{$!.inspect}" }
73   - p $@.first
74   - puts *$@
75   - binding.pry
76   - exit
77   - end
78   -
79   - end
80   -
81   - def get_block(blk_hash)
82   - Block.where("block_hash = decode(?, 'hex')", blk_hash).first.to_protocol rescue nil
83   - end
84   -
85   - def get_block_by_depth(depth)
86   - Block.where(:depth => depth).first.to_protocol rescue nil
87   - end
88   -
89   - def get_block_depth(blk_hash)
90   - block = Block.where("block_hash = decode(?, 'hex')", blk_hash).first
91   - block.depth
92   - end
93   -
94   - def store_tx(tx)
95   - return nil unless tx
96   -
97   - if transaction = get_tx(tx.hash)
98   - log.debug { "Tx #{tx.hash} already stored"}
99   - return false
100   - end
101   -
102   - transaction = Transaction.from_protocol(tx)
103   -
104   - begin
105   - if transaction.save
106   - log.info { "Tx #{tx.hash} saved" }
107   - return true
108   - else
109   - log.warn { "Error saving tx #{tx.hash}" }
110   - return false
111   - end
112   - rescue
113   - log.error { "Exception trying to save tx: #{$!.message}" }
114   - return false
115   - end
116   - end
117   -
118   - def get_tx(tx_hash)
119   - tx = Transaction.where("transaction_hash = decode('#{tx_hash}', 'hex')").first
120   - return nil unless tx
121   - tx.to_protocol
122   - end
123   -
124   - end
125   -
126   -
127   - module StorageModel
128   -
129   - def log
130   - Bitcoin::Storage::log
131   - end
132   -
133   - def hth(h); h.unpack("H*")[0]; end
134   - def htb(h); [h].pack("H*"); end
135   -
136   - def bts data
137   - connection.escape_bytea(data)
138   - end
139   -
140   - end
141   -
142   - ActiverecordStore.constants.each do |c|
143   - const = ActiverecordStore.const_get(c)
144   - const.extend(StorageModel)
145   - end
146   -
147   -end
lib/bitcoin/storage/activerecord_store/base.rb
1   -module Bitcoin::Storage::Backends::ActiverecordStore
2   -
3   - module Base
4   -
5   - def self.included(base)
6   - base.extend Bitcoin::Util
7   - base.instance_eval do
8   -
9   - def log
10   - Bitcoin::Storage.log
11   - end
12   - end
13   - end
14   - end
15   -
16   -end
lib/bitcoin/storage/activerecord_store/block.rb
1   -require 'pry'
2   -module Bitcoin::Storage::Backends::ActiverecordStore
3   -
4   - class Block < ActiveRecord::Base
5   -
6   - include Base
7   -
8   - set_primary_key :block_id
9   -
10   - has_many :transactions_parents
11   -
12   - def transactions
13   - transactions_parents.sort_by(&:index_in_block).map(&:transaction)
14   - end
15   -
16   - # get block with given hash (in hex)
17   - def self.get hash
18   - Block.where("block_hash = decode(?, 'hex')", hash).first rescue nil
19   - end
20   -
21   - # get previous block
22   - def prev
23   - Block.where("block_hash = decode('#{hth(self.prev_block_hash)}', 'hex')").first
24   - end
25   -
26   - # get next block
27   - def next
28   - Block.where("prev_block_hash = decode('#{hth(self.block_hash)}', 'hex')").first
29   - end
30   -
31   - # get total value of all this block's outputs values
32   - def total_value
33   - connection.query("SELECT sum(outputs.value) FROM transactions_parents
34   - LEFT JOIN transactions
35   - ON transactions.transaction_id = transactions_parents.transaction_id
36   - LEFT JOIN outputs
37   - ON outputs.transaction_id = transactions.transaction_id
38   - WHERE block_id = '#{id}'")[0][0]
39   - end
40   -
41   -
42   - def to_hash
43   - {
44   - "ver" => version,
45   - "time" => when_created.to_i,
46   - "bits" => ((bits_head << 24) | bits_body),
47   - "nonce" => nonce,
48   - "prev_block" => Bitcoin::hth(prev_block_hash),
49   - "mrkl_root" => Bitcoin::hth(merkle),
50   - "tx" => transactions.map(&:to_hash)
51   - }
52   - end
53   -
54   - def to_protocol
55   - Bitcoin::Protocol::Block.from_hash(to_hash)
56   - end
57   -
58   - def self.from_protocol blk
59   -
60   - prev_block = get(hth(blk.prev_block.reverse))
61   - unless prev_block # ||
62   - unless blk.hash == Bitcoin::network[:genesis_hash]
63   - log.warn { "INVALID BLOCK: #{blk.hash}" }
64   - return nil
65   - end
66   - end
67   -
68   - block = new({
69   - :block_hash => blk.hash,
70   - :space => 0,
71   - :depth => (prev_block.depth + 1 rescue 0),
72   - :version => blk.ver,
73   - :prev_block_hash => (hth(prev_block.block_hash) rescue hth("\x00"*32)),
74   - :merkle => hth(blk.mrkl_root.reverse),
75   - :when_created => Time.at(blk.time),
76   - :when_found => Time.now,
77   - :nonce => blk.nonce,
78   - :span_left => 0,
79   - :span_right => 0,
80   - :bits_head => (blk.bits >> 24),
81   - :bits_body => (blk.bits & 0x00ffffff),
82   - :block_size => blk.payload.size,
83   - })
84   -
85   - blk.tx.each_with_index do |tx, idx|
86   - begin
87   - log.debug { "tx: #{tx.hash} (#{idx+1}/#{blk.tx.count})" }
88   - transaction = Transaction.from_protocol(tx)
89   - parent = TransactionsParent.new
90   - parent.transaction = transaction
91   - parent.index_in_block = idx
92   - block.transactions_parents << parent
93   - rescue
94   - log.error { "ERROR ADDING TX: #{tx.hash}" }
95   - p $!
96   - p *$@
97   - binding.pry
98   - File.open("./errors/#{Time.now.strftime("%H-%M-%S-")}-block-#{blk.hash}-tx-#{idx}", 'w') do |f|
99   - f.puts($!.inspect)
100   - $!.backtrace.each {|l| f.puts(l)}
101   - f.puts; f.puts; f.puts; f.puts
102   - f.write(blk.payload)
103   - end
104   - end
105   - end
106   -
107   - block
108   - end
109   -
110   - def save *args
111   - res = connection.query("INSERT INTO blocks (
112   - block_id,
113   - block_hash,
114   - space,
115   - depth,
116   - span_left,
117   - span_right,
118   - version,
119   - prev_block_hash,
120   - merkle,
121   - when_created,
122   - when_found,
123   - bits_head,
124   - bits_body,
125   - nonce,
126   - block_size
127   - ) VALUES (
128   - DEFAULT,
129   - decode('#{block_hash}', 'hex'),
130   - nextval('blocks_space_sequence'),
131   - '#{depth}',
132   - 0,
133   - 0,
134   - '#{version}',
135   - decode('#{prev_block_hash}', 'hex'),
136   - decode('#{merkle}', 'hex'),
137   - '#{when_created.to_s(:db)}',
138   - '#{when_found.to_s(:db)}',
139   - '#{bits_head}',
140   - '#{bits_body}',
141   - '#{nonce}',
142   - '#{block_size}'
143   - ) RETURNING block_id")
144   -
145   -
146   - transactions_parents.each do |parent|
147   - parent.block_id = res[0][0]
148   - parent.save
149   - end
150   -
151   - res[0][0]
152   - end
153   - end
154   -
155   -
156   -
157   -end
lib/bitcoin/storage/activerecord_store/chain.rb
1   -class Chain < ActiveRecord::Base::Backends::ActiverecordStore
2   -
3   - set_primary_key :chain_id
4   -
5   -end
lib/bitcoin/storage/activerecord_store/input.rb
1   -module Bitcoin::Storage::Backends::ActiverecordStore
2   -
3   - class Input < ActiveRecord::Base
4   -
5   - include Base
6   -
7   - set_primary_key :input_id
8   -
9   - belongs_to :transaction
10   -
11   -
12   - def previous_output
13   - return nil if transaction.coinbase
14   - ptx = Transaction.get(Bitcoin::hth(previous_output_hash))
15   - Output.where("transaction_id = '#{ptx.transaction_id}'
16   - AND index_in_parent = '#{previous_output_index}'").first
17   - end
18   -
19   - def coinbase?
20   - previous_output_index == 4294967295 &&
21   - previous_output_hash == "\x00"*32
22   - end
23   -
24   - # def verify
25   - # return nil if transaction.coinbase
26   - # unless previous_output.transaction.coinbase
27   - # previous_output.transaction.verify
28   - # end
29   - # inscript = Bitcoin::Script.new(script)
30   - # outscript = Bitcoin::Script.new(previous_output.script)
31   - # outscript.run(inscript.chunks) do |*args|
32   - # # OP_CHECKSIG always true
33   - # true
34   - # end
35   - # end
36   -
37   - def self.from_protocol txin
38   - new({
39   - :script => txin[3],
40   - :previous_output_hash => txin[0].reverse,
41   - :previous_output_index => txin[1],
42   - :sequence => txin[4].unpack("I")[0]
43   - })
44   - end
45   -
46   - def save *args
47   - res = connection.query("INSERT INTO inputs
48   - (input_id, transaction_id, index_in_parent,
49   - script, previous_output_hash, previous_output_index, sequence)
50   - VALUES (DEFAULT, '#{transaction_id}', '#{index_in_parent}',
51   - decode('#{Bitcoin::hth(script)}', 'hex'),
52   - decode('#{Bitcoin::hth(previous_output_hash)}', 'hex'),
53   - '#{previous_output_index}', '#{sequence}') \
54   - RETURNING input_id")
55   - res[0]
56   - end
57   -
58   - end
59   -
60   -end
lib/bitcoin/storage/activerecord_store/inventory_request.rb
1   -class InventoryRequest < ActiveRecord::Base
2   -
3   - set_primary_key :inventory_id
4   -
5   -end
lib/bitcoin/storage/activerecord_store/output.rb
1   -module Bitcoin::Storage::Backends::ActiverecordStore
2   -
3   - class Output < ActiveRecord::Base
4   -
5   - include Base
6   -
7   - set_primary_key :output_id
8   -
9   - belongs_to :transaction
10   -
11   - def next_input
12   - res = connection.query("SELECT input_id FROM inputs WHERE
13   - previous_output_hash = decode('#{Bitcoin::hth(transaction.transaction_hash)}', 'hex') AND
14   - previous_output_index = '#{index_in_parent}'")
15   - Input.find(res[0][0]) rescue nil
16   - end
17   -
18   - def value
19   - attributes['value']
20   - end
21   -
22   - def self.from_protocol txout
23   - raise ArgumentError.new("value too high: #{txout[0]}") if txout[0] > 21e14
24   - new({
25   - :value => txout[0] / 1e8,
26   - :script => txout[2],
27   - })
28   - end
29   -
30   - def save *args
31   - res = connection.query("INSERT INTO outputs (
32   - output_id, transaction_id, index_in_parent, script, value)
33   - VALUES (DEFAULT, '#{transaction_id}', '#{index_in_parent}',
34   - decode('#{Bitcoin::hth(script)}', 'hex'), '#{attributes['value']}')
35   - RETURNING output_id")
36   - return res[0]
37   - end
38   -
39   - end
40   -
41   -end
lib/bitcoin/storage/activerecord_store/transaction.rb
1   -module Bitcoin::Storage::Backends::ActiverecordStore
2   -
3   - class Transaction < ActiveRecord::Base
4   -
5   - include Base
6   -
7   - set_primary_key :transaction_id
8   -
9   - has_one :transactions_parent
10   - has_one :block, :through => :transactions_parent
11   - has_many :inputs
12   - has_many :outputs
13   -
14   - def self.get hash
15   - Transaction.where("transaction_hash = decode(?, 'hex')", hash).first rescue nil
16   - end
17   -
18   - def output_value
19   - connection.query("SELECT sum(value) FROM outputs WHERE transaction_id = '#{transaction_id}'")[0][0]
20   - end
21   -
22   - # def verify
23   - # inputs.each do |input|
24   - # return false unless input.verify
25   - # end
26   - # true
27   - # end
28   -
29   - def to_hash
30   - h = {
31   - "hash" => Bitcoin::hth(transaction_hash), "ver" => version,
32   - "vin_sz" => inputs.size, "vout_sz" => outputs.size,
33   - "lock_time" => locktime, "size" => transaction_size,
34   - "in" => inputs.map {|i|
35   - {'prev_out' => {
36   - 'hash' => Bitcoin::hth(i.previous_output_hash),
37   - 'n' => i.previous_output_index},
38   - 'scriptSig' => Bitcoin::Script.new(i.script).to_string}
39   - },
40   - "out" => outputs.map {|o|
41   - {"value" => o.value,
42   - "scriptPubKey" => Bitcoin::Script.new(o.script).to_string}
43   - }
44   - }
45   - if (i=inputs[0]) && i.coinbase?
46   - h["in"][0] = {
47   - "prev_out" => {
48   - 'hash' => Bitcoin::hth(i.previous_output_hash),
49   - 'n' => i.previous_output_index},
50   - 'coinbase' => i.script.unpack("H*")[0]
51   - }
52   - end
53   - h
54   - end
55   -
56   - def to_protocol
57   - Bitcoin::Protocol::Tx.from_hash(to_hash)
58   - end
59   -
60   -
61   - def self.from_protocol(tx)
62   - transaction = new({
63   - :transaction_hash => tx.hash,
64   - :version => tx.ver,
65   - :locktime => tx.lock_time,
66   - :coinbase => (tx.in.size == 1 && tx.in[0].coinbase?),
67   - :when_found => Time.now,
68   - :transaction_size => tx.payload.size,
69   - })
70   -
71   - tx.in.each_with_index do |txin, idx|
72   - log.debug { "txin: #{idx+1}/#{tx.in.count}" }
73   - input = Input.from_protocol(txin)
74   - input.index_in_parent = idx
75   - transaction.inputs << input
76   - end
77   -
78   - tx.out.each_with_index do |txout, idx|
79   - log.debug { "txout: #{idx+1}/#{tx.out.count}" }
80   - output = Output.from_protocol(txout)
81   - output.index_in_parent = idx
82   - transaction.outputs << output
83   - end
84   - transaction
85   - end
86   -
87   - def save *args
88   - res = connection.query("SELECT insert_transaction(
89   - decode('#{transaction_hash}', 'hex'), '#{version}', '#{locktime}', '#{coinbase}', '#{transaction_size}')")
90   - (inputs + outputs).each do |xput|
91   - xput.transaction_id = res[0][0]
92   - xput.save
93   - end
94   - res[0][0]
95   - end
96   -
97   - def is_coinbase?
98   -
99   - end
100   -
101   - end
102   -
103   -
104   -end
lib/bitcoin/storage/activerecord_store/transactions_parent.rb
1   -module Bitcoin::Storage::Backends::ActiverecordStore
2   -
3   - class TransactionsParent < ActiveRecord::Base
4   -
5   - include Base
6   -
7   - belongs_to :block
8   - belongs_to :transaction
9   -
10   - def save *args
11   - transaction_id = transaction.save(*args)
12   - res = connection.query("INSERT INTO transactions_parents
13   - (transaction_id, block_id, index_in_block) VALUES
14   - ('#{transaction_id}', '#{block_id}', '#{index_in_block}')")
15   - end
16   -
17   - end
18   -
19   -end
lib/bitcoin/storage/models.rb
... ... @@ -7,12 +7,13 @@
7 7 class Block < Bitcoin::Protocol::Block
8 8  
9 9 attr_accessor :ver, :prev_block, :mrkl_root, :time, :bits, :nonce, :tx
10   - attr_reader :store, :id, :depth
  10 + attr_reader :store, :id, :depth, :chain
11 11  
12 12 def initialize store, data
13 13 @store = store
14 14 @id = data[:id]
15 15 @depth = data[:depth]
  16 + @chain = data[:chain]
16 17 @tx = []
17 18 end
18 19  
... ... @@ -109,6 +110,10 @@
109 110 # get the single address this txout corresponds to (first for multisig tx)
110 111 def get_addresses
111 112 Bitcoin::Script.new(@pk_script).get_addresses
  113 + end
  114 +
  115 + def type
  116 + Bitcoin::Script.new(@pk_script).type
112 117 end
113 118  
114 119 end
lib/bitcoin/storage/sequel.rb
... ... @@ -11,6 +11,10 @@
11 11  
12 12 class SequelStore < StoreBase
13 13  
  14 + MAIN = 0
  15 + SIDE = 1
  16 + ORPHAN = 2
  17 +
14 18 attr_accessor :db
15 19  
16 20 include Bitcoin::Storage::Backends::SequelMigrations
17 21  
18 22  
19 23  
20 24  
21 25  
22 26  
... ... @@ -30,35 +34,81 @@
30 34 [:blk, :blk_tx, :tx, :txin, :txout].each {|table| @db[table].delete}
31 35 end
32 36  
33   - def store_block(blk)
34   - @log.debug { "Storing tx #{blk.hash} (#{blk.to_payload.bytesize} bytes)" }
35   - @db.transaction do
36   - block = @db[:blk][:hash => htb(blk.hash).to_sequel_blob]
37   - if block
38   - @log.info { "skipping already existing block: #{blk.hash}" }
39   - return false
  37 + def reorg(blk)
  38 + new, old = [], []
  39 + while blk[:chain] != MAIN do
  40 + new << blk
  41 + blk = @db[:blk][:hash => blk[:prev_hash].to_sequel_blob]
  42 + return unless blk
  43 + end
  44 +
  45 + head = @db[:blk].filter(:chain => MAIN).order(:depth).last
  46 + while head != blk do
  47 + old << head
  48 + head = @db[:blk][:hash => head[:prev_hash].to_sequel_blob]
  49 + end
  50 +
  51 + @log.info { "reorg new main: #{new.map{|b|hth(b[:hash])}.inspect} " }
  52 + @log.info { "reorg new side: #{old.map{|b|hth(b[:hash])}.inspect}" }
  53 +
  54 + new.each {|b| @db[:blk].filter(:hash => b[:hash].to_sequel_blob).update(:chain => MAIN) }
  55 + old.each {|b| @db[:blk].filter(:hash => b[:hash].to_sequel_blob).update(:chain => SIDE) }