tapyrus 0.2.7 → 0.2.12

Sign up to get free protection for your applications and to get access to all the features.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +37 -0
  3. data/.prettierignore +3 -0
  4. data/.prettierrc.yaml +3 -0
  5. data/.ruby-version +1 -1
  6. data/CODE_OF_CONDUCT.md +7 -7
  7. data/README.md +14 -17
  8. data/Rakefile +3 -3
  9. data/lib/openassets/marker_output.rb +0 -4
  10. data/lib/openassets/payload.rb +4 -10
  11. data/lib/openassets.rb +0 -2
  12. data/lib/schnorr/sign_to_contract.rb +51 -0
  13. data/lib/schnorr/signature.rb +3 -6
  14. data/lib/schnorr.rb +14 -9
  15. data/lib/tapyrus/base58.rb +7 -6
  16. data/lib/tapyrus/bip175.rb +78 -0
  17. data/lib/tapyrus/block.rb +1 -2
  18. data/lib/tapyrus/block_header.rb +15 -9
  19. data/lib/tapyrus/bloom_filter.rb +5 -3
  20. data/lib/tapyrus/chain_params.rb +1 -4
  21. data/lib/tapyrus/chainparams/dev.yml +3 -2
  22. data/lib/tapyrus/chainparams/prod.yml +3 -2
  23. data/lib/tapyrus/constants.rb +29 -23
  24. data/lib/tapyrus/errors.rb +1 -3
  25. data/lib/tapyrus/ext/ecdsa.rb +4 -4
  26. data/lib/tapyrus/ext/json_parser.rb +1 -4
  27. data/lib/tapyrus/ext.rb +1 -1
  28. data/lib/tapyrus/ext_key.rb +44 -32
  29. data/lib/tapyrus/key.rb +31 -35
  30. data/lib/tapyrus/key_path.rb +15 -12
  31. data/lib/tapyrus/logger.rb +20 -16
  32. data/lib/tapyrus/merkle_tree.rb +22 -20
  33. data/lib/tapyrus/message/addr.rb +1 -7
  34. data/lib/tapyrus/message/base.rb +0 -3
  35. data/lib/tapyrus/message/block.rb +2 -9
  36. data/lib/tapyrus/message/block_transaction_request.rb +3 -6
  37. data/lib/tapyrus/message/block_transactions.rb +2 -6
  38. data/lib/tapyrus/message/block_txn.rb +0 -4
  39. data/lib/tapyrus/message/cmpct_block.rb +1 -7
  40. data/lib/tapyrus/message/error.rb +1 -4
  41. data/lib/tapyrus/message/fee_filter.rb +1 -4
  42. data/lib/tapyrus/message/filter_add.rb +0 -4
  43. data/lib/tapyrus/message/filter_clear.rb +0 -4
  44. data/lib/tapyrus/message/filter_load.rb +2 -5
  45. data/lib/tapyrus/message/get_addr.rb +0 -4
  46. data/lib/tapyrus/message/get_block_txn.rb +0 -4
  47. data/lib/tapyrus/message/get_blocks.rb +0 -3
  48. data/lib/tapyrus/message/get_data.rb +1 -4
  49. data/lib/tapyrus/message/get_headers.rb +1 -3
  50. data/lib/tapyrus/message/header_and_short_ids.rb +3 -9
  51. data/lib/tapyrus/message/headers.rb +0 -4
  52. data/lib/tapyrus/message/headers_parser.rb +3 -8
  53. data/lib/tapyrus/message/inv.rb +1 -4
  54. data/lib/tapyrus/message/inventories_parser.rb +2 -7
  55. data/lib/tapyrus/message/inventory.rb +12 -5
  56. data/lib/tapyrus/message/mem_pool.rb +0 -4
  57. data/lib/tapyrus/message/merkle_block.rb +4 -9
  58. data/lib/tapyrus/message/network_addr.rb +7 -6
  59. data/lib/tapyrus/message/not_found.rb +0 -3
  60. data/lib/tapyrus/message/ping.rb +0 -3
  61. data/lib/tapyrus/message/pong.rb +0 -3
  62. data/lib/tapyrus/message/prefilled_tx.rb +0 -4
  63. data/lib/tapyrus/message/reject.rb +0 -3
  64. data/lib/tapyrus/message/send_cmpct.rb +1 -3
  65. data/lib/tapyrus/message/send_headers.rb +0 -3
  66. data/lib/tapyrus/message/tx.rb +0 -4
  67. data/lib/tapyrus/message/ver_ack.rb +1 -5
  68. data/lib/tapyrus/message/version.rb +2 -5
  69. data/lib/tapyrus/message.rb +14 -16
  70. data/lib/tapyrus/mnemonic.rb +17 -15
  71. data/lib/tapyrus/network/connection.rb +0 -3
  72. data/lib/tapyrus/network/message_handler.rb +61 -60
  73. data/lib/tapyrus/network/peer.rb +13 -12
  74. data/lib/tapyrus/network/peer_discovery.rb +10 -9
  75. data/lib/tapyrus/network/pool.rb +12 -12
  76. data/lib/tapyrus/network.rb +0 -2
  77. data/lib/tapyrus/node/cli.rb +12 -14
  78. data/lib/tapyrus/node/configuration.rb +1 -3
  79. data/lib/tapyrus/node/spv.rb +2 -3
  80. data/lib/tapyrus/node.rb +1 -1
  81. data/lib/tapyrus/opcodes.rb +9 -7
  82. data/lib/tapyrus/out_point.rb +5 -5
  83. data/lib/tapyrus/rpc/http_server.rb +21 -22
  84. data/lib/tapyrus/rpc/request_handler.rb +16 -21
  85. data/lib/tapyrus/rpc/tapyrus_core_client.rb +67 -25
  86. data/lib/tapyrus/rpc.rb +1 -0
  87. data/lib/tapyrus/script/color.rb +10 -0
  88. data/lib/tapyrus/script/multisig.rb +13 -12
  89. data/lib/tapyrus/script/script.rb +93 -88
  90. data/lib/tapyrus/script/script_error.rb +1 -4
  91. data/lib/tapyrus/script/script_interpreter.rb +439 -399
  92. data/lib/tapyrus/script/tx_checker.rb +20 -10
  93. data/lib/tapyrus/secp256k1/native.rb +14 -15
  94. data/lib/tapyrus/secp256k1/rfc6979.rb +7 -4
  95. data/lib/tapyrus/secp256k1/ruby.rb +10 -12
  96. data/lib/tapyrus/secp256k1.rb +0 -4
  97. data/lib/tapyrus/slip39/share.rb +41 -29
  98. data/lib/tapyrus/slip39/sss.rb +92 -49
  99. data/lib/tapyrus/slip39.rb +20 -5
  100. data/lib/tapyrus/store/chain_entry.rb +0 -4
  101. data/lib/tapyrus/store/db/level_db.rb +5 -9
  102. data/lib/tapyrus/store/db.rb +0 -2
  103. data/lib/tapyrus/store/spv_chain.rb +11 -17
  104. data/lib/tapyrus/store.rb +1 -3
  105. data/lib/tapyrus/tx.rb +45 -37
  106. data/lib/tapyrus/tx_builder.rb +160 -0
  107. data/lib/tapyrus/tx_in.rb +1 -6
  108. data/lib/tapyrus/tx_out.rb +2 -7
  109. data/lib/tapyrus/util.rb +7 -9
  110. data/lib/tapyrus/validation.rb +12 -11
  111. data/lib/tapyrus/version.rb +1 -1
  112. data/lib/tapyrus/wallet/account.rb +22 -18
  113. data/lib/tapyrus/wallet/base.rb +12 -9
  114. data/lib/tapyrus/wallet/db.rb +6 -9
  115. data/lib/tapyrus/wallet/master_key.rb +2 -4
  116. data/lib/tapyrus.rb +7 -22
  117. data/tapyrusrb.gemspec +13 -14
  118. metadata +26 -7
  119. data/.travis.yml +0 -14
