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
data/lib/bitcoin/protocol.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
require 'socket'
|
|
2
4
|
require 'digest/sha2'
|
|
3
5
|
require 'json'
|
|
@@ -5,6 +7,12 @@ require 'json'
|
|
|
5
7
|
module Bitcoin
|
|
6
8
|
module Protocol
|
|
7
9
|
|
|
10
|
+
# bitcoin/src/main.h
|
|
11
|
+
MAX_INV_SZ = 50000
|
|
12
|
+
|
|
13
|
+
# BIP 0031, pong message, is enabled for all versions AFTER this one
|
|
14
|
+
BIP0031_VERSION = 60000
|
|
15
|
+
|
|
8
16
|
autoload :TxIn, 'bitcoin/protocol/txin'
|
|
9
17
|
autoload :TxOut, 'bitcoin/protocol/txout'
|
|
10
18
|
autoload :Tx, 'bitcoin/protocol/tx'
|
|
@@ -12,15 +20,15 @@ module Bitcoin
|
|
|
12
20
|
autoload :Addr, 'bitcoin/protocol/address'
|
|
13
21
|
autoload :Alert, 'bitcoin/protocol/alert'
|
|
14
22
|
autoload :Version, 'bitcoin/protocol/version'
|
|
23
|
+
autoload :AuxPow, 'bitcoin/protocol/aux_pow'
|
|
15
24
|
|
|
16
25
|
autoload :Handler, 'bitcoin/protocol/handler'
|
|
17
26
|
autoload :Parser, 'bitcoin/protocol/parser'
|
|
18
27
|
|
|
19
|
-
VERSION = 60001
|
|
20
|
-
|
|
21
|
-
DNS_Seed = [ "bitseed.xf2.org", "bitseed.bitcoin.org.uk" ]
|
|
22
28
|
Uniq = rand(0xffffffffffffffff)
|
|
23
29
|
|
|
30
|
+
# var_int refers to https://en.bitcoin.it/wiki/Protocol_specification#Variable_length_integer and is what Satoshi called "CompactSize"
|
|
31
|
+
# BitcoinQT has later added even more compact format called CVarInt to use in its local block storage. CVarInt is not implemented here.
|
|
24
32
|
def self.unpack_var_int(payload)
|
|
25
33
|
case payload.unpack("C")[0] # TODO add test cases
|
|
26
34
|
when 0xfd; payload.unpack("xva*")
|
|
@@ -30,6 +38,16 @@ module Bitcoin
|
|
|
30
38
|
end
|
|
31
39
|
end
|
|
32
40
|
|
|
41
|
+
def self.unpack_var_int_from_io(io)
|
|
42
|
+
uchar = io.read(1).unpack("C")[0]
|
|
43
|
+
case uchar
|
|
44
|
+
when 0xfd; io.read(2).unpack("v")[0]
|
|
45
|
+
when 0xfe; io.read(4).unpack("V")[0]
|
|
46
|
+
when 0xff; io.read(8).unpack("Q")[0]
|
|
47
|
+
else; uchar
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
33
51
|
def self.pack_var_int(i)
|
|
34
52
|
if i < 0xfd; [ i].pack("C")
|
|
35
53
|
elsif i <= 0xffff; [0xfd, i].pack("Cv")
|
|
@@ -44,6 +62,11 @@ module Bitcoin
|
|
|
44
62
|
size > 0 ? (string, payload = payload.unpack("a#{size}a*")) : [nil, payload]
|
|
45
63
|
end
|
|
46
64
|
|
|
65
|
+
def self.unpack_var_string_from_io(buf)
|
|
66
|
+
size = unpack_var_int_from_io(buf)
|
|
67
|
+
size > 0 ? buf.read(size) : nil
|
|
68
|
+
end
|
|
69
|
+
|
|
47
70
|
def self.pack_var_string(payload)
|
|
48
71
|
pack_var_int(payload.bytesize) + payload
|
|
49
72
|
end
|
|
@@ -60,13 +83,14 @@ module Bitcoin
|
|
|
60
83
|
[(0...size).map{ i, payload = unpack_var_int(payload); i }, payload]
|
|
61
84
|
end
|
|
62
85
|
|
|
86
|
+
BINARY = Encoding.find('ASCII-8BIT')
|
|
63
87
|
|
|
64
88
|
def self.pkt(command, payload)
|
|
65
89
|
cmd = command.ljust(12, "\x00")[0...12]
|
|
66
|
-
length = [payload.bytesize].pack("
|
|
90
|
+
length = [payload.bytesize].pack("V")
|
|
67
91
|
checksum = Digest::SHA256.digest(Digest::SHA256.digest(payload))[0...4]
|
|
68
|
-
|
|
69
|
-
|
|
92
|
+
pkt = "".force_encoding(BINARY)
|
|
93
|
+
pkt << Bitcoin.network[:magic_head].force_encoding(BINARY) << cmd.force_encoding(BINARY) << length << checksum << payload.force_encoding(BINARY)
|
|
70
94
|
end
|
|
71
95
|
|
|
72
96
|
def self.version_pkt(from_id, from=nil, to=nil, last_block=nil, time=nil, user_agent=nil, version=nil)
|
|
@@ -98,39 +122,36 @@ module Bitcoin
|
|
|
98
122
|
TypeLookup = Hash[:tx, 1, :block, 2, nil, 0]
|
|
99
123
|
|
|
100
124
|
def self.getdata_pkt(type, hashes)
|
|
101
|
-
return if hashes.size
|
|
102
|
-
t = [ TypeLookup[type] ].pack("
|
|
103
|
-
pkt("getdata",
|
|
125
|
+
return if hashes.size > MAX_INV_SZ
|
|
126
|
+
t = [ TypeLookup[type] ].pack("V")
|
|
127
|
+
pkt("getdata", pack_var_int(hashes.size) + hashes.map{|hash| t + hash[0..32].reverse }.join)
|
|
104
128
|
end
|
|
105
129
|
|
|
106
130
|
def self.inv_pkt(type, hashes)
|
|
107
|
-
return if hashes.size
|
|
108
|
-
t = [ TypeLookup[type] ].pack("
|
|
109
|
-
pkt("inv",
|
|
131
|
+
return if hashes.size > MAX_INV_SZ
|
|
132
|
+
t = [ TypeLookup[type] ].pack("V")
|
|
133
|
+
pkt("inv", pack_var_int(hashes.size) + hashes.map{|hash| t + hash[0..32].reverse }.join)
|
|
110
134
|
end
|
|
111
135
|
|
|
112
136
|
DEFAULT_STOP_HASH = "00"*32
|
|
113
137
|
|
|
114
|
-
def self.locator_payload(locator_hashes, stop_hash)
|
|
138
|
+
def self.locator_payload(version, locator_hashes, stop_hash)
|
|
115
139
|
payload = [
|
|
116
|
-
|
|
140
|
+
[version].pack("V"),
|
|
117
141
|
pack_var_int(locator_hashes.size),
|
|
118
|
-
locator_hashes.map{|l|
|
|
119
|
-
|
|
142
|
+
locator_hashes.map{|l| l.htb_reverse }.join,
|
|
143
|
+
stop_hash.htb_reverse
|
|
120
144
|
].join
|
|
121
145
|
end
|
|
122
146
|
|
|
123
|
-
def self.getblocks_pkt(locator_hashes, stop_hash=DEFAULT_STOP_HASH)
|
|
124
|
-
pkt "getblocks", locator_payload(locator_hashes, stop_hash)
|
|
147
|
+
def self.getblocks_pkt(version, locator_hashes, stop_hash=DEFAULT_STOP_HASH)
|
|
148
|
+
pkt "getblocks", locator_payload(version, locator_hashes, stop_hash)
|
|
125
149
|
end
|
|
126
150
|
|
|
127
|
-
def self.getheaders_pkt(locator_hashes, stop_hash=DEFAULT_STOP_HASH)
|
|
128
|
-
pkt "getheaders", locator_payload(locator_hashes, stop_hash)
|
|
151
|
+
def self.getheaders_pkt(version, locator_hashes, stop_hash=DEFAULT_STOP_HASH)
|
|
152
|
+
pkt "getheaders", locator_payload(version, locator_hashes, stop_hash)
|
|
129
153
|
end
|
|
130
154
|
|
|
131
|
-
def self.hth(h); h.unpack("H*")[0]; end
|
|
132
|
-
def self.htb(h); [h].pack("H*"); end
|
|
133
|
-
|
|
134
155
|
def self.read_binary_file(path)
|
|
135
156
|
File.open(path, 'rb'){|f| f.read }
|
|
136
157
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
module Bitcoin
|
|
2
4
|
module Protocol
|
|
3
5
|
|
|
@@ -15,7 +17,7 @@ module Bitcoin
|
|
|
15
17
|
# create addr from raw binary +data+
|
|
16
18
|
def initialize(data = nil)
|
|
17
19
|
if data
|
|
18
|
-
self[:time], self[:service], self[:ip], self[:port] = data.unpack("
|
|
20
|
+
self[:time], self[:service], self[:ip], self[:port] = data.unpack("VQx12a4n")
|
|
19
21
|
self[:ip] = ip.unpack("C*").join(".")
|
|
20
22
|
else
|
|
21
23
|
self[:time], self[:service] = Time.now.to_i, 1
|
|
@@ -30,7 +32,7 @@ module Bitcoin
|
|
|
30
32
|
|
|
31
33
|
def to_payload
|
|
32
34
|
ip = self[:ip].split(".").map(&:to_i)
|
|
33
|
-
[ time, service, ("\x00"*10)+"\xff\xff", *ip, port ].pack("
|
|
35
|
+
[ time, service, ("\x00"*10)+"\xff\xff", *ip, port ].pack("VQa12C4n")
|
|
34
36
|
end
|
|
35
37
|
|
|
36
38
|
def string
|
|
@@ -38,7 +40,7 @@ module Bitcoin
|
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
def self.pkt(*addrs)
|
|
41
|
-
addrs = addrs.select{|i| i.is_a?(Bitcoin::Protocol::Addr) }
|
|
43
|
+
addrs = addrs.select{|i| i.is_a?(Bitcoin::Protocol::Addr) && i.ip =~ /^\d+\.\d+\.\d+\.\d+$/ }
|
|
42
44
|
length = Bitcoin::Protocol.pack_var_int(addrs.size)
|
|
43
45
|
Bitcoin::Protocol.pkt("addr", length + addrs.map(&:to_payload).join)
|
|
44
46
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
module Bitcoin
|
|
2
4
|
module Protocol
|
|
3
5
|
|
|
@@ -11,13 +13,10 @@ module Protocol
|
|
|
11
13
|
super(*values)
|
|
12
14
|
end
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
Valid_Keys = [ "04fc9702847840aaf195de8442ebecedf5b095cdbb9bc716bda9110971b28a49e0ead8564ff0db22209e0374782c093bb899692d524e9d6a6956e7c5ecbcd68284" ]
|
|
16
|
-
|
|
17
16
|
def valid_signature?
|
|
18
17
|
return false unless @payload && @signature
|
|
19
18
|
hash = Digest::SHA256.digest(Digest::SHA256.digest(@payload))
|
|
20
|
-
|
|
19
|
+
Bitcoin.network[:alert_pubkeys].any?{|public_key| Bitcoin.verify_signature(hash, @signature, public_key) }
|
|
21
20
|
end
|
|
22
21
|
|
|
23
22
|
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
3
|
+
module Bitcoin
|
|
4
|
+
module Protocol
|
|
5
|
+
|
|
6
|
+
# Auxiliary Proof-of-Work for merge-mined blockchains
|
|
7
|
+
class AuxPow
|
|
8
|
+
|
|
9
|
+
# Coinbase transaction linking the aux to its parent block
|
|
10
|
+
attr_accessor :tx
|
|
11
|
+
|
|
12
|
+
# Hash of the block header
|
|
13
|
+
attr_accessor :block_hash
|
|
14
|
+
|
|
15
|
+
# Merkle branches to bring the transaction to the block's merkle root
|
|
16
|
+
attr_accessor :branch
|
|
17
|
+
|
|
18
|
+
# Index of this transaction in the merkle tree
|
|
19
|
+
attr_accessor :mrkl_index
|
|
20
|
+
|
|
21
|
+
# Merkle branches linking this aux chains to the aux root
|
|
22
|
+
attr_accessor :aux_branch
|
|
23
|
+
|
|
24
|
+
# Index of "this" block chain in the aux chain list
|
|
25
|
+
attr_accessor :aux_index
|
|
26
|
+
|
|
27
|
+
# Parent block header
|
|
28
|
+
attr_accessor :parent_block
|
|
29
|
+
|
|
30
|
+
def initialize(data)
|
|
31
|
+
parse_data (data) if data
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def parse_data(data)
|
|
35
|
+
@tx = P::Tx.new(nil)
|
|
36
|
+
payload = @tx.parse_data(data)
|
|
37
|
+
@block_hash, payload = payload.unpack("a32a*")
|
|
38
|
+
|
|
39
|
+
branch_count, payload = P.unpack_var_int(payload)
|
|
40
|
+
@branch = []
|
|
41
|
+
branch_count.times {
|
|
42
|
+
b, payload = payload.unpack("a32a*")
|
|
43
|
+
@branch << b
|
|
44
|
+
}
|
|
45
|
+
@mrkl_index, payload = payload.unpack("Ia*")
|
|
46
|
+
|
|
47
|
+
@aux_branch = []
|
|
48
|
+
aux_branch_count, payload = P.unpack_var_int(payload)
|
|
49
|
+
aux_branch_count.times {
|
|
50
|
+
b, payload = payload.unpack("a32a*")
|
|
51
|
+
@aux_branch << b
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
@aux_index, payload = payload.unpack("Ia*")
|
|
55
|
+
block, payload = payload.unpack("a80a*")
|
|
56
|
+
@parent_block = P::Block.new(block)
|
|
57
|
+
|
|
58
|
+
payload
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def parse_data_from_io(data)
|
|
62
|
+
@tx = P::Tx.new(nil)
|
|
63
|
+
@tx.parse_data_from_io(data)
|
|
64
|
+
|
|
65
|
+
@block_hash = data.read(32)
|
|
66
|
+
branch_count = P.unpack_var_int_from_io(data)
|
|
67
|
+
@branch = []
|
|
68
|
+
branch_count.times{ @branch << data.read(32) }
|
|
69
|
+
@mrkl_index = data.read(4).unpack("I")[0]
|
|
70
|
+
|
|
71
|
+
@aux_branch = []
|
|
72
|
+
aux_branch_count = P.unpack_var_int_from_io(data)
|
|
73
|
+
aux_branch_count.times{ @aux_branch << data.read(32) }
|
|
74
|
+
|
|
75
|
+
@aux_index = data.read(4).unpack("I")[0]
|
|
76
|
+
block = data.read(80)
|
|
77
|
+
@parent_block = P::Block.new(block)
|
|
78
|
+
|
|
79
|
+
data
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def to_payload
|
|
84
|
+
payload = @tx.to_payload
|
|
85
|
+
payload << @block_hash
|
|
86
|
+
payload << P.pack_var_int(@branch.count)
|
|
87
|
+
payload << @branch.join
|
|
88
|
+
payload << [@mrkl_index].pack("I")
|
|
89
|
+
payload << P.pack_var_int(@aux_branch.count)
|
|
90
|
+
payload << @aux_branch.join
|
|
91
|
+
payload << [@aux_index].pack("I")
|
|
92
|
+
payload << @parent_block.to_payload
|
|
93
|
+
payload
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def self.from_hash h
|
|
97
|
+
aux_pow = new(nil)
|
|
98
|
+
aux_pow.instance_eval do
|
|
99
|
+
@tx = P::Tx.from_hash(h['tx'])
|
|
100
|
+
@block_hash = h['block_hash'].htb
|
|
101
|
+
@branch = h['branch'].map(&:htb)
|
|
102
|
+
@mrkl_index = h['mrkl_index']
|
|
103
|
+
@aux_branch = h['aux_branch'].map(&:htb)
|
|
104
|
+
@aux_index = h['aux_index']
|
|
105
|
+
@parent_block = P::Block.from_hash(h['parent_block'])
|
|
106
|
+
end
|
|
107
|
+
aux_pow
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def to_hash
|
|
111
|
+
{ 'tx' => @tx.to_hash,
|
|
112
|
+
'block_hash' => @block_hash.hth,
|
|
113
|
+
'branch' => @branch.map(&:hth),
|
|
114
|
+
'mrkl_index' => @mrkl_index,
|
|
115
|
+
'aux_branch' => @aux_branch.map(&:hth),
|
|
116
|
+
'aux_index' => @aux_index,
|
|
117
|
+
'parent_block' => @parent_block.to_hash }
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -1,8 +1,15 @@
|
|
|
1
|
+
# encoding: ascii-8bit
|
|
2
|
+
|
|
1
3
|
module Bitcoin
|
|
2
4
|
module Protocol
|
|
3
5
|
|
|
4
6
|
class Block
|
|
5
7
|
|
|
8
|
+
BLOCK_VERSION_DEFAULT = (1 << 0)
|
|
9
|
+
BLOCK_VERSION_AUXPOW = (1 << 8)
|
|
10
|
+
BLOCK_VERSION_CHAIN_START = (1 << 16)
|
|
11
|
+
BLOCK_VERSION_CHAIN_END = (1 << 30)
|
|
12
|
+
|
|
6
13
|
# block hash
|
|
7
14
|
attr_accessor :hash
|
|
8
15
|
|
|
@@ -30,6 +37,9 @@ module Bitcoin
|
|
|
30
37
|
# raw protocol payload
|
|
31
38
|
attr_accessor :payload
|
|
32
39
|
|
|
40
|
+
# AuxPow linking the block to a merge-mined chain
|
|
41
|
+
attr_accessor :aux_pow
|
|
42
|
+
|
|
33
43
|
alias :transactions :tx
|
|
34
44
|
|
|
35
45
|
# compare to another block
|
|
@@ -37,58 +47,95 @@ module Bitcoin
|
|
|
37
47
|
@hash == other.hash
|
|
38
48
|
end
|
|
39
49
|
|
|
50
|
+
def binary_hash
|
|
51
|
+
[@hash].pack("H*")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def prev_block_hex
|
|
55
|
+
@prev_block_hex ||= @prev_block.reverse.unpack("H*")[0]
|
|
56
|
+
end
|
|
57
|
+
|
|
40
58
|
# create block from raw binary +data+
|
|
41
59
|
def initialize(data)
|
|
42
60
|
@tx = []
|
|
43
|
-
|
|
61
|
+
parse_data_from_io(data) if data
|
|
44
62
|
end
|
|
45
63
|
|
|
46
64
|
# parse raw binary data
|
|
47
65
|
def parse_data(data)
|
|
48
|
-
|
|
66
|
+
buf = parse_data_from_io(data)
|
|
67
|
+
buf.eof? ? true : buf.read
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# parse raw binary data
|
|
71
|
+
def parse_data_from_io(buf, header_only=false)
|
|
72
|
+
buf = buf.is_a?(String) ? StringIO.new(buf) : buf
|
|
73
|
+
@ver, @prev_block, @mrkl_root, @time, @bits, @nonce = buf.read(80).unpack("Va32a32VVV")
|
|
49
74
|
recalc_block_hash
|
|
50
75
|
|
|
51
|
-
|
|
52
|
-
|
|
76
|
+
if (@ver & BLOCK_VERSION_AUXPOW) > 0
|
|
77
|
+
@aux_pow = AuxPow.new(nil)
|
|
78
|
+
@aux_pow.parse_data_from_io(buf)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
return buf if buf.eof?
|
|
82
|
+
|
|
83
|
+
tx_size = Protocol.unpack_var_int_from_io(buf)
|
|
84
|
+
@tx_count = tx_size
|
|
85
|
+
return buf if header_only
|
|
86
|
+
|
|
87
|
+
tx_size.times{ break if payload == true
|
|
53
88
|
t = Tx.new(nil)
|
|
54
|
-
payload = t.
|
|
89
|
+
payload = t.parse_data_from_io(buf)
|
|
55
90
|
@tx << t
|
|
56
91
|
}
|
|
57
92
|
|
|
58
93
|
@payload = to_payload
|
|
59
|
-
|
|
94
|
+
buf
|
|
60
95
|
end
|
|
61
96
|
|
|
62
97
|
# recalculate the block hash
|
|
63
98
|
def recalc_block_hash
|
|
64
|
-
@hash = Bitcoin.block_hash(
|
|
99
|
+
@hash = Bitcoin.block_hash(@prev_block.reverse_hth, @mrkl_root.reverse_hth, @time, @bits, @nonce, @ver)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def recalc_mrkl_root
|
|
103
|
+
@mrkl_root = Bitcoin.hash_mrkl_tree( @tx.map(&:hash) ).last.htb_reverse
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# verify mrkl tree
|
|
107
|
+
def verify_mrkl_root
|
|
108
|
+
@mrkl_root.reverse_hth == Bitcoin.hash_mrkl_tree( @tx.map(&:hash) ).last
|
|
65
109
|
end
|
|
66
110
|
|
|
67
111
|
# get the block header info
|
|
68
112
|
# [<version>, <prev_block>, <merkle_root>, <time>, <bits>, <nonce>, <txcount>, <size>]
|
|
69
113
|
def header_info
|
|
70
|
-
[@ver,
|
|
114
|
+
[@ver, @prev_block.reverse_hth, @mrkl_root.reverse_hth, Time.at(@time), @bits, @nonce, @tx.size, @payload.size]
|
|
71
115
|
end
|
|
72
116
|
|
|
73
|
-
def hth(h); h.reverse.unpack("H*")[0]; end
|
|
74
|
-
def htb(s); [s].pack('H*').reverse; end
|
|
75
|
-
|
|
76
117
|
# convert to raw binary format
|
|
77
118
|
def to_payload
|
|
78
|
-
head = [@ver, @prev_block, @mrkl_root, @time, @bits, @nonce].pack("
|
|
79
|
-
|
|
119
|
+
head = [@ver, @prev_block, @mrkl_root, @time, @bits, @nonce].pack("Va32a32VVV")
|
|
120
|
+
head << @aux_pow.to_payload if @aux_pow
|
|
121
|
+
return head if @tx.size == 0
|
|
122
|
+
head << Protocol.pack_var_int(@tx.size)
|
|
123
|
+
@tx.each{|tx| head << tx.to_payload }
|
|
124
|
+
head
|
|
80
125
|
end
|
|
81
126
|
|
|
82
127
|
# convert to ruby hash (see also #from_hash)
|
|
83
128
|
def to_hash
|
|
84
|
-
{
|
|
129
|
+
h = {
|
|
85
130
|
'hash' => @hash, 'ver' => @ver,
|
|
86
|
-
'prev_block' =>
|
|
131
|
+
'prev_block' => @prev_block.reverse_hth, 'mrkl_root' => @mrkl_root.reverse_hth,
|
|
87
132
|
'time' => @time, 'bits' => @bits, 'nonce' => @nonce,
|
|
88
133
|
'n_tx' => @tx.size, 'size' => (@payload||to_payload).bytesize,
|
|
89
134
|
'tx' => @tx.map{|i| i.to_hash },
|
|
90
135
|
'mrkl_tree' => Bitcoin.hash_mrkl_tree( @tx.map{|i| i.hash } )
|
|
91
136
|
}
|
|
137
|
+
h['aux_pow'] = @aux_pow.to_hash if @aux_pow
|
|
138
|
+
h
|
|
92
139
|
end
|
|
93
140
|
|
|
94
141
|
def hextarget
|
|
@@ -103,6 +150,22 @@ module Bitcoin
|
|
|
103
150
|
Bitcoin.block_difficulty(@bits)
|
|
104
151
|
end
|
|
105
152
|
|
|
153
|
+
# introduced in block version 2 by BIP_0034
|
|
154
|
+
# blockchain height as seen by the block itself.
|
|
155
|
+
# do not trust this value, instead verify with chain storage.
|
|
156
|
+
def bip34_block_height(height=nil)
|
|
157
|
+
return nil unless @ver >= 2
|
|
158
|
+
if height # generate height binary
|
|
159
|
+
buf = [height].pack("V").gsub(/\x00+$/,"")
|
|
160
|
+
[buf.bytesize, buf].pack("Ca*")
|
|
161
|
+
else
|
|
162
|
+
coinbase = @tx.first.inputs.first.script_sig
|
|
163
|
+
coinbase[1..coinbase[0].ord].ljust(4, "\x00").unpack("V").first
|
|
164
|
+
end
|
|
165
|
+
rescue
|
|
166
|
+
nil
|
|
167
|
+
end
|
|
168
|
+
|
|
106
169
|
# convert to json representation as seen in the block explorer.
|
|
107
170
|
# (see also #from_json)
|
|
108
171
|
def to_json(options = {:space => ''}, *a)
|
|
@@ -116,13 +179,19 @@ module Bitcoin
|
|
|
116
179
|
end
|
|
117
180
|
|
|
118
181
|
# parse ruby hash (see also #to_hash)
|
|
119
|
-
def self.from_hash(h)
|
|
182
|
+
def self.from_hash(h, do_raise=true)
|
|
120
183
|
blk = new(nil)
|
|
121
184
|
blk.instance_eval{
|
|
122
185
|
@ver, @time, @bits, @nonce = h.values_at('ver', 'time', 'bits', 'nonce')
|
|
123
|
-
@prev_block, @mrkl_root = h.values_at('prev_block', 'mrkl_root').map{|i|
|
|
124
|
-
recalc_block_hash
|
|
186
|
+
@prev_block, @mrkl_root = h.values_at('prev_block', 'mrkl_root').map{|i| i.htb_reverse }
|
|
187
|
+
unless h['hash'] == recalc_block_hash
|
|
188
|
+
raise "Block hash mismatch! Claimed: #{h['hash']}, Actual: #{@hash}" if do_raise
|
|
189
|
+
end
|
|
190
|
+
@aux_pow = AuxPow.from_hash(h['aux_pow']) if h['aux_pow']
|
|
125
191
|
h['tx'].each{|tx| @tx << Tx.from_hash(tx) }
|
|
192
|
+
if h['tx'].any? && !Bitcoin.freicoin?
|
|
193
|
+
(raise "Block merkle root mismatch! Block: #{h['hash']}" unless verify_mrkl_root) if do_raise
|
|
194
|
+
end
|
|
126
195
|
}
|
|
127
196
|
blk
|
|
128
197
|
end
|
|
@@ -148,6 +217,17 @@ module Bitcoin
|
|
|
148
217
|
|
|
149
218
|
# read json block from a file
|
|
150
219
|
def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end
|
|
220
|
+
|
|
221
|
+
def validator(store, prev_block = nil)
|
|
222
|
+
@validator ||= Bitcoin::Validation::Block.new(self, store, prev_block)
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# get the (statistical) amount of work that was needed to generate this block.
|
|
226
|
+
def block_work
|
|
227
|
+
target = Bitcoin.decode_compact_bits(@bits)
|
|
228
|
+
(2**256) / (target.to_i(16) + 1)
|
|
229
|
+
end
|
|
230
|
+
|
|
151
231
|
end
|
|
152
232
|
|
|
153
233
|
end
|