btcruby 0.0.0 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +18 -0
- data/.travis.yml +7 -0
- data/FAQ.md +7 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +18 -0
- data/HOWTO.md +17 -0
- data/LICENSE +19 -0
- data/README.md +59 -0
- data/Rakefile +6 -0
- data/TODO.txt +40 -0
- data/bin/console +19 -0
- data/btcruby.gemspec +20 -0
- data/documentation/address.md +73 -0
- data/documentation/base58.md +52 -0
- data/documentation/block.md +127 -0
- data/documentation/block_header.md +120 -0
- data/documentation/constants.md +88 -0
- data/documentation/data.md +54 -0
- data/documentation/diagnostics.md +90 -0
- data/documentation/extensions.md +76 -0
- data/documentation/hash_functions.md +58 -0
- data/documentation/hash_id.md +22 -0
- data/documentation/index.md +230 -0
- data/documentation/key.md +177 -0
- data/documentation/keychain.md +180 -0
- data/documentation/network.md +75 -0
- data/documentation/opcode.md +220 -0
- data/documentation/openssl.md +7 -0
- data/documentation/p2pkh.md +71 -0
- data/documentation/p2sh.md +64 -0
- data/documentation/proof_of_work.md +84 -0
- data/documentation/script.md +280 -0
- data/documentation/signature.md +71 -0
- data/documentation/transaction.md +213 -0
- data/documentation/transaction_builder.md +188 -0
- data/documentation/transaction_input.md +133 -0
- data/documentation/transaction_output.md +130 -0
- data/documentation/wif.md +72 -0
- data/documentation/wire_format.md +70 -0
- data/lib/btcruby/address.rb +296 -0
- data/lib/btcruby/base58.rb +108 -0
- data/lib/btcruby/big_number.rb +47 -0
- data/lib/btcruby/block.rb +170 -0
- data/lib/btcruby/block_header.rb +231 -0
- data/lib/btcruby/constants.rb +59 -0
- data/lib/btcruby/currency_formatter.rb +64 -0
- data/lib/btcruby/data.rb +98 -0
- data/lib/btcruby/diagnostics.rb +92 -0
- data/lib/btcruby/errors.rb +8 -0
- data/lib/btcruby/extensions.rb +65 -0
- data/lib/btcruby/hash_functions.rb +54 -0
- data/lib/btcruby/hash_id.rb +18 -0
- data/lib/btcruby/key.rb +517 -0
- data/lib/btcruby/keychain.rb +464 -0
- data/lib/btcruby/network.rb +73 -0
- data/lib/btcruby/opcode.rb +197 -0
- data/lib/btcruby/open_assets/asset.rb +35 -0
- data/lib/btcruby/open_assets/asset_address.rb +49 -0
- data/lib/btcruby/open_assets/asset_definition.rb +75 -0
- data/lib/btcruby/open_assets/asset_id.rb +24 -0
- data/lib/btcruby/open_assets/asset_marker.rb +94 -0
- data/lib/btcruby/open_assets/asset_processor.rb +377 -0
- data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
- data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
- data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
- data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
- data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
- data/lib/btcruby/open_assets.rb +26 -0
- data/lib/btcruby/openssl.rb +536 -0
- data/lib/btcruby/proof_of_work.rb +110 -0
- data/lib/btcruby/safety.rb +26 -0
- data/lib/btcruby/script.rb +733 -0
- data/lib/btcruby/signature_hashtype.rb +37 -0
- data/lib/btcruby/transaction.rb +511 -0
- data/lib/btcruby/transaction_builder/errors.rb +15 -0
- data/lib/btcruby/transaction_builder/provider.rb +54 -0
- data/lib/btcruby/transaction_builder/result.rb +73 -0
- data/lib/btcruby/transaction_builder/signer.rb +28 -0
- data/lib/btcruby/transaction_builder.rb +520 -0
- data/lib/btcruby/transaction_input.rb +298 -0
- data/lib/btcruby/transaction_outpoint.rb +30 -0
- data/lib/btcruby/transaction_output.rb +315 -0
- data/lib/btcruby/version.rb +3 -0
- data/lib/btcruby/wif.rb +118 -0
- data/lib/btcruby/wire_format.rb +362 -0
- data/lib/btcruby.rb +44 -2
- data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
- data/sample_code/creating_a_transaction_manually.rb +44 -0
- data/sample_code/generating_an_address.rb +20 -0
- data/sample_code/using_transaction_builder.rb +49 -0
- data/spec/address_spec.rb +206 -0
- data/spec/all.rb +6 -0
- data/spec/base58_spec.rb +83 -0
- data/spec/block_header_spec.rb +18 -0
- data/spec/block_spec.rb +18 -0
- data/spec/currency_formatter_spec.rb +46 -0
- data/spec/data_spec.rb +50 -0
- data/spec/diagnostics_spec.rb +41 -0
- data/spec/key_spec.rb +205 -0
- data/spec/keychain_spec.rb +261 -0
- data/spec/network_spec.rb +48 -0
- data/spec/open_assets/asset_address_spec.rb +33 -0
- data/spec/open_assets/asset_id_spec.rb +15 -0
- data/spec/open_assets/asset_marker_spec.rb +47 -0
- data/spec/open_assets/asset_processor_spec.rb +567 -0
- data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
- data/spec/open_assets/asset_transaction_spec.rb +70 -0
- data/spec/proof_of_work_spec.rb +53 -0
- data/spec/script_spec.rb +66 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/transaction_builder_spec.rb +338 -0
- data/spec/transaction_spec.rb +162 -0
- data/spec/wire_format_spec.rb +283 -0
- metadata +141 -7
@@ -0,0 +1,298 @@
|
|
1
|
+
# Transaction input (aka "txin") represents a reference to another transaction's output.
|
2
|
+
# Reference is defined by tx hash + tx output index.
|
3
|
+
# Signature script is used to prove ownership of the corresponding tx output.
|
4
|
+
# Sequence is used to require different signatures when tx is updated. It is only relevant when tx lock_time > 0.
|
5
|
+
module BTC
|
6
|
+
class TransactionInput
|
7
|
+
|
8
|
+
INVALID_INDEX = 0xFFFFFFFF # aka "(unsigned int) -1" in BitcoinQT.
|
9
|
+
MAX_SEQUENCE = 0xFFFFFFFF
|
10
|
+
ZERO_HASH256 = "\x00".b*32
|
11
|
+
|
12
|
+
# Hash of the previous transaction (raw binary hash)
|
13
|
+
attr_accessor :previous_hash
|
14
|
+
|
15
|
+
# ID of a previous transaction (reversed hash in hex)
|
16
|
+
attr_accessor :previous_id
|
17
|
+
|
18
|
+
# Index of the previous transaction's output (uint32_t).
|
19
|
+
attr_accessor :previous_index
|
20
|
+
|
21
|
+
# BTC::Script instance that proves ownership of the previous transaction output.
|
22
|
+
# We intentionally do not call it "script" to avoid accidental confusion with
|
23
|
+
# TransactionOutput#script.
|
24
|
+
attr_accessor :signature_script
|
25
|
+
|
26
|
+
# Binary String contained in signature script in coinbase input.
|
27
|
+
# Returns nil if it is not a coinbase input.
|
28
|
+
attr_accessor :coinbase_data
|
29
|
+
|
30
|
+
# Input sequence (uint32_t). Default is maximum value 0xFFFFFFFF.
|
31
|
+
# Sequence is used to update a timelocked tx stored in memory of the nodes. It is only relevant when tx lock_time > 0.
|
32
|
+
# Currently, for DoS and security reasons, nodes do not store timelocked transactions making the sequence number meaningless.
|
33
|
+
attr_accessor :sequence
|
34
|
+
|
35
|
+
# Binary representation of the input in wire format (aka payload).
|
36
|
+
attr_reader :data
|
37
|
+
|
38
|
+
# Dictionary representation of transaction ready to be encoded in JSON, PropertyList etc.
|
39
|
+
attr_reader :dictionary
|
40
|
+
|
41
|
+
# Optional reference to the owning transaction.
|
42
|
+
# It is set in `tx.add_input` and reset to nil in `tx.remove_all_inputs`.
|
43
|
+
# Default is nil.
|
44
|
+
attr_accessor :transaction
|
45
|
+
|
46
|
+
# Optional index within owning transaction.
|
47
|
+
# It is set in `tx.add_input` and reset to nil in `tx.remove_all_inputs`.
|
48
|
+
# Default is nil.
|
49
|
+
attr_accessor :index
|
50
|
+
|
51
|
+
# Optional attribute referencing an output that this input is spending.
|
52
|
+
attr_accessor :transaction_output
|
53
|
+
|
54
|
+
# Optional attribute containing a value in the corresponding output (in satoshis).
|
55
|
+
# Default is transaction_output.value or nil.
|
56
|
+
attr_accessor :value
|
57
|
+
|
58
|
+
# Initializes transaction input with its attributes. Every attribute has a valid default value.
|
59
|
+
def initialize(data: nil,
|
60
|
+
stream: nil,
|
61
|
+
dictionary: nil,
|
62
|
+
previous_hash: ZERO_HASH256,
|
63
|
+
previous_id: nil,
|
64
|
+
previous_index: INVALID_INDEX,
|
65
|
+
coinbase_data: nil,
|
66
|
+
signature_script: BTC::Script.new,
|
67
|
+
sequence: MAX_SEQUENCE,
|
68
|
+
|
69
|
+
# optional attributes
|
70
|
+
transaction: nil,
|
71
|
+
transaction_output: nil,
|
72
|
+
value: nil)
|
73
|
+
if stream || data
|
74
|
+
init_with_stream(stream || StringIO.new(data))
|
75
|
+
elsif dictionary
|
76
|
+
init_with_dictionary(dictionary)
|
77
|
+
else
|
78
|
+
@previous_hash = previous_hash || ZERO_HASH256
|
79
|
+
@previous_hash = BTC.hash_from_id(previous_id) if previous_id
|
80
|
+
@previous_index = previous_index || INVALID_INDEX
|
81
|
+
@coinbase_data = coinbase_data
|
82
|
+
@signature_script = signature_script || BTC::Script.new
|
83
|
+
@sequence = sequence || MAX_SEQUENCE
|
84
|
+
end
|
85
|
+
|
86
|
+
@transaction = transaction
|
87
|
+
|
88
|
+
@transaction_output = transaction_output
|
89
|
+
# Try to set outpoint data based on transaction output.
|
90
|
+
if @transaction_output
|
91
|
+
if @previous_hash == ZERO_HASH256
|
92
|
+
@previous_hash = @transaction_output.transaction_hash || ZERO_HASH256
|
93
|
+
end
|
94
|
+
if @previous_index == INVALID_INDEX
|
95
|
+
@previous_index = @transaction_output.index || INVALID_INDEX
|
96
|
+
end
|
97
|
+
end
|
98
|
+
@value = value
|
99
|
+
end
|
100
|
+
|
101
|
+
def init_with_stream(stream)
|
102
|
+
if stream.eof?
|
103
|
+
raise ArgumentError, "Can't parse transaction input from stream because it is already closed."
|
104
|
+
end
|
105
|
+
|
106
|
+
if !(@previous_hash = stream.read(32)) || @previous_hash.bytesize != 32
|
107
|
+
raise ArgumentError, "Failed to read 32-byte previous_hash from stream."
|
108
|
+
end
|
109
|
+
|
110
|
+
if !(@previous_index = BTC::WireFormat.read_uint32le(stream: stream).first)
|
111
|
+
raise ArgumentError, "Failed to read previous_index from stream."
|
112
|
+
end
|
113
|
+
|
114
|
+
is_coinbase = (@previous_hash == ZERO_HASH256 && @previous_index == INVALID_INDEX)
|
115
|
+
|
116
|
+
if !(scriptdata = BTC::WireFormat.read_string(stream: stream).first)
|
117
|
+
raise ArgumentError, "Failed to read signature_script data from stream."
|
118
|
+
end
|
119
|
+
|
120
|
+
@coinbase_data = nil
|
121
|
+
@signature_script = nil
|
122
|
+
|
123
|
+
if is_coinbase
|
124
|
+
@coinbase_data = scriptdata
|
125
|
+
else
|
126
|
+
@signature_script = BTC::Script.new(data: scriptdata)
|
127
|
+
end
|
128
|
+
|
129
|
+
if !(@sequence = BTC::WireFormat.read_uint32le(stream: stream).first)
|
130
|
+
raise ArgumentError, "Failed to read sequence from stream."
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
def init_with_dictionary(dict)
|
135
|
+
raise ArgumentError, "Dictionary is missing" if !dict
|
136
|
+
|
137
|
+
# Supports bitcoin-QT RPC format
|
138
|
+
|
139
|
+
if dict["prev_out"] && !dict["prev_out"].is_a?(Hash)
|
140
|
+
raise ArgumentError, "prev_out is not a dictionary."
|
141
|
+
end
|
142
|
+
|
143
|
+
prevhash = ZERO_HASH256
|
144
|
+
previndex = INVALID_INDEX
|
145
|
+
script = nil
|
146
|
+
seq = MAX_SEQUENCE
|
147
|
+
|
148
|
+
if dict["prev_out"]
|
149
|
+
if hashhex = dict["prev_out"]["hash"]
|
150
|
+
prevhash = BTC::Data.data_from_hex(hashhex)
|
151
|
+
if prevhash.bytesize != 32
|
152
|
+
raise ArgumentError, "prev_out.hash is not 32 bytes long."
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
if n = dict["prev_out"]["n"]
|
157
|
+
index = n.to_i
|
158
|
+
if index < 0 || index > 0xffffffff
|
159
|
+
raise ArgumentError, "prev_out.n is out of bounds (#{index})."
|
160
|
+
end
|
161
|
+
previndex = index
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
coinbase_data = nil
|
166
|
+
script = nil
|
167
|
+
if hex = dict["coinbase"]
|
168
|
+
coinbase_data = BTC::Data.data_from_hex(hex)
|
169
|
+
elsif dict["scriptSig"]
|
170
|
+
if dict["scriptSig"].is_a?(Hash)
|
171
|
+
if hex = dict["scriptSig"]["hex"]
|
172
|
+
script = BTC::Script.new(data: BTC::Data.data_from_hex(hex))
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
if dict["sequence"]
|
178
|
+
seq = dict["sequence"].to_i
|
179
|
+
if seq < 0 || seq > MAX_SEQUENCE
|
180
|
+
raise ArgumentError, "sequence is out of bounds (#{index})."
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
@previous_hash = prevhash
|
185
|
+
@previous_index = previndex,
|
186
|
+
@coinbase_data = coinbase_data
|
187
|
+
@signature_script = script
|
188
|
+
@sequence = seq
|
189
|
+
end
|
190
|
+
|
191
|
+
def self.with_data(data)
|
192
|
+
raise ArgumentError, "Use TransactionInput.new(data: ...) instead"
|
193
|
+
end
|
194
|
+
|
195
|
+
def self.with_stream(stream)
|
196
|
+
raise ArgumentError, "Use TransactionInput.new(stream: ...) instead"
|
197
|
+
end
|
198
|
+
|
199
|
+
def self.with_dictionary(dict)
|
200
|
+
raise ArgumentError, "Use TransactionInput.new(dictionary: ...) instead"
|
201
|
+
end
|
202
|
+
|
203
|
+
# Returns true if this input is a coinbase input.
|
204
|
+
def coinbase?
|
205
|
+
return self.previous_index == INVALID_INDEX && self.previous_hash == ZERO_HASH256
|
206
|
+
end
|
207
|
+
|
208
|
+
def previous_id
|
209
|
+
BTC.id_from_hash(self.previous_hash)
|
210
|
+
end
|
211
|
+
|
212
|
+
def previous_id=(txid)
|
213
|
+
self.previous_hash = BTC.hash_from_id(txid)
|
214
|
+
end
|
215
|
+
|
216
|
+
def value
|
217
|
+
return @value if @value
|
218
|
+
return @transaction_output.value if @transaction_output
|
219
|
+
return nil
|
220
|
+
end
|
221
|
+
|
222
|
+
def data
|
223
|
+
data = "".b
|
224
|
+
data << BTC::Data.ensure_binary_encoding(self.previous_hash)
|
225
|
+
data << BTC::WireFormat.encode_uint32le(self.previous_index)
|
226
|
+
if coinbase?
|
227
|
+
data << BTC::WireFormat.encode_string(self.coinbase_data)
|
228
|
+
else
|
229
|
+
data << BTC::WireFormat.encode_string(self.signature_script.data)
|
230
|
+
end
|
231
|
+
data << BTC::WireFormat.encode_uint32le(self.sequence)
|
232
|
+
data
|
233
|
+
end
|
234
|
+
|
235
|
+
def dictionary
|
236
|
+
dict = {}
|
237
|
+
|
238
|
+
dict["prev_out"] = {
|
239
|
+
"hash" => BTC::Data.hex_from_data(self.previous_hash),
|
240
|
+
"n" => self.previous_index
|
241
|
+
}
|
242
|
+
|
243
|
+
if self.coinbase?
|
244
|
+
dict["coinbase"] = BTC::Data.hex_from_data(self.coinbase_data)
|
245
|
+
else
|
246
|
+
dict["scriptSig"] = {
|
247
|
+
"asm" => self.signature_script.to_s,
|
248
|
+
"hex" => BTC::Data.hex_from_data(self.signature_script.data)
|
249
|
+
}
|
250
|
+
end
|
251
|
+
|
252
|
+
dict["sequence"] = self.sequence
|
253
|
+
|
254
|
+
dict
|
255
|
+
end
|
256
|
+
|
257
|
+
def to_h
|
258
|
+
self.dictionary
|
259
|
+
end
|
260
|
+
|
261
|
+
def to_s
|
262
|
+
BTC::Data.hex_from_data(self.data)
|
263
|
+
end
|
264
|
+
|
265
|
+
def ==(other)
|
266
|
+
return true if super(other)
|
267
|
+
return true if self.data == other.data
|
268
|
+
return false
|
269
|
+
end
|
270
|
+
|
271
|
+
# Makes a deep copy of a transaction input
|
272
|
+
def dup
|
273
|
+
TransactionInput.new(previous_hash: @previous_hash.dup,
|
274
|
+
previous_index: @previous_index,
|
275
|
+
signature_script: @signature_script ? @signature_script.dup : nil,
|
276
|
+
coinbase_data: @coinbase_data,
|
277
|
+
sequence: @sequence,
|
278
|
+
transaction: @transaction,
|
279
|
+
transaction_output: @transaction_output, # not dup-ing txout because it's a transient object without #==
|
280
|
+
value: @value)
|
281
|
+
end
|
282
|
+
|
283
|
+
def inspect(style = :full)
|
284
|
+
if style == :full
|
285
|
+
%{#<#{self.class.name} prev:#{self.previous_id}[#{self.previous_index}]} +
|
286
|
+
%{ script:#{self.signature_script.to_s.inspect}} +
|
287
|
+
%{ seq:#{self.sequence}} +
|
288
|
+
%{>}
|
289
|
+
else
|
290
|
+
%{#<#{self.class.name} prev:#{self.previous_id[0,10]}[#{self.previous_index}]} +
|
291
|
+
%{ script:#{self.signature_script.to_s.inspect}} +
|
292
|
+
(self.sequence != MAX_SEQUENCE ? %{ seq:#{self.sequence}} : %{}) +
|
293
|
+
%{>}
|
294
|
+
end
|
295
|
+
end
|
296
|
+
|
297
|
+
end
|
298
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module BTC
|
2
|
+
# Represents a reference to a previous transaction.
|
3
|
+
class TransactionOutpoint
|
4
|
+
attr_accessor :transaction_hash
|
5
|
+
attr_accessor :transaction_id
|
6
|
+
attr_accessor :index
|
7
|
+
|
8
|
+
def initialize(transaction_hash: nil, transaction_id: nil, index: 0)
|
9
|
+
@transaction_hash = transaction_hash
|
10
|
+
self.transaction_id = transaction_id if transaction_id
|
11
|
+
@index = index
|
12
|
+
end
|
13
|
+
|
14
|
+
def transaction_id=(txid)
|
15
|
+
self.transaction_hash = BTC.hash_from_id(txid)
|
16
|
+
end
|
17
|
+
|
18
|
+
def transaction_id
|
19
|
+
BTC.id_from_hash(self.transaction_hash)
|
20
|
+
end
|
21
|
+
|
22
|
+
def outpoint_id
|
23
|
+
%{#{transaction_id}:#{index}}
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
outpoint_id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,315 @@
|
|
1
|
+
# Transaction output (aka "tx out") is a value with rules attached in form of a script.
|
2
|
+
# To spend money one need to choose a transaction output and provide an appropriate
|
3
|
+
# input which makes the script execute with success.
|
4
|
+
module BTC
|
5
|
+
class TransactionOutput
|
6
|
+
|
7
|
+
# Core attributes.
|
8
|
+
|
9
|
+
# Value of output in satoshis.
|
10
|
+
attr_accessor :value
|
11
|
+
|
12
|
+
# BTC::Script defining redemption rules for this output (aka scriptPubKey or pk_script)
|
13
|
+
attr_accessor :script
|
14
|
+
|
15
|
+
|
16
|
+
# Derived attributes.
|
17
|
+
|
18
|
+
# Serialized binary form of the output (payload)
|
19
|
+
attr_reader :data
|
20
|
+
|
21
|
+
# Dictionary representation of transaction ready to be encoded in JSON, PropertyList etc.
|
22
|
+
attr_reader :dictionary
|
23
|
+
|
24
|
+
|
25
|
+
# Optional attributes.
|
26
|
+
|
27
|
+
# These are not derived from tx data, but attached externally (e.g. via external APIs).
|
28
|
+
# 'index', 'confirmations' and 'transaction_hash' are optional attributes updated in certain context.
|
29
|
+
# E.g. when loading unspent outputs from Chain.com, all these attributes will be set.
|
30
|
+
# index and transaction_hash are kept up to date when output is added/removed from the transaction.
|
31
|
+
|
32
|
+
# Reference to the owning transaction. It is set on tx.add_output() and
|
33
|
+
# reset to nil on tx.remove_all_outputs. Default is nil.
|
34
|
+
attr_accessor :transaction
|
35
|
+
|
36
|
+
# Identifier of the transaction. Default is nil.
|
37
|
+
attr_accessor :transaction_hash
|
38
|
+
|
39
|
+
# Transaction ID. Always in sync with transaction_hash. Default is nil.
|
40
|
+
attr_accessor :transaction_id
|
41
|
+
|
42
|
+
# Index of this output in its transaction. Default is nil (unknown).
|
43
|
+
attr_accessor :index
|
44
|
+
|
45
|
+
# Binary hash of the block at which transaction was included.
|
46
|
+
# If not confirmed or not available, equals nil.
|
47
|
+
attr_accessor :block_hash
|
48
|
+
attr_accessor :block_id
|
49
|
+
|
50
|
+
# Height of the block at which transaction was included.
|
51
|
+
# If not confirmed equals -1.
|
52
|
+
# Note: `block_height` might not be provided by some APIs while `confirmations` may be.
|
53
|
+
# Default value is derived from `transaction` if possible or equals nil.
|
54
|
+
attr_accessor :block_height
|
55
|
+
|
56
|
+
# Time of the block at which tx was included (::Time instance or nil).
|
57
|
+
# Default value is derived from `transaction` if possible or equals nil.
|
58
|
+
attr_accessor :block_time
|
59
|
+
|
60
|
+
# Number of confirmations.
|
61
|
+
# Default value is derived from `transaction` if possible or equals nil.
|
62
|
+
attr_accessor :confirmations
|
63
|
+
|
64
|
+
# If available, returns whether this output is spent (true or false).
|
65
|
+
# Default is nil.
|
66
|
+
# See also `spent_confirmations`.
|
67
|
+
attr_accessor :spent
|
68
|
+
|
69
|
+
# If this transaction is spent, contains number of confirmations of the spending transaction.
|
70
|
+
# Returns nil if not available or output is not spent.
|
71
|
+
# Returns 0 if spending transaction is unconfirmed.
|
72
|
+
attr_accessor :spent_confirmations
|
73
|
+
|
74
|
+
def initialize(data: nil,
|
75
|
+
stream: nil,
|
76
|
+
dictionary: nil,
|
77
|
+
value: 0,
|
78
|
+
script: BTC::Script.new,
|
79
|
+
|
80
|
+
# optional attributes
|
81
|
+
transaction: nil,
|
82
|
+
transaction_hash: nil,
|
83
|
+
transaction_id: nil,
|
84
|
+
index: nil,
|
85
|
+
block_hash: nil,
|
86
|
+
block_id: nil,
|
87
|
+
block_height: nil,
|
88
|
+
block_time: nil,
|
89
|
+
confirmations: nil,
|
90
|
+
spent: nil,
|
91
|
+
spent_confirmations: nil)
|
92
|
+
|
93
|
+
if stream || data
|
94
|
+
init_with_stream(stream || StringIO.new(data))
|
95
|
+
elsif dictionary
|
96
|
+
init_with_dictionary(dictionary)
|
97
|
+
else
|
98
|
+
@value = value || 0
|
99
|
+
@script = script || BTC::Script.new
|
100
|
+
end
|
101
|
+
|
102
|
+
@transaction = transaction
|
103
|
+
@transaction_hash = transaction_hash
|
104
|
+
@transaction_hash = BTC.hash_from_id(transaction_id) if transaction_id
|
105
|
+
@index = index
|
106
|
+
@block_hash = block_hash
|
107
|
+
@block_hash = BTC.hash_from_id(block_id) if block_id
|
108
|
+
@block_height = block_height
|
109
|
+
@block_time = block_time
|
110
|
+
@confirmations = confirmations
|
111
|
+
@spent = spent
|
112
|
+
@spent_confirmations = spent_confirmations
|
113
|
+
end
|
114
|
+
|
115
|
+
def init_with_stream(stream)
|
116
|
+
if stream.eof?
|
117
|
+
raise ArgumentError, "Can't parse transaction output from stream because it is already closed."
|
118
|
+
end
|
119
|
+
|
120
|
+
# Read value
|
121
|
+
if !(@value = BTC::WireFormat.read_int64le(stream: stream).first)
|
122
|
+
raise ArgumentError, "Failed to read output value from stream."
|
123
|
+
end
|
124
|
+
|
125
|
+
# Read script
|
126
|
+
if !(scriptdata = BTC::WireFormat.read_string(stream: stream).first)
|
127
|
+
raise ArgumentError, "Failed to read output script data from stream."
|
128
|
+
end
|
129
|
+
|
130
|
+
@script = BTC::Script.new(data: scriptdata)
|
131
|
+
end
|
132
|
+
|
133
|
+
def init_with_dictionary(dict)
|
134
|
+
@value = 0
|
135
|
+
if amount_string = dict["value"]
|
136
|
+
@value = CurrencyFormatter.btc_long_formatter.number_from_string(amount_string)
|
137
|
+
if !@value
|
138
|
+
raise ArgumentError, "Failed to parse bitcoin amount from dictionary 'value': #{amount_string.inspect}"
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
@script = nil
|
143
|
+
if dict["scriptPubKey"] && dict["scriptPubKey"].is_a?(Hash)
|
144
|
+
if hex = dict["scriptPubKey"]["hex"]
|
145
|
+
@script = Script.new(data: BTC::Data.data_from_hex(hex))
|
146
|
+
if !@script
|
147
|
+
raise ArgumentError, "Failed to parse script from scriptPubKey.hex"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.with_data(data)
|
154
|
+
raise ArgumentError, "Use TransactionOutput.new(data: ...) instead"
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.with_stream(stream)
|
158
|
+
raise ArgumentError, "Use TransactionOutput.new(stream: ...) instead"
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.with_dictionary(dict)
|
162
|
+
raise ArgumentError, "Use TransactionOutput.new(dictionary: ...) instead"
|
163
|
+
end
|
164
|
+
|
165
|
+
def data
|
166
|
+
data = "".b
|
167
|
+
data << BTC::WireFormat.encode_int64le(self.value)
|
168
|
+
data << BTC::WireFormat.encode_string(self.script.data)
|
169
|
+
data
|
170
|
+
end
|
171
|
+
|
172
|
+
def dictionary
|
173
|
+
{
|
174
|
+
"value" => CurrencyFormatter.btc_long_formatter.string_from_number(self.value),
|
175
|
+
"scriptPubKey" => {
|
176
|
+
"asm" => self.script.to_s,
|
177
|
+
"hex" => BTC::Data.hex_from_data(self.script.data)
|
178
|
+
}
|
179
|
+
}
|
180
|
+
end
|
181
|
+
|
182
|
+
def transaction=(tx)
|
183
|
+
@transaction = tx
|
184
|
+
@transaction_hash = nil
|
185
|
+
@outpoint = nil
|
186
|
+
end
|
187
|
+
|
188
|
+
def index=(i)
|
189
|
+
@index = i
|
190
|
+
@outpoint = nil
|
191
|
+
end
|
192
|
+
|
193
|
+
def transaction_hash
|
194
|
+
return @transaction_hash if @transaction_hash
|
195
|
+
return @transaction.transaction_hash if @transaction
|
196
|
+
return nil
|
197
|
+
end
|
198
|
+
|
199
|
+
def transaction_hash=(txhash)
|
200
|
+
@transaction_hash = txhash
|
201
|
+
@outpoint = nil
|
202
|
+
end
|
203
|
+
|
204
|
+
def transaction_id=(txid)
|
205
|
+
self.transaction_hash = BTC.hash_from_id(txid)
|
206
|
+
end
|
207
|
+
|
208
|
+
def transaction_id
|
209
|
+
BTC.id_from_hash(self.transaction_hash)
|
210
|
+
end
|
211
|
+
|
212
|
+
def outpoint
|
213
|
+
return @outpoint if @outpoint
|
214
|
+
if transaction_hash && index
|
215
|
+
@outpoint = TransactionOutpoint.new(transaction_hash: transaction_hash, index: index)
|
216
|
+
end
|
217
|
+
@outpoint
|
218
|
+
end
|
219
|
+
|
220
|
+
def outpoint_id
|
221
|
+
outpoint.outpoint_id
|
222
|
+
end
|
223
|
+
|
224
|
+
def block_id
|
225
|
+
BTC.id_from_hash(self.block_hash)
|
226
|
+
end
|
227
|
+
|
228
|
+
def block_id=(block_id)
|
229
|
+
self.block_hash = BTC.hash_from_id(block_id)
|
230
|
+
end
|
231
|
+
|
232
|
+
def block_hash
|
233
|
+
return @block_hash if @block_hash
|
234
|
+
return @transaction.block_hash if @transaction
|
235
|
+
return nil
|
236
|
+
end
|
237
|
+
|
238
|
+
def block_height
|
239
|
+
return @block_height if @block_height
|
240
|
+
return @transaction.block_height if @transaction
|
241
|
+
return nil
|
242
|
+
end
|
243
|
+
|
244
|
+
def block_time
|
245
|
+
return @block_time if @block_time
|
246
|
+
return @transaction.block_time if @transaction
|
247
|
+
return nil
|
248
|
+
end
|
249
|
+
|
250
|
+
def confirmations
|
251
|
+
return @confirmations if @confirmations
|
252
|
+
return @transaction.confirmations if @transaction
|
253
|
+
return nil
|
254
|
+
end
|
255
|
+
|
256
|
+
# Returns `true` if this transaction output contains an Open Assets marker.
|
257
|
+
# Does not perform expensive validation.
|
258
|
+
# Use this method to quickly filter out non-asset transactions.
|
259
|
+
def open_assets_marker?
|
260
|
+
self.script.open_assets_marker?
|
261
|
+
end
|
262
|
+
|
263
|
+
def dust?(relay_fee_rate = Transaction::DEFAULT_RELAY_FEE_RATE)
|
264
|
+
return self.value < self.dust_limit(relay_fee_rate)
|
265
|
+
end
|
266
|
+
|
267
|
+
def dust_limit(relay_fee_rate = Transaction::DEFAULT_RELAY_FEE_RATE)
|
268
|
+
# "Dust" is defined in terms of Transaction::DEFAULT_RELAY_FEE_RATE,
|
269
|
+
# which has units satoshis-per-kilobyte.
|
270
|
+
# If you'd pay more than 1/3 in fees
|
271
|
+
# to spend something, then we consider it dust.
|
272
|
+
# A typical txout is 34 bytes big, and will
|
273
|
+
# need a TransactionInput of at least 148 bytes to spend:
|
274
|
+
# so dust is a txout less than 546 satoshis (3*(34+148))
|
275
|
+
# with default relay_fee_rate.
|
276
|
+
size = self.data.bytesize + 148
|
277
|
+
return 3*Transaction.compute_fee(size, fee_rate: relay_fee_rate)
|
278
|
+
end
|
279
|
+
|
280
|
+
def to_h
|
281
|
+
self.dictionary
|
282
|
+
end
|
283
|
+
|
284
|
+
def to_s
|
285
|
+
BTC::Data.hex_from_data(self.data)
|
286
|
+
end
|
287
|
+
|
288
|
+
def ==(other)
|
289
|
+
return true if super(other)
|
290
|
+
return true if self.data == other.data
|
291
|
+
return false
|
292
|
+
end
|
293
|
+
|
294
|
+
# Makes a deep copy of a transaction output
|
295
|
+
def dup
|
296
|
+
TransactionOutput.new(value: @value,
|
297
|
+
script: @script.dup,
|
298
|
+
transaction: @transaction,
|
299
|
+
transaction_hash: @transaction_hash,
|
300
|
+
index: @index,
|
301
|
+
block_hash: @block_hash,
|
302
|
+
block_height: @block_height,
|
303
|
+
block_time: @block_time,
|
304
|
+
confirmations: @confirmations,
|
305
|
+
spent: @spent,
|
306
|
+
spent_confirmations: @spent_confirmations)
|
307
|
+
end
|
308
|
+
|
309
|
+
def inspect(style = :full)
|
310
|
+
%{#<#{self.class.name} value:#{CurrencyFormatter.btc_long_formatter.string_from_number(self.value)}} +
|
311
|
+
%{ script:#{self.script.to_s.inspect}>}
|
312
|
+
end
|
313
|
+
|
314
|
+
end
|
315
|
+
end
|