bitcoinrb 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
|