bitcoinrb 0.1.3 → 0.1.4
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 +4 -4
- data/.travis.yml +4 -0
- data/README.md +31 -9
- data/bitcoinrb.conf.sample +0 -0
- data/bitcoinrb.gemspec +6 -2
- data/exe/bitcoinrb-cli +2 -2
- data/lib/bitcoin/block_header.rb +9 -2
- data/lib/bitcoin/chainparams/regtest.yml +1 -1
- data/lib/bitcoin/constants.rb +3 -0
- data/lib/bitcoin/key.rb +70 -5
- data/lib/bitcoin/logger.rb +25 -1
- data/lib/bitcoin/message/addr.rb +0 -39
- data/lib/bitcoin/message/headers.rb +1 -0
- data/lib/bitcoin/message/inventory.rb +4 -0
- data/lib/bitcoin/message/network_addr.rb +44 -0
- data/lib/bitcoin/message/version.rb +8 -3
- data/lib/bitcoin/message.rb +12 -0
- data/lib/bitcoin/mnemonic.rb +2 -2
- data/lib/bitcoin/network/connection.rb +14 -1
- data/lib/bitcoin/network/message_handler.rb +52 -19
- data/lib/bitcoin/network/peer.rb +142 -5
- data/lib/bitcoin/network/peer_discovery.rb +16 -8
- data/lib/bitcoin/network/pool.rb +39 -14
- data/lib/bitcoin/network.rb +0 -1
- data/lib/bitcoin/node/cli.rb +66 -0
- data/lib/bitcoin/node/configuration.rb +37 -0
- data/lib/bitcoin/node/spv.rb +13 -3
- data/lib/bitcoin/node.rb +2 -1
- data/lib/bitcoin/rpc/http_server.rb +56 -0
- data/lib/bitcoin/rpc/request_handler.rb +84 -0
- data/lib/bitcoin/rpc.rb +6 -0
- data/lib/bitcoin/script/multisig.rb +92 -0
- data/lib/bitcoin/script/script.rb +17 -7
- data/lib/bitcoin/script/script_interpreter.rb +2 -38
- data/lib/bitcoin/store/chain_entry.rb +64 -0
- data/lib/bitcoin/store/db/level_db.rb +101 -0
- data/lib/bitcoin/store/db.rb +9 -0
- data/lib/bitcoin/store/spv_chain.rb +96 -0
- data/lib/bitcoin/store.rb +5 -1
- data/lib/bitcoin/tx.rb +6 -2
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +82 -0
- data/lib/bitcoin/wallet/base.rb +84 -0
- data/lib/bitcoin/wallet/db.rb +57 -0
- data/lib/bitcoin/wallet/master_key.rb +100 -0
- data/lib/bitcoin/wallet.rb +8 -0
- data/lib/bitcoin.rb +4 -0
- data/lib/openassets/payload.rb +6 -2
- metadata +70 -13
- data/lib/bitcoin/node/spv_block_chain.rb +0 -15
- data/lib/bitcoin/store/spv_block_store.rb +0 -11
@@ -0,0 +1,84 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module RPC
|
3
|
+
|
4
|
+
# RPC server's request handler.
|
5
|
+
module RequestHandler
|
6
|
+
|
7
|
+
# Returns an object containing various state info regarding blockchain processing.
|
8
|
+
def getblockchaininfo
|
9
|
+
h = {}
|
10
|
+
h[:chain] = Bitcoin.chain_params.network
|
11
|
+
best_block = node.chain.latest_block
|
12
|
+
h[:headers] = best_block.height
|
13
|
+
h[:bestblockhash] = best_block.hash
|
14
|
+
h[:chainwork] = best_block.header.work
|
15
|
+
h[:mediantime] = node.chain.mtp(best_block.hash)
|
16
|
+
h
|
17
|
+
end
|
18
|
+
|
19
|
+
# shutdown node
|
20
|
+
def stop
|
21
|
+
node.shutdown
|
22
|
+
end
|
23
|
+
|
24
|
+
# get block header information.
|
25
|
+
def getblockheader(hash, verbose)
|
26
|
+
entry = node.chain.find_entry_by_hash(hash)
|
27
|
+
if verbose
|
28
|
+
{
|
29
|
+
hash: hash,
|
30
|
+
height: entry.height,
|
31
|
+
version: entry.header.version,
|
32
|
+
versionHex: entry.header.version.to_s(16),
|
33
|
+
merkleroot: entry.header.merkle_root,
|
34
|
+
time: entry.header.time,
|
35
|
+
mediantime: node.chain.mtp(hash),
|
36
|
+
nonce: entry.header.nonce,
|
37
|
+
bits: entry.header.bits.to_s(16),
|
38
|
+
previousblockhash: entry.prev_hash,
|
39
|
+
nextblockhash: node.chain.next_hash(hash)
|
40
|
+
}
|
41
|
+
else
|
42
|
+
entry.header.to_payload.bth
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns connected peer information.
|
47
|
+
def getpeerinfo
|
48
|
+
node.pool.peers.map do |peer|
|
49
|
+
local_addr = peer.remote_version.remote_addr[0..peer.remote_version.remote_addr.rindex(':')] + '18333'
|
50
|
+
{
|
51
|
+
id: peer.id,
|
52
|
+
addr: "#{peer.host}:#{peer.port}",
|
53
|
+
addrlocal: local_addr,
|
54
|
+
services: peer.remote_version.services.to_s(16).rjust(16, '0'),
|
55
|
+
relaytxes: peer.remote_version.relay,
|
56
|
+
lastsend: peer.last_send,
|
57
|
+
lastrecv: peer.last_recv,
|
58
|
+
bytessent: peer.bytes_sent,
|
59
|
+
bytesrecv: peer.bytes_recv,
|
60
|
+
conntime: peer.conn_time,
|
61
|
+
pingtime: peer.ping_time,
|
62
|
+
minping: peer.min_ping,
|
63
|
+
version: peer.remote_version.version,
|
64
|
+
subver: peer.remote_version.user_agent,
|
65
|
+
inbound: !peer.outbound?,
|
66
|
+
startingheight: peer.remote_version.start_height,
|
67
|
+
best_hash: peer.best_hash,
|
68
|
+
best_height: peer.best_height
|
69
|
+
}
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# broadcast transaction
|
74
|
+
def sendrawtransaction(hex_tx)
|
75
|
+
tx = Bitcoin::Tx.parse_from_payload(hex_tx.htb)
|
76
|
+
# TODO check wether tx is valid
|
77
|
+
node.broadcast(tx)
|
78
|
+
tx.txid
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|
data/lib/bitcoin/rpc.rb
ADDED
@@ -0,0 +1,92 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
|
3
|
+
# utility for multisig
|
4
|
+
module Multisig
|
5
|
+
include Bitcoin::Opcodes
|
6
|
+
|
7
|
+
def self.prefix
|
8
|
+
[OP_0].pack("C*")
|
9
|
+
end
|
10
|
+
|
11
|
+
# generate input script sig spending a multisig output script.
|
12
|
+
# returns a raw binary script sig of the form:
|
13
|
+
# OP_0 <sig> [<sig> ...]
|
14
|
+
# @param [[String]] array of signatures
|
15
|
+
# @return [String] script_sig for multisig
|
16
|
+
def self.to_multisig_script_sig(*sigs)
|
17
|
+
hash_type = sigs.last.is_a?(Numeric) ? sigs.pop : SIGHASH_TYPE[:all]
|
18
|
+
sigs.reverse.inject(prefix) { |joined, sig| add_sig_to_multisig_script_sig(sig, joined, hash_type) }
|
19
|
+
end
|
20
|
+
|
21
|
+
# take a multisig script sig (or p2sh multisig script sig) and add
|
22
|
+
# another signature to it after the OP_0. Used to sign a tx by
|
23
|
+
# multiple parties. Signatures must be in the same order as the
|
24
|
+
# pubkeys in the output script being redeemed.
|
25
|
+
def self.add_sig_to_multisig_script_sig(sig_to_add, script_sig, hash_type = SIGHASH_TYPE[:all])
|
26
|
+
signature = sig_to_add + [hash_type].pack("C*")
|
27
|
+
offset = script_sig.empty? ? 0 : 1
|
28
|
+
script_sig.insert(offset, Bitcoin::Script.pack_pushdata(signature))
|
29
|
+
end
|
30
|
+
|
31
|
+
# generate input script sig spending a p2sh-multisig output script.
|
32
|
+
# returns a raw binary script sig of the form:
|
33
|
+
# OP_0 <sig> [<sig> ...] <redeem_script>
|
34
|
+
# @param [Script] redeem_script
|
35
|
+
# @param [[String]] array of signatures
|
36
|
+
# @return [String] script_sig for multisig
|
37
|
+
def self.to_p2sh_multisig_script_sig(redeem_script, *sigs)
|
38
|
+
to_multisig_script_sig(*sigs.flatten) + Bitcoin::Script.pack_pushdata(redeem_script)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Sort signatures in the given +script_sig+ according to the order of pubkeys in
|
42
|
+
# the redeem script. Also needs the +sig_hash+ to match signatures to pubkeys.
|
43
|
+
# @param [String] signature for multisig.
|
44
|
+
# @param [String] sig_hash to be signed.
|
45
|
+
# @return [String] sorted sig_hash.
|
46
|
+
def self.sort_p2sh_multisig_signatures(script_sig, sig_hash)
|
47
|
+
script = Bitcoin::Script.parse_from_payload(script_sig)
|
48
|
+
redeem_script = Bitcoin::Script.parse_from_payload(script.chunks[-1].pushed_data)
|
49
|
+
pubkeys = redeem_script.get_multisig_pubkeys
|
50
|
+
|
51
|
+
# find the pubkey for each signature by trying to verify it
|
52
|
+
sigs = Hash[script.chunks[1...-1].map.with_index do |sig, idx|
|
53
|
+
sig = sig.pushed_data
|
54
|
+
pubkey = pubkeys.map do |key|
|
55
|
+
Bitcoin::Key.new(pubkey: key.bth).verify(sig, sig_hash) ? key : nil
|
56
|
+
end.compact.first
|
57
|
+
raise "Key for signature ##{idx} not found in redeem script!" unless pubkey
|
58
|
+
[pubkey, sig]
|
59
|
+
end]
|
60
|
+
|
61
|
+
prefix + pubkeys.map { |k| sigs[k] ? Bitcoin::Script.pack_pushdata(sigs[k]) : nil }.join +
|
62
|
+
Bitcoin::Script.pack_pushdata(script.chunks[-1].pushed_data)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.add_sig_to_multisig_script_witness(sig_to_add, script_witness, hash_type = SIGHASH_TYPE[:all])
|
66
|
+
signature = sig_to_add + [hash_type].pack("C*")
|
67
|
+
script_witness.stack << signature
|
68
|
+
end
|
69
|
+
|
70
|
+
# Sort signatures in the given +script_witness+ according to the order of pubkeys in
|
71
|
+
# the redeem script. Also needs the +sig_hash+ to match signatures to pubkeys.
|
72
|
+
# @param [ScriptWitness] script_witness for multisig.
|
73
|
+
# @param [String] sig_hash to be signed.
|
74
|
+
def self.sort_witness_multisig_signatures(script_witness, sig_hash)
|
75
|
+
redeem_script = Bitcoin::Script.parse_from_payload(script_witness.stack[-1])
|
76
|
+
pubkeys = redeem_script.get_multisig_pubkeys
|
77
|
+
sigs = Hash[script_witness.stack[1...-1].map.with_index do |sig, idx|
|
78
|
+
pubkey = pubkeys.map do |key|
|
79
|
+
Bitcoin::Key.new(pubkey: key.bth).verify(sig, sig_hash) ? key : nil
|
80
|
+
end.compact.first
|
81
|
+
raise "Key for signature ##{idx} not found in redeem script!" unless pubkey
|
82
|
+
[pubkey, sig]
|
83
|
+
end]
|
84
|
+
script_witness.stack.clear
|
85
|
+
script_witness.stack << ''
|
86
|
+
pubkeys.each do |pubkey|
|
87
|
+
script_witness.stack << sigs[pubkey] if sigs[pubkey]
|
88
|
+
end
|
89
|
+
script_witness.stack << redeem_script.to_payload
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -35,6 +35,11 @@ module Bitcoin
|
|
35
35
|
Script.new << OP_HASH160 << to_hash160 << OP_EQUAL
|
36
36
|
end
|
37
37
|
|
38
|
+
def get_multisig_pubkeys
|
39
|
+
num = Bitcoin::Opcodes.opcode_to_small_int(chunks[-2].bth.to_i(16))
|
40
|
+
(1..num).map{ |i| chunks[i].pushed_data }
|
41
|
+
end
|
42
|
+
|
38
43
|
# generate m of n multisig script
|
39
44
|
# @param [String] m the number of signatures required for multisig
|
40
45
|
# @param [Array] pubkeys array of public keys that compose multisig
|
@@ -43,6 +48,7 @@ module Bitcoin
|
|
43
48
|
new << m << pubkeys << pubkeys.size << OP_CHECKMULTISIG
|
44
49
|
end
|
45
50
|
|
51
|
+
|
46
52
|
# generate p2wsh script for +redeem_script+
|
47
53
|
# @param [Script] redeem_script target redeem script
|
48
54
|
# @param [Script] p2wsh script
|
@@ -109,9 +115,8 @@ module Bitcoin
|
|
109
115
|
|
110
116
|
def to_addr
|
111
117
|
return p2pkh_addr if p2pkh?
|
112
|
-
return p2wpkh_addr if p2wpkh?
|
113
|
-
return p2wsh_addr if p2wsh?
|
114
118
|
return p2sh_addr if p2sh?
|
119
|
+
return bech32_addr if witness_program?
|
115
120
|
end
|
116
121
|
|
117
122
|
# check whether standard script.
|
@@ -258,11 +263,16 @@ module Bitcoin
|
|
258
263
|
|
259
264
|
def to_s
|
260
265
|
chunks.map { |c|
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
+
case c
|
267
|
+
when Integer
|
268
|
+
opcode_to_name(c)
|
269
|
+
when String
|
270
|
+
if c.pushdata?
|
271
|
+
v = Opcodes.opcode_to_small_int(c.ord)
|
272
|
+
v ? v : c.pushed_data.bth
|
273
|
+
else
|
274
|
+
Opcodes.opcode_to_name(c.ord)
|
275
|
+
end
|
266
276
|
end
|
267
277
|
}.join(' ')
|
268
278
|
end
|
@@ -589,7 +589,7 @@ module Bitcoin
|
|
589
589
|
|
590
590
|
def check_signature_encoding(sig)
|
591
591
|
return true if sig.size.zero?
|
592
|
-
if (flag?(SCRIPT_VERIFY_DERSIG) || flag?(SCRIPT_VERIFY_LOW_S) || flag?(SCRIPT_VERIFY_STRICTENC)) && !valid_signature_encoding?(sig)
|
592
|
+
if (flag?(SCRIPT_VERIFY_DERSIG) || flag?(SCRIPT_VERIFY_LOW_S) || flag?(SCRIPT_VERIFY_STRICTENC)) && !Key.valid_signature_encoding?(sig.htb)
|
593
593
|
return set_error(SCRIPT_ERR_SIG_DER)
|
594
594
|
elsif flag?(SCRIPT_VERIFY_LOW_S) && !low_der_signature?(sig)
|
595
595
|
return false
|
@@ -599,44 +599,8 @@ module Bitcoin
|
|
599
599
|
true
|
600
600
|
end
|
601
601
|
|
602
|
-
# check +sig+ (hex) is correct der encoding.
|
603
|
-
# This function is consensus-critical since BIP66.
|
604
|
-
def valid_signature_encoding?(signature)
|
605
|
-
sig = signature.htb
|
606
|
-
return false if sig.bytesize < 9 || sig.bytesize > 73 # Minimum and maximum size check
|
607
|
-
|
608
|
-
s = sig.unpack('C*')
|
609
|
-
|
610
|
-
return false if s[0] != 0x30 || s[1] != s.size - 3 # A signature is of type 0x30 (compound). Make sure the length covers the entire signature.
|
611
|
-
|
612
|
-
len_r = s[3]
|
613
|
-
return false if 5 + len_r >= s.size # Make sure the length of the S element is still inside the signature.
|
614
|
-
|
615
|
-
len_s = s[5 + len_r]
|
616
|
-
return false unless len_r + len_s + 7 == s.size #Verify that the length of the signature matches the sum of the length of the elements.
|
617
|
-
|
618
|
-
return false unless s[2] == 0x02 # Check whether the R element is an integer.
|
619
|
-
|
620
|
-
return false if len_r == 0 # Zero-length integers are not allowed for R.
|
621
|
-
|
622
|
-
return false unless s[4] & 0x80 == 0 # Negative numbers are not allowed for R.
|
623
|
-
|
624
|
-
# Null bytes at the start of R are not allowed, unless R would otherwise be interpreted as a negative number.
|
625
|
-
return false if len_r > 1 && (s[4] == 0x00) && (s[5] & 0x80 == 0)
|
626
|
-
|
627
|
-
return false unless s[len_r + 4] == 0x02 # Check whether the S element is an integer.
|
628
|
-
|
629
|
-
return false if len_s == 0 # Zero-length integers are not allowed for S.
|
630
|
-
return false unless (s[len_r + 6] & 0x80) == 0 # Negative numbers are not allowed for S.
|
631
|
-
|
632
|
-
# Null bytes at the start of S are not allowed, unless S would otherwise be interpreted as a negative number.
|
633
|
-
return false if len_s > 1 && (s[len_r + 6] == 0x00) && (s[len_r + 7] & 0x80 == 0)
|
634
|
-
|
635
|
-
true
|
636
|
-
end
|
637
|
-
|
638
602
|
def low_der_signature?(sig)
|
639
|
-
return set_error(SCRIPT_ERR_SIG_DER) unless valid_signature_encoding?(sig)
|
603
|
+
return set_error(SCRIPT_ERR_SIG_DER) unless Key.valid_signature_encoding?(sig.htb)
|
640
604
|
return set_error(SCRIPT_ERR_SIG_HIGH_S) unless Key.low_signature?(sig.htb)
|
641
605
|
true
|
642
606
|
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Store
|
3
|
+
|
4
|
+
# wrap a block header object with extra data.
|
5
|
+
class ChainEntry
|
6
|
+
|
7
|
+
attr_reader :header
|
8
|
+
attr_reader :height
|
9
|
+
|
10
|
+
# @param [Bitcoin::BlockHeader] header a block header.
|
11
|
+
# @param [Integer] height a block height.
|
12
|
+
def initialize(header, height)
|
13
|
+
@header = header
|
14
|
+
@height = height
|
15
|
+
end
|
16
|
+
|
17
|
+
# get database key
|
18
|
+
def key
|
19
|
+
Bitcoin::Store::KEY_PREFIX[:entry] + header.hash
|
20
|
+
end
|
21
|
+
|
22
|
+
# block hash
|
23
|
+
def hash
|
24
|
+
header.hash
|
25
|
+
end
|
26
|
+
|
27
|
+
# previous block hash
|
28
|
+
def prev_hash
|
29
|
+
header.prev_hash
|
30
|
+
end
|
31
|
+
|
32
|
+
# whether genesis block
|
33
|
+
def genesis?
|
34
|
+
Bitcoin.chain_params.genesis_block.header == header
|
35
|
+
end
|
36
|
+
|
37
|
+
# @param [String] payload a payload with binary format.
|
38
|
+
def self.parse_from_payload(payload)
|
39
|
+
buf = StringIO.new(payload)
|
40
|
+
len = Bitcoin.unpack_var_int_from_io(buf)
|
41
|
+
height = buf.read(len).reverse.bth.to_i(16)
|
42
|
+
new(Bitcoin::BlockHeader.parse_from_payload(buf.read(80)), height)
|
43
|
+
end
|
44
|
+
|
45
|
+
# build next block +StoredBlock+ instance.
|
46
|
+
# @param [Bitcoin::BlockHeader] next_block a next block candidate header.
|
47
|
+
# @return [Bitcoin::Store::ChainEntry] a next stored block (not saved).
|
48
|
+
def build_next_block(next_block)
|
49
|
+
ChainEntry.new(next_block, height + 1)
|
50
|
+
end
|
51
|
+
|
52
|
+
# generate payload
|
53
|
+
def to_payload
|
54
|
+
height_value = height.to_s(16)
|
55
|
+
height_value = '0' + height_value if height_value.length.odd?
|
56
|
+
height_value = height_value.htb.reverse
|
57
|
+
Bitcoin.pack_var_int(height_value.bytesize) + height_value + header.to_payload
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'leveldb'
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
module Store
|
5
|
+
module DB
|
6
|
+
|
7
|
+
class LevelDB
|
8
|
+
|
9
|
+
attr_reader :db
|
10
|
+
attr_reader :logger
|
11
|
+
|
12
|
+
def initialize(path = "#{Bitcoin.base_dir}/db/spv")
|
13
|
+
# @logger = Bitcoin::Logger.create(:debug)
|
14
|
+
FileUtils.mkdir_p(path)
|
15
|
+
@db = ::LevelDB::DB.new(path)
|
16
|
+
# logger.debug 'Opened LevelDB successfully.'
|
17
|
+
end
|
18
|
+
|
19
|
+
# put data into LevelDB.
|
20
|
+
# @param [Object] key a key.
|
21
|
+
# @param [Object] value a value.
|
22
|
+
def put(key, value)
|
23
|
+
# logger.debug "put #{key} data"
|
24
|
+
db.put(key, value)
|
25
|
+
end
|
26
|
+
|
27
|
+
# get value from specified key.
|
28
|
+
# @param [Object] key a key.
|
29
|
+
# @return[Object] the stored value.
|
30
|
+
def get(key)
|
31
|
+
db.get(key)
|
32
|
+
end
|
33
|
+
|
34
|
+
# get best block hash.
|
35
|
+
def best_hash
|
36
|
+
db.get(KEY_PREFIX[:best])
|
37
|
+
end
|
38
|
+
|
39
|
+
# delete specified key data.
|
40
|
+
def delete(key)
|
41
|
+
db.delete(key)
|
42
|
+
end
|
43
|
+
|
44
|
+
# get block hash specified +height+
|
45
|
+
def get_hash_from_height(height)
|
46
|
+
db.get(height_key(height))
|
47
|
+
end
|
48
|
+
|
49
|
+
# get next block hash specified +hash+
|
50
|
+
def next_hash(hash)
|
51
|
+
db.get(KEY_PREFIX[:next] + hash)
|
52
|
+
end
|
53
|
+
|
54
|
+
# get entry payload
|
55
|
+
# @param [String] hash the hash with hex format.
|
56
|
+
# @return [String] the ChainEntry payload.
|
57
|
+
def get_entry_payload_from_hash(hash)
|
58
|
+
db.get(KEY_PREFIX[:entry] + hash)
|
59
|
+
end
|
60
|
+
|
61
|
+
def save_entry(entry)
|
62
|
+
db.batch do
|
63
|
+
db.put(entry.key ,entry.to_payload)
|
64
|
+
db.put(height_key(entry.height), entry.hash)
|
65
|
+
connect_entry(entry)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def close
|
70
|
+
db.close
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
# generate height key
|
76
|
+
def height_key(height)
|
77
|
+
height = height.to_s(16)
|
78
|
+
height = '0' + height if height.bytesize.odd?
|
79
|
+
KEY_PREFIX[:height] + height.htb.reverse.bth
|
80
|
+
end
|
81
|
+
|
82
|
+
def connect_entry(entry)
|
83
|
+
unless entry.genesis?
|
84
|
+
tip_block = Bitcoin::Store::ChainEntry.parse_from_payload(get_entry_payload_from_hash(best_hash))
|
85
|
+
unless tip_block.hash == entry.prev_hash
|
86
|
+
raise "entry(#{entry.hash}) does not reference current best block hash(#{tip_block.hash})"
|
87
|
+
end
|
88
|
+
unless tip_block.height + 1 == entry.height
|
89
|
+
raise "block height is small than current best block."
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
db.put(KEY_PREFIX[:best], entry.hash)
|
95
|
+
db.put(KEY_PREFIX[:next] + entry.prev_hash, entry.hash)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
|
3
|
+
module Store
|
4
|
+
|
5
|
+
KEY_PREFIX = {
|
6
|
+
entry: 'e', # key: block hash, value: Bitcoin::Store::ChainEntry payload
|
7
|
+
height: 'h', # key: block height, value: block hash.
|
8
|
+
best: 'B', # value: best block hash.
|
9
|
+
next: 'n' # key: block hash, value: A hash of the next block of the specified hash
|
10
|
+
}
|
11
|
+
|
12
|
+
class SPVChain
|
13
|
+
|
14
|
+
attr_reader :db
|
15
|
+
attr_reader :logger
|
16
|
+
|
17
|
+
def initialize(db = Bitcoin::Store::DB::LevelDB.new)
|
18
|
+
@db = db # TODO multiple db switch
|
19
|
+
@logger = Bitcoin::Logger.create(:debug)
|
20
|
+
initialize_block
|
21
|
+
end
|
22
|
+
|
23
|
+
# get latest block in the store.
|
24
|
+
# @return[Bitcoin::Store::ChainEntry]
|
25
|
+
def latest_block
|
26
|
+
hash = db.best_hash
|
27
|
+
return nil unless hash
|
28
|
+
find_entry_by_hash(hash)
|
29
|
+
end
|
30
|
+
|
31
|
+
# find block entry with the specified height.
|
32
|
+
def find_entry_by_height(height)
|
33
|
+
find_entry_by_hash(db.get_hash_from_height(height))
|
34
|
+
end
|
35
|
+
|
36
|
+
# find block entry with the specified hash
|
37
|
+
def find_entry_by_hash(hash)
|
38
|
+
payload = db.get_entry_payload_from_hash(hash)
|
39
|
+
ChainEntry.parse_from_payload(payload)
|
40
|
+
end
|
41
|
+
|
42
|
+
# append block header to chain.
|
43
|
+
# @param [Bitcoin::BlockHeader] header a block header.
|
44
|
+
# @return [Bitcoin::Store::ChainEntry] appended block header entry.
|
45
|
+
def append_header(header)
|
46
|
+
logger.debug("append header #{header.hash}")
|
47
|
+
raise "this header is invalid. #{header.hash}" unless header.valid?
|
48
|
+
best_block = latest_block
|
49
|
+
current_height = best_block.height
|
50
|
+
if best_block.hash == header.prev_hash
|
51
|
+
entry = Bitcoin::Store::ChainEntry.new(header, current_height + 1)
|
52
|
+
db.save_entry(entry)
|
53
|
+
entry
|
54
|
+
else
|
55
|
+
# TODO implements recovery process
|
56
|
+
raise "header's previous hash(#{header.prev_hash}) does not match current best block's(#{best_block.hash})."
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# get next block hash for specified +hash+
|
61
|
+
# @param [String] hash the block hash
|
62
|
+
# @return [String] the next block hash. If it does not exist yet, return nil.
|
63
|
+
def next_hash(hash)
|
64
|
+
db.next_hash(hash)
|
65
|
+
end
|
66
|
+
|
67
|
+
# get median time past for specified block +hash+
|
68
|
+
# @param [String] hash the block hash.
|
69
|
+
# @return [Integer] the median time past value.
|
70
|
+
def mtp(hash)
|
71
|
+
time = []
|
72
|
+
Bitcoin::MEDIAN_TIME_SPAN.times do
|
73
|
+
entry = find_entry_by_hash(hash)
|
74
|
+
time << entry.header.time
|
75
|
+
hash = entry.header.prev_hash
|
76
|
+
end
|
77
|
+
time.sort!
|
78
|
+
time[time.size / 2]
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
# if database is empty, put genesis block.
|
84
|
+
def initialize_block
|
85
|
+
unless latest_block
|
86
|
+
block = Bitcoin.chain_params.genesis_block
|
87
|
+
genesis = ChainEntry.new(block.header, 0)
|
88
|
+
db.save_entry(genesis)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
data/lib/bitcoin/store.rb
CHANGED
@@ -1,7 +1,11 @@
|
|
1
|
+
require 'leveldb'
|
2
|
+
|
1
3
|
module Bitcoin
|
2
4
|
module Store
|
3
5
|
|
4
|
-
autoload :
|
6
|
+
autoload :DB, 'bitcoin/store/db'
|
7
|
+
autoload :SPVChain, 'bitcoin/store/spv_chain'
|
8
|
+
autoload :ChainEntry, 'bitcoin/store/chain_entry'
|
5
9
|
|
6
10
|
end
|
7
11
|
end
|
data/lib/bitcoin/tx.rb
CHANGED
@@ -35,8 +35,12 @@ module Bitcoin
|
|
35
35
|
if in_count.zero?
|
36
36
|
tx.marker = 0
|
37
37
|
tx.flag = buf.read(1).unpack('c').first
|
38
|
-
|
39
|
-
|
38
|
+
if tx.flag.zero?
|
39
|
+
buf.pos -= 1
|
40
|
+
else
|
41
|
+
in_count = Bitcoin.unpack_var_int_from_io(buf)
|
42
|
+
witness = true
|
43
|
+
end
|
40
44
|
end
|
41
45
|
|
42
46
|
in_count.times do
|
data/lib/bitcoin/version.rb
CHANGED
@@ -0,0 +1,82 @@
|
|
1
|
+
module Bitcoin
|
2
|
+
module Wallet
|
3
|
+
|
4
|
+
# the account in BIP-44
|
5
|
+
class Account
|
6
|
+
|
7
|
+
PURPOSE_TYPE = {legacy: 44, nested_witness: 49}
|
8
|
+
|
9
|
+
attr_reader :purpose # either 44 or 49
|
10
|
+
attr_reader :index # BIP-44 index
|
11
|
+
attr_reader :name # account name
|
12
|
+
attr_accessor :receive_depth # receive address depth
|
13
|
+
attr_accessor :change_depth # change address depth
|
14
|
+
attr_accessor :lookahead
|
15
|
+
attr_accessor :wallet
|
16
|
+
|
17
|
+
def initialize(purpose = PURPOSE_TYPE[:legacy], index = 0, name = '')
|
18
|
+
@purpose = purpose
|
19
|
+
@index = index
|
20
|
+
@name = name
|
21
|
+
@receive_depth = 0
|
22
|
+
@change_depth = 0
|
23
|
+
@lookahead = 10
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.parse_from_payload(payload)
|
27
|
+
name, payload = Bitcoin.unpack_var_string(payload)
|
28
|
+
name = name.force_encoding('utf-8')
|
29
|
+
purpose, index, receive_depth, change_depth, lookahead = payload.unpack('I*')
|
30
|
+
a = Account.new(purpose, index, name)
|
31
|
+
a.receive_depth = receive_depth
|
32
|
+
a.change_depth = change_depth
|
33
|
+
a.lookahead = lookahead
|
34
|
+
a
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_payload
|
38
|
+
payload = Bitcoin.pack_var_string(name.unpack('H*').first.htb)
|
39
|
+
payload << [purpose, index, receive_depth, change_depth, lookahead].pack('I*')
|
40
|
+
payload
|
41
|
+
end
|
42
|
+
|
43
|
+
# whether support witness
|
44
|
+
def witness?
|
45
|
+
purpose == PURPOSE_TYPE[:nested_witness]
|
46
|
+
end
|
47
|
+
|
48
|
+
def init
|
49
|
+
lookahead.times do |index|
|
50
|
+
derive_receive(index)
|
51
|
+
derive_change(index)
|
52
|
+
end
|
53
|
+
@index = wallet.accounts.size
|
54
|
+
save
|
55
|
+
end
|
56
|
+
|
57
|
+
# derive receive key
|
58
|
+
def derive_receive(index)
|
59
|
+
derive(0, index)
|
60
|
+
end
|
61
|
+
|
62
|
+
# derive change key
|
63
|
+
def derive_change(index)
|
64
|
+
derive(1, index)
|
65
|
+
end
|
66
|
+
|
67
|
+
# save this account payload to database.
|
68
|
+
def save
|
69
|
+
wallet.db.save_account(self)
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# derive key
|
75
|
+
def derive(branch, index)
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
end
|