lian / bitcoin-ruby

bitcoin utils and protocol in ruby.
Other
922 stars 322 forks source link

Undefined method 'bytesize' for nil:NilClass Error #310

Closed oguzsoft closed 3 years ago

oguzsoft commented 3 years ago

NoMethodError (undefined method `bytesize' for nil:NilClass)

Has anyone encountered this error before when create a new transaction? Sometimes when creating output, sometimes when creating input. I don't know why? Also, sometimes is working :). I will understand if it never works, ı have an error. Note: Everytime, sender address has a enough coin and recipient address is valid address.

azuchi commented 3 years ago

The above information alone does not tell us where the error occurred. Can you provide a stack trace or some other way to see the error? Also, the payload of the transaction where the error occurred.

oguzsoft commented 3 years ago

Firstly I'm doing a blockchain scan. If there is an address belonging to me on the 'to' side or 'change' side of a transaction, I save the details of this transaction in the database as spent=false.

Step of construct transaction When a send request comes while a creating inputs, I sort the amount values ​​of hot_wallet's unspent transactions in ascending order and ı loop until it exceeds the amount of sending requests. In here, If you have a suggestion I would like to know your suggestion. While a creating outputs, ı use address and amount of transaction_request param.

Finally If transaction is created successfuly and If transaction broadcasted is success, ı update as spent=true ı used transaction(should_be_spent)

Descriptions

A example of transaction request param:

[
  { 'address' => 'my7q6HZfQnnuFXU9rdLzzatpopbx69K5KC',
    'amount' => '0.00002',
  },
  { 'address' => 'mjFEuoARzZRV9PAfJhr3ELRYE7xMtNwt8j',
    'amount' => '0.00001',
  }
]

Construct tx method

    def construct_transaction(transaction_requests)
      total_amount_of_all_withdrawals = transaction_requests.map { |w| (w['amount'].to_d * 10**8)}.sum.to_i 
      fee = 1000
      total = total_amount_of_all_withdrawals + fee

      unspent_transactions = get_utxos(hot_wallet.address, hot_wallet.construct_key)
      sorted_utxos = unspent_transactions.sort_by { |i| (i[:value].to_d * 10**8).to_i }
      should_be_spent = []
      input_amount = 0

      new_tx = build_tx do |t|
        sorted_utxos.each do |input|
          t.input do |i|
            i.prev_out input[:tx]
            i.prev_out_index input[:index]
            i.signature_key ::Bitcoin::Key.new(input[:key_as_hex])
          end
          input_amount += input[:value]
          should_be_spent << input[:tx].hash
          break if input_amount >= total
        end

        transaction_requests.each do |tx_req|
          t.output do |o|
            o.value (tx_req['amount'].to_d * 10**8).to_i
            o.script { |s| s.recipient tx_req['address'] }
          end
        end

        change = input_amount - total
        if change > 0
          t.output do |o|
            o.value change
            o.script { |s| s.recipient hot_wallet.address }
          end
        end
      end

      [new_tx.hash, new_tx.to_payload.bth, should_be_spent]
    end

As I said above, sometimes it works properly. But sometimes ı get an error: 'Undefined bytesize method' for nil:NilClass. I think probably, while creating output, o.script is nil... But this is only guess. If there is any place you cannot understand, ı would be happy to answer.

azuchi commented 3 years ago

The bytesize calculation is used by many pieces of code in the library, and an error is occurring somewhere in one of them. To determine where the error is occurring, you need a stack trace with the line number of the error.

oguzsoft commented 3 years ago
NoMethodError: undefined method `bytesize' for nil:NilClass
from /Users/oguz/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/bitcoin-ruby-0.0.20/lib/bitcoin/protocol/txout.rb:90:in `pk_script='
[7] pry(main)> wtf?
Exception: NoMethodError: undefined method `bytesize' for nil:NilClass
--
0: /Users/oguz/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/bitcoin-ruby-0.0.20/lib/bitcoin/protocol/txout.rb:90:in `pk_script='
1: /Users/oguz/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/bitcoin-ruby-0.0.20/lib/bitcoin/builder.rb:549:in `script'
2: /Users/oguz/Workspace/Locale/foo/foo_project/app/services/hot_wallet_raw_transaction_maker_service/bitcoin.rb:88:in `block (3 levels) in construct_transaction'
3: /Users/oguz/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/bitcoin-ruby-0.0.20/lib/bitcoin/builder.rb:167:in `output'
4: /Users/oguz/Workspace/Locale/foo/foo_project/app/services/hot_wallet_raw_transaction_maker_service/bitcoin.rb:86:in `block (2 levels) in construct_transaction'
5: /Users/oguz/Workspace/Locale/foo/foo_project/app/services/hot_wallet_raw_transaction_maker_service/bitcoin.rb:85:in `each'
6: /Users/oguz/Workspace/Locale/foo/foo_project/app/services/hot_wallet_raw_transaction_maker_service/bitcoin.rb:85:in `block in construct_transaction'
7: /Users/oguz/.rbenv/versions/2.6.6/lib/ruby/gems/2.6.0/gems/bitcoin-ruby-0.0.20/lib/bitcoin/builder.rb:20:in `build_tx'
8: /Users/oguz/Workspace/Locale/foo/foo_project/app/services/hot_wallet_raw_transaction_maker_service/bitcoin.rb:73:in `construct_transaction'
9: /Users/oguz/Workspace/Locale/foo/foo_project/app/services/hot_wallet_raw_transaction_maker_service/bitcoin.rb:21:in `call'
[8] pry(main)>
 # line 2: /Users/oguz/Workspace/Locale/foo/foo_project/app/services/hot_wallet_raw_transaction_maker_service/bitcoin.rb:88:in `block (3 levels) in construct_transaction'
85: transaction_requests.each do |tx_req|
86:  t.output do |o|
87:    o.value (tx_req['amount'].to_d * 10**8).to_i
88:    o.script { |s| s.recipient tx_req['address'] }
89:  end
90: end
# line 8: /Users/oguz/Workspace/Locale/foo/foo_project/app/services/hot_wallet_raw_transaction_maker_service/bitcoin.rb:73:in `construct_transaction'

73: new_tx = build_tx do |t|
        sorted_utxos.each do |input|
          t.input do |i|
            i.prev_out input[:tx]
            i.prev_out_index input[:index]
.
.
.

construct_transaction method is called from call method.

# line 9: /Users/oguz/Workspace/Locale/foo/foo_project/app/services/hot_wallet_raw_transaction_maker_service/bitcoin.rb:21:in `call'
azuchi commented 3 years ago

According to the stack trace

88: o.script { |s| s.recipient tx_req['address'] }

There seems to be an error in Script.to_address_script that is called inside above code. In other words, there are cases where Script.to_address_script(tx_req ['address']) returns nil. You should check the data in tx_req ['address'] that Script.to_address_script cannot interpret.

oguzsoft commented 3 years ago

I don't know how to do it more correctly. What would you think about this? I really couldn't understand this error.

[84, 93] in /Users/oguz/Workspace/Locale/foo/foo_project/app/services/hot_wallet_raw_transaction_maker_service/bitcoin.rb
   84:
   85:         transaction_requests.each do |tx_req|
   86:           t.output do |o|
   87:             o.value (tx_req['amount'].to_d * 10**8).to_i
   88:             byebug
=> 89:             o.script { |s| s.recipient tx_req['address'] }
   90:           end
   91:         end
   92:
   93:
(byebug) tx_req
{"address"=>"my7q6HZfQnnuFXU9rdLzzatpopbx69K5KC", "amount"=>"0.00002"}
(byebug) tx_req['address']
"my7q6HZfQnnuFXU9rdLzzatpopbx69K5KC"
(byebug) tx_req['amount']
"0.00002"
(byebug) o.value (tx_req['amount'].to_d * 10**8).to_i
2000
(byebug) o.script { |s| s.recipient tx_req['address'] }
*** NoMethodError Exception: undefined method `bytesize' for nil:NilClass

nil
(byebug)
azuchi commented 3 years ago

hmm, I wrote code snippet:

require 'bigdecimal/util'
Bitcoin.network = :testnet

include Bitcoin::Builder
tx_req = {}
tx_req['address'] = 'my7q6HZfQnnuFXU9rdLzzatpopbx69K5KC'
tx_req['amount'] = "0.00002"
new_tx = build_tx do |t|
  t.output do |o|
    o.value (tx_req['amount'].to_d * 10**8).to_i
    o.script { |s| s.recipient tx_req['address'] }
  end
end

it' work fine. Is the network testnet using Bitcoin.network = :testnet?

If you use other network, code will raise NoMethodError Exception: undefined method bytesize for nil:NilClass. Output Bitcoin.network_name when executing the code, you can see the applied network.

oguzsoft commented 3 years ago

Actually I was configuring Bitcoin.network in another file. In now I moved this to in construct_transaction method. It's work fine. I will try many construct transactions and ı will write the results... Thank you so much for your help.

oguzsoft commented 3 years ago

Its work fine... Thank you @azuchi