@@ -1,9 +1,7 @@
1
1
  module Tapyrus
2
2
  module Network
3
-
4
3
  # remote peer class.
5
4
  class Peer
6
-
7
5
  # Interval for pinging peers.
8
6
  PING_INTERVAL = 2 * 60
9
7
 
@@ -22,13 +20,16 @@ module Tapyrus
22
20
  attr_accessor :outbound # TODO need implements to accept inbound connection
23
21
  attr_accessor :best_hash
24
22
  attr_accessor :best_height
23
+
25
24
  # remote peer info
26
25
  attr_reader :host
27
26
  attr_reader :port
27
+
28
28
  # remote peer connection
29
29
  attr_accessor :conn
30
30
  attr_accessor :connected
31
31
  attr_accessor :primary
32
+
32
33
  # parent pool
33
34
  attr_reader :pool
34
35
  attr_reader :chain
@@ -51,7 +52,8 @@ module Tapyrus
51
52
  @relay = configuration.conf[:relay]
52
53
  current_height = @chain.latest_block.height
53
54
  remote_addr = Tapyrus::Message::NetworkAddr.new(ip: host, port: port, time: nil)
54
- @local_version = Tapyrus::Message::Version.new(remote_addr: remote_addr, start_height: current_height, relay: @relay)
55
+ @local_version =
56
+ Tapyrus::Message::Version.new(remote_addr: remote_addr, start_height: current_height, relay: @relay)
55
57
  end
