Class: Bitcoin::Storage::Backends::StoreBase
- Inherits:
-
Object
- Object
- Bitcoin::Storage::Backends::StoreBase
- Defined in:
- lib/bitcoin/storage/storage.rb
Overview
Base class for storage backends. Every backend must overwrite the “Not implemented” methods and provide an implementation specific to the storage. Also, before returning the objects, they should be wrapped inside the appropriate Bitcoin::Storage::Models class.
Direct Known Subclasses
Constant Summary collapse
- MAIN =
main branch (longest valid chain)
0- SIDE =
side branch (connected, valid, but too short)
1- ORPHAN =
orphan branch (not connected to main branch / genesis block)
2- SCRIPT_TYPES =
possible script types
[:unknown, :pubkey, :hash160, :multisig, :p2sh, :op_return]
- ADDRESS_TYPES =
possible address types
[:hash160, :p2sh]
- DEFAULT_CONFIG =
{}
Instance Attribute Summary collapse
-
#config ⇒ Object
Returns the value of attribute config.
-
#log ⇒ Object
readonly
Returns the value of attribute log.
Instance Method Summary collapse
- #add_watched_address(address) ⇒ Object
-
#backend_name ⇒ Object
name of the storage backend currently in use (“sequel” or “utxo”).
-
#check_consistency(count) ⇒ Object
check data consistency of the top
countblocks. -
#get_balance(hash160_or_addr, unconfirmed = false) ⇒ Object
get balance for given
hash160. -
#get_block(blk_hash) ⇒ Object
get block with given
blk_hash. -
#get_block_by_depth(depth) ⇒ Object
get block with given
depthfrom main chain. -
#get_block_by_id(block_id) ⇒ Object
get block by given
block_id. -
#get_block_by_prev_hash(prev_hash) ⇒ Object
get block with given
prev_hash. -
#get_block_by_tx(tx_hash) ⇒ Object
get block that includes tx with given
tx_hash. -
#get_block_id_for_tx_id(tx_id) ⇒ Object
get block id in main chain by given
tx_id. -
#get_depth ⇒ Object
return depth of the head block.
-
#get_head ⇒ Object
get the hash of the leading block.
-
#get_idx_from_tx_hash(tx_hash) ⇒ Object
Grab the position of a tx in a given block.
-
#get_locator(pointer = get_head) ⇒ Object
compute blockchain locator.
-
#get_tx(tx_hash) ⇒ Object
get tx with given
tx_hash. -
#get_tx_by_id(tx_id) ⇒ Object
get tx with given
tx_id. -
#get_txin_for_txout(tx_hash, txout_idx) ⇒ Object
get corresponding txin for the txout in transaction
tx_hashwith indextxout_idx. -
#get_txins_for_txouts(txouts) ⇒ Object
get an array of corresponding txins for provided
txoutstxouts = [tx_hash, tx_idx] can be overwritten by specific storage for opimization. -
#get_txouts_for_address(address, unconfirmed = false) ⇒ Object
collect all txouts containing a standard tx to given
address. -
#get_txouts_for_pk_script(script) ⇒ Object
collect all txouts containing the given
script. -
#get_txs(tx_hashes) ⇒ Object
get more than one tx by
tx_hashes, returns an array can be reimplemented by specific storage for optimization. -
#get_unspent_txouts_for_address(address, unconfirmed = false) ⇒ Object
collect all unspent txouts containing a standard tx to given
address. -
#has_block(blk_hash) ⇒ Object
check if block with given
blk_hashis already stored. -
#has_tx(tx_hash) ⇒ Object
check if tx with given
tx_hashis already stored. -
#import(filename, max_depth = nil) ⇒ Object
import satoshi bitcoind blk0001.dat blockchain file.
- #in_sync? ⇒ Boolean
- #init_store_connection ⇒ Object
-
#initialize(config = {}, getblocks_callback = nil) ⇒ StoreBase
constructor
A new instance of StoreBase.
-
#new_block(blk) ⇒ Object
handle a new block incoming from the network.
- #new_tx(tx) ⇒ Object
-
#parse_script(txout, i, tx_hash = "", tx_idx) ⇒ Object
parse script and collect address/txout mappings to index.
-
#persist_block(blk) ⇒ Object
persist given block
blkto storage. - #push_notification(channel, message) ⇒ Object
- #rescan ⇒ Object
-
#reset ⇒ Object
reset the store; delete all data.
-
#store_block(blk) ⇒ Object
store given block
blk. -
#store_tx(tx, validate = true) ⇒ Object
store given
tx. - #subscribe(channel) ⇒ Object
-
#update_block(hash, attrs) ⇒ Object
update
attrsfor block with givenhash.
Constructor Details
#initialize(config = {}, getblocks_callback = nil) ⇒ StoreBase
Returns a new instance of StoreBase.
60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/bitcoin/storage/storage.rb', line 60 def initialize(config = {}, getblocks_callback = nil) # merge all the configuration defaults, keeping the most specific ones. store_ancestors = self.class.ancestors.select {|a| a.name =~ /StoreBase$/ }.reverse base = store_ancestors.reduce(store_ancestors[0]::DEFAULT_CONFIG) do |config, ancestor| config.merge(ancestor::DEFAULT_CONFIG) end @config = base.merge(self.class::DEFAULT_CONFIG).merge(config) @log = config[:log] || Bitcoin::Storage.log @log.level = @config[:log_level] if @config[:log_level] init_store_connection @getblocks_callback = getblocks_callback @checkpoints = Bitcoin.network[:checkpoints] || {} @watched_addrs = [] @notifiers = {} end |
Instance Attribute Details
#config ⇒ Object
Returns the value of attribute config.
58 59 60 |
# File 'lib/bitcoin/storage/storage.rb', line 58 def config @config end |
#log ⇒ Object (readonly)
Returns the value of attribute log.
56 57 58 |
# File 'lib/bitcoin/storage/storage.rb', line 56 def log @log end |
Instance Method Details
#add_watched_address(address) ⇒ Object
381 382 383 384 385 |
# File 'lib/bitcoin/storage/storage.rb', line 381 def add_watched_address address hash160 = Bitcoin.hash160_from_address(address) @db[:addr].insert(hash160: hash160) unless @db[:addr][hash160: hash160] @watched_addrs << hash160 unless @watched_addrs.include?(hash160) end |
#backend_name ⇒ Object
name of the storage backend currently in use (“sequel” or “utxo”)
80 81 82 |
# File 'lib/bitcoin/storage/storage.rb', line 80 def backend_name self.class.name.split("::")[-1].split("Store")[0].downcase end |
#check_consistency(count) ⇒ Object
check data consistency of the top count blocks.
90 91 92 |
# File 'lib/bitcoin/storage/storage.rb', line 90 def check_consistency count raise "Not implemented" end |
#get_balance(hash160_or_addr, unconfirmed = false) ⇒ Object
get balance for given hash160
340 341 342 343 344 345 346 347 348 349 350 |
# File 'lib/bitcoin/storage/storage.rb', line 340 def get_balance(hash160_or_addr, unconfirmed = false) if Bitcoin.valid_address?(hash160_or_addr) txouts = get_txouts_for_address(hash160_or_addr) else txouts = get_txouts_for_hash160(hash160_or_addr, :hash160, unconfirmed) end unspent = txouts.select {|o| o.get_next_in.nil?} unspent.map(&:value).inject {|a,b| a+=b; a} || 0 rescue nil end |
#get_block(blk_hash) ⇒ Object
get block with given blk_hash
252 253 254 |
# File 'lib/bitcoin/storage/storage.rb', line 252 def get_block(blk_hash) raise "Not implemented" end |
#get_block_by_depth(depth) ⇒ Object
get block with given depth from main chain
257 258 259 |
# File 'lib/bitcoin/storage/storage.rb', line 257 def get_block_by_depth(depth) raise "Not implemented" end |
#get_block_by_id(block_id) ⇒ Object
get block by given block_id
272 273 274 |
# File 'lib/bitcoin/storage/storage.rb', line 272 def get_block_by_id(block_id) raise "Not implemented" end |
#get_block_by_prev_hash(prev_hash) ⇒ Object
get block with given prev_hash
262 263 264 |
# File 'lib/bitcoin/storage/storage.rb', line 262 def get_block_by_prev_hash(prev_hash) raise "Not implemented" end |
#get_block_by_tx(tx_hash) ⇒ Object
get block that includes tx with given tx_hash
267 268 269 |
# File 'lib/bitcoin/storage/storage.rb', line 267 def get_block_by_tx(tx_hash) raise "Not implemented" end |
#get_block_id_for_tx_id(tx_id) ⇒ Object
get block id in main chain by given tx_id
277 278 279 |
# File 'lib/bitcoin/storage/storage.rb', line 277 def get_block_id_for_tx_id(tx_id) get_tx_by_id(tx_id).blk_id rescue nil # tx.blk_id is always in main chain end |
#get_depth ⇒ Object
return depth of the head block
222 223 224 |
# File 'lib/bitcoin/storage/storage.rb', line 222 def get_depth raise "Not implemented" end |
#get_head ⇒ Object
get the hash of the leading block
217 218 219 |
# File 'lib/bitcoin/storage/storage.rb', line 217 def get_head raise "Not implemented" end |
#get_idx_from_tx_hash(tx_hash) ⇒ Object
Grab the position of a tx in a given block
311 312 313 |
# File 'lib/bitcoin/storage/storage.rb', line 311 def get_idx_from_tx_hash(tx_hash) raise "Not implemented" end |
#get_locator(pointer = get_head) ⇒ Object
compute blockchain locator
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 |
# File 'lib/bitcoin/storage/storage.rb', line 227 def get_locator pointer = get_head if @locator locator, head = @locator if head == pointer return locator end end return [("\x00"*32).hth] if get_depth == -1 locator, step, orig_pointer = [], 1, pointer while pointer && pointer.hash != Bitcoin::network[:genesis_hash] locator << pointer.hash depth = pointer.depth - step break unless depth > 0 prev_block = get_block_by_depth(depth) # TODO break unless prev_block pointer = prev_block step *= 2 if locator.size > 10 end locator << Bitcoin::network[:genesis_hash] @locator = [locator, orig_pointer] locator end |
#get_tx(tx_hash) ⇒ Object
get tx with given tx_hash
295 296 297 |
# File 'lib/bitcoin/storage/storage.rb', line 295 def get_tx(tx_hash) raise "Not implemented" end |
#get_tx_by_id(tx_id) ⇒ Object
get tx with given tx_id
306 307 308 |
# File 'lib/bitcoin/storage/storage.rb', line 306 def get_tx_by_id(tx_id) raise "Not implemented" end |
#get_txin_for_txout(tx_hash, txout_idx) ⇒ Object
get corresponding txin for the txout in transaction tx_hash with index txout_idx
283 284 285 |
# File 'lib/bitcoin/storage/storage.rb', line 283 def get_txin_for_txout(tx_hash, txout_idx) raise "Not implemented" end |
#get_txins_for_txouts(txouts) ⇒ Object
get an array of corresponding txins for provided txouts txouts = [tx_hash, tx_idx] can be overwritten by specific storage for opimization
290 291 292 |
# File 'lib/bitcoin/storage/storage.rb', line 290 def get_txins_for_txouts(txouts) txouts.map{|tx_hash, tx_idx| get_txin_for_txout(tx_hash, tx_idx) }.compact end |
#get_txouts_for_address(address, unconfirmed = false) ⇒ Object
collect all txouts containing a standard tx to given address
323 324 325 326 327 |
# File 'lib/bitcoin/storage/storage.rb', line 323 def get_txouts_for_address(address, unconfirmed = false) hash160 = Bitcoin.hash160_from_address(address) type = Bitcoin.address_type(address) get_txouts_for_hash160(hash160, type, unconfirmed) end |
#get_txouts_for_pk_script(script) ⇒ Object
collect all txouts containing the given script
317 318 319 |
# File 'lib/bitcoin/storage/storage.rb', line 317 def get_txouts_for_pk_script(script) raise "Not implemented" end |
#get_txs(tx_hashes) ⇒ Object
get more than one tx by tx_hashes, returns an array can be reimplemented by specific storage for optimization
301 302 303 |
# File 'lib/bitcoin/storage/storage.rb', line 301 def get_txs(tx_hashes) tx_hashes.map {|h| get_tx(h)}.compact end |
#get_unspent_txouts_for_address(address, unconfirmed = false) ⇒ Object
collect all unspent txouts containing a standard tx to given address
331 332 333 334 335 336 337 |
# File 'lib/bitcoin/storage/storage.rb', line 331 def get_unspent_txouts_for_address(address, unconfirmed = false) txouts = self.get_txouts_for_address(address, unconfirmed) txouts.select! do |t| not t.get_next_in end txouts end |
#has_block(blk_hash) ⇒ Object
check if block with given blk_hash is already stored
207 208 209 |
# File 'lib/bitcoin/storage/storage.rb', line 207 def has_block(blk_hash) raise "Not implemented" end |
#has_tx(tx_hash) ⇒ Object
check if tx with given tx_hash is already stored
212 213 214 |
# File 'lib/bitcoin/storage/storage.rb', line 212 def has_tx(tx_hash) raise "Not implemented" end |
#import(filename, max_depth = nil) ⇒ Object
import satoshi bitcoind blk0001.dat blockchain file
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/bitcoin/storage/storage.rb', line 392 def import filename, max_depth = nil if File.file?(filename) log.info { "Importing #{filename}" } File.open(filename) do |file| until file.eof? magic = file.read(4) # bitcoind pads the ends of the block files so that it doesn't # have to reallocate space on every new block. break if magic == "\0\0\0\0" raise "invalid network magic" unless Bitcoin.network[:magic_head] == magic size = file.read(4).unpack("L")[0] blk = Bitcoin::P::Block.new(file.read(size)) depth, chain = new_block(blk) break if max_depth && depth >= max_depth end end elsif File.directory?(filename) Dir.entries(filename).sort.each do |file| next unless file =~ /^blk.*?\.dat$/ import(File.join(filename, file), max_depth) end else raise "Import dir/file #{filename} not found" end end |
#in_sync? ⇒ Boolean
420 421 422 |
# File 'lib/bitcoin/storage/storage.rb', line 420 def in_sync? (get_head && (Time.now - get_head.time).to_i < 3600) ? true : false end |
#init_store_connection ⇒ Object
76 77 |
# File 'lib/bitcoin/storage/storage.rb', line 76 def init_store_connection end |
#new_block(blk) ⇒ Object
handle a new block incoming from the network
96 97 98 99 100 101 102 103 |
# File 'lib/bitcoin/storage/storage.rb', line 96 def new_block blk time = Time.now res = store_block(blk) log.info { "block #{blk.hash} " + "[#{res[0]}, #{['main', 'side', 'orphan'][res[1]]}] " + "(#{"%.4fs, %3dtx, %.3fkb" % [(Time.now - time), blk.tx.size, blk.payload.bytesize.to_f/1000]})" } if res && res[1] res end |
#new_tx(tx) ⇒ Object
197 198 199 |
# File 'lib/bitcoin/storage/storage.rb', line 197 def new_tx(tx) store_tx(tx) end |
#parse_script(txout, i, tx_hash = "", tx_idx) ⇒ Object
parse script and collect address/txout mappings to index
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 |
# File 'lib/bitcoin/storage/storage.rb', line 353 def parse_script txout, i, tx_hash = "", tx_idx addrs, names = [], [] script = Bitcoin::Script.new(txout.pk_script) rescue nil if script if script.is_hash160? || script.is_pubkey? || script.is_p2sh? addrs << [i, script.get_address] elsif script.is_multisig? script.get_multisig_addresses.map do |address| addrs << [i, address] end elsif Bitcoin.namecoin? && script.is_namecoin? addrs << [i, script.get_address] names << [i, script] elsif script.is_op_return? log.info { "Ignoring OP_RETURN script: #{script.get_op_return_data}" } else log.info { "Unknown script type in txout #{tx_hash}:#{tx_idx}" } log.debug { script.to_string } end script_type = SCRIPT_TYPES.index(script.type) else log.error { "Error parsing script #{tx_hash}:#{tx_idx}" } script_type = SCRIPT_TYPES.index(:unknown) end [script_type, addrs, names] end |
#persist_block(blk) ⇒ Object
persist given block blk to storage.
187 188 189 |
# File 'lib/bitcoin/storage/storage.rb', line 187 def persist_block(blk) raise "Not implemented" end |
#push_notification(channel, message) ⇒ Object
424 425 426 |
# File 'lib/bitcoin/storage/storage.rb', line 424 def push_notification channel, @notifiers[channel.to_sym].push() if @notifiers[channel.to_sym] end |
#rescan ⇒ Object
387 388 389 |
# File 'lib/bitcoin/storage/storage.rb', line 387 def rescan raise "Not implemented" end |
#reset ⇒ Object
reset the store; delete all data
85 86 87 |
# File 'lib/bitcoin/storage/storage.rb', line 85 def reset raise "Not implemented" end |
#store_block(blk) ⇒ Object
store given block blk. determine branch/chain and dept of block. trigger reorg if side branch becomes longer than current main chain and connect orpans.
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 |
# File 'lib/bitcoin/storage/storage.rb', line 108 def store_block blk log.debug { "new block #{blk.hash}" } existing = get_block(blk.hash) if existing && existing.chain == MAIN log.debug { "=> exists (#{existing.depth}, #{existing.chain})" } return [existing.depth] end prev_block = get_block(blk.prev_block.reverse_hth) unless @config[:skip_validation] validator = blk.validator(self, prev_block) validator.validate(rules: [:syntax], raise_errors: true) end if !prev_block || prev_block.chain == ORPHAN if blk.hash == Bitcoin.network[:genesis_hash] log.debug { "=> genesis (0)" } return persist_block(blk, MAIN, 0) else depth = prev_block ? prev_block.depth + 1 : 0 log.debug { "=> orphan (#{depth})" } return [0, 2] unless (in_sync? || Bitcoin.network_name =~ /testnet/) return persist_block(blk, ORPHAN, depth) end end depth = prev_block.depth + 1 checkpoint = @checkpoints[depth] if checkpoint && blk.hash != checkpoint log.warn "Block #{depth} doesn't match checkpoint #{checkpoint}" exit if depth > get_depth # TODO: handle checkpoint mismatch properly end if prev_block.chain == MAIN if prev_block == get_head log.debug { "=> main (#{depth})" } if !@config[:skip_validation] && ( !@checkpoints.any? || depth > @checkpoints.keys.last ) if self.class.name =~ /UtxoStore/ @config[:utxo_cache] = 0 @config[:block_cache] = 120 end validator.validate(rules: [:context], raise_errors: true) end res = persist_block(blk, MAIN, depth, prev_block.work) push_notification(:block, [blk, *res]) return res else log.debug { "=> side (#{depth})" } return persist_block(blk, SIDE, depth, prev_block.work) end else head = get_head if prev_block.work + blk.block_work <= head.work log.debug { "=> side (#{depth})" } return persist_block(blk, SIDE, depth, prev_block.work) else log.debug { "=> reorg" } new_main, new_side = [], [] fork_block = prev_block while fork_block.chain != MAIN new_main << fork_block.hash fork_block = fork_block.get_prev_block end b = fork_block while b = b.get_next_block new_side << b.hash end log.debug { "new main: #{new_main.inspect}" } log.debug { "new side: #{new_side.inspect}" } push_notification(:reorg, [ new_main, new_side ]) reorg(new_side.reverse, new_main.reverse) return persist_block(blk, MAIN, depth, prev_block.work) end end end |
#store_tx(tx, validate = true) ⇒ Object
store given tx
202 203 204 |
# File 'lib/bitcoin/storage/storage.rb', line 202 def store_tx(tx, validate = true) raise "Not implemented" end |
#subscribe(channel) ⇒ Object
428 429 430 431 |
# File 'lib/bitcoin/storage/storage.rb', line 428 def subscribe channel @notifiers[channel.to_sym] ||= EM::Channel.new @notifiers[channel.to_sym].subscribe {|*data| yield(*data) } end |
#update_block(hash, attrs) ⇒ Object
update attrs for block with given hash. typically used to update the chain value during reorg.
193 194 195 |
# File 'lib/bitcoin/storage/storage.rb', line 193 def update_block(hash, attrs) raise "Not implemented" end |