Class: BTC::TransactionBuilder
- Inherits:
-
Object
- Object
- BTC::TransactionBuilder
- Includes:
- Opcodes
- Defined in:
- lib/btcruby/transaction_builder.rb,
lib/btcruby/transaction_builder/errors.rb,
lib/btcruby/transaction_builder/result.rb,
lib/btcruby/transaction_builder/signer.rb,
lib/btcruby/transaction_builder/provider.rb
Overview
TransactionBuilder composes and optionally sings a transaction.
Defined Under Namespace
Modules: Provider, Signer Classes: Error, InsufficientFundsError, MissingChangeAddressError, MissingUnspentOutputsError, Result
Constant Summary
Constants included from Opcodes
Opcodes::OPCODE_NAME_TO_VALUE, Opcodes::OPCODE_VALUE_TO_NAME, Opcodes::OP_0, Opcodes::OP_0NOTEQUAL, Opcodes::OP_1, Opcodes::OP_10, Opcodes::OP_11, Opcodes::OP_12, Opcodes::OP_13, Opcodes::OP_14, Opcodes::OP_15, Opcodes::OP_16, Opcodes::OP_1ADD, Opcodes::OP_1NEGATE, Opcodes::OP_1SUB, Opcodes::OP_2, Opcodes::OP_2DIV, Opcodes::OP_2DROP, Opcodes::OP_2DUP, Opcodes::OP_2MUL, Opcodes::OP_2OVER, Opcodes::OP_2ROT, Opcodes::OP_2SWAP, Opcodes::OP_3, Opcodes::OP_3DUP, Opcodes::OP_4, Opcodes::OP_5, Opcodes::OP_6, Opcodes::OP_7, Opcodes::OP_8, Opcodes::OP_9, Opcodes::OP_ABS, Opcodes::OP_ADD, Opcodes::OP_AND, Opcodes::OP_BOOLAND, Opcodes::OP_BOOLOR, Opcodes::OP_CAT, Opcodes::OP_CHECKLOCKTIMEVERIFY, Opcodes::OP_CHECKMULTISIG, Opcodes::OP_CHECKMULTISIGVERIFY, Opcodes::OP_CHECKSIG, Opcodes::OP_CHECKSIGVERIFY, Opcodes::OP_CODESEPARATOR, Opcodes::OP_DEPTH, Opcodes::OP_DIV, Opcodes::OP_DROP, Opcodes::OP_DUP, Opcodes::OP_ELSE, Opcodes::OP_ENDIF, Opcodes::OP_EQUAL, Opcodes::OP_EQUALVERIFY, Opcodes::OP_FALSE, Opcodes::OP_FROMALTSTACK, Opcodes::OP_GREATERTHAN, Opcodes::OP_GREATERTHANOREQUAL, Opcodes::OP_HASH160, Opcodes::OP_HASH256, Opcodes::OP_IF, Opcodes::OP_IFDUP, Opcodes::OP_INVALIDOPCODE, Opcodes::OP_INVERT, Opcodes::OP_LEFT, Opcodes::OP_LESSTHAN, Opcodes::OP_LESSTHANOREQUAL, Opcodes::OP_LSHIFT, Opcodes::OP_MAX, Opcodes::OP_MIN, Opcodes::OP_MOD, Opcodes::OP_MUL, Opcodes::OP_NEGATE, Opcodes::OP_NIP, Opcodes::OP_NOP, Opcodes::OP_NOP1, Opcodes::OP_NOP10, Opcodes::OP_NOP2, Opcodes::OP_NOP3, Opcodes::OP_NOP4, Opcodes::OP_NOP5, Opcodes::OP_NOP6, Opcodes::OP_NOP7, Opcodes::OP_NOP8, Opcodes::OP_NOP9, Opcodes::OP_NOT, Opcodes::OP_NOTIF, Opcodes::OP_NUMEQUAL, Opcodes::OP_NUMEQUALVERIFY, Opcodes::OP_NUMNOTEQUAL, Opcodes::OP_OR, Opcodes::OP_OVER, Opcodes::OP_PICK, Opcodes::OP_PUSHDATA1, Opcodes::OP_PUSHDATA2, Opcodes::OP_PUSHDATA4, Opcodes::OP_RESERVED, Opcodes::OP_RESERVED1, Opcodes::OP_RESERVED2, Opcodes::OP_RETURN, Opcodes::OP_RIGHT, Opcodes::OP_RIPEMD160, Opcodes::OP_ROLL, Opcodes::OP_ROT, Opcodes::OP_RSHIFT, Opcodes::OP_SHA1, Opcodes::OP_SHA256, Opcodes::OP_SIZE, Opcodes::OP_SUB, Opcodes::OP_SUBSTR, Opcodes::OP_SWAP, Opcodes::OP_TOALTSTACK, Opcodes::OP_TRUE, Opcodes::OP_TUCK, Opcodes::OP_VER, Opcodes::OP_VERIF, Opcodes::OP_VERIFY, Opcodes::OP_VERNOTIF, Opcodes::OP_WITHIN, Opcodes::OP_XOR
Instance Attribute Summary collapse
-
#change_address ⇒ Object
Change address (base58-encoded string or BTC::Address).
-
#dust_change ⇒ Object
Amount of change that can be forgone as a mining fee if there are no more unspent outputs available.
-
#fee_rate ⇒ Object
Miner’s fee per kilobyte (1000 bytes).
-
#input_addresses ⇒ Object
Addresses from which to fetch the inputs.
-
#keep_unspent_outputs_order ⇒ Object
If true, does not sort unspent_outputs by confirmations number.
-
#minimum_change ⇒ Object
Minimum amount of change below which transaction is not composed.
-
#network ⇒ Object
Implementation of the attributes declared above ===============================================.
-
#outputs ⇒ Object
An array of BTC::TransactionOutput instances specifying how many coins to spend and how.
-
#outputs_amount ⇒ Object
readonly
A total amount in satoshis to be spent in all outputs (not including change output).
-
#outputs_size ⇒ Object
readonly
A total size of all outputs in bytes (including change output).
-
#parent_transactions ⇒ Object
Array of non-published transactions, which outputs can be consumed.
-
#prepended_unspent_outputs ⇒ Object
Prepended unspent outputs with valid ‘index` and `transaction_hash` attributes.
-
#provider ⇒ Object
Provider of the unspent outputs.
-
#public_addresses ⇒ Object
readonly
Public addresses used on the inputs.
-
#signer ⇒ Object
Signer for the inputs.
-
#unspent_outputs ⇒ Object
Actual available BTC::TransactionOutput’s to spend.
Instance Method Summary collapse
-
#build ⇒ Object
Attempts to build a transaction.
- #internal_provider ⇒ Object
Instance Attribute Details
#change_address ⇒ Object
Change address (base58-encoded string or BTC::Address). Must be specified, but may not be used if change is too small.
29 30 31 |
# File 'lib/btcruby/transaction_builder.rb', line 29 def change_address @change_address end |
#dust_change ⇒ Object
Amount of change that can be forgone as a mining fee if there are no more unspent outputs available. If equals zero, no amount is allowed to be forgone. Default value equals minimum_change. This means builder will never fail with “insufficient funds” just because it could not find enough unspents for big enough change. In worst case it will forgo the change as a part of the mining fee. Set to 0 to avoid wasting a single satoshi.
75 76 77 |
# File 'lib/btcruby/transaction_builder.rb', line 75 def dust_change @dust_change end |
#fee_rate ⇒ Object
Miner’s fee per kilobyte (1000 bytes). Default is Transaction::DEFAULT_FEE_RATE
61 62 63 |
# File 'lib/btcruby/transaction_builder.rb', line 61 def fee_rate @fee_rate end |
#input_addresses ⇒ Object
Addresses from which to fetch the inputs. Could be base58-encoded address or BTC::Address instances. If any address is a WIF, the corresponding input will be automatically signed with SIGHASH_ALL. Otherwise, the signature_script in the input will be set to output script from unspent output.
35 36 37 |
# File 'lib/btcruby/transaction_builder.rb', line 35 def input_addresses @input_addresses end |
#keep_unspent_outputs_order ⇒ Object
If true, does not sort unspent_outputs by confirmations number. Default is false, but order will be preserved if #confirmations attribute is nil.
79 80 81 |
# File 'lib/btcruby/transaction_builder.rb', line 79 def keep_unspent_outputs_order @keep_unspent_outputs_order end |
#minimum_change ⇒ Object
Minimum amount of change below which transaction is not composed. If change amount is non-zero and below this value, more unspent outputs are used. If change amount is zero, change output is not even created and this attribute is not used. Default value equals fee_rate.
67 68 69 |
# File 'lib/btcruby/transaction_builder.rb', line 67 def minimum_change @minimum_change end |
#network ⇒ Object
Implementation of the attributes declared above
21 22 23 |
# File 'lib/btcruby/transaction_builder.rb', line 21 def network @network end |
#outputs ⇒ Object
An array of BTC::TransactionOutput instances specifying how many coins to spend and how. If the array is empty, all unspent outputs are spent to the change address.
25 26 27 |
# File 'lib/btcruby/transaction_builder.rb', line 25 def outputs @outputs end |
#outputs_amount ⇒ Object (readonly)
A total amount in satoshis to be spent in all outputs (not including change output). If equals ‘nil`, all available unspent outputs for the public_address are expected to be returned (“swiping the keys”).
90 91 92 |
# File 'lib/btcruby/transaction_builder.rb', line 90 def outputs_amount @outputs_amount end |
#outputs_size ⇒ Object (readonly)
A total size of all outputs in bytes (including change output).
93 94 95 |
# File 'lib/btcruby/transaction_builder.rb', line 93 def outputs_size @outputs_size end |
#parent_transactions ⇒ Object
Array of non-published transactions, which outputs can be consumed.
49 50 51 |
# File 'lib/btcruby/transaction_builder.rb', line 49 def parent_transactions @parent_transactions end |
#prepended_unspent_outputs ⇒ Object
Prepended unspent outputs with valid ‘index` and `transaction_hash` attributes. These will be included before some optional unspent outputs.
39 40 41 |
# File 'lib/btcruby/transaction_builder.rb', line 39 def prepended_unspent_outputs @prepended_unspent_outputs end |
#provider ⇒ Object
Provider of the unspent outputs. If not specified, ‘unspent_outputs` must be provided.
53 54 55 |
# File 'lib/btcruby/transaction_builder.rb', line 53 def provider @provider end |
#public_addresses ⇒ Object (readonly)
Public addresses used on the inputs. Composed by converting all ‘WIF` instance in `input_addresses` to corresponding public addresses.
85 86 87 |
# File 'lib/btcruby/transaction_builder.rb', line 85 def public_addresses @public_addresses end |
#signer ⇒ Object
Signer for the inputs. If not provided, inputs will be left unsigned.
57 58 59 |
# File 'lib/btcruby/transaction_builder.rb', line 57 def signer @signer end |
#unspent_outputs ⇒ Object
Actual available BTC::TransactionOutput’s to spend. If not specified, builder will fetch and remember unspent outputs using ‘provider`. Only necessary inputs will be selected for spending. If TransactionOutput#confirmations attribute is not nil, outputs are sorted from oldest to newest, unless #keep_unspent_outputs_order is set to true.
46 47 48 |
# File 'lib/btcruby/transaction_builder.rb', line 46 def unspent_outputs @unspent_outputs end |
Instance Method Details
#build ⇒ Object
Attempts to build a transaction
177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/btcruby/transaction_builder.rb', line 177 def build if !self.change_address raise MissingChangeAddressError end result = Result.new add_input_for_utxo = proc do |utxo| raise ArgumentError, "Unspent output must contain index" if !utxo.index raise ArgumentError, "Unspent output must contain transaction_hash" if !utxo.transaction_hash if !self.internal_provider.consumed_unspent_output?(utxo) self.internal_provider.consume_unspent_output(utxo) result.inputs_amount += utxo.value txin = TransactionInput.new( previous_hash: utxo.transaction_hash, previous_index: utxo.index, # put the output script here so the signer knows which key to use. signature_script: utxo.script) txin.transaction_output = utxo result.transaction.add_input(txin) else # UTXO was already consumed possibly by another Tx Builder sharing the same provider. end end # If no outputs are specified, spend all utxos to a change address, minus the mining fee. if (self.outputs || []).size == 0 result.inputs_amount = 0 (self.prepended_unspent_outputs || []).each(&add_input_for_utxo) (self.unspent_outputs || []).each(&add_input_for_utxo) if result.transaction.inputs.size == 0 raise MissingUnspentOutputsError, "Missing unspent outputs" end # Value will be determined after computing the fee change_output = TransactionOutput.new(value: 0, script: self.change_address.public_address.script) result.transaction.add_output(change_output) result.fee = compute_fee_for_transaction(result.transaction, self.fee_rate) result.outputs_amount = result.inputs_amount - result.fee result.change_amount = 0 # Check if inputs cover the fees if result.outputs_amount < 0 raise InsufficientFundsError end # Warn if the output amount is relatively small. if result.outputs_amount < result.fee Diagnostics.current.("BTC::TransactionBuilder: Warning! Spending all unspent outputs returns less than a mining fee. (#{result.outputs_amount} < #{result.fee})") end # Set the output value as needed result.transaction.outputs[0].value = result.outputs_amount # For each address that is a WIF, locate the matching input and sign it. attempt_to_sign_transaction(result) return result end # if no specific outputs required # We are having one or more outputs (e.g. normal payment) # Need to find appropriate unspents and compose a transaction. # Prepare all outputs. # result.outputs_amount will also contain a fee after it's calculated. (self.outputs || []).each do |txout| result.outputs_amount += txout.value result.transaction.add_output(txout) end # We'll determine final change value depending on inputs. # Setting default to MAX_MONEY will protect against a bug when we fail to update the amount and # spend unexpected amount on mining fees. change_output = TransactionOutput.new(value: MAX_MONEY, script: self.change_address.public_address.script) result.transaction.add_output(change_output) mandatory_utxos = (self.prepended_unspent_outputs || []).to_a.dup # We have specific outputs with specific amounts, so we need to select the best amount of coins. # To play nice with BIP32/BIP44 change addresses (that need to monitor an increasing amount of addresses), # we'll spend oldest outputs first and will add more and more newer outputs until we cover fees # and change output is either zero or above the dust limit. # If `confirmations` attribute is nil, order is preserved. utxos = self.unspent_outputs || [] if self.keep_unspent_outputs_order sorted_utxos = utxos.to_a.dup else sorted_utxos = utxos.to_a.sort_by{|txout| -(txout.confirmations || -1) } # oldest first end if self.parent_transactions # Can repeat some outputs in mandatory_utxos or sorted_utxos, # but double-spending will be prevented by provider. self.parent_transactions.each do |parenttx| sorted_utxos += parenttx.outputs end end all_utxos = (sorted_utxos + mandatory_utxos) while true if (sorted_utxos.size + mandatory_utxos.size) == 0 raise InsufficientFundsError end utxo = nil while (sorted_utxos.size + mandatory_utxos.size) > 0 utxo = if mandatory_utxos.size > 0 mandatory_utxos.shift else sorted_utxos.shift end if utxo.value > 0 && !utxo.script.op_return_script? && !self.internal_provider.consumed_unspent_output?(utxo) self.internal_provider.consume_unspent_output(utxo) break else #puts "CONSUMED UTXO, SKIPPING: #{utxo.transaction_id}:#{utxo.index} (#{utxo.value})" # Continue reading utxos to find one that is not consumed yet. utxo = nil end end if !utxo # puts "ALL UTXOS:" # all_utxos.each do |utxo| # puts "--> #{utxo.transaction_id}:#{utxo.index} (#{utxo.value})" # end raise InsufficientFundsError end result.inputs_amount += utxo.value raise ArgumentError, "Transaction hash must be non-nil in unspent output" if !utxo.transaction_hash raise ArgumentError, "Index must be non-nil in unspent output" if !utxo.index txin = TransactionInput.new(previous_hash: utxo.transaction_hash, previous_index: utxo.index, # put the output script here so the signer knows which key to use. signature_script: utxo.script) txin.transaction_output = utxo result.transaction.add_input(txin) # Do not check the result before we included all the mandatory utxos. if mandatory_utxos.size == 0 # Before computing the fee, quick check if we have enough inputs to cover the outputs. # If not, go and add one more utxo before wasting time computing fees. if result.inputs_amount >= result.outputs_amount fee = compute_fee_for_transaction(result.transaction, self.fee_rate) change = result.inputs_amount - result.outputs_amount - fee if change >= self.minimum_change # We have a big enough change, set missing values and return. change_output.value = change result.change_amount = change result.outputs_amount += change result.fee = fee attempt_to_sign_transaction(result) return result elsif change > self.dust_change && change < self.minimum_change # We have a shitty change: not small enough to forgo, not big enough to be useful. # Try adding more utxos on the next cycle (or fail if no more utxos are available). elsif change >= 0 && change <= self.dust_change # This also includes the case when change is exactly zero satoshis. # Remove the change output, keep existing outputs_amount, set fee and try to sign. result.transaction.outputs = result.transaction.outputs[0, result.transaction.outputs.size - 1] result.change_amount = 0 result.fee = fee attempt_to_sign_transaction(result) return result else # Change is negative, we need more funds for this transaction. # Try adding more utxos on the next cycle. end end # if inputs_amount >= outputs_amount end # if no more mandatory outputs to add end # while true end |