56
58
 
57
59
  def connect
@@ -84,16 +86,17 @@ module Tapyrus
84
86
  def post_handshake
85
87
  @connected = true
86
88
  pool.handle_new_peer(self)
89
+
87
90
  # require remote peer to use headers message instead fo inv message.
88
91
  conn.send_message(Tapyrus::Message::SendHeaders.new)
89
- EM.add_periodic_timer(PING_INTERVAL) {send_ping}
92
+ EM.add_periodic_timer(PING_INTERVAL) { send_ping }
90
93
  end
91
94
 
92
95
  # start block header download
93
96
  def start_block_header_download
94
97
  logger.info("[#{addr}] start block header download.")
95
- get_headers = Tapyrus::Message::GetHeaders.new(
96
- Tapyrus.chain_params.protocol_version, [chain.latest_block.block_hash])
98
+ get_headers =
99
+ Tapyrus::Message::GetHeaders.new(Tapyrus.chain_params.protocol_version, [chain.latest_block.block_hash])
97
100
  conn.send_message(get_headers)
98
101
  end
99
102
 
@@ -129,7 +132,7 @@ module Tapyrus
129
132
  @best_height = entry.height
130
133
  end
131
134
  pool.changed
132
- pool.notify_observers(:header, {hash: @best_hash, height: @best_height})
135
+ pool.notify_observers(:header, { hash: @best_hash, height: @best_height })
133
136
  start_block_header_download if headers.headers.size > 0 # next header download
134
137
  end
135
138
 
@@ -156,14 +159,13 @@ module Tapyrus
156
159
 
157
160
  # send +addr+ message to remote peer
158
161
  def send_addrs
159
- addrs = pool.peers.select{|p|p != self}.map(&:to_network_addr)
162
+ addrs = pool.peers.select { |p| p != self }.map(&:to_network_addr)
160
163
  conn.send_message(Tapyrus::Message::Addr.new(addrs))
161
164
  end
162
165
 
163
166
  # handle block inv message.
164
167
  def handle_block_inv(hashes)
165
- getdata = Tapyrus::Message::GetData.new(
166
- hashes.map{|h|Tapyrus::Message::Inventory.new(block_type, h)})
168
+ getdata = Tapyrus::Message::GetData.new(hashes.map { |h| Tapyrus::Message::Inventory.new(block_type, h) })
167
169
  conn.send_message(getdata)
168
170
  end
169
171
 
@@ -188,8 +190,7 @@ module Tapyrus
188
190
 
189
191
  # send filterload message.
190
192
  def send_filter_load(bloom)
191
- filter_load = Tapyrus::Message::FilterLoad.new(
192
- bloom, Tapyrus::Message::FilterLoad::BLOOM_UPDATE_ALL)
193
+ filter_load = Tapyrus::Message::FilterLoad.new(bloom, Tapyrus::Message::FilterLoad::BLOOM_UPDATE_ALL)
193
194
  conn.send_message(filter_load)
194
195
  end
195
196
 
@@ -1,8 +1,6 @@
1
1
  module Tapyrus
2
2
  module Network
3
-
4
3
  class PeerDiscovery
5
-
6
4
  attr_reader :logger, :configuration
7
5
 
8
6
  def initialize(configuration)
@@ -28,14 +26,17 @@ module Tapyrus
28
26
 
29
27
  def find_from_dns_seeds
30
28
  logger.debug 'discover peer address from DNS seeds.'
31
- dns_seeds.map { |seed|
32
- begin
33
- Socket.getaddrinfo(seed, Tapyrus.chain_params.default_port).map{|a|a[2]}.uniq
34
- rescue SocketError => e
35
- logger.error "SocketError occurred when load DNS seed: #{seed}, error: #{e.message}"
36
- nil
29
+ dns_seeds
30
+ .map do |seed|
31
+ begin
32
+ Socket.getaddrinfo(seed, Tapyrus.chain_params.default_port).map { |a| a[2] }.uniq
33
+ rescue SocketError => e
34
+ logger.error "SocketError occurred when load DNS seed: #{seed}, error: #{e.message}"
35
+ nil
36
+ end
37
37
  end
38
- }.flatten.compact
38
+ .flatten
39
+ .compact
39
40
  end
