bitcoin-ruby 0.0.5 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/.travis.yml +2 -2
- data/COPYING +1 -1
- data/Gemfile +5 -11
- data/README.rdoc +11 -5
- data/Rakefile +5 -0
- data/bin/bitcoin_node +11 -29
- data/bin/bitcoin_node_cli +81 -0
- data/bin/bitcoin_wallet +9 -6
- data/doc/NODE.rdoc +79 -26
- data/examples/bbe_verify_tx.rb +1 -1
- data/examples/index_nhash.rb +24 -0
- data/examples/reindex_p2sh_addrs.rb +44 -0
- data/lib/bitcoin.rb +135 -20
- data/lib/bitcoin/builder.rb +233 -63
- data/lib/bitcoin/key.rb +89 -16
- data/lib/bitcoin/litecoin.rb +13 -11
- data/lib/bitcoin/namecoin.rb +5 -4
- data/lib/bitcoin/network/command_client.rb +23 -13
- data/lib/bitcoin/network/command_handler.rb +336 -131
- data/lib/bitcoin/network/connection_handler.rb +14 -13
- data/lib/bitcoin/network/node.rb +61 -20
- data/lib/bitcoin/protocol.rb +5 -1
- data/lib/bitcoin/protocol/block.rb +15 -3
- data/lib/bitcoin/protocol/parser.rb +3 -3
- data/lib/bitcoin/protocol/tx.rb +82 -20
- data/lib/bitcoin/protocol/txin.rb +7 -0
- data/lib/bitcoin/protocol/txout.rb +12 -9
- data/lib/bitcoin/script.rb +329 -75
- data/lib/bitcoin/storage/dummy/dummy_store.rb +23 -4
- data/lib/bitcoin/storage/models.rb +6 -11
- data/lib/bitcoin/storage/sequel/migrations/005_change_tx_hash_to_bytea.rb +14 -0
- data/lib/bitcoin/storage/sequel/migrations/006_add_tx_nhash.rb +31 -0
- data/lib/bitcoin/storage/sequel/migrations/007_add_prev_out_index_index.rb +16 -0
- data/lib/bitcoin/storage/sequel/migrations/008_add_txin_p2sh_type.rb +31 -0
- data/lib/bitcoin/storage/sequel/migrations/009_add_addrs_type.rb +56 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +168 -70
- data/lib/bitcoin/storage/storage.rb +161 -97
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +1 -1
- data/lib/bitcoin/storage/utxo/migrations/004_add_addrs_type.rb +14 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +25 -12
- data/lib/bitcoin/validation.rb +87 -56
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bitcoin_spec.rb +38 -0
- data/spec/bitcoin/builder_spec.rb +177 -0
- data/spec/bitcoin/fixtures/litecoin-tx-f5aa30f574e3b6f1a3d99c07a6356ba812aabb9661e1d5f71edff828cbd5c996.json +259 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-265322.bin +0 -0
- data/spec/bitcoin/fixtures/tx-0295028ef826b2a188409cb905b631faebb9bb3cdf14510571c5f4bd8591338f.json +64 -0
- data/spec/bitcoin/fixtures/tx-03339a725007a279484fb6f5361f522dd1cf4d0923d30e6b973290dba4275f92.json +64 -0
- data/spec/bitcoin/fixtures/tx-0ce7e5238fbdb6c086cf1b384b21b827e91cc23f360417265874a5a0d86ce367.json +64 -0
- data/spec/bitcoin/fixtures/tx-0ef34c49f630aea17df0080728b0fc67bf5f87fbda936934a4b11b4a69d7821e.json +64 -0
- data/spec/bitcoin/fixtures/tx-1129d2a8bd5bb3a81e54dc96a90f1f6b2544575748caa17243470935c5dd91b7.json +28 -0
- data/spec/bitcoin/fixtures/tx-19aa42fee0fa57c45d3b16488198b27caaacc4ff5794510d0c17f173f05587ff.json +23 -0
- data/spec/bitcoin/fixtures/tx-1a4f3b9dc4494aeedeb39f30dd37e60541b2abe3ed4977992017cc0ad4f44956.json +64 -0
- data/spec/bitcoin/fixtures/tx-1f9191dcf2b1844ca28c6ef4b969e1d5fab70a5e3c56b7007949e55851cb0c4f.json +64 -0
- data/spec/bitcoin/fixtures/tx-22cd5fef23684d7b304e119bedffde6f54538d3d54a5bfa237e20dc2d9b4b5ad.json +64 -0
- data/spec/bitcoin/fixtures/tx-2958fb00b4fd6fe0353503b886eb9a193d502f4fd5fc042d5e03216ba918bbd6.json +64 -0
- data/spec/bitcoin/fixtures/tx-29f277145749ad6efbed3ae6ce301f8d33c585ec26b7c044ad93c2f866e9e942.json +64 -0
- data/spec/bitcoin/fixtures/tx-2c5e5376c20e9cc78d0fb771730e5d840cc2096eff0ef045b599fe92475ace1c.json +28 -0
- data/spec/bitcoin/fixtures/tx-2c63aa814701cef5dbd4bbaddab3fea9117028f2434dddcdab8339141e9b14d1.json +30 -0
- data/spec/bitcoin/fixtures/tx-326882a7f22b5191f1a0cc9962ca4b878cd969cf3b3a70887aece4d801a0ba5e.json +23 -0
- data/spec/bitcoin/fixtures/tx-345bed8785c3282a264ffb0dbee61cde54854f10e16f1b3e75b7f2d9f62946f2.json +64 -0
- data/spec/bitcoin/fixtures/tx-39ba7440b7103557560cc8ce258009936796485aaf8b478e66ab4cb97c66e31b.json +32 -0
- data/spec/bitcoin/fixtures/tx-3a04d57a833367f1655cc5ec3beb587888ef4977a86caa8c8ad4ba7cc717eae7.json +64 -0
- data/spec/bitcoin/fixtures/tx-4142ee4877eb116abf955a7ec6ef2dc38133b793df762b76d75e3d7d4d8badc9.json +38 -0
- data/spec/bitcoin/fixtures/tx-46224764c7870f95b58f155bce1e38d4da8e99d42dbb632d0dd7c07e092ee5aa.json +23 -0
- data/spec/bitcoin/fixtures/tx-5df1375ffe61ac35ca178ebb0cab9ea26dedbd0e96005dfcee7e379fa513232f.json +30 -0
- data/spec/bitcoin/fixtures/tx-62d9a565bd7b5344c5352e3e9e5f40fa4bbd467fa19c87357216ec8777ba1cce.json +64 -0
- data/spec/bitcoin/fixtures/tx-6327783a064d4e350c454ad5cd90201aedf65b1fc524e73709c52f0163739190.json +23 -0
- data/spec/bitcoin/fixtures/tx-6606c366a487bff9e412d0b6c09c14916319932db5954bf5d8719f43f828a3ba.json +27 -0
- data/spec/bitcoin/fixtures/tx-6aaf18b9f1283b939d8e5d40ff5f8a435229f4178372659cc3a0bce4e262bf78.json +28 -0
- data/spec/bitcoin/fixtures/tx-6b48bba6f6d2286d7ec0883c0fc3085955090813a4c94980466611c798b868cc.json +64 -0
- data/spec/bitcoin/fixtures/tx-70cfbc6690f9ab46712db44e3079ac227962b2771a9341d4233d898b521619ef.json +40 -0
- data/spec/bitcoin/fixtures/tx-7a1a9db42f065f75110fcdb1bc415549c8ef7670417ba1d35a67f1b8adc562c1.json +64 -0
- data/spec/bitcoin/fixtures/tx-9a768fc7d0c4bdc86e25154357ef7c0063ca21310e5740a2f12f90b7455184a7.json +64 -0
- data/spec/bitcoin/fixtures/tx-9cad8d523a0694f2509d092c39cebc8046adae62b4e4297102d568191d9478d8.json +64 -0
- data/spec/bitcoin/fixtures/tx-9e052eb694bd7e15906433f064dff0161a12fd325c1124537766377004023c6f.json +64 -0
- data/spec/bitcoin/fixtures/tx-a955032f4d6b0c9bfe8cad8f00a8933790b9c1dc28c82e0f48e75b35da0e4944.json +23 -0
- data/spec/bitcoin/fixtures/tx-aab7ef280abbb9cc6fbaf524d2645c3daf4fcca2b3f53370e618d9cedf65f1f8.json +23 -0
- data/spec/bitcoin/fixtures/tx-ab9805c6d57d7070d9a42c5176e47bb705023e6b67249fb6760880548298e742.json +27 -0
- data/spec/bitcoin/fixtures/tx-ad4bcf3241e5d2ad140564e20db3567d41594cf4c2012433fe46a2b70e0d87b8.json +64 -0
- data/spec/bitcoin/fixtures/tx-b5b598de91787439afd5938116654e0b16b7a0d0f82742ba37564219c5afcbf9.json +27 -0
- data/spec/bitcoin/fixtures/tx-b8fd633e7713a43d5ac87266adc78444669b987a56b3a65fb92d58c2c4b0e84d.json +28 -0
- data/spec/bitcoin/fixtures/tx-bbca0628c42cb8bf7c3f4b2ad688fa56da5308dd2a10255da89fb1f46e6e413d.json +36 -0
- data/spec/bitcoin/fixtures/tx-bc7fd132fcf817918334822ee6d9bd95c889099c96e07ca2c1eb2cc70db63224.json +23 -0
- data/spec/bitcoin/fixtures/tx-c192b74844e4837a34c4a5a97b438f1c111405b01b99e2d12b7c96d07fc74c04.json +28 -0
- data/spec/bitcoin/fixtures/tx-e335562f7e297aadeed88e5954bc4eeb8dc00b31d829eedb232e39d672b0c009.json +406 -0
- data/spec/bitcoin/fixtures/tx-eb3b82c0884e3efa6d8b0be55b4915eb20be124c9766245bcc7f34fdac32bccb.json +35 -0
- data/spec/bitcoin/fixtures/tx-fee1b9b85531c8fb6cd7831f83490c7f2aa768b6eefe29854ef5e89ce7b9ecb1.json +64 -0
- data/spec/bitcoin/fixtures/txscript-invalid-too-many-sigops-followed-by-invalid-pushdata.bin +1 -0
- data/spec/bitcoin/helpers/fake_blockchain.rb +183 -0
- data/spec/bitcoin/key_spec.rb +79 -8
- data/spec/bitcoin/namecoin_spec.rb +1 -1
- data/spec/bitcoin/node/command_api_spec.rb +373 -86
- data/spec/bitcoin/performance/storage_spec.rb +41 -0
- data/spec/bitcoin/protocol/addr_spec.rb +7 -5
- data/spec/bitcoin/protocol/aux_pow_spec.rb +1 -0
- data/spec/bitcoin/protocol/block_spec.rb +6 -0
- data/spec/bitcoin/protocol/tx_spec.rb +184 -1
- data/spec/bitcoin/protocol/txin_spec.rb +27 -0
- data/spec/bitcoin/protocol/txout_spec.rb +27 -0
- data/spec/bitcoin/script/opcodes_spec.rb +74 -3
- data/spec/bitcoin/script/script_spec.rb +271 -0
- data/spec/bitcoin/spec_helper.rb +34 -6
- data/spec/bitcoin/storage/models_spec.rb +104 -0
- data/spec/bitcoin/storage/reorg_spec.rb +42 -11
- data/spec/bitcoin/storage/storage_spec.rb +58 -15
- data/spec/bitcoin/storage/validation_spec.rb +44 -14
- data/spec/bitcoin/wallet/keygenerator_spec.rb +6 -3
- data/spec/bitcoin/wallet/keystore_spec.rb +3 -3
- data/spec/bitcoin/wallet/wallet_spec.rb +87 -89
- metadata +117 -11
data/lib/bitcoin/validation.rb
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
1
|
# encoding: ascii-8bit
|
|
2
2
|
|
|
3
|
+
# Validates blocks and transactions before they are accepted into the local blockchain.
|
|
4
|
+
# There are two modes of validation, "syntax" and "context". "syntax" validates everything
|
|
5
|
+
# that can be validated without access to the rest of the blockchain, for example that the
|
|
6
|
+
# block hash matches the claimed difficulty, and the tx hashes add up to the given merkle
|
|
7
|
+
# root, etc. The "context" rules include the checks that need to cross-reference data
|
|
8
|
+
# against the local database, like comparing the difficulty target to the last blocks, or
|
|
9
|
+
# checking for doublespends. (Suggestions for better names for these modes are welcome!)
|
|
10
|
+
# Everything accepted into the local storage should at least be syntax-validated, but it
|
|
11
|
+
# should be possible to skip context-validation when the current block is already known,
|
|
12
|
+
# for example when checkpoints are used.
|
|
3
13
|
module Bitcoin::Validation
|
|
4
|
-
|
|
5
|
-
# maximum size of a block (in bytes)
|
|
6
|
-
MAX_BLOCK_SIZE = 1_000_000
|
|
7
|
-
|
|
8
|
-
# maximum number of signature operations in a block
|
|
9
|
-
MAX_BLOCK_SIGOPS = MAX_BLOCK_SIZE / 50
|
|
10
|
-
|
|
11
|
-
# maximum integer value
|
|
12
|
-
INT_MAX = 0xffffffff
|
|
13
|
-
|
|
14
|
-
# number of confirmations required before coinbase tx can be spent
|
|
15
|
-
COINBASE_MATURITY = 100
|
|
16
|
-
|
|
17
|
-
# interval (in blocks) for difficulty retarget
|
|
18
|
-
RETARGET = 2016
|
|
19
|
-
|
|
20
|
-
# interval (in blocks) for mining reward reduction
|
|
21
|
-
REWARD_DROP = 210_000
|
|
22
|
-
|
|
14
|
+
|
|
23
15
|
class ValidationError < StandardError
|
|
24
16
|
end
|
|
25
17
|
|
|
@@ -38,6 +30,11 @@ module Bitcoin::Validation
|
|
|
38
30
|
RULES[:context] -= [:difficulty, :coinbase_value]
|
|
39
31
|
end
|
|
40
32
|
|
|
33
|
+
if Bitcoin.litecoin?
|
|
34
|
+
RULES[:syntax] -= [:bits]
|
|
35
|
+
RULES[:syntax] += [:scrypt_bits]
|
|
36
|
+
end
|
|
37
|
+
|
|
41
38
|
# validate block rules. +opts+ are:
|
|
42
39
|
# rules:: which rulesets to validate (default: [:syntax, :context])
|
|
43
40
|
# raise_errors:: whether to raise ValidationError on failure (default: false)
|
|
@@ -82,6 +79,13 @@ module Bitcoin::Validation
|
|
|
82
79
|
actual <= expected || [actual, expected]
|
|
83
80
|
end
|
|
84
81
|
|
|
82
|
+
# check that block hash matches claimed bits using Scrypt hash
|
|
83
|
+
def scrypt_bits
|
|
84
|
+
actual = block.recalc_block_scrypt_hash.to_i(16)
|
|
85
|
+
expected = Bitcoin.decode_compact_bits(block.bits).to_i(16)
|
|
86
|
+
actual <= expected || [actual, expected]
|
|
87
|
+
end
|
|
88
|
+
|
|
85
89
|
# check that block time is not greater than max
|
|
86
90
|
def max_timestamp
|
|
87
91
|
time, max = block.time, Time.now.to_i + 2*60*60
|
|
@@ -102,7 +106,7 @@ module Bitcoin::Validation
|
|
|
102
106
|
|
|
103
107
|
# check that coinbase value is valid; no more than reward + fees
|
|
104
108
|
def coinbase_value
|
|
105
|
-
reward = ((50.0 / (2 ** (store.get_depth / REWARD_DROP.to_f).floor)) * 1e8).to_i
|
|
109
|
+
reward = ((50.0 / (2 ** (store.get_depth / Bitcoin::REWARD_DROP.to_f).floor)) * 1e8).to_i
|
|
106
110
|
fees = 0
|
|
107
111
|
block.tx[1..-1].map.with_index do |t, idx|
|
|
108
112
|
val = tx_validators[idx]
|
|
@@ -127,7 +131,7 @@ module Bitcoin::Validation
|
|
|
127
131
|
|
|
128
132
|
# check that bits satisfy required difficulty
|
|
129
133
|
def difficulty
|
|
130
|
-
return true if Bitcoin.
|
|
134
|
+
return true if Bitcoin.network[:no_difficulty] == true
|
|
131
135
|
block.bits == next_bits_required || [block.bits, next_bits_required]
|
|
132
136
|
end
|
|
133
137
|
|
|
@@ -146,8 +150,11 @@ module Bitcoin::Validation
|
|
|
146
150
|
block.time > min_time || [block.time, min_time]
|
|
147
151
|
end
|
|
148
152
|
|
|
149
|
-
#
|
|
153
|
+
# Run all syntax checks on transactions
|
|
150
154
|
def transactions_syntax
|
|
155
|
+
# check if there are no double spends within this block
|
|
156
|
+
return false if block.tx.map(&:in).flatten.map {|i| [i.prev_out, i.prev_out_index] }.uniq! != nil
|
|
157
|
+
|
|
151
158
|
tx_validators.all?{|v|
|
|
152
159
|
begin
|
|
153
160
|
v.validate(rules: [:syntax], raise_errors: true)
|
|
@@ -158,6 +165,7 @@ module Bitcoin::Validation
|
|
|
158
165
|
}
|
|
159
166
|
end
|
|
160
167
|
|
|
168
|
+
# Run all context checks on transactions
|
|
161
169
|
def transactions_context
|
|
162
170
|
tx_validators.all?{|v|
|
|
163
171
|
begin
|
|
@@ -169,21 +177,42 @@ module Bitcoin::Validation
|
|
|
169
177
|
}
|
|
170
178
|
end
|
|
171
179
|
|
|
180
|
+
# Get validators for all tx objects in the current block
|
|
172
181
|
def tx_validators
|
|
173
|
-
@tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block)
|
|
182
|
+
@tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block, self)}
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Fetch all prev_txs that will be needed for validation
|
|
186
|
+
# Used for optimization in tx validators
|
|
187
|
+
def prev_txs_hash
|
|
188
|
+
@prev_tx_hash ||= (
|
|
189
|
+
inputs = block.tx[1..-1].map {|tx| tx.in }.flatten
|
|
190
|
+
txs = store.get_txs(inputs.map{|i| i.prev_out.reverse_hth })
|
|
191
|
+
Hash[*txs.map {|tx| [tx.hash, tx] }.flatten]
|
|
192
|
+
)
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# Fetch all prev_outs that already have a next_in, i.e. are already spent.
|
|
196
|
+
def spent_outs_txins
|
|
197
|
+
@spent_outs_txins ||= (
|
|
198
|
+
next_ins = store.get_txins_for_txouts(block.tx[1..-1].map(&:in).flatten.map.with_index {|txin, idx| [txin.prev_out.reverse_hth, txin.prev_out_index] })
|
|
199
|
+
# Only returns next_ins that are in blocks in the main chain
|
|
200
|
+
next_ins.select {|i| store.get_block_id_for_tx_id(i.tx_id) }
|
|
201
|
+
)
|
|
174
202
|
end
|
|
175
203
|
|
|
176
204
|
def next_bits_required
|
|
177
|
-
|
|
205
|
+
retarget = (Bitcoin.network[:retarget_interval] || Bitcoin::RETARGET_INTERVAL)
|
|
206
|
+
index = (prev_block.depth + 1) / retarget
|
|
178
207
|
max_target = Bitcoin.decode_compact_bits(Bitcoin.network[:proof_of_work_limit]).to_i(16)
|
|
179
208
|
return Bitcoin.network[:proof_of_work_limit] if index == 0
|
|
180
|
-
return prev_block.bits if (prev_block.depth + 1) %
|
|
209
|
+
return prev_block.bits if (prev_block.depth + 1) % retarget != 0
|
|
181
210
|
last = store.db[:blk][hash: prev_block.hash.htb.blob]
|
|
182
211
|
first = store.db[:blk][hash: last[:prev_hash].blob]
|
|
183
|
-
(
|
|
212
|
+
(retarget - 2).times { first = store.db[:blk][hash: first[:prev_hash].blob] }
|
|
184
213
|
|
|
185
214
|
nActualTimespan = last[:time] - first[:time]
|
|
186
|
-
nTargetTimespan =
|
|
215
|
+
nTargetTimespan = retarget * 600
|
|
187
216
|
|
|
188
217
|
nActualTimespan = [nActualTimespan, nTargetTimespan/4].max
|
|
189
218
|
nActualTimespan = [nActualTimespan, nTargetTimespan*4].min
|
|
@@ -202,11 +231,11 @@ module Bitcoin::Validation
|
|
|
202
231
|
end
|
|
203
232
|
|
|
204
233
|
class Tx
|
|
205
|
-
attr_accessor :tx, :store, :error
|
|
234
|
+
attr_accessor :tx, :store, :error, :block_validator
|
|
206
235
|
|
|
207
236
|
RULES = {
|
|
208
237
|
syntax: [:hash, :lists, :max_size, :output_values, :inputs, :lock_time, :standard],
|
|
209
|
-
context: [:prev_out, :signatures, :
|
|
238
|
+
context: [:prev_out, :signatures, :not_spent, :input_values, :output_sum]
|
|
210
239
|
}
|
|
211
240
|
|
|
212
241
|
# validate tx rules. +opts+ are:
|
|
@@ -236,10 +265,14 @@ module Bitcoin::Validation
|
|
|
236
265
|
"b3c19d78b4953b694717a47d9852f8ea1ccd4cf93a45ba2e43a0f97d7cdb2655"
|
|
237
266
|
]
|
|
238
267
|
|
|
239
|
-
#
|
|
240
|
-
#
|
|
241
|
-
|
|
268
|
+
# Setup new validator for given +tx+, validating context with +store+.
|
|
269
|
+
# Also needs the +block+ that includes the tx to be validated, to find
|
|
270
|
+
# prev_outs for chains of txs inside the block.
|
|
271
|
+
# Optionally accepts the validator object for the block, to optimize fetching
|
|
272
|
+
# prev_txs and checking for doublespends.
|
|
273
|
+
def initialize(tx, store, block = nil, block_validator = nil)
|
|
242
274
|
@tx, @store, @block, @errors = tx, store, block, []
|
|
275
|
+
@block_validator = block_validator
|
|
243
276
|
end
|
|
244
277
|
|
|
245
278
|
# check that tx hash matches data
|
|
@@ -255,7 +288,7 @@ module Bitcoin::Validation
|
|
|
255
288
|
|
|
256
289
|
# check that tx size doesn't exceed MAX_BLOCK_SIZE.
|
|
257
290
|
def max_size
|
|
258
|
-
tx.to_payload.bytesize <= MAX_BLOCK_SIZE || [tx.to_payload.bytesize, MAX_BLOCK_SIZE]
|
|
291
|
+
tx.to_payload.bytesize <= Bitcoin::MAX_BLOCK_SIZE || [tx.to_payload.bytesize, Bitcoin::MAX_BLOCK_SIZE]
|
|
259
292
|
end
|
|
260
293
|
|
|
261
294
|
# check that total output value doesn't exceed MAX_MONEY.
|
|
@@ -272,7 +305,7 @@ module Bitcoin::Validation
|
|
|
272
305
|
|
|
273
306
|
# check that lock_time doesn't exceed INT_MAX
|
|
274
307
|
def lock_time
|
|
275
|
-
tx.lock_time <=
|
|
308
|
+
tx.lock_time <= Bitcoin::UINT32_MAX || [tx.lock_time, Bitcoin::UINT32_MAX]
|
|
276
309
|
end
|
|
277
310
|
|
|
278
311
|
# check that min_size is at least 86 bytes
|
|
@@ -308,16 +341,23 @@ module Bitcoin::Validation
|
|
|
308
341
|
sigs.all? || sigs.map.with_index {|s, i| s ? nil : i }.compact
|
|
309
342
|
end
|
|
310
343
|
|
|
311
|
-
# check that none of the prev_outs are already spent in the main chain
|
|
312
|
-
def
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
344
|
+
# check that none of the prev_outs are already spent in the main chain or in the current block
|
|
345
|
+
def not_spent
|
|
346
|
+
# if we received cached spents, use it
|
|
347
|
+
return block_validator.spent_outs_txins.empty? if block_validator
|
|
348
|
+
|
|
349
|
+
# find all spent txouts
|
|
350
|
+
next_ins = store.get_txins_for_txouts(tx.in.map.with_index {|txin, idx| [txin.prev_out.reverse_hth, txin.prev_out_index] })
|
|
351
|
+
|
|
352
|
+
# no txouts found spending these txins, we can safely return true
|
|
353
|
+
return true if next_ins.empty?
|
|
354
|
+
|
|
355
|
+
# there were some txouts spending these txins, verify that they are not on the main chain
|
|
356
|
+
next_ins.select! {|i| i.get_tx.blk_id } # blk_id is only set for tx in the main chain
|
|
357
|
+
return true if next_ins.empty?
|
|
358
|
+
|
|
359
|
+
# now we know some txouts are already spent, return tx_idxs for debugging purposes
|
|
360
|
+
return next_ins.map {|i| i.get_prev_out.tx_idx }
|
|
321
361
|
end
|
|
322
362
|
|
|
323
363
|
# check that the total input value doesn't exceed MAX_MONEY
|
|
@@ -341,18 +381,9 @@ module Bitcoin::Validation
|
|
|
341
381
|
# only returns tx that are in a block in the main chain or the current block.
|
|
342
382
|
def prev_txs
|
|
343
383
|
@prev_txs ||= tx.in.map {|i|
|
|
344
|
-
prev_tx = store.get_tx(i.prev_out.reverse_hth)
|
|
345
|
-
next prev_tx
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if store.class.name =~ /SequelStore/
|
|
349
|
-
block = store.db[:blk][id: prev_tx.blk_id] if prev_tx
|
|
350
|
-
next prev_tx if block && block[:chain] == 0
|
|
351
|
-
else
|
|
352
|
-
next prev_tx if prev_tx && prev_tx.get_block && prev_tx.get_block.chain == 0
|
|
353
|
-
end
|
|
354
|
-
next nil if !@block
|
|
355
|
-
@block.tx.find {|t| t.binary_hash == i.prev_out }
|
|
384
|
+
prev_tx = block_validator ? block_validator.prev_txs_hash[i.prev_out.reverse_hth] : store.get_tx(i.prev_out.reverse_hth)
|
|
385
|
+
next prev_tx if prev_tx && prev_tx.blk_id # blk_id is set only if it's in the main chain
|
|
386
|
+
@block.tx.find {|t| t.binary_hash == i.prev_out } if @block
|
|
356
387
|
}.compact
|
|
357
388
|
end
|
|
358
389
|
|
data/lib/bitcoin/version.rb
CHANGED
|
@@ -24,6 +24,44 @@ describe 'Bitcoin Address/Hash160/PubKey' do
|
|
|
24
24
|
.should == "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa"
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
it 'bitcoin-address from pubkey' do
|
|
28
|
+
Bitcoin::network = :testnet
|
|
29
|
+
Bitcoin.pubkey_to_address("029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4")
|
|
30
|
+
.should == "mu6vSuyvpVxvDAJyZczjxaU56pXLNBSf9C"
|
|
31
|
+
Bitcoin.pubkey_to_address("049e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4b82314814017e4e9b06a0fd8e01772bb410cb1c36cfc2d03079c315bc7494b86")
|
|
32
|
+
.should == "n4bZ82i9SdLj6YauPn3PPKFRhQHMZrdaPq"
|
|
33
|
+
|
|
34
|
+
Bitcoin::network = :bitcoin
|
|
35
|
+
Bitcoin.pubkey_to_address("029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4")
|
|
36
|
+
.should == "1Eay9rtx1UXfS3qMr42N8fFkEpvdR2euvg"
|
|
37
|
+
Bitcoin.pubkey_to_address("049e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4b82314814017e4e9b06a0fd8e01772bb410cb1c36cfc2d03079c315bc7494b86")
|
|
38
|
+
.should == "1Q5bpydAdbuUKS7HgD51ZQ36qQgeiN8cBE"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
it 'bitcoin p2sh multisig address from pubkeys' do
|
|
42
|
+
Bitcoin::network = :testnet
|
|
43
|
+
address, redeem_script = Bitcoin.pubkeys_to_p2sh_multisig_address(2, "029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4",
|
|
44
|
+
"0299acf23a65c31fe02052d7474769529c21612b1afa56cc149747fe63867592ec")
|
|
45
|
+
address.should == "2NGaiH7MNYWhsWPQKudZEvy8KnoWPfuGPg1"
|
|
46
|
+
redeem_script.hth.should == "52" + # OP_2
|
|
47
|
+
"21" + "029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4" + # pubkey.bytesize + pubkey
|
|
48
|
+
"21" + "0299acf23a65c31fe02052d7474769529c21612b1afa56cc149747fe63867592ec" + # pubkey.bytesize + pubkey
|
|
49
|
+
"52" + # OP_2
|
|
50
|
+
"ae" # OP_CHECKMULTISIG
|
|
51
|
+
|
|
52
|
+
Bitcoin::network = :bitcoin
|
|
53
|
+
address, redeem_script = Bitcoin.pubkeys_to_p2sh_multisig_address(2, "029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4",
|
|
54
|
+
"0299acf23a65c31fe02052d7474769529c21612b1afa56cc149747fe63867592ec",
|
|
55
|
+
"020b16a7227f873ac68cf3140f1101d2eda5acb28bf3e7d546409139caf25142e4")
|
|
56
|
+
address.should == "38eiL6Jac27TVAu83wj81Miso9rXiVqgcP"
|
|
57
|
+
redeem_script.hth.should == "52" + # OP_2
|
|
58
|
+
"21" + "029e31ccb7308c2525d542024b8119a3ab3767933e82aedd1471f9c714d998d1b4" + # pubkey.bytesize + pubkey
|
|
59
|
+
"21" + "0299acf23a65c31fe02052d7474769529c21612b1afa56cc149747fe63867592ec" + # pubkey.bytesize + pubkey
|
|
60
|
+
"21" + "020b16a7227f873ac68cf3140f1101d2eda5acb28bf3e7d546409139caf25142e4" + # pubkey.bytesize + pubkey
|
|
61
|
+
"53" + # OP_3
|
|
62
|
+
"ae" # OP_CHECKMULTISIG
|
|
63
|
+
end
|
|
64
|
+
|
|
27
65
|
it 'bitcoin p2sh address from bitcoin-hash160' do
|
|
28
66
|
Bitcoin::network = :testnet
|
|
29
67
|
Bitcoin.hash160_to_p2sh_address("d11e2f2f385efeecd30f867f1d55c0bc8a27f29e")
|
|
@@ -85,6 +85,29 @@ describe "Bitcoin::Builder" do
|
|
|
85
85
|
tx2.out[0].pk_script.should == tx.out[0].pk_script
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
+
it "should allow txin.prev_out as tx or hash" do
|
|
89
|
+
prev_tx = @block.tx[0]
|
|
90
|
+
tx1 = build_tx do |t|
|
|
91
|
+
t.input {|i| i.prev_out prev_tx, 0 }
|
|
92
|
+
end
|
|
93
|
+
tx2 = build_tx do |t|
|
|
94
|
+
t.input {|i| i.prev_out prev_tx.hash, 0, prev_tx.out[0].pk_script }
|
|
95
|
+
end
|
|
96
|
+
tx1.in[0].should == tx2.in[0]
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
it "should provide txout#to shortcut" do
|
|
101
|
+
tx1 = build_tx do |t|
|
|
102
|
+
t.output {|o| o.value 123; o.to @keys[1].addr }
|
|
103
|
+
end
|
|
104
|
+
tx2 = build_tx do |t|
|
|
105
|
+
t.output {|o| o.value 123
|
|
106
|
+
o.script {|s| s.recipient @keys[1].addr } }
|
|
107
|
+
end
|
|
108
|
+
tx1.out[0].should == tx2.out[0]
|
|
109
|
+
end
|
|
110
|
+
|
|
88
111
|
it "should build unsigned transactions and add the signature hash" do
|
|
89
112
|
tx = build_tx do |t|
|
|
90
113
|
t.input do |i|
|
|
@@ -102,6 +125,106 @@ describe "Bitcoin::Builder" do
|
|
|
102
125
|
tx.in[0].sig_hash.should != nil
|
|
103
126
|
end
|
|
104
127
|
|
|
128
|
+
it "should build unsigned multisig transactions and add the signature hash" do
|
|
129
|
+
tx1 = build_tx do |t|
|
|
130
|
+
t.input {|i| i.prev_out(@block.tx[0], 0); i.signature_key(@keys[0]) }
|
|
131
|
+
t.output do |o|
|
|
132
|
+
o.value 123
|
|
133
|
+
o.to [2, *@keys[0..2].map(&:pub)], :multisig
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
tx2 = build_tx do |t|
|
|
138
|
+
t.input do |i|
|
|
139
|
+
i.prev_out tx1, 0
|
|
140
|
+
i.signature_key @keys[0]
|
|
141
|
+
end
|
|
142
|
+
t.output {|o| o.value 123; o.to @keys[0].addr }
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
tx2.is_a?(Bitcoin::P::Tx).should == true
|
|
146
|
+
tx2.in[0].sig_hash.should != nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "should build unsigned p2sh multisig transactions and add the signature hash" do
|
|
150
|
+
tx1 = build_tx do |t|
|
|
151
|
+
t.input {|i| i.prev_out(@block.tx[0], 0); i.signature_key(@keys[0]) }
|
|
152
|
+
t.output do |o|
|
|
153
|
+
o.value 123
|
|
154
|
+
o.to [2, *@keys[0..2].map(&:pub)], :p2sh_multisig
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
tx2 = build_tx do |t|
|
|
159
|
+
t.input do |i|
|
|
160
|
+
i.prev_out tx1, 0
|
|
161
|
+
i.signature_key @keys[0]
|
|
162
|
+
i.redeem_script tx1.out[0].redeem_script
|
|
163
|
+
end
|
|
164
|
+
t.output {|o| o.value 123; o.to @keys[0].addr }
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
tx2.is_a?(Bitcoin::P::Tx).should == true
|
|
168
|
+
tx2.in[0].sig_hash.should != nil
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
it "should add change output" do
|
|
172
|
+
change_address = Bitcoin::Key.generate.addr
|
|
173
|
+
tx = build_tx(input_value: @block.tx[0].out.map(&:value).inject(:+),
|
|
174
|
+
change_address: change_address) do |t|
|
|
175
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @keys[0] }
|
|
176
|
+
t.output {|o| o.value 12345; o.script {|s| s.recipient @keys[1].addr } }
|
|
177
|
+
end
|
|
178
|
+
tx.out.count.should == 2
|
|
179
|
+
tx.out.last.value.should == 50e8 - 12345
|
|
180
|
+
Bitcoin::Script.new(tx.out.last.pk_script).get_address.should == change_address
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
it "should add change output and leave fee" do
|
|
184
|
+
change_address = Bitcoin::Key.generate.addr
|
|
185
|
+
tx = build_tx(input_value: @block.tx[0].out.map(&:value).inject(:+),
|
|
186
|
+
change_address: change_address, leave_fee: true) do |t|
|
|
187
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @keys[0] }
|
|
188
|
+
t.output {|o| o.value 12345; o.script {|s| s.recipient @keys[1].addr } }
|
|
189
|
+
end
|
|
190
|
+
tx.out.count.should == 2
|
|
191
|
+
tx.out.last.value.should == 50e8 - 12345 - Bitcoin.network[:min_tx_fee]
|
|
192
|
+
Bitcoin::Script.new(tx.out.last.pk_script).get_address.should == change_address
|
|
193
|
+
|
|
194
|
+
tx = build_tx(input_value: @block.tx[0].out.map(&:value).inject(:+),
|
|
195
|
+
change_address: change_address, leave_fee: true) do |t|
|
|
196
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @keys[0] }
|
|
197
|
+
49.times { t.output {|o| o.value 1e8; o.script {|s| s.recipient @keys[1].addr } } }
|
|
198
|
+
t.output {|o| o.value(1e8 - 10000); o.script {|s| s.recipient @keys[1].addr } }
|
|
199
|
+
end
|
|
200
|
+
tx.out.size.should == 50
|
|
201
|
+
tx.out.map(&:value).inject(:+).should == 50e8 - 10000
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
it "randomize_outputs should not modify output values or fees" do
|
|
205
|
+
change_address = Bitcoin::Key.generate.addr
|
|
206
|
+
tx = build_tx(input_value: @block.tx[0].out.map(&:value).inject(:+),
|
|
207
|
+
change_address: change_address, leave_fee: true) do |t|
|
|
208
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @keys[0] }
|
|
209
|
+
t.output {|o| o.value 12345; o.script {|s| s.recipient @keys[1].addr } }
|
|
210
|
+
t.randomize_outputs
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
tx.out.count.should == 2
|
|
214
|
+
tx.out.last.value.should == 50e8 - 12345 - Bitcoin.network[:min_tx_fee]
|
|
215
|
+
Bitcoin::Script.new(tx.out.last.pk_script).get_address.should == change_address
|
|
216
|
+
|
|
217
|
+
tx = build_tx(input_value: @block.tx[0].out.map(&:value).inject(:+),
|
|
218
|
+
change_address: change_address, leave_fee: true) do |t|
|
|
219
|
+
t.input {|i| i.prev_out @block.tx[0]; i.prev_out_index 0; i.signature_key @keys[0] }
|
|
220
|
+
49.times { t.output {|o| o.value 1e8; o.script {|s| s.recipient @keys[1].addr } } }
|
|
221
|
+
t.output {|o| o.value(1e8 - 10000); o.script {|s| s.recipient @keys[1].addr } }
|
|
222
|
+
t.randomize_outputs
|
|
223
|
+
end
|
|
224
|
+
tx.out.size.should == 50
|
|
225
|
+
tx.out.map(&:value).inject(:+).should == 50e8 - 10000
|
|
226
|
+
end
|
|
227
|
+
|
|
105
228
|
it "should build address script" do
|
|
106
229
|
key = Bitcoin::Key.generate
|
|
107
230
|
s = script {|s| s.type :address; s.recipient key.addr }
|
|
@@ -121,4 +244,58 @@ describe "Bitcoin::Builder" do
|
|
|
121
244
|
Bitcoin::Script.new(s).to_string.should == "1 #{keys[0].pub} #{keys[1].pub} 2 OP_CHECKMULTISIG"
|
|
122
245
|
end
|
|
123
246
|
|
|
247
|
+
it "should build and spend multisig output" do
|
|
248
|
+
tx1 = build_tx do |t|
|
|
249
|
+
t.input {|i| i.prev_out(@block.tx[0], 0); i.signature_key(@keys[0]) }
|
|
250
|
+
t.output do |o|
|
|
251
|
+
o.value 123
|
|
252
|
+
o.to [2, *@keys[0..2].map(&:pub)], :multisig
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
Bitcoin::Script.new(tx1.out[0].pk_script).to_string.should ==
|
|
257
|
+
"2 #{@keys[0..2].map(&:pub).join(' ')} 3 OP_CHECKMULTISIG"
|
|
258
|
+
|
|
259
|
+
tx2 = build_tx do |t|
|
|
260
|
+
t.input do |i|
|
|
261
|
+
i.prev_out tx1, 0
|
|
262
|
+
i.signature_key @keys[0..1]
|
|
263
|
+
end
|
|
264
|
+
t.output {|o| o.value 123; o.to @keys[0].addr }
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
tx2.verify_input_signature(0, tx1).should == true
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
it "should build and spend p2sh multisig output" do
|
|
271
|
+
tx1 = build_tx do |t|
|
|
272
|
+
t.input {|i| i.prev_out(@block.tx[0], 0); i.signature_key(@keys[0]) }
|
|
273
|
+
t.output do |o|
|
|
274
|
+
o.value 123
|
|
275
|
+
o.to [2, *@keys[0..2].map(&:pub)], :p2sh_multisig
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
Bitcoin::Script.new(tx1.out[0].pk_script).to_string.should ==
|
|
280
|
+
"OP_HASH160 #{Bitcoin.hash160(tx1.out[0].redeem_script.hth)} OP_EQUAL"
|
|
281
|
+
|
|
282
|
+
tx2 = build_tx do |t|
|
|
283
|
+
t.input do |i|
|
|
284
|
+
i.prev_out tx1, 0
|
|
285
|
+
# provide 2 required keys for signing
|
|
286
|
+
i.signature_key @keys[0..1]
|
|
287
|
+
# provide the redeem script from the previous output
|
|
288
|
+
i.redeem_script tx1.out[0].redeem_script
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
t.output {|o| o.value 123; o.to @keys[0].addr }
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
script = Bitcoin::Script.new(tx2.in[0].script_sig, tx1.out[0].pk_script)
|
|
295
|
+
# check script execution is valid
|
|
296
|
+
script.run { true }.should == true
|
|
297
|
+
# check signatures are valid
|
|
298
|
+
tx2.verify_input_signature(0, tx1).should == true
|
|
299
|
+
end
|
|
300
|
+
|
|
124
301
|
end
|