class Bitcoin::Store::UtxoDB

Constants

KEY_PREFIX

Attributes

level_db[R]
logger[R]

Public Class Methods

new(path = " click to toggle source
# File lib/bitcoin/store/utxo_db.rb, line 18
def initialize(path = "#{Bitcoin.base_dir}/db/utxo")
  FileUtils.mkdir_p(path)
  @level_db = ::LevelDBNative::DB.new(path)
  @logger = Bitcoin::Logger.create(:debug)
end

Public Instance Methods

close() click to toggle source
# File lib/bitcoin/store/utxo_db.rb, line 24
def close
  level_db.close
end
delete_utxo(out_point) click to toggle source

Delete utxo from db

@param [Bitcoin::Outpoint] out_point @return [Bitcoin::Wallet::Utxo]

# File lib/bitcoin/store/utxo_db.rb, line 110
def delete_utxo(out_point)
  level_db.batch do
    # [:out_point]
    key = KEY_PREFIX[:out_point] + out_point.to_hex
    return unless level_db.contains?(key)
    utxo = Bitcoin::Wallet::Utxo.parse_from_payload(level_db.get(key))
    level_db.delete(key)

    # [:script]
    if utxo.script_pubkey
      key = KEY_PREFIX[:script] + utxo.script_pubkey.to_hex + out_point.to_hex
      level_db.delete(key)
    end

    if utxo.block_height
      # [:height]
      key = KEY_PREFIX[:height] + [utxo.block_height].pack('N').bth + out_point.to_hex
      level_db.delete(key)

      # [:block]
      key = KEY_PREFIX[:block] + [utxo.block_height, utxo.index].pack('N2').bth
      level_db.delete(key)
    end

    # handles both [:tx_hash] and [:tx_payload]
    if utxo.tx_hash
      key = KEY_PREFIX[:tx_hash] + utxo.tx_hash
      level_db.delete(key)

      key = KEY_PREFIX[:tx_payload] + utxo.tx_hash
      level_db.delete(key)
    end

    utxo
  end
end
get_balance(account, current_block_height: 9999999, min: 0, max: 9999999) click to toggle source

@param [Bitcoin::Wallet::Account] return [Bitcoin::Wallet::Utxo …]

# File lib/bitcoin/store/utxo_db.rb, line 186
def get_balance(account, current_block_height: 9999999, min: 0, max: 9999999)
  list_unspent_in_account(account, current_block_height: current_block_height, min: min, max: max).sum { |u| u.value }
end
get_tx(tx_hash) click to toggle source

Get transaction stored via save_tx and save_tx_position

@param [string] tx_hash @return [block_height, tx_index, tx_payload]

# File lib/bitcoin/store/utxo_db.rb, line 97
def get_tx(tx_hash)
  key = KEY_PREFIX[:tx_hash] + tx_hash
  return [] unless level_db.contains?(key)
  block_height, tx_index = level_db.get(key).htb.unpack('N2')
  key = KEY_PREFIX[:tx_payload] + tx_hash
  tx_payload = level_db.get(key)
  [block_height, tx_index, tx_payload]
end
get_utxo(out_point) click to toggle source

Get utxo of the specified out point

@param [Bitcoin::Outpoint] out_point @return [Bitcoin::Wallet::Utxo]

# File lib/bitcoin/store/utxo_db.rb, line 151
def get_utxo(out_point)
  level_db.batch do
    key = KEY_PREFIX[:out_point] + out_point.to_hex
    return unless level_db.contains?(key)
    return Bitcoin::Wallet::Utxo.parse_from_payload(level_db.get(key))
  end
end
list_unspent(current_block_height: 9999999, min: 0, max: 9999999, addresses: nil) click to toggle source

return [Bitcoin::Wallet::Utxo …]

# File lib/bitcoin/store/utxo_db.rb, line 160
def list_unspent(current_block_height: 9999999, min: 0, max: 9999999, addresses: nil)
  if addresses
    list_unspent_by_addresses(current_block_height, min: min, max: max, addresses: addresses)
  else
    list_unspent_by_block_height(current_block_height, min: min, max: max)
  end
end
list_unspent_in_account(account, current_block_height: 9999999, min: 0, max: 9999999) click to toggle source

@param [Bitcoin::Wallet::Account] return [Bitcoin::Wallet::Utxo …]

# File lib/bitcoin/store/utxo_db.rb, line 170
def list_unspent_in_account(account, current_block_height: 9999999, min: 0, max: 9999999)
  return [] unless account

  script_pubkeys = case account.purpose
    when Bitcoin::Wallet::Account::PURPOSE_TYPE[:legacy]
      account.watch_targets.map { |t| Bitcoin::Script.to_p2pkh(t).to_hex }
    when Bitcoin::Wallet::Account::PURPOSE_TYPE[:nested_witness]
      account.watch_targets.map { |t| Bitcoin::Script.to_p2wpkh(t).to_p2sh.to_hex }
    when Bitcoin::Wallet::Account::PURPOSE_TYPE[:native_segwit]
      account.watch_targets.map { |t| Bitcoin::Script.to_p2wpkh(t).to_hex }
    end
  list_unspent_by_script_pubkeys(current_block_height, min: min, max: max, script_pubkeys: script_pubkeys)
end
save_tx(tx_hash, tx_payload) click to toggle source

Save payload of a transaction into db

@param [String] tx_hash @param [String] tx_payload

# File lib/bitcoin/store/utxo_db.rb, line 32
def save_tx(tx_hash, tx_payload)
  logger.info("UtxoDB#save_tx:#{[tx_hash, tx_payload]}")
  level_db.batch do
    # tx_hash -> [block_height, tx_index]
    key = KEY_PREFIX[:tx_payload] + tx_hash
    level_db.put(key, tx_payload)
  end
end
save_tx_position(tx_hash, block_height, tx_index) click to toggle source

Save tx position (block height and index in the block) into db When node receives `header` message, node should call save_tx_position to store block height and its index.

@param [String] tx_hash @param [Integer] block_height @param [Integer] tx_index

# File lib/bitcoin/store/utxo_db.rb, line 47
def save_tx_position(tx_hash, block_height, tx_index)
  logger.info("UtxoDB#save_tx_position:#{[tx_hash, block_height, tx_index]}")
  level_db.batch do
    # tx_hash -> [block_height, tx_index]
    key = KEY_PREFIX[:tx_hash] + tx_hash
    level_db.put(key, [block_height, tx_index].pack('N2').bth)

    # block_hash and tx_index -> tx_hash
    key = KEY_PREFIX[:block] + [block_height, tx_index].pack('N2').bth
    level_db.put(key, tx_hash)
  end
end
save_utxo(out_point, value, script_pubkey, block_height=nil) click to toggle source

Save utxo into db

@param [Bitcoin::OutPoint] out_point @param [Double] value @param [Bitcoin::Script] script_pubkey @param [Integer] block_height

# File lib/bitcoin/store/utxo_db.rb, line 66
def save_utxo(out_point, value, script_pubkey, block_height=nil)
  logger.info("UtxoDB#save_utxo:#{[out_point, value, script_pubkey, block_height]}")
  level_db.batch do
    utxo = Bitcoin::Wallet::Utxo.new(out_point.tx_hash, out_point.index, value, script_pubkey, block_height)
    payload = utxo.to_payload

    # out_point
    key = KEY_PREFIX[:out_point] + out_point.to_hex
    return if level_db.contains?(key)
    level_db.put(key, payload)

    # script_pubkey
    if script_pubkey
      key = KEY_PREFIX[:script] + script_pubkey.to_hex + out_point.to_hex
      level_db.put(key, payload)
    end

    # height
    unless block_height.nil?
      key = KEY_PREFIX[:height] + [block_height].pack('N').bth + out_point.to_hex
      level_db.put(key, payload)
    end

    utxo
  end
end

Private Instance Methods

list_unspent_by_addresses(current_block_height, min: 0, max: 9999999, addresses: []) click to toggle source
# File lib/bitcoin/store/utxo_db.rb, line 210
def list_unspent_by_addresses(current_block_height, min: 0, max: 9999999, addresses: [])
  script_pubkeys = addresses.map { |a| Bitcoin::Script.parse_from_addr(a).to_hex }
  list_unspent_by_script_pubkeys(current_block_height, min: min, max: max, script_pubkeys: script_pubkeys)
end
list_unspent_by_block_height(current_block_height, min: 0, max: 9999999) click to toggle source
# File lib/bitcoin/store/utxo_db.rb, line 202
def list_unspent_by_block_height(current_block_height, min: 0, max: 9999999)
  max_height = [current_block_height - min, 0].max
  min_height = [current_block_height - max, 0].max
  from = KEY_PREFIX[:height] + [min_height].pack('N').bth + '000000000000000000000000000000000000000000000000000000000000000000000000'
  to = KEY_PREFIX[:height] + [max_height].pack('N').bth + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
  utxos_between(from, to)
end
list_unspent_by_script_pubkeys(current_block_height, min: 0, max: 9999999, script_pubkeys: []) click to toggle source
# File lib/bitcoin/store/utxo_db.rb, line 215
def list_unspent_by_script_pubkeys(current_block_height, min: 0, max: 9999999, script_pubkeys: [])
  max_height = current_block_height - min
  min_height = current_block_height - max
  script_pubkeys.map do |key|
    from = KEY_PREFIX[:script] + key + '000000000000000000000000000000000000000000000000000000000000000000000000'
    to = KEY_PREFIX[:script] + key + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
    utxos_between(from, to).with_height(min_height, max_height)
  end.flatten
end
utxos_between(from, to) click to toggle source
# File lib/bitcoin/store/utxo_db.rb, line 192
def utxos_between(from, to)
  level_db.each(from: from, to: to).map { |k, v| Bitcoin::Wallet::Utxo.parse_from_payload(v) }
end