40
41
  end
41
42
  end
@@ -1,11 +1,11 @@
1
1
  module Tapyrus
2
-
3
2
  module Network
4
-
5
3
  # Time between pings automatically sent out for latency probing and keepalive (in seconds).
6
4
  PING_INTERVAL = 2 * 60
5
+
7
6
  # Time after which to disconnect, after waiting for a ping response (or inactivity).
8
7
  TIMEOUT_INTERVAL = 20 * 60
8
+
9
9
  # Maximum number of automatic outgoing nodes
10
10
  MAX_OUTBOUND_CONNECTIONS = 4
11
11
 
@@ -112,23 +112,23 @@ module Tapyrus
112
112
  # allocate new peer id
113
113
  def allocate_peer_id
114
114
  id = 0
115
- until peers.empty? || peers.find{|p|p.id == id}.nil?
116
- id += 1
117
- end
115
+ id += 1 until peers.empty? || peers.find { |p| p.id == id }.nil?
118
116
  id
119
117
  end
120
118
 
121
119
  def connect(addr_list)
122
120
  port = Tapyrus.chain_params.default_port
123
121
 
124
- EM::Iterator.new(addr_list, Tapyrus::PARALLEL_THREAD).each do |ip, iter|
125
- if pending_peers.size + peers.size < MAX_OUTBOUND_CONNECTIONS
126
- peer = Peer.new(ip, port, self, @configuration)
127
- pending_peers << peer
128
- peer.connect
129
- iter.next
122
+ EM::Iterator
123
+ .new(addr_list, Tapyrus::PARALLEL_THREAD)
124
+ .each do |ip, iter|
125
+ if pending_peers.size + peers.size < MAX_OUTBOUND_CONNECTIONS
126
+ peer = Peer.new(ip, port, self, @configuration)
127
+ pending_peers << peer
128
+ peer.connect
129
+ iter.next
130
+ end
130
131
  end
131
- end
132
132
  end
133
133
  end
134
134
  end
@@ -2,12 +2,10 @@ require 'eventmachine'
2
2
 
3
3
  module Tapyrus
4
4
  module Network
5
-
6
5
  autoload :MessageHandler, 'tapyrus/network/message_handler'
7
6
  autoload :Connection, 'tapyrus/network/connection'
8
7
  autoload :Pool, 'tapyrus/network/pool'
9
8
  autoload :Peer, 'tapyrus/network/peer'
10
9
  autoload :PeerDiscovery, 'tapyrus/network/peer_discovery'
11
-
12
10
  end
13
11
  end
@@ -4,9 +4,7 @@ require 'json'
4
4
 
5
5
  module Tapyrus
6
6
  module Node
7
-
8
7
  class CLI < Thor
9
-
10
8
  class_option :network, aliases: '-n', default: :prod
11
9
 
12
10
  desc 'getblockchaininfo', 'Returns an object containing various state info regarding blockchain processing.'
@@ -19,7 +17,8 @@ module Tapyrus
19
17
  request('stop')
20
18
  end
21
19
 
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>.'
20
+ desc 'getblockheader "hash" ( verbose )',
21
+ '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
22
  def getblockheader(hash, verbose = true)
24
23
  verbose = verbose.is_a?(String) ? (verbose == 'true') : verbose
25
24
  request('getblockheader', hash, verbose)
@@ -30,7 +29,8 @@ module Tapyrus
30
29
  request('getpeerinfo')
31
30
  end
32
31
 
33
- desc 'decoderawtransaction "hexstring"', 'Return a JSON object representing the serialized, hex-encoded transaction.'
32
+ desc 'decoderawtransaction "hexstring"',
33
+ 'Return a JSON object representing the serialized, hex-encoded transaction.'
34
34
  def decoderawtransaction(hexstring)
35
35
  request('decoderawtransaction', hexstring)
36
36
  end
@@ -47,12 +47,14 @@ module Tapyrus
47
47
  request('sendrawtransaction', hex_tx)
48
48
  end
49
49
 
50
- desc 'createwallet "wallet_id"', 'Create new HD wallet. It returns an error if an existing wallet_id is specified. '
50
+ desc 'createwallet "wallet_id"',
51
+ 'Create new HD wallet. It returns an error if an existing wallet_id is specified. '
51
52
  def createwallet(wallet_id)
52
53
  request('createwallet', wallet_id)
53
54
  end
54
55
 
55
- desc 'listwallets', 'Returns a list of currently loaded wallets. For full information on the wallet, use "getwalletinfo"'
56
+ desc 'listwallets',
57
+ 'Returns a list of currently loaded wallets. For full information on the wallet, use "getwalletinfo"'
56
58
  def listwallets
