lib/bitcoin/wallet/wallet.rb in bitcoin-ruby-0.0.1 vs lib/bitcoin/wallet/wallet.rb in bitcoin-ruby-0.0.2

- old
+ new

@@ -1,5 +1,7 @@ +# encoding: ascii-8bit + Bitcoin.require_dependency :eventmachine, exit: false # The wallet implementation consists of several concepts: # Wallet:: the high-level API used to manage a wallet # SimpleKeyStore:: key store to manage keys/addresses/labels @@ -13,32 +15,36 @@ # connect with a CommandClient to relay those transactions through a node. # # TODO: new tx notification, keygenerators, keystore cleanup class Wallet - include Bitcoin::Builder + include Bitcoin + include Builder # the keystore (SimpleKeyStore) managing keys/addresses/labels attr_reader :keystore # the Storage which holds the blockchain attr_reader :storage # open wallet with given +storage+ Storage backend, +keystore+ SimpleKeyStore # and +selector+ SimpleCoinSelector - def initialize storage, keystore, selector + def initialize storage, keystore, selector = SimpleCoinSelector @storage = storage @keystore = keystore @selector = selector @callbacks = {} - connect_node if defined?(EM) + + @keystore.keys.each {|key| @storage.add_watched_address(key[:addr]) } + # connect_node if defined?(EM) + end def connect_node return unless EM.reactor_running? host, port = "127.0.0.1", 9999 - @node = Bitcoin::Network::CommandClient.connect(host, port, self, @storage) do + @node = Network::CommandClient.connect(host, port, self, @storage) do on_connected { request :monitor, "block", "tx" } on_block do |block, depth| EM.defer do block['tx'].each do |tx| relevant, tx = @args[0].check_tx(tx['hash']) @@ -76,11 +82,11 @@ return false end def log return @log if @log - @log = Bitcoin::Logger.create("wallet") + @log = Logger.create("wallet") @log.level = :debug @log end # call the callback specified by +name+ passing in +args+ @@ -103,17 +109,16 @@ # get all Storage::Models::TxOut concerning any address from this wallet def get_txouts(unconfirmed = false) txouts = @keystore.keys.map {|k| @storage.get_txouts_for_address(k[:addr])}.flatten.uniq - unconfirmed ? txouts : txouts.select {|o| !!o.get_tx.get_block} + (unconfirmed || @storage.class.name =~ /Utxo/) ? txouts : txouts.select {|o| !!o.get_tx.get_block} end # get total balance for all addresses in this wallet def get_balance(unconfirmed = false) values = get_txouts(unconfirmed).select{|o| !o.get_next_in}.map(&:value) - ([0] + values).inject(:+) end # list all addresses in this wallet def addrs @@ -121,10 +126,11 @@ end # add +key+ to wallet def add_key key @keystore.add_key(key) + @storage.add_watched_address(key[:addr]) end # set label for key +old+ to +new+ def label old, new @keystore.label_key(old, new) @@ -142,13 +148,25 @@ end end # create new key and return its address def get_new_addr - @keystore.new_key.addr + key = @keystore.new_key + @storage.add_watched_address(key.addr) + key.addr end + def import_key base58, label = nil + key = @keystore.import(base58, label) + @storage.add_watched_address(key.addr) + key.addr + end + + def rescan + @storage.rescan + end + # get SimpleCoinSelector with txouts for this wallet def get_selector @selector.new(get_txouts) end @@ -159,23 +177,40 @@ # examples: # [:address, <addr>, <value>] # [:multisig, 2, 3, <addr>, <addr>, <addr>, <value>] # # inputs are selected automatically by the SimpleCoinSelector. - # + # # change_policy controls where the change_output is spent to. # see #get_change_addr def new_tx outputs, fee = 0, change_policy = :back - output_value = outputs.map{|o|o[-1]}.inject(:+) + output_value = outputs.map{|o| o[-1] }.inject(:+) prev_outs = get_selector.select(output_value) - return nil if !prev_outs + raise "Insufficient funds." if !prev_outs + if Bitcoin.namecoin? + prev_out = nil + outputs.each do |out| + if out[0] == :name_firstupdate + name_hash = Bitcoin.hash160(out[2] + out[1].hth) + break if prev_out = get_txouts.find {|o| + o.type == :name_new && o.script.get_namecoin_hash == name_hash } + elsif out[0] == :name_update + break if prev_out = storage.name_show(out[1]).get_txout rescue nil + end + end + if outputs.find{|o| [:name_firstupdate, :name_update].include?(o[0]) } + raise "previous name tx not found in wallet." unless prev_out + prev_outs += [prev_out] + end + end input_value = prev_outs.map(&:value).inject(:+) - return nil unless input_value >= (output_value + fee) + raise "Insufficient funds." unless input_value >= (output_value + fee) - tx = tx do |t| + tx = build_tx do |t| + t.version 0x7100 if Bitcoin.namecoin? && outputs.find {|o| o[0].to_s =~ /^name_/ } outputs.each do |type, *addrs, value| t.output do |o| o.value value o.script do |s| s.type type @@ -199,21 +234,24 @@ prev_outs.each_with_index do |prev_out, idx| t.input do |i| prev_tx = prev_out.get_tx i.prev_out prev_tx i.prev_out_index prev_tx.out.index(prev_out) - pk_script = Bitcoin::Script.new(prev_out.pk_script) - if pk_script.is_pubkey? || pk_script.is_hash160? + pk_script = Script.new(prev_out.pk_script) + if pk_script.is_pubkey? || pk_script.is_hash160? || pk_script.is_namecoin? i.signature_key @keystore.key(prev_out.get_address)[:key] elsif pk_script.is_multisig? raise "multisig not implemented" end end end end + # TODO: spend multisig outputs again # TODO: verify signatures - Bitcoin::Protocol::Tx.new(tx.to_payload) + raise "Payload Error" unless P::Tx.new(tx.to_payload).to_payload == tx.to_payload + + tx end protected # get address to send change output to.