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
@@ -6,7 +6,8 @@ module Bitcoin
|
|
6
6
|
|
7
7
|
# handle p2p message.
|
8
8
|
def handle(message)
|
9
|
-
|
9
|
+
peer.last_recv = Time.now.to_i
|
10
|
+
peer.bytes_recv += message.bytesize
|
10
11
|
begin
|
11
12
|
parse(message)
|
12
13
|
rescue Bitcoin::Message::Error => e
|
@@ -20,7 +21,7 @@ module Bitcoin
|
|
20
21
|
command, payload, rest = parse_header
|
21
22
|
return unless command
|
22
23
|
|
23
|
-
|
24
|
+
defer_handle_command(command, payload)
|
24
25
|
@message = ""
|
25
26
|
parse(rest) if rest && rest.bytesize > 0
|
26
27
|
end
|
@@ -40,8 +41,20 @@ module Bitcoin
|
|
40
41
|
[command, payload, rest]
|
41
42
|
end
|
42
43
|
|
44
|
+
# handle command with EM#defer
|
45
|
+
def defer_handle_command(command, payload)
|
46
|
+
operation = proc {handle_command(command, payload)}
|
47
|
+
callback = proc{|result|}
|
48
|
+
errback = proc{|e|
|
49
|
+
logger.error("error occurred. #{e.message}")
|
50
|
+
logger.error(e.backtrace)
|
51
|
+
peer.handle_error(e)
|
52
|
+
}
|
53
|
+
EM.defer(operation, callback, errback)
|
54
|
+
end
|
55
|
+
|
43
56
|
def handle_command(command, payload)
|
44
|
-
logger.info("[#{addr}] process command #{command}.
|
57
|
+
logger.info("[#{addr}] process command #{command}.")
|
45
58
|
case command
|
46
59
|
when Bitcoin::Message::Version::COMMAND
|
47
60
|
on_version(Bitcoin::Message::Version.parse_from_payload(payload))
|
@@ -77,6 +90,8 @@ module Bitcoin
|
|
77
90
|
on_send_cmpct(Bitcoin::Message::SendCmpct.parse_from_payload(payload))
|
78
91
|
when Bitcoin::Message::Inv::COMMAND
|
79
92
|
on_inv(Bitcoin::Message::Inv.parse_from_payload(payload))
|
93
|
+
when Bitcoin::Message::MerkleBlock::COMMAND
|
94
|
+
on_merkle_block(Bitcoin::Message::MerkleBlock.parse_from_payload(payload))
|
80
95
|
else
|
81
96
|
logger.warn("unsupported command received. #{command}")
|
82
97
|
close("with command #{command}")
|
@@ -85,14 +100,10 @@ module Bitcoin
|
|
85
100
|
|
86
101
|
def send_message(msg)
|
87
102
|
logger.info "send message #{msg.class::COMMAND}"
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
def begin_handshake
|
93
|
-
logger.info "begin handshake with #{addr}"
|
94
|
-
ver = Bitcoin::Message::Version.new(remote_addr: addr, start_height: 0) # TODO use start_height in wallet
|
95
|
-
send_message(ver)
|
103
|
+
pkt = msg.to_pkt
|
104
|
+
peer.last_send = Time.now.to_i
|
105
|
+
peer.bytes_sent = pkt.bytesize
|
106
|
+
send_data(pkt)
|
96
107
|
end
|
97
108
|
|
98
109
|
def handshake_done
|
@@ -114,6 +125,7 @@ module Bitcoin
|
|
114
125
|
|
115
126
|
def on_get_addr
|
116
127
|
logger.info('receive getaddr message.')
|
128
|
+
peer.send_addrs
|
117
129
|
end
|
118
130
|
|
119
131
|
def on_addr(addr)
|
@@ -138,26 +150,31 @@ module Bitcoin
|
|
138
150
|
|
139
151
|
def on_pong(pong)
|
140
152
|
logger.info("receive pong message. #{pong.build_json}")
|
141
|
-
|
153
|
+
if pong.nonce == peer.last_ping_nonce
|
154
|
+
peer.last_ping_nonce = nil
|
155
|
+
peer.last_pong = Time.now.to_i
|
156
|
+
else
|
157
|
+
logger.debug "The remote peer sent the wrong nonce (#{pong.nonce})."
|
158
|
+
end
|
142
159
|
end
|
143
160
|
|
144
161
|
def on_get_headers(headers)
|
145
|
-
logger.info(
|
162
|
+
logger.info('receive getheaders message.')
|
146
163
|
# TODO
|
147
164
|
end
|
148
165
|
|
149
166
|
def on_headers(headers)
|
150
|
-
logger.info(
|
151
|
-
|
167
|
+
logger.info('receive headers message.')
|
168
|
+
peer.handle_headers(headers)
|
152
169
|
end
|
153
170
|
|
154
171
|
def on_block(block)
|
155
|
-
logger.info(
|
172
|
+
logger.info('receive block message.')
|
156
173
|
# TODO
|
157
174
|
end
|
158
175
|
|
159
176
|
def on_tx(tx)
|
160
|
-
logger.info(
|
177
|
+
logger.info('receive tx message.')
|
161
178
|
# TODO
|
162
179
|
end
|
163
180
|
|
@@ -182,10 +199,26 @@ module Bitcoin
|
|
182
199
|
end
|
183
200
|
|
184
201
|
def on_inv(inv)
|
185
|
-
logger.info(
|
186
|
-
|
202
|
+
logger.info('receive inv message.')
|
203
|
+
blocks = []
|
204
|
+
txs = []
|
205
|
+
inv.inventories.each do |i|
|
206
|
+
case i.identifier
|
207
|
+
when Bitcoin::Message::Inventory::MSG_TX
|
208
|
+
txs << i.hash
|
209
|
+
when Bitcoin::Message::Inventory::MSG_BLOCK
|
210
|
+
blocks << i.hash
|
211
|
+
else
|
212
|
+
logger.warn("[#{addr}] peer sent unknown inv type: #{i.identifier}")
|
213
|
+
end
|
214
|
+
end
|
215
|
+
logger.info("receive block= #{blocks.size}, txs: #{txs.size}")
|
216
|
+
peer.handle_block_inv(blocks) unless blocks.empty?
|
187
217
|
end
|
188
218
|
|
219
|
+
def on_merkle_block(merkle_block)
|
220
|
+
logger.info("receive merkle block message. #{merkle_block.build_json}")
|
221
|
+
end
|
189
222
|
|
190
223
|
end
|
191
224
|
end
|
data/lib/bitcoin/network/peer.rb
CHANGED
@@ -4,21 +4,52 @@ module Bitcoin
|
|
4
4
|
# remote peer class.
|
5
5
|
class Peer
|
6
6
|
|
7
|
+
# Interval for pinging peers.
|
8
|
+
PING_INTERVAL = 30
|
9
|
+
|
10
|
+
attr_reader :logger
|
11
|
+
attr_accessor :id
|
12
|
+
attr_accessor :local_version
|
13
|
+
attr_accessor :last_send
|
14
|
+
attr_accessor :last_recv
|
15
|
+
attr_accessor :bytes_sent
|
16
|
+
attr_accessor :bytes_recv
|
17
|
+
attr_accessor :conn_time
|
18
|
+
attr_accessor :last_ping
|
19
|
+
attr_accessor :last_ping_nonce
|
20
|
+
attr_accessor :last_pong
|
21
|
+
attr_accessor :min_ping
|
22
|
+
attr_accessor :outbound # TODO need implements to accept inbound connection
|
23
|
+
attr_accessor :best_hash
|
24
|
+
attr_accessor :best_height
|
7
25
|
# remote peer info
|
8
26
|
attr_reader :host
|
9
27
|
attr_reader :port
|
10
28
|
# remote peer connection
|
11
29
|
attr_accessor :conn
|
12
30
|
attr_accessor :connected
|
31
|
+
attr_accessor :primary
|
13
32
|
# parent pool
|
14
33
|
attr_reader :pool
|
34
|
+
attr_reader :chain
|
15
35
|
attr_accessor :fee_rate
|
16
36
|
|
17
37
|
def initialize(host, port, pool)
|
18
38
|
@host = host
|
19
39
|
@port = port
|
20
40
|
@pool = pool
|
41
|
+
@chain = pool.chain
|
21
42
|
@connected = false
|
43
|
+
@primary = false
|
44
|
+
@logger = Bitcoin::Logger.create(:debug)
|
45
|
+
@outbound = true
|
46
|
+
@best_hash = -1
|
47
|
+
@best_height = -1
|
48
|
+
@min_ping = -1
|
49
|
+
@bytes_sent = 0
|
50
|
+
@bytes_recv = 0
|
51
|
+
current_height = @chain.latest_block.height
|
52
|
+
@local_version = Bitcoin::Message::Version.new(remote_addr: addr, start_height: current_height)
|
22
53
|
end
|
23
54
|
|
24
55
|
def connect
|
@@ -29,24 +60,130 @@ module Bitcoin
|
|
29
60
|
@connected
|
30
61
|
end
|
31
62
|
|
63
|
+
def outbound?
|
64
|
+
@outbound
|
65
|
+
end
|
66
|
+
|
32
67
|
def addr
|
33
68
|
"#{host}:#{port}"
|
34
69
|
end
|
35
70
|
|
71
|
+
# calculate ping-pong time.
|
72
|
+
def ping_time
|
73
|
+
last_pong ? (last_pong - last_ping) / 1e6 : -1
|
74
|
+
end
|
75
|
+
|
76
|
+
# set last pong
|
77
|
+
def last_pong=(time)
|
78
|
+
@last_pong = time
|
79
|
+
@min_ping = ping_time if min_ping == -1 || ping_time < min_ping
|
80
|
+
end
|
81
|
+
|
36
82
|
def post_handshake
|
37
83
|
@connected = true
|
38
84
|
pool.handle_new_peer(self)
|
85
|
+
# require remote peer to use headers message instead fo inv message.
|
86
|
+
conn.send_message(Bitcoin::Message::SendHeaders.new)
|
87
|
+
EM.add_periodic_timer(PING_INTERVAL) {send_ping}
|
88
|
+
end
|
89
|
+
|
90
|
+
# start block header download
|
91
|
+
def start_block_header_download
|
92
|
+
logger.info("[#{addr}] start block header download.")
|
93
|
+
get_headers = Bitcoin::Message::GetHeaders.new(
|
94
|
+
Bitcoin.chain_params.protocol_version, [chain.latest_block.hash])
|
95
|
+
conn.send_message(get_headers)
|
39
96
|
end
|
40
97
|
|
41
98
|
# broadcast tx.
|
42
99
|
def broadcast_tx(tx)
|
43
|
-
send_message(Bitcoin::Message::Tx.new(tx, ))
|
100
|
+
conn.send_message(Bitcoin::Message::Tx.new(tx, support_witness?))
|
101
|
+
end
|
102
|
+
|
103
|
+
# check the remote peer support witness.
|
104
|
+
def support_witness?
|
105
|
+
return false unless remote_version
|
106
|
+
remote_version.services & Bitcoin::Message::SERVICE_FLAGS[:witness] > 0
|
107
|
+
end
|
108
|
+
|
109
|
+
# check the remote peer supports compact block.
|
110
|
+
def support_cmpct?
|
111
|
+
return false if remote_version.version < Bitcoin::Message::VERSION[:compact]
|
112
|
+
return true unless local_version.services & Bitcoin::Message::SERVICE_FLAGS[:witness] > 0
|
113
|
+
return false unless support_witness?
|
114
|
+
remote_version.version >= Bitcoin::Message::VERSION[:compact_witness]
|
115
|
+
end
|
116
|
+
|
117
|
+
# get peer's block type.
|
118
|
+
def block_type
|
119
|
+
Bitcoin::Message::Inventory::MSG_FILTERED_BLOCK # TODO need other implementation
|
120
|
+
end
|
121
|
+
|
122
|
+
# get remote peer's version message.
|
123
|
+
# @return [Bitcoin::Message::Version]
|
124
|
+
def remote_version
|
125
|
+
conn.version
|
126
|
+
end
|
127
|
+
|
128
|
+
# Whether to try and download blocks and transactions from this peer.
|
129
|
+
def primary?
|
130
|
+
primary
|
131
|
+
end
|
132
|
+
|
133
|
+
# handle headers message
|
134
|
+
# @params [Bitcoin::Message::Headers]
|
135
|
+
def handle_headers(headers)
|
136
|
+
headers.headers.each do |header|
|
137
|
+
break unless header.valid?
|
138
|
+
entry = chain.append_header(header)
|
139
|
+
@best_hash = entry.hash
|
140
|
+
@best_height = entry.height
|
141
|
+
end
|
142
|
+
start_block_header_download if headers.headers.size > 0 # next header download
|
143
|
+
end
|
144
|
+
|
145
|
+
# handle error
|
146
|
+
def handle_error(e)
|
147
|
+
pool.handle_error(e)
|
148
|
+
end
|
149
|
+
|
150
|
+
# close peer connection.
|
151
|
+
def close(msg = '')
|
152
|
+
conn.close(msg)
|
153
|
+
end
|
154
|
+
|
155
|
+
# generate Bitcoin::Message::NetworkAddr object from this peer info.
|
156
|
+
# @return [Bitcoin::Message::NetworkAddr]
|
157
|
+
def to_network_addr
|
158
|
+
v = remote_version
|
159
|
+
addr = Bitcoin::Message::NetworkAddr.new
|
160
|
+
addr.time = v.timestamp
|
161
|
+
addr.services = v.services
|
162
|
+
addr.ip = host
|
163
|
+
addr.port = port
|
164
|
+
addr
|
165
|
+
end
|
166
|
+
|
167
|
+
# send +addr+ message to remote peer
|
168
|
+
def send_addrs
|
169
|
+
addrs = pool.peers.select{|p|p != self}.map(&:to_network_addr)
|
170
|
+
conn.send_message(Bitcoin::Message::Addr.new(addrs))
|
171
|
+
end
|
172
|
+
|
173
|
+
# handle block inv message.
|
174
|
+
def handle_block_inv(hashes)
|
175
|
+
getdata = Bitcoin::Message::GetData.new(
|
176
|
+
hashes.map{|h|Bitcoin::Message::Inventory.new(block_type, h)})
|
177
|
+
conn.send_message(getdata)
|
44
178
|
end
|
45
179
|
|
46
|
-
#
|
47
|
-
def
|
48
|
-
|
49
|
-
|
180
|
+
# send ping message.
|
181
|
+
def send_ping
|
182
|
+
ping = Bitcoin::Message::Ping.new
|
183
|
+
@last_ping = Time.now.to_i
|
184
|
+
@last_pong = -1
|
185
|
+
@last_ping_nonce = ping.nonce
|
186
|
+
conn.send_message(ping)
|
50
187
|
end
|
51
188
|
|
52
189
|
end
|
@@ -3,32 +3,40 @@ module Bitcoin
|
|
3
3
|
|
4
4
|
class PeerDiscovery
|
5
5
|
|
6
|
-
attr_reader :logger
|
6
|
+
attr_reader :logger, :configuration
|
7
7
|
|
8
|
-
def initialize
|
8
|
+
def initialize(configuration)
|
9
9
|
@logger = Bitcoin::Logger.create(:debug)
|
10
|
+
@configuration = configuration
|
10
11
|
end
|
11
12
|
|
12
13
|
# get peer addresses, from DNS seeds.
|
13
14
|
def peers
|
14
15
|
# TODO add find from previous connected peer at first.
|
15
|
-
find_from_dns_seeds
|
16
|
+
(find_from_dns_seeds + seeds).uniq
|
16
17
|
end
|
17
18
|
|
18
19
|
private
|
19
20
|
|
21
|
+
def dns_seeds
|
22
|
+
Bitcoin.chain_params.dns_seeds || []
|
23
|
+
end
|
24
|
+
|
25
|
+
def seeds
|
26
|
+
[*configuration.conf[:connect]]
|
27
|
+
end
|
28
|
+
|
20
29
|
def find_from_dns_seeds
|
21
30
|
logger.debug 'discover peer address from DNS seeds.'
|
22
|
-
|
31
|
+
dns_seeds.map { |seed|
|
23
32
|
begin
|
24
33
|
Socket.getaddrinfo(seed, Bitcoin.chain_params.default_port).map{|a|a[2]}.uniq
|
25
34
|
rescue SocketError => e
|
26
35
|
logger.error "SocketError occurred when load DNS seed: #{seed}, error: #{e.message}"
|
36
|
+
nil
|
27
37
|
end
|
28
|
-
}.flatten
|
38
|
+
}.flatten.compact
|
29
39
|
end
|
30
|
-
|
31
40
|
end
|
32
|
-
|
33
41
|
end
|
34
|
-
end
|
42
|
+
end
|
data/lib/bitcoin/network/pool.rb
CHANGED
@@ -20,13 +20,13 @@ module Bitcoin
|
|
20
20
|
attr_reader :peer_discovery
|
21
21
|
attr_accessor :started
|
22
22
|
|
23
|
-
def initialize
|
23
|
+
def initialize(chain, configuration)
|
24
24
|
@peers = []
|
25
25
|
@pending_peers = []
|
26
26
|
@max_outbound = MAX_OUTBOUND_CONNECTIONS
|
27
|
-
@chain =
|
27
|
+
@chain = chain
|
28
28
|
@logger = Bitcoin::Logger.create(:debug)
|
29
|
-
@peer_discovery = PeerDiscovery.new
|
29
|
+
@peer_discovery = PeerDiscovery.new(configuration)
|
30
30
|
@started = false
|
31
31
|
end
|
32
32
|
|
@@ -35,14 +35,14 @@ module Bitcoin
|
|
35
35
|
raise 'Cannot start a peer pool twice.' if started
|
36
36
|
logger.debug 'Start connecting other pears.'
|
37
37
|
addr_list = peer_discovery.peers
|
38
|
+
|
38
39
|
port = Bitcoin.chain_params.default_port
|
39
|
-
|
40
|
-
if
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
end
|
40
|
+
EM::Iterator.new(addr_list, Bitcoin::PARALLEL_THREAD).each do |ip, iter|
|
41
|
+
if pending_peers.size < MAX_OUTBOUND_CONNECTIONS
|
42
|
+
peer = Peer.new(ip, port, self)
|
43
|
+
pending_peers << peer
|
44
|
+
peer.connect
|
45
|
+
iter.next
|
46
46
|
end
|
47
47
|
end
|
48
48
|
@started = true
|
@@ -50,25 +50,50 @@ module Bitcoin
|
|
50
50
|
|
51
51
|
# detect new peer connection.
|
52
52
|
def handle_new_peer(peer)
|
53
|
-
logger.debug "connected new peer #{peer.addr}"
|
53
|
+
logger.debug "connected new peer #{peer.addr}."
|
54
|
+
peer.id = allocate_peer_id
|
55
|
+
unless peers.find(&:primary?)
|
56
|
+
peer.primary = true
|
57
|
+
peer.start_block_header_download
|
58
|
+
end
|
54
59
|
peers << peer
|
55
60
|
pending_peers.delete(peer)
|
56
61
|
end
|
57
62
|
|
58
63
|
# terminate peers.
|
59
64
|
def terminate
|
60
|
-
peers.each {|peer| peer.close('terminate')}
|
65
|
+
peers.each { |peer| peer.close('terminate') }
|
66
|
+
pending_peers.each { |peer| peer.close('terminate') }
|
61
67
|
@peers = []
|
62
68
|
@started = false
|
63
69
|
end
|
64
70
|
|
71
|
+
# broadcast tx to connecting peer.
|
65
72
|
def broadcast(tx)
|
66
|
-
peers.each {|peer| peer.
|
73
|
+
peers.each { |peer| peer.broadcast_tx(tx) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def handle_error(e)
|
77
|
+
terminate
|
67
78
|
end
|
68
79
|
|
69
80
|
private
|
70
81
|
|
82
|
+
# get primary peer
|
83
|
+
def primary_peer
|
84
|
+
peers.find(&:primary?)
|
85
|
+
end
|
86
|
+
|
87
|
+
# allocate new peer id
|
88
|
+
def allocate_peer_id
|
89
|
+
id = 0
|
90
|
+
until peers.empty? || peers.find{|p|p.id == id}.nil?
|
91
|
+
id += 1
|
92
|
+
end
|
93
|
+
id
|
94
|
+
end
|
95
|
+
|
71
96
|
end
|
72
97
|
|
73
98
|
end
|
74
|
-
end
|
99
|
+
end
|
data/lib/bitcoin/network.rb
CHANGED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'rest-client'
|
2
|
+
require 'thor'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Bitcoin
|
6
|
+
module Node
|
7
|
+
|
8
|
+
class CLI < Thor
|
9
|
+
|
10
|
+
class_option :network, aliases: '-n', default: :mainnet
|
11
|
+
|
12
|
+
desc 'getblockchaininfo', 'Returns an object containing various state info regarding blockchain processing.'
|
13
|
+
def getblockchaininfo
|
14
|
+
request('getblockchaininfo')
|
15
|
+
end
|
16
|
+
|
17
|
+
desc 'stop', 'Stop Bitcoin server.'
|
18
|
+
def stop
|
19
|
+
request('stop')
|
20
|
+
end
|
21
|
+
|
22
|
+
desc 'getblockheader "hash" ( verbose )', 'If verbose is false, returns a string that is serialized, hex-encoded data for blockheader "hash". If verbose is true, returns an Object with information about blockheader <hash>.'
|
23
|
+
def getblockheader(hash, verbose = true)
|
24
|
+
verbose = verbose.is_a?(String) ? (verbose == 'true') : verbose
|
25
|
+
request('getblockheader', hash, verbose)
|
26
|
+
end
|
27
|
+
|
28
|
+
desc 'getpeerinfo', 'Returns data about each connected network node as a json array of objects.'
|
29
|
+
def getpeerinfo
|
30
|
+
request('getpeerinfo')
|
31
|
+
end
|
32
|
+
|
33
|
+
desc 'sendrawtransaction', 'Submits raw transaction (serialized, hex-encoded) to local node and network.'
|
34
|
+
def sendrawtransaction(hex_tx)
|
35
|
+
request('sendrawtransaction', hex_tx)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def config
|
41
|
+
opts = {}
|
42
|
+
opts[:network] = options['network'] if options['network']
|
43
|
+
@conf ||= Bitcoin::Node::Configuration.new(opts)
|
44
|
+
end
|
45
|
+
|
46
|
+
def request(command, *params)
|
47
|
+
data = {
|
48
|
+
:method => command,
|
49
|
+
:params => params,
|
50
|
+
:id => 'jsonrpc'
|
51
|
+
}
|
52
|
+
begin
|
53
|
+
RestClient::Request.execute(method: :post, url: config.server_url, payload: data.to_json,
|
54
|
+
headers: {content_type: :json}) do |response, request, result|
|
55
|
+
return false if !result.kind_of?(Net::HTTPSuccess) && response.empty?
|
56
|
+
json = JSON.parse(response.to_str)
|
57
|
+
puts JSON.pretty_generate(json)
|
58
|
+
end
|
59
|
+
rescue Exception => e
|
60
|
+
puts e.message
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'iniparse'
|
2
|
+
|
3
|
+
module Bitcoin
|
4
|
+
module Node
|
5
|
+
class Configuration
|
6
|
+
|
7
|
+
attr_reader :conf
|
8
|
+
|
9
|
+
def initialize(opts = {})
|
10
|
+
# TODO apply configuration file.
|
11
|
+
opts[:network] = :mainnet unless opts[:network]
|
12
|
+
Bitcoin.chain_params = opts[:network]
|
13
|
+
|
14
|
+
begin
|
15
|
+
ini_file = IniParse.parse(File.read("#{Bitcoin.base_dir}/bitcoinrb.conf"))
|
16
|
+
@conf = Hash[ ini_file.to_h['__anonymous__'].map{|k,v| [k.to_sym, v] } ]
|
17
|
+
rescue => e
|
18
|
+
@conf = {}
|
19
|
+
end
|
20
|
+
@conf.merge!(opts)
|
21
|
+
end
|
22
|
+
|
23
|
+
def host
|
24
|
+
'localhost'
|
25
|
+
end
|
26
|
+
|
27
|
+
def port
|
28
|
+
Bitcoin.chain_params.default_port - 1
|
29
|
+
end
|
30
|
+
|
31
|
+
def server_url
|
32
|
+
"http://#{host}:#{port}"
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/bitcoin/node/spv.rb
CHANGED
@@ -4,21 +4,31 @@ module Bitcoin
|
|
4
4
|
# SPV class
|
5
5
|
class SPV
|
6
6
|
|
7
|
+
attr_reader :chain
|
7
8
|
attr_reader :pool
|
8
9
|
attr_reader :logger
|
9
10
|
attr_accessor :running
|
11
|
+
attr_reader :configuration
|
12
|
+
attr_accessor :server
|
10
13
|
|
11
|
-
def initialize
|
12
|
-
@
|
14
|
+
def initialize(configuration)
|
15
|
+
@chain = Bitcoin::Store::SPVChain.new
|
16
|
+
@configuration = configuration
|
17
|
+
@pool = Bitcoin::Network::Pool.new(@chain, @configuration)
|
13
18
|
@logger = Bitcoin::Logger.create(:debug)
|
14
19
|
@running = false
|
15
20
|
end
|
16
21
|
|
17
22
|
# open the node.
|
18
23
|
def run
|
24
|
+
# TODO need process running check.
|
19
25
|
return if running
|
20
26
|
logger.debug 'SPV node start running.'
|
21
|
-
|
27
|
+
EM.run do
|
28
|
+
# EM.start_server('0.0.0.0', Bitcoin.chain_params.default_port, Bitcoin::Network::InboundConnector, self)
|
29
|
+
pool.start
|
30
|
+
@server = Bitcoin::RPC::HttpServer.run(self, configuration.port)
|
31
|
+
end
|
22
32
|
end
|
23
33
|
|
24
34
|
# close the node.
|
data/lib/bitcoin/node.rb
CHANGED
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'evma_httpserver'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Bitcoin
|
5
|
+
module RPC
|
6
|
+
|
7
|
+
# Bitcoinrb RPC server.
|
8
|
+
class HttpServer < EM::Connection
|
9
|
+
include EM::HttpServer
|
10
|
+
include RequestHandler
|
11
|
+
|
12
|
+
attr_reader :node
|
13
|
+
attr_accessor :logger
|
14
|
+
|
15
|
+
def initialize(node)
|
16
|
+
@node = node
|
17
|
+
@logger = Bitcoin::Logger.create(:debug)
|
18
|
+
end
|
19
|
+
|
20
|
+
def post_init
|
21
|
+
super
|
22
|
+
logger.debug 'start http server.'
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.run(node, port = 8332)
|
26
|
+
EM.start_server('0.0.0.0', port, HttpServer, node)
|
27
|
+
end
|
28
|
+
|
29
|
+
# process http request.
|
30
|
+
def process_http_request
|
31
|
+
operation = proc {
|
32
|
+
command, args = parse_json_params
|
33
|
+
logger.debug("process http request. command = #{command}")
|
34
|
+
send(command, *args).to_json
|
35
|
+
}
|
36
|
+
callback = proc{ |result|
|
37
|
+
response = EM::DelegatedHttpResponse.new(self)
|
38
|
+
response.status = 200
|
39
|
+
response.content_type 'application/json'
|
40
|
+
response.content = result
|
41
|
+
response.send_response
|
42
|
+
}
|
43
|
+
EM.defer(operation, callback)
|
44
|
+
end
|
45
|
+
|
46
|
+
# parse request parameter.
|
47
|
+
# @return [Array] the array of command and args
|
48
|
+
def parse_json_params
|
49
|
+
params = JSON.parse(@http_post_content)
|
50
|
+
[params['method'], params['params']]
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|