57
59
  request('listwallets')
58
60
  end
@@ -67,7 +69,8 @@ module Tapyrus
67
69
  request('listaccounts')
68
70
  end
69
71
 
70
- desc 'encryptwallet "passphrase"', 'Encrypts the wallet with "passphrase". This is for first time encryption.After this, any calls that interact with private keys such as sending or signing will require the passphrase to be set prior the making these calls.'
72
+ desc 'encryptwallet "passphrase"',
73
+ 'Encrypts the wallet with "passphrase". This is for first time encryption.After this, any calls that interact with private keys such as sending or signing will require the passphrase to be set prior the making these calls.'
71
74
  def encryptwallet(passhphrase)
72
75
  request('encryptwallet', passhphrase)
73
76
  end
@@ -86,15 +89,11 @@ module Tapyrus
86
89
  end
87
90
 
88
91
  def request(command, *params)
89
- data = {
90
- :method => command,
91
- :params => params,
92
- :id => 'jsonrpc'
93
- }
92
+ data = { method: command, params: params, id: 'jsonrpc' }
94
93
  begin
95
94
  uri = URI.parse(config.server_url)
96
95
  http = Net::HTTP.new(uri.hostname, uri.port)
97
- http.use_ssl = uri.scheme === "https"
96
+ http.use_ssl = uri.scheme === 'https'
98
97
  request = Net::HTTP::Post.new('/')
99
98
  request.content_type = 'application/json'
100
99
  request.body = data.to_json
@@ -110,7 +109,6 @@ module Tapyrus
110
109
  puts e.message
111
110
  end
112
111
  end
113
-
114
112
  end
115
113
  end
116
114
  end
@@ -3,7 +3,6 @@ require 'iniparse'
3
3
  module Tapyrus
4
4
  module Node
5
5
  class Configuration
6
-
7
6
  attr_reader :conf
8
7
 
9
8
  def initialize(opts = {})
@@ -14,7 +13,7 @@ module Tapyrus
14
13
 
15
14
  begin
16
15
  ini_file = IniParse.parse(File.read("#{Tapyrus.base_dir}/tapyrusrb.conf"))
17
- @conf = Hash[ ini_file.to_h['__anonymous__'].map{|k,v| [k.to_sym, v] } ]
16
+ @conf = Hash[ini_file.to_h['__anonymous__'].map { |k, v| [k.to_sym, v] }]
18
17
  rescue => e
19
18
  @conf = {}
20
19
  end
@@ -32,7 +31,6 @@ module Tapyrus
32
31
  def server_url
33
32
  "http://#{host}:#{port}"
34
33
  end
35
-
36
34
  end
37
35
  end
38
36
  end
@@ -1,9 +1,7 @@
1
1
  module Tapyrus
2
2
  module Node
3
-
4
3
  # SPV class
5
4
  class SPV
6
-
7
5
  attr_reader :chain
8
6
  attr_reader :pool
9
7
  attr_reader :logger
@@ -20,6 +18,7 @@ module Tapyrus
20
18
  @logger = Tapyrus::Logger.create(:debug)
21
19
  @running = false
22
20
  @wallet = Tapyrus::Wallet::Base.current_wallet
21
+
23
22
  # TODO : optimize bloom filter parameters
24
23
  setup_filter
25
24
  end
@@ -72,7 +71,7 @@ module Tapyrus
72
71
 
73
72
  def setup_filter
74
73
  @bloom = Tapyrus::BloomFilter.create_filter(512, 0.01)
75
- wallet.watch_targets.each{|t|bloom.add(t.htb)} if wallet
74
+ wallet.watch_targets.each { |t| bloom.add(t.htb) } if wallet
76
75
  end
77
76
  end
78
77
  end
data/lib/tapyrus/node.rb CHANGED
@@ -4,4 +4,4 @@ module Tapyrus
4
4
  autoload :CLI, 'tapyrus/node/cli'
5
5
  autoload :Configuration, 'tapyrus/node/configuration'
6
6
  end
7
- end
7
+ end
@@ -1,8 +1,6 @@
1
1
  module Tapyrus
2
-
3
2
  # https://bitcoin.org/en/developer-reference#opcodes
4
3
  module Opcodes
5
-
6
4
  module_function
7
5
 
8
6
  # https://en.bitcoin.it/wiki/Script#Constants
@@ -111,7 +109,7 @@ module Tapyrus
111
109
  OP_HASH256 = 0xaa
112
110
  OP_CODESEPARATOR = 0xab
