Class: Bitcoin::Store::UtxoDB

Inherits:
Object
  • Object
show all
Defined in:
lib/bitcoin/store/utxo_db.rb

Constant Summary collapse

KEY_PREFIX =
{
  out_point: 'o',        # key: out_point(tx_hash and index), value: Utxo
  script: 's',           # key: script_pubkey and out_point(tx_hash and index), value: Utxo
  height: 'h',           # key: block_height and out_point, value: Utxo
  tx_hash: 't',          # key: tx_hash of transaction, value: [block_height, tx_index]
  block: 'b',            # key: block_height and tx_index, value: tx_hash
  tx_payload: 'p',       # key: tx_hash, value: Tx
}

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path = "#{Bitcoin.base_dir}/db/utxo") ⇒ UtxoDB

Returns a new instance of UtxoDB.



18
19
20
21
22
# 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

Instance Attribute Details

#level_dbObject (readonly)

Returns the value of attribute level_db.



16
17
18
# File 'lib/bitcoin/store/utxo_db.rb', line 16

def level_db
  @level_db
end

#loggerObject (readonly)

Returns the value of attribute logger.



16
17
18
# File 'lib/bitcoin/store/utxo_db.rb', line 16

def logger
  @logger
end

Instance Method Details

#closeObject



24
25
26
# File 'lib/bitcoin/store/utxo_db.rb', line 24

def close
  level_db.close
end

#delete_utxo(out_point) ⇒ Bitcoin::Wallet::Utxo

Delete utxo from db

Parameters:

  • out_point (Bitcoin::Outpoint)

Returns:



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
# 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) ⇒ Object

return [Bitcoin::Wallet::Utxo …]

Parameters:



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

def get_balance(, current_block_height: 9999999, min: 0, max: 9999999)
  (, current_block_height: current_block_height, min: min, max: max).sum { |u| u.value }
end

#get_tx(tx_hash) ⇒ block_height, ...

Get transaction stored via save_tx and save_tx_position

Parameters:

  • tx_hash (string)

Returns:

  • (block_height, tx_index, tx_payload)


97
98
99
100
101
102
103
104
# 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) ⇒ Bitcoin::Wallet::Utxo

Get utxo of the specified out point

Parameters:

  • out_point (Bitcoin::Outpoint)

Returns:



151
152
153
154
155
156
157
# 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) ⇒ Object

return [Bitcoin::Wallet::Utxo …]



160
161
162
163
164
165
166
# 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) ⇒ Object

return [Bitcoin::Wallet::Utxo …]

Parameters:



170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/bitcoin/store/utxo_db.rb', line 170

def (, current_block_height: 9999999, min: 0, max: 9999999)
  return [] unless 

  script_pubkeys = case .purpose
    when Bitcoin::Wallet::Account::PURPOSE_TYPE[:legacy]
      .watch_targets.map { |t| Bitcoin::Script.to_p2pkh(t).to_hex }
    when Bitcoin::Wallet::Account::PURPOSE_TYPE[:nested_witness]
      .watch_targets.map { |t| Bitcoin::Script.to_p2wpkh(t).to_p2sh.to_hex }
    when Bitcoin::Wallet::Account::PURPOSE_TYPE[:native_segwit]
      .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) ⇒ Object

Save payload of a transaction into db

Parameters:



32
33
34
35
36
37
38
39
# 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) ⇒ Object

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.

Parameters:



47
48
49
50
51
52
53
54
55
56
57
58
# 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) ⇒ Object

Save utxo into db

Parameters:



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# 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