bitcoin-ruby 0.0.1 → 0.0.2
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.
- data/.gitignore +4 -1
- data/Gemfile +21 -0
- data/README.rdoc +85 -25
- data/Rakefile +7 -3
- data/bin/bitcoin_node +39 -42
- data/bin/bitcoin_shell +1 -0
- data/bin/bitcoin_wallet +129 -53
- data/bitcoin-ruby.gemspec +4 -7
- data/concept-examples/blockchain-pow.rb +1 -1
- data/doc/CONFIG.rdoc +5 -5
- data/doc/EXAMPLES.rdoc +9 -5
- data/doc/NAMECOIN.rdoc +34 -0
- data/doc/NODE.rdoc +147 -10
- data/examples/balance.rb +10 -4
- data/examples/bbe_verify_tx.rb +7 -2
- data/examples/forwarder.rb +73 -0
- data/examples/generate_tx.rb +34 -0
- data/examples/simple_network_monitor_and_util.rb +187 -0
- data/examples/verify_tx.rb +1 -1
- data/lib/bitcoin.rb +308 -18
- data/lib/bitcoin/builder.rb +62 -36
- data/lib/bitcoin/config.rb +2 -0
- data/lib/bitcoin/connection.rb +11 -8
- data/lib/bitcoin/electrum/mnemonic.rb +162 -0
- data/lib/bitcoin/ffi/openssl.rb +187 -21
- data/lib/bitcoin/gui/addr_view.rb +2 -0
- data/lib/bitcoin/gui/conn_view.rb +2 -0
- data/lib/bitcoin/gui/connection.rb +2 -0
- data/lib/bitcoin/gui/em_gtk.rb +2 -0
- data/lib/bitcoin/gui/gui.rb +2 -0
- data/lib/bitcoin/gui/helpers.rb +2 -0
- data/lib/bitcoin/gui/tree_view.rb +2 -0
- data/lib/bitcoin/gui/tx_view.rb +2 -0
- data/lib/bitcoin/key.rb +77 -11
- data/lib/bitcoin/litecoin.rb +81 -0
- data/lib/bitcoin/logger.rb +20 -1
- data/lib/bitcoin/namecoin.rb +279 -0
- data/lib/bitcoin/network/command_client.rb +7 -6
- data/lib/bitcoin/network/command_handler.rb +229 -43
- data/lib/bitcoin/network/connection_handler.rb +182 -70
- data/lib/bitcoin/network/node.rb +231 -106
- data/lib/bitcoin/protocol.rb +44 -23
- data/lib/bitcoin/protocol/address.rb +5 -3
- data/lib/bitcoin/protocol/alert.rb +3 -4
- data/lib/bitcoin/protocol/aux_pow.rb +123 -0
- data/lib/bitcoin/protocol/block.rb +98 -18
- data/lib/bitcoin/protocol/handler.rb +6 -5
- data/lib/bitcoin/protocol/parser.rb +44 -19
- data/lib/bitcoin/protocol/tx.rb +105 -52
- data/lib/bitcoin/protocol/txin.rb +39 -19
- data/lib/bitcoin/protocol/txout.rb +28 -13
- data/lib/bitcoin/protocol/version.rb +16 -7
- data/lib/bitcoin/script.rb +579 -122
- data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
- data/lib/bitcoin/storage/models.rb +20 -7
- data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
- data/lib/bitcoin/storage/storage.rb +233 -28
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
- data/lib/bitcoin/validation.rb +369 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/coinselector.rb +3 -0
- data/lib/bitcoin/wallet/keygenerator.rb +3 -1
- data/lib/bitcoin/wallet/keystore.rb +6 -2
- data/lib/bitcoin/wallet/txdp.rb +6 -4
- data/lib/bitcoin/wallet/wallet.rb +54 -16
- data/spec/bitcoin/bitcoin_spec.rb +48 -3
- data/spec/bitcoin/builder_spec.rb +40 -17
- data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
- data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
- data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
- data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
- data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
- data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
- data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
- data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
- data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
- data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
- data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
- data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
- data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
- data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
- data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
- data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
- data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
- data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
- data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
- data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
- data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
- data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
- data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
- data/spec/bitcoin/key_spec.rb +128 -3
- data/spec/bitcoin/namecoin_spec.rb +182 -0
- data/spec/bitcoin/network_spec.rb +5 -3
- data/spec/bitcoin/node/command_api_spec.rb +376 -0
- data/spec/bitcoin/protocol/addr_spec.rb +2 -0
- data/spec/bitcoin/protocol/alert_spec.rb +2 -0
- data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
- data/spec/bitcoin/protocol/block_spec.rb +134 -39
- data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
- data/spec/bitcoin/protocol/inv_spec.rb +10 -8
- data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
- data/spec/bitcoin/protocol/ping_spec.rb +2 -0
- data/spec/bitcoin/protocol/tx_spec.rb +83 -17
- data/spec/bitcoin/protocol/version_spec.rb +7 -5
- data/spec/bitcoin/script/opcodes_spec.rb +412 -133
- data/spec/bitcoin/script/script_spec.rb +112 -13
- data/spec/bitcoin/spec_helper.rb +68 -0
- data/spec/bitcoin/storage/reorg_spec.rb +199 -0
- data/spec/bitcoin/storage/storage_spec.rb +337 -0
- data/spec/bitcoin/storage/validation_spec.rb +261 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
- data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
- metadata +105 -51
- data/lib/bitcoin/storage/sequel.rb +0 -335
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
- data/spec/bitcoin/reorg_spec.rb +0 -129
- data/spec/bitcoin/storage_spec.rb +0 -229
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
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
|
+
|
|
23
|
+
class ValidationError < StandardError
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Block
|
|
28
|
+
attr_accessor :block, :store, :prev_block, :error
|
|
29
|
+
|
|
30
|
+
RULES = {
|
|
31
|
+
syntax: [:hash, :tx_list, :bits, :max_timestamp, :coinbase, :coinbase_scriptsig, :mrkl_root, :transactions_syntax],
|
|
32
|
+
context: [:prev_hash, :difficulty, :coinbase_value, :min_timestamp, :transactions_context]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
# TODO merged mining validations
|
|
36
|
+
if Bitcoin.namecoin?
|
|
37
|
+
RULES[:syntax] -= [:bits, :coinbase, :coinbase_scriptsig, :mrkl_root]
|
|
38
|
+
RULES[:context] -= [:difficulty, :coinbase_value]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# validate block rules. +opts+ are:
|
|
42
|
+
# rules:: which rulesets to validate (default: [:syntax, :context])
|
|
43
|
+
# raise_errors:: whether to raise ValidationError on failure (default: false)
|
|
44
|
+
def validate(opts = {})
|
|
45
|
+
return true if KNOWN_EXCEPTIONS.include?(block.hash)
|
|
46
|
+
opts[:rules] ||= [:syntax, :context]
|
|
47
|
+
opts[:rules].each do |name|
|
|
48
|
+
store.log.debug { "validating block #{name} #{block.hash} (#{block.to_payload.bytesize} bytes)" }
|
|
49
|
+
RULES[name].each.with_index do |rule, i|
|
|
50
|
+
unless (res = send(rule)) && res == true
|
|
51
|
+
raise ValidationError, "block error: #{name} check #{i} - #{rule} failed" if opts[:raise_errors]
|
|
52
|
+
@error = [rule, res]
|
|
53
|
+
return false
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
true
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# setup new validator for given +block+, validating context with +store+,
|
|
61
|
+
# optionally passing the +prev_block+ for optimization.
|
|
62
|
+
def initialize block, store, prev_block = nil
|
|
63
|
+
@block, @store, @error = block, store, nil
|
|
64
|
+
@prev_block = prev_block || store.get_block(block.prev_block.reverse_hth)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# check that block hash matches header
|
|
68
|
+
def hash
|
|
69
|
+
claimed = block.hash; real = block.recalc_block_hash
|
|
70
|
+
claimed == real || [claimed, real]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# check that block has at least one tx (the coinbase)
|
|
74
|
+
def tx_list
|
|
75
|
+
block.tx.any? || block.tx.size
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# check that block hash matches claimed bits
|
|
79
|
+
def bits
|
|
80
|
+
actual = block.hash.to_i(16)
|
|
81
|
+
expected = Bitcoin.decode_compact_bits(block.bits).to_i(16)
|
|
82
|
+
actual <= expected || [actual, expected]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# check that block time is not greater than max
|
|
86
|
+
def max_timestamp
|
|
87
|
+
time, max = block.time, Time.now.to_i + 2*60*60
|
|
88
|
+
time < max || [time, max]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# check that coinbase is present
|
|
92
|
+
def coinbase
|
|
93
|
+
coinbase, *rest = block.tx.map{|t| t.inputs.size == 1 && t.inputs.first.coinbase? }
|
|
94
|
+
(coinbase && rest.none?) || [coinbase ? 1 : 0, rest.select{|r| r}.size]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# check that coinbase scriptsig is valid
|
|
98
|
+
def coinbase_scriptsig
|
|
99
|
+
size = block.tx.first.in.first.script_sig.bytesize
|
|
100
|
+
size.between?(2,100) || [size, 2, 100]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# check that coinbase value is valid; no more than reward + fees
|
|
104
|
+
def coinbase_value
|
|
105
|
+
reward = ((50.0 / (2 ** (store.get_depth / REWARD_DROP.to_f).floor)) * 1e8).to_i
|
|
106
|
+
fees = 0
|
|
107
|
+
block.tx[1..-1].map.with_index do |t, idx|
|
|
108
|
+
val = tx_validators[idx]
|
|
109
|
+
fees += t.in.map.with_index {|i, idx|
|
|
110
|
+
val.prev_txs[idx].out[i.prev_out_index].value rescue 0
|
|
111
|
+
}.inject(:+)
|
|
112
|
+
val.clear_cache # memory optimization on large coinbases, see testnet3 block 4110
|
|
113
|
+
end
|
|
114
|
+
coinbase_output = block.tx[0].out.map(&:value).inject(:+)
|
|
115
|
+
coinbase_output <= reward + fees || [coinbase_output, reward, fees]
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# check that merkle root matches transaction hashes
|
|
119
|
+
def mrkl_root
|
|
120
|
+
actual, expected = block.mrkl_root.reverse_hth, Bitcoin.hash_mrkl_tree(block.tx.map(&:hash))[-1]
|
|
121
|
+
actual == expected || [actual, expected]
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def prev_hash
|
|
125
|
+
@prev_block && @prev_block.hash == block.prev_block.reverse_hth
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# check that bits satisfy required difficulty
|
|
129
|
+
def difficulty
|
|
130
|
+
return true if Bitcoin.network_name == :testnet3
|
|
131
|
+
block.bits == next_bits_required || [block.bits, next_bits_required]
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# check that timestamp is newer than the median of the last 11 blocks
|
|
135
|
+
def min_timestamp
|
|
136
|
+
return true if store.get_depth <= 11
|
|
137
|
+
d = store.get_depth
|
|
138
|
+
first = store.db[:blk][hash: block.prev_block.reverse.blob]
|
|
139
|
+
times = [first[:time]]
|
|
140
|
+
(10).times { first = store.db[:blk][hash: first[:prev_hash].blob]
|
|
141
|
+
times << first[:time] }
|
|
142
|
+
times.sort!
|
|
143
|
+
mid, rem = times.size.divmod(2)
|
|
144
|
+
min_time = (rem == 0 ? times[mid-1, 2].inject(:+) / 2.0 : times[mid])
|
|
145
|
+
|
|
146
|
+
block.time > min_time || [block.time, min_time]
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
# check transactions
|
|
150
|
+
def transactions_syntax
|
|
151
|
+
tx_validators.all?{|v|
|
|
152
|
+
begin
|
|
153
|
+
v.validate(rules: [:syntax], raise_errors: true)
|
|
154
|
+
rescue ValidationError
|
|
155
|
+
store.log.info { $!.message }
|
|
156
|
+
return false
|
|
157
|
+
end
|
|
158
|
+
}
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def transactions_context
|
|
162
|
+
tx_validators.all?{|v|
|
|
163
|
+
begin
|
|
164
|
+
v.validate(rules: [:context], raise_errors: true)
|
|
165
|
+
rescue ValidationError
|
|
166
|
+
store.log.info { $!.message }
|
|
167
|
+
return false
|
|
168
|
+
end
|
|
169
|
+
}
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def tx_validators
|
|
173
|
+
@tx_validators ||= block.tx[1..-1].map {|tx| tx.validator(store, block) }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def next_bits_required
|
|
177
|
+
index = (prev_block.depth + 1) / RETARGET
|
|
178
|
+
max_target = Bitcoin.decode_compact_bits(Bitcoin.network[:proof_of_work_limit]).to_i(16)
|
|
179
|
+
return Bitcoin.network[:proof_of_work_limit] if index == 0
|
|
180
|
+
return prev_block.bits if (prev_block.depth + 1) % RETARGET != 0
|
|
181
|
+
last = store.db[:blk][hash: prev_block.hash.htb.blob]
|
|
182
|
+
first = store.db[:blk][hash: last[:prev_hash].blob]
|
|
183
|
+
(RETARGET-2).times { first = store.db[:blk][hash: first[:prev_hash].blob] }
|
|
184
|
+
|
|
185
|
+
nActualTimespan = last[:time] - first[:time]
|
|
186
|
+
nTargetTimespan = RETARGET * 600
|
|
187
|
+
|
|
188
|
+
nActualTimespan = [nActualTimespan, nTargetTimespan/4].max
|
|
189
|
+
nActualTimespan = [nActualTimespan, nTargetTimespan*4].min
|
|
190
|
+
|
|
191
|
+
target = Bitcoin.decode_compact_bits(last[:bits]).to_i(16)
|
|
192
|
+
new_target = [max_target, (target * nActualTimespan)/nTargetTimespan].min
|
|
193
|
+
Bitcoin.encode_compact_bits new_target.to_s(16)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
KNOWN_EXCEPTIONS = [
|
|
197
|
+
Bitcoin.network[:genesis_hash], # genesis block
|
|
198
|
+
"00000000000a4d0a398161ffc163c503763b1f4360639393e0e4c8e300e0caec", # BIP30 exception
|
|
199
|
+
"00000000000743f190a18c5577a3c2d2a1f610ae9601ac046a38084ccb7cd721", # BIP30 exception
|
|
200
|
+
]
|
|
201
|
+
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
class Tx
|
|
205
|
+
attr_accessor :tx, :store, :error
|
|
206
|
+
|
|
207
|
+
RULES = {
|
|
208
|
+
syntax: [:hash, :lists, :max_size, :output_values, :inputs, :lock_time, :standard],
|
|
209
|
+
context: [:prev_out, :signatures, :spent, :input_values, :output_sum]
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
# validate tx rules. +opts+ are:
|
|
213
|
+
# rules:: which rulesets to validate (default: [:syntax, :context])
|
|
214
|
+
# raise_errors:: whether to raise ValidationError on failure (default: false)
|
|
215
|
+
def validate(opts = {})
|
|
216
|
+
return true if KNOWN_EXCEPTIONS.include?(tx.hash)
|
|
217
|
+
opts[:rules] ||= [:syntax, :context]
|
|
218
|
+
opts[:rules].each do |name|
|
|
219
|
+
store.log.debug { "validating tx #{name} #{tx.hash} (#{tx.to_payload.bytesize} bytes)" } if store
|
|
220
|
+
RULES[name].each.with_index do |rule, i|
|
|
221
|
+
unless (res = send(rule)) && res == true
|
|
222
|
+
raise ValidationError, "tx error: #{name} check #{i} - #{rule} failed" if opts[:raise_errors]
|
|
223
|
+
@error = [rule, res]
|
|
224
|
+
return false
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
clear_cache # memory optimizatons
|
|
229
|
+
true
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
KNOWN_EXCEPTIONS = [
|
|
233
|
+
# p2sh with invalid inner script, accepted by old miner before 4-2012 switchover
|
|
234
|
+
"6a26d2ecb67f27d1fa5524763b49029d7106e91e3cc05743073461a719776192",
|
|
235
|
+
# p2sh with invalid inner script, accepted by old miner before 4-2012 switchover (testnet)
|
|
236
|
+
"b3c19d78b4953b694717a47d9852f8ea1ccd4cf93a45ba2e43a0f97d7cdb2655"
|
|
237
|
+
]
|
|
238
|
+
|
|
239
|
+
# setup new validator for given +tx+, validating context with +store+.
|
|
240
|
+
# also needs the +block+ to find prev_outs for chains of tx inside one block.
|
|
241
|
+
def initialize tx, store, block = nil
|
|
242
|
+
@tx, @store, @block, @errors = tx, store, block, []
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# check that tx hash matches data
|
|
246
|
+
def hash
|
|
247
|
+
generated_hash = tx.generate_hash(tx.to_payload)
|
|
248
|
+
tx.hash == generated_hash || [tx.hash, generated_hash]
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
# check that tx has at least one input and one output
|
|
252
|
+
def lists
|
|
253
|
+
(tx.in.any? && tx.out.any?) || [tx.in.size, tx.out.size]
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# check that tx size doesn't exceed MAX_BLOCK_SIZE.
|
|
257
|
+
def max_size
|
|
258
|
+
tx.to_payload.bytesize <= MAX_BLOCK_SIZE || [tx.to_payload.bytesize, MAX_BLOCK_SIZE]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# check that total output value doesn't exceed MAX_MONEY.
|
|
262
|
+
def output_values
|
|
263
|
+
total = tx.out.inject(0) {|e, out| e + out.value }
|
|
264
|
+
total <= Bitcoin::network[:max_money] || [total, Bitcoin::network[:max_money]]
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
# check that none of the inputs is coinbase
|
|
268
|
+
# (coinbase tx do not get validated)
|
|
269
|
+
def inputs
|
|
270
|
+
tx.inputs.none?(&:coinbase?) || [tx.inputs.index(tx.inputs.find(&:coinbase?))]
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# check that lock_time doesn't exceed INT_MAX
|
|
274
|
+
def lock_time
|
|
275
|
+
tx.lock_time <= INT_MAX || [tx.lock_time, INT_MAX]
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
# check that min_size is at least 86 bytes
|
|
279
|
+
# (smaller tx can't be valid / do anything useful)
|
|
280
|
+
def min_size
|
|
281
|
+
tx.to_payload.bytesize >= 86 || [tx.to_payload.bytesize, 86]
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# check that tx matches "standard" rules.
|
|
285
|
+
# this is currently disabled since not all miners enforce it.
|
|
286
|
+
def standard
|
|
287
|
+
return true # not enforced by all miners
|
|
288
|
+
return false unless min_size
|
|
289
|
+
tx.out.all? {|o| Bitcoin::Script.new(o.pk_script).is_standard? }
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
# check that all prev_outs exist
|
|
293
|
+
# (and are in a block in the main chain, or the current block; see #prev_txs)
|
|
294
|
+
def prev_out
|
|
295
|
+
missing = tx.in.reject.with_index {|txin, idx|
|
|
296
|
+
prev_txs[idx].out[txin.prev_out_index] rescue false }
|
|
297
|
+
return true if prev_txs.size == tx.in.size && missing.empty?
|
|
298
|
+
|
|
299
|
+
missing.each {|i| store.log.warn { "prev out #{i.prev_out.reverse_hth}:#{i.prev_out_index} missing" } }
|
|
300
|
+
missing.map {|i| [i.prev_out.reverse_hth, i.prev_out_index] }
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
# TODO: validate coinbase maturity
|
|
304
|
+
|
|
305
|
+
# check that all input signatures are valid
|
|
306
|
+
def signatures
|
|
307
|
+
sigs = tx.in.map.with_index {|txin, idx| tx.verify_input_signature(idx, prev_txs[idx], (@block ? @block.time : 0)) }
|
|
308
|
+
sigs.all? || sigs.map.with_index {|s, i| s ? nil : i }.compact
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# check that none of the prev_outs are already spent in the main chain
|
|
312
|
+
def spent
|
|
313
|
+
spent = tx.in.map.with_index {|txin, idx|
|
|
314
|
+
next false if @block && @block.tx.include?(prev_txs[idx])
|
|
315
|
+
next false unless next_in = prev_txs[idx].out[txin.prev_out_index].get_next_in
|
|
316
|
+
next false unless next_tx = next_in.get_tx
|
|
317
|
+
next false unless next_block = next_tx.get_block
|
|
318
|
+
next_block.chain == Bitcoin::Storage::Backends::StoreBase::MAIN
|
|
319
|
+
}
|
|
320
|
+
spent.none? || spent.map.with_index {|s, i| s ? i : nil }
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# check that the total input value doesn't exceed MAX_MONEY
|
|
324
|
+
def input_values
|
|
325
|
+
total_in < Bitcoin::network[:max_money] || [total_in, Bitcoin::network[:max_money]]
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
# check that the total output value doesn't exceed the total input value
|
|
329
|
+
def output_sum
|
|
330
|
+
total_in >= total_out || [total_out, total_in]
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# empty prev txs cache
|
|
334
|
+
def clear_cache
|
|
335
|
+
@prev_txs = nil
|
|
336
|
+
@total_in = nil
|
|
337
|
+
@total_out = nil
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# collect prev_txs needed to verify the inputs of this tx.
|
|
341
|
+
# only returns tx that are in a block in the main chain or the current block.
|
|
342
|
+
def prev_txs
|
|
343
|
+
@prev_txs ||= tx.in.map {|i|
|
|
344
|
+
prev_tx = store.get_tx(i.prev_out.reverse_hth)
|
|
345
|
+
next prev_tx if store.class.name =~ /UtxoStore/ && prev_tx
|
|
346
|
+
next nil if !prev_tx && !@block
|
|
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 }
|
|
356
|
+
}.compact
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def total_in
|
|
361
|
+
@total_in ||= tx.in.each_with_index.inject(0){|acc,(input,idx)| acc + prev_txs[idx].out[input.prev_out_index].value }
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def total_out
|
|
365
|
+
@total_out ||= tx.out.inject(0){|acc,output| acc + output.value }
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
end
|
|
369
|
+
end
|
data/lib/bitcoin/version.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
module Bitcoin::Wallet
|
|
2
4
|
|
|
3
5
|
# select unspent txouts to be used by the Wallet when creating a new transaction
|
|
@@ -14,6 +16,7 @@ module Bitcoin::Wallet
|
|
|
14
16
|
@txouts.each do |txout|
|
|
15
17
|
begin
|
|
16
18
|
next if txout.get_next_in
|
|
19
|
+
next if Bitcoin.namecoin? && txout.type.to_s =~ /^name_/
|
|
17
20
|
next unless txout.get_address
|
|
18
21
|
next unless txout.get_tx.get_block
|
|
19
22
|
txouts << txout
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
module Bitcoin::Wallet
|
|
2
4
|
|
|
3
5
|
# Deterministic key generator as described in
|
|
4
6
|
# https://bitcointalk.org/index.php?topic=11665.0.
|
|
5
|
-
#
|
|
7
|
+
#
|
|
6
8
|
# Takes a seed and generates an arbitrary amount of keys.
|
|
7
9
|
# Protects against brute-force attacks by requiring the
|
|
8
10
|
# key hash to fit a difficulty target, much like the block chain.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
require 'json'
|
|
2
4
|
require 'stringio'
|
|
3
5
|
|
|
@@ -12,6 +14,7 @@ module Bitcoin::Wallet
|
|
|
12
14
|
# [config] Hash of settings ({:file => "/foo/bar.json"})
|
|
13
15
|
def initialize config
|
|
14
16
|
@config = Hash[config.map{|k,v|[k.to_sym,v]}]
|
|
17
|
+
@config[:file].sub!("~", ENV["HOME"]) if @config[:file].is_a?(String)
|
|
15
18
|
@keys = []
|
|
16
19
|
load_keys
|
|
17
20
|
end
|
|
@@ -92,9 +95,10 @@ module Bitcoin::Wallet
|
|
|
92
95
|
def import(base58, label = nil)
|
|
93
96
|
raise ArgumentError, "Label #{label} already in use" if label && find_key(label)
|
|
94
97
|
key = Bitcoin::Key.from_base58(base58)
|
|
98
|
+
raise ArgumentError, "Address #{key.addr} already in use" if label && find_key(key.addr)
|
|
95
99
|
@keys << {:label => label, :addr => key.addr, :key => key}
|
|
96
100
|
save_keys
|
|
97
|
-
key
|
|
101
|
+
key
|
|
98
102
|
end
|
|
99
103
|
|
|
100
104
|
# Load keys from file.
|
|
@@ -139,7 +143,7 @@ module Bitcoin::Wallet
|
|
|
139
143
|
@config[:file].reopen
|
|
140
144
|
dumper.call(@config[:file])
|
|
141
145
|
@config[:file].rewind
|
|
142
|
-
|
|
146
|
+
else
|
|
143
147
|
File.open(@config[:file], 'w'){|file| dumper.call(file) }
|
|
144
148
|
end
|
|
145
149
|
end
|
data/lib/bitcoin/wallet/txdp.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
class Bitcoin::Wallet::TxDP
|
|
2
4
|
|
|
3
5
|
attr_accessor :id, :tx, :inputs
|
|
@@ -7,7 +9,7 @@ class Bitcoin::Wallet::TxDP
|
|
|
7
9
|
@inputs = []
|
|
8
10
|
return unless tx.any?
|
|
9
11
|
@tx[0].in.each_with_index do |input, i|
|
|
10
|
-
prev_out_hash = input.prev_out.
|
|
12
|
+
prev_out_hash = input.prev_out.reverse_hth
|
|
11
13
|
prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
|
|
12
14
|
raise "prev tx #{prev_out_hash} not found" unless prev_tx
|
|
13
15
|
prev_out = prev_tx.out[input.prev_out_index]
|
|
@@ -27,7 +29,7 @@ class Bitcoin::Wallet::TxDP
|
|
|
27
29
|
def sign_inputs
|
|
28
30
|
@inputs.each_with_index do |txin, i|
|
|
29
31
|
input = @tx[0].in[i]
|
|
30
|
-
prev_out_hash = input.prev_out.
|
|
32
|
+
prev_out_hash = input.prev_out.reverse_hth
|
|
31
33
|
prev_tx = @tx[1..-1].find {|tx| tx.hash == prev_out_hash}
|
|
32
34
|
raise "prev tx #{prev_out_hash} not found" unless prev_tx
|
|
33
35
|
prev_out = prev_tx.out[input.prev_out_index]
|
|
@@ -47,7 +49,7 @@ class Bitcoin::Wallet::TxDP
|
|
|
47
49
|
def serialize
|
|
48
50
|
lines = []
|
|
49
51
|
lines << "-----BEGIN-TRANSACTION-#{@id}".ljust(80, '-')
|
|
50
|
-
size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").
|
|
52
|
+
size = [@tx.first.to_payload.bytesize].pack("C").ljust(2, "\x00").reverse_hth
|
|
51
53
|
lines << "_TXDIST_#{Bitcoin.network[:magic_head].unpack("H*")[0]}_#{@id}_#{size}"
|
|
52
54
|
tx = @tx.map(&:to_payload).join.unpack("H*")[0]
|
|
53
55
|
tx_str = ""; tx.split('').each_with_index{|c,i| tx_str << (i % 80 == 0 ? "\n#{c}" : c)}
|
|
@@ -57,7 +59,7 @@ class Bitcoin::Wallet::TxDP
|
|
|
57
59
|
next unless input[1]
|
|
58
60
|
input[1].each do |sig|
|
|
59
61
|
size = [sig[1]].pack("H*").bytesize
|
|
60
|
-
size = [size].pack("C").ljust(2, "\x00").
|
|
62
|
+
size = [size].pack("C").ljust(2, "\x00").reverse_hth
|
|
61
63
|
lines << "_SIG_#{sig[0]}_#{idx.to_s.rjust(2, '0')}_#{size}"
|
|
62
64
|
sig_str = ""; sig[1].split('').each_with_index{|c,i| sig_str << (i % 80 == 0 ? "\n#{c}" : c)}
|
|
63
65
|
lines << sig_str.strip
|