113
111
  OP_CHECKSIG = 0xac
114
- OP_CHECKSIGVERIFY= 0xad
112
+ OP_CHECKSIGVERIFY = 0xad
115
113
  OP_CHECKMULTISIG = 0xae
116
114
  OP_CHECKMULTISIGVERIFY = 0xaf
117
115
 
@@ -124,7 +122,7 @@ module Tapyrus
124
122
  OP_VER = 0x62
125
123
  OP_VERIF = 0x65
126
124
  OP_VERNOTIF = 0x66
127
- OP_RESERVED1= 0x89
125
+ OP_RESERVED1 = 0x89
128
126
  OP_RESERVED2 = 0x8a
129
127
 
130
128
  OP_NOP1 = 0xb0
@@ -147,7 +145,12 @@ module Tapyrus
147
145
  OP_COLOR = 0xbc
148
146
 
149
147
  DUPLICATE_KEY = [:OP_NOP2, :OP_NOP3]
150
- OPCODES_MAP = Hash[*(constants.grep(/^OP_/) - [:OP_NOP2, :OP_NOP3, :OP_CHECKLOCKTIMEVERIFY, :OP_CHECKSEQUENCEVERIFY]).map { |c| [const_get(c), c.to_s] }.flatten]
148
+ OPCODES_MAP =
149
+ Hash[
150
+ *(constants.grep(/^OP_/) - [:OP_NOP2, :OP_NOP3, :OP_CHECKLOCKTIMEVERIFY, :OP_CHECKSEQUENCEVERIFY]).map do |c|
151
+ [const_get(c), c.to_s]
152
+ end.flatten
153
+ ]
151
154
  NAME_MAP = Hash[*constants.grep(/^OP_/).map { |c| [c.to_s, const_get(c)] }.flatten]
152
155
 
153
156
  def opcode_to_name(opcode)
@@ -178,6 +181,5 @@ module Tapyrus
178
181
  return opcode - (OP_1 - 1) if opcode >= OP_1 && opcode <= OP_16
179
182
  nil
180
183
  end
181
-
182
184
  end
183
- end
185
+ end
@@ -1,10 +1,8 @@
1
1
  module Tapyrus
2
-
3
2
  # outpoint class
4
3
  class OutPoint
5
-
6
4
  COINBASE_HASH = '0000000000000000000000000000000000000000000000000000000000000000'
7
- COINBASE_INDEX = 4294967295
5
+ COINBASE_INDEX = 4_294_967_295
8
6
 
9
7
  attr_reader :tx_hash
10
8
  attr_reader :index
@@ -34,11 +32,13 @@ module Tapyrus
34
32
  index >= 0 && (!coinbase? && tx_hash != COINBASE_HASH)
35
33
  end
36
34
 
35
+ def ==(other)
36
+ to_payload == other&.to_payload
37
+ end
38
+
37
39
  # convert hash to txid
38
40
  def txid
39
41
  tx_hash.rhex
40
42
  end
41
-
42
43
  end
43
-
44
44
  end
@@ -3,7 +3,6 @@ require 'json'
3
3
 
4
4
  module Tapyrus
5
5
  module RPC
6
-
7
6
  # Tapyrusrb RPC server.
8
7
  class HttpServer < EM::Connection
9
8
  include EM::HttpServer
@@ -28,27 +27,29 @@ module Tapyrus
28
27
 
29
28
  # process http request.
30
29
  def process_http_request
31
- operation = proc {
32
- command, args = parse_json_params
33
- logger.debug("process http request. command = #{command}")
34
- begin
35
- send(command, *args).to_json
36
- rescue Exception => e
37
- e
30
+ operation =
31
+ proc do
32
+ command, args = parse_json_params
33
+ logger.debug("process http request. command = #{command}")
34
+ begin
35
+ send(command, *args).to_json
36
+ rescue Exception => e
37
+ e
38
+ end
38
39
  end
39
- }
40
- callback = proc{ |result|
41
- response = EM::DelegatedHttpResponse.new(self)
42
- if result.is_a?(Exception)
43
- response.status = 500
44
- response.content = result.message
45
- else
46
- response.status = 200
47
- response.content = result
40
+ callback =
41
+ proc do |result|
42
+ response = EM::DelegatedHttpResponse.new(self)
43
+ if result.is_a?(Exception)
44
+ response.status = 500
45
+ response.content = result.message
46
+ else
47
+ response.status = 200
48
+ response.content = result
49
+ end
50
+ response.content_type 'application/json'
51
+ response.send_response
48
52
  end
49
- response.content_type 'application/json'
50
- response.send_response
51
- }
52
53
  EM.defer(operation, callback)
53
54
  end
54
55
 
@@ -58,8 +59,6 @@ module Tapyrus
58
59
  params = JSON.parse(@http_post_content)
59
60
  [params['method'], params['params']]
60
61
  end
61
-
62
62
  end
63
-
64
63
  end
65
64
  end
@@ -1,9 +1,7 @@
1
1
  module Tapyrus
2
2
  module RPC
3
-
4
3
  # RPC server's request handler.
5
4
  module RequestHandler
6
-
7
5
  # Returns an object containing various state info regarding blockchain processing.
8
6
  def getblockchaininfo
9
7
  h = {}
@@ -28,19 +26,19 @@ module Tapyrus
28
26
  raise ArgumentError.new('Block not found') unless entry
29
27
  if verbose
30
28
  {
31
- hash: block_id,
32
- height: entry.height,
33
- features: entry.header.features,
34
- featuresHex: entry.header.features.to_even_length_hex.ljust(8, '0'),
35
- merkleroot: entry.header.merkle_root.rhex,
36
- immutablemerkleroot: entry.header.im_merkle_root.rhex,
37
- time: entry.header.time,
38
- mediantime: node.chain.mtp(block_hash),
39
- xfield_type: entry.header.x_field_type,
40
- xfield: entry.header.x_field,
41
- proof: entry.header.proof,
42
- previousblockhash: entry.prev_hash.rhex,
43
- nextblockhash: node.chain.next_hash(block_hash).rhex
29
+ hash: block_id,
30
+ height: entry.height,
31
+ features: entry.header.features,
32
+ featuresHex: entry.header.features.to_even_length_hex.ljust(8, '0'),
33
+ merkleroot: entry.header.merkle_root.rhex,
34
+ immutablemerkleroot: entry.header.im_merkle_root.rhex,
35
+ time: entry.header.time,
36
+ mediantime: node.chain.mtp(block_hash),
37
+ xfield_type: entry.header.x_field_type,
38
+ xfield: entry.header.x_field,
39
+ proof: entry.header.proof,
40
+ previousblockhash: entry.prev_hash.rhex,
41
+ nextblockhash: node.chain.next_hash(block_hash).rhex
44
42
  }
45
43
  else
46
44
  entry.header.to_hex
@@ -77,6 +75,7 @@ module Tapyrus
77
75
  # broadcast transaction
78
76
  def sendrawtransaction(hex_tx)
79
77
  tx = Tapyrus::Tx.parse_from_payload(hex_tx.htb)
78
+
80
79
  # TODO check wether tx is valid
81
80
  node.broadcast(tx)
82
81
  tx.txid
@@ -110,7 +109,7 @@ module Tapyrus
110
109
  def createwallet(wallet_id = 1, wallet_path_prefix = Tapyrus::Wallet::Base.default_path_prefix)
111
110
  wallet = Tapyrus::Wallet::Base.create(wallet_id, wallet_path_prefix)
112
111
  node.wallet = wallet unless node.wallet
113
- {wallet_id: wallet.wallet_id, mnemonic: wallet.master_key.mnemonic}
112
+ { wallet_id: wallet.wallet_id, mnemonic: wallet.master_key.mnemonic }
114
113
  end
115
114
 
116
115
  # get wallet list.
@@ -127,9 +126,7 @@ module Tapyrus
127
126
  def listaccounts
128
127
  return {} unless node.wallet
129
128
  accounts = {}
130
- node.wallet.accounts.each do |a|
131
- accounts[a.name] = node.wallet.get_balance(a)
132
- end
129
+ node.wallet.accounts.each { |a| accounts[a.name] = node.wallet.get_balance(a) }
133
130
  accounts
134
131
  end
135
132
 
@@ -144,8 +141,6 @@ module Tapyrus
144
141
  def getnewaddress(account_name)
145
142
  node.wallet.generate_new_address(account_name)
146
143
  end
147
-
148
144
  end
149
-
150
145
  end
151
146
  end
@@ -3,6 +3,40 @@ require 'json/pure'
3
3
 
4
4
  module Tapyrus
5
5
  module RPC
6
+ # Throw when happened anything http's error with connect to server.
7
+ #
8
+ # Almost case this exception happened from 401 Unauthorized or 500 Internal Server Error.
9
+ # And also, throw by cause of other http's errors.
10
+ #
11
+ # You can pull RPC error message when happened 500 Internal Server Error, like below:
12
+ #
13
+ # rescue Tapyrus::RPC::Error => ex
14
+ # if ex.message.response_code == 500
15
+ # puts ex.message[:rpc_error]
16
+ # end
17
+ # end
18
+ class Error < StandardError
19
+ attr_reader :response_code, :response_msg, :rpc_error
20
+
21
+ def initialize(response_code, response_msg, rpc_error)
22
+ @response_code = response_code
23
+ @response_msg = response_msg
24
+ @rpc_error = rpc_error
25
+ end
26
+
27
+ def message
28
+ @message ||=
29
+ begin
30
+ m = { response_code: response_code, response_msg: response_msg }
31
+ m.merge!(rpc_error: rpc_error) if rpc_error
32
+ m
33
+ end
34
+ end
35
+
36
+ def to_s
37
+ message.to_s
38
+ end
39
+ end
6
40
 
7
41
  # Client implementation for RPC to Tapyrus Core.
8
42
  #
@@ -16,25 +50,21 @@ module Tapyrus
16
50
  # client.getblockchaininfo
17
51
  #
18
52
  class TapyrusCoreClient
19
-
20
53
  attr_reader :config
21
54
 
22
55
  # @param [Hash] config a configuration required to connect to Bitcoin Core.
23
56
  def initialize(config)
24
57
  @config = config
25
58
 
26
- commands = request(:help).split("\n").inject([]) do |memo_ary, line|
27
- if !line.empty? && !line.start_with?('==')
28
- memo_ary << line.split(' ').first.to_sym
29
- end
30
- memo_ary
31
- end
32
- TapyrusCoreClient.class_eval do
33
- commands.each do |command|
34
- define_method(command) do |*params|
35
- request(command, *params)
59
+ commands =
60
+ request(:help)
61
+ .split("\n")
62
+ .inject([]) do |memo_ary, line|
63
+ memo_ary << line.split(' ').first.to_sym if !line.empty? && !line.start_with?('==')
64
+ memo_ary
36
65
  end
37
- end
66
+ TapyrusCoreClient.class_eval do
67
+ commands.each { |command| define_method(command) { |*params| request(command, *params) } }
38
68
  end
39
69
  end
40
70
 
@@ -42,33 +72,45 @@ module Tapyrus
42
72
 
43
73
  def server_url
44
74
  url = "#{config[:schema]}://#{config[:user]}:#{config[:password]}@#{config[:host]}:#{config[:port]}"
45
- if !config[:wallet].nil? && !config[:wallet].empty?
46
- url += "/wallet/#{config[:wallet]}"
47
- end
75
+ url += "/wallet/#{config[:wallet]}" if !config[:wallet].nil? && !config[:wallet].empty?
48
76
  url
49
77
  end
50
78
 
51
79
  def request(command, *params)
52
- data = {
53
- :method => command,
54
- :params => params,
55
- :id => 'jsonrpc'
56
- }
80
+ data = { method: command, params: params, id: 'jsonrpc' }
57
81
  uri = URI.parse(server_url)
58
82
  http = Net::HTTP.new(uri.hostname, uri.port)
59
- http.use_ssl = uri.scheme === "https"
83
+ http.use_ssl = uri.scheme === 'https'
60
84
  request = Net::HTTP::Post.new(uri.path.empty? ? '/' : uri.path)
61
85
  request.basic_auth(uri.user, uri.password)
62
86
  request.content_type = 'application/json'
63
87
  request.body = data.to_json
64
88
  response = http.request(request)
65
- body = response.body
66
- response = Tapyrus::Ext::JsonParser.new(body.gsub(/\\u([\da-fA-F]{4})/) { [$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8') }).parse
67
- raise response['error'].to_json if response['error']
89
+ raise error!(response) unless response.is_a? Net::HTTPOK
90
+ response = Tapyrus::RPC.response_body2json(response.body)
68
91
  response['result']
69
92
  end
70
93
 
94
+ def error!(response)
95
+ rpc_error =
96
+ begin
97
+ Tapyrus::RPC.response_body2json(response.body)['error']
98
+ rescue JSON::ParserError => _
99
+ # if RPC server don't send error message.
100
+ end
101
+
102
+ raise Error.new(response.code, response.msg, rpc_error)
103
+ end
104
+ end
105
+
106
+ def response_body2json(body)
107
+ Tapyrus::Ext::JsonParser.new(
108
+ body.gsub(/\\u([\da-fA-F]{4})/) do
109
+ [$1].pack('H*').unpack('n*').pack('U*').encode('ISO-8859-1').force_encoding('UTF-8')
110
+ end
111
+ ).parse
71
112
  end
72
113
 
114
+ module_function :response_body2json
73
115
  end
74
- end
116
+ end