tapyrus 0.1.0

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.
Files changed (128) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +12 -0
  7. data/CODE_OF_CONDUCT.md +49 -0
  8. data/Gemfile +6 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +100 -0
  11. data/Rakefile +6 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/exe/tapyrusrb-cli +5 -0
  15. data/exe/tapyrusrbd +41 -0
  16. data/lib/openassets/marker_output.rb +20 -0
  17. data/lib/openassets/payload.rb +54 -0
  18. data/lib/openassets/util.rb +28 -0
  19. data/lib/openassets.rb +9 -0
  20. data/lib/tapyrus/base58.rb +38 -0
  21. data/lib/tapyrus/block.rb +77 -0
  22. data/lib/tapyrus/block_header.rb +88 -0
  23. data/lib/tapyrus/bloom_filter.rb +78 -0
  24. data/lib/tapyrus/chain_params.rb +90 -0
  25. data/lib/tapyrus/chainparams/mainnet.yml +41 -0
  26. data/lib/tapyrus/chainparams/regtest.yml +38 -0
  27. data/lib/tapyrus/chainparams/testnet.yml +41 -0
  28. data/lib/tapyrus/constants.rb +195 -0
  29. data/lib/tapyrus/descriptor.rb +147 -0
  30. data/lib/tapyrus/ext_key.rb +337 -0
  31. data/lib/tapyrus/key.rb +296 -0
  32. data/lib/tapyrus/key_path.rb +26 -0
  33. data/lib/tapyrus/logger.rb +42 -0
  34. data/lib/tapyrus/merkle_tree.rb +149 -0
  35. data/lib/tapyrus/message/addr.rb +35 -0
  36. data/lib/tapyrus/message/base.rb +28 -0
  37. data/lib/tapyrus/message/block.rb +46 -0
  38. data/lib/tapyrus/message/block_transaction_request.rb +45 -0
  39. data/lib/tapyrus/message/block_transactions.rb +31 -0
  40. data/lib/tapyrus/message/block_txn.rb +27 -0
  41. data/lib/tapyrus/message/cmpct_block.rb +42 -0
  42. data/lib/tapyrus/message/error.rb +10 -0
  43. data/lib/tapyrus/message/fee_filter.rb +27 -0
  44. data/lib/tapyrus/message/filter_add.rb +28 -0
  45. data/lib/tapyrus/message/filter_clear.rb +17 -0
  46. data/lib/tapyrus/message/filter_load.rb +39 -0
  47. data/lib/tapyrus/message/get_addr.rb +17 -0
  48. data/lib/tapyrus/message/get_block_txn.rb +27 -0
  49. data/lib/tapyrus/message/get_blocks.rb +29 -0
  50. data/lib/tapyrus/message/get_data.rb +21 -0
  51. data/lib/tapyrus/message/get_headers.rb +28 -0
  52. data/lib/tapyrus/message/header_and_short_ids.rb +57 -0
  53. data/lib/tapyrus/message/headers.rb +35 -0
  54. data/lib/tapyrus/message/headers_parser.rb +24 -0
  55. data/lib/tapyrus/message/inv.rb +21 -0
  56. data/lib/tapyrus/message/inventories_parser.rb +23 -0
  57. data/lib/tapyrus/message/inventory.rb +51 -0
  58. data/lib/tapyrus/message/mem_pool.rb +17 -0
  59. data/lib/tapyrus/message/merkle_block.rb +42 -0
  60. data/lib/tapyrus/message/network_addr.rb +63 -0
  61. data/lib/tapyrus/message/not_found.rb +21 -0
  62. data/lib/tapyrus/message/ping.rb +30 -0
  63. data/lib/tapyrus/message/pong.rb +26 -0
  64. data/lib/tapyrus/message/prefilled_tx.rb +29 -0
  65. data/lib/tapyrus/message/reject.rb +46 -0
  66. data/lib/tapyrus/message/send_cmpct.rb +43 -0
  67. data/lib/tapyrus/message/send_headers.rb +16 -0
  68. data/lib/tapyrus/message/tx.rb +30 -0
  69. data/lib/tapyrus/message/ver_ack.rb +17 -0
  70. data/lib/tapyrus/message/version.rb +69 -0
  71. data/lib/tapyrus/message.rb +70 -0
  72. data/lib/tapyrus/mnemonic/wordlist/chinese_simplified.txt +2048 -0
  73. data/lib/tapyrus/mnemonic/wordlist/chinese_traditional.txt +2048 -0
  74. data/lib/tapyrus/mnemonic/wordlist/english.txt +2048 -0
  75. data/lib/tapyrus/mnemonic/wordlist/french.txt +2048 -0
  76. data/lib/tapyrus/mnemonic/wordlist/italian.txt +2048 -0
  77. data/lib/tapyrus/mnemonic/wordlist/japanese.txt +2048 -0
  78. data/lib/tapyrus/mnemonic/wordlist/spanish.txt +2048 -0
  79. data/lib/tapyrus/mnemonic.rb +77 -0
  80. data/lib/tapyrus/network/connection.rb +73 -0
  81. data/lib/tapyrus/network/message_handler.rb +241 -0
  82. data/lib/tapyrus/network/peer.rb +223 -0
  83. data/lib/tapyrus/network/peer_discovery.rb +42 -0
  84. data/lib/tapyrus/network/pool.rb +135 -0
  85. data/lib/tapyrus/network.rb +13 -0
  86. data/lib/tapyrus/node/cli.rb +112 -0
  87. data/lib/tapyrus/node/configuration.rb +38 -0
  88. data/lib/tapyrus/node/spv.rb +79 -0
  89. data/lib/tapyrus/node.rb +7 -0
  90. data/lib/tapyrus/opcodes.rb +178 -0
  91. data/lib/tapyrus/out_point.rb +44 -0
  92. data/lib/tapyrus/rpc/http_server.rb +65 -0
  93. data/lib/tapyrus/rpc/request_handler.rb +150 -0
  94. data/lib/tapyrus/rpc/tapyrus_core_client.rb +72 -0
  95. data/lib/tapyrus/rpc.rb +7 -0
  96. data/lib/tapyrus/script/multisig.rb +92 -0
  97. data/lib/tapyrus/script/script.rb +551 -0
  98. data/lib/tapyrus/script/script_error.rb +111 -0
  99. data/lib/tapyrus/script/script_interpreter.rb +668 -0
  100. data/lib/tapyrus/script/tx_checker.rb +81 -0
  101. data/lib/tapyrus/script_witness.rb +38 -0
  102. data/lib/tapyrus/secp256k1/native.rb +174 -0
  103. data/lib/tapyrus/secp256k1/ruby.rb +123 -0
  104. data/lib/tapyrus/secp256k1.rb +12 -0
  105. data/lib/tapyrus/slip39/share.rb +122 -0
  106. data/lib/tapyrus/slip39/sss.rb +245 -0
  107. data/lib/tapyrus/slip39/wordlist/english.txt +1024 -0
  108. data/lib/tapyrus/slip39.rb +93 -0
  109. data/lib/tapyrus/store/chain_entry.rb +67 -0
  110. data/lib/tapyrus/store/db/level_db.rb +98 -0
  111. data/lib/tapyrus/store/db.rb +9 -0
  112. data/lib/tapyrus/store/spv_chain.rb +101 -0
  113. data/lib/tapyrus/store.rb +9 -0
  114. data/lib/tapyrus/tx.rb +347 -0
  115. data/lib/tapyrus/tx_in.rb +89 -0
  116. data/lib/tapyrus/tx_out.rb +74 -0
  117. data/lib/tapyrus/util.rb +133 -0
  118. data/lib/tapyrus/validation.rb +115 -0
  119. data/lib/tapyrus/version.rb +3 -0
  120. data/lib/tapyrus/wallet/account.rb +151 -0
  121. data/lib/tapyrus/wallet/base.rb +162 -0
  122. data/lib/tapyrus/wallet/db.rb +81 -0
  123. data/lib/tapyrus/wallet/master_key.rb +110 -0
  124. data/lib/tapyrus/wallet.rb +8 -0
  125. data/lib/tapyrus.rb +219 -0
  126. data/tapyrusrb.conf.sample +0 -0
  127. data/tapyrusrb.gemspec +47 -0
  128. metadata +451 -0
@@ -0,0 +1,77 @@
1
+ module Tapyrus
2
+
3
+ # Mnemonic code for generating deterministic keys
4
+ # https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
5
+ class Mnemonic
6
+
7
+ WORD_DIR = "#{__dir__}/mnemonic/wordlist"
8
+
9
+ attr_reader :word_list
10
+
11
+ def initialize(word_list)
12
+ raise ArgumentError, 'specified language is not supported.' unless Mnemonic.word_lists.include?(word_list)
13
+ @word_list = word_list
14
+ end
15
+
16
+ # get support language list
17
+ def self.word_lists
18
+ Dir.glob("#{WORD_DIR}/**.txt").map{|f| f.gsub("#{WORD_DIR}/", '').gsub('.txt', '') }
19
+ end
20
+
21
+ # generate entropy from mnemonic word
22
+ # @param [Array[String]] words the array of mnemonic word.
23
+ # @return [String] an entropy with hex format.
24
+ def to_entropy(words)
25
+ word_master = load_words
26
+ mnemonic = words.map do |w|
27
+ index = word_master.index(w.downcase)
28
+ raise IndexError, 'word not found in words list.' unless index
29
+ index.to_s(2).rjust(11, '0')
30
+ end.join
31
+ entropy = mnemonic.slice(0, (mnemonic.length * 32) / 33)
32
+ checksum = mnemonic.gsub(entropy, '')
33
+ raise SecurityError, 'checksum mismatch.' unless checksum == checksum(entropy)
34
+ [entropy].pack('B*').bth
35
+ end
36
+
37
+ # generate mnemonic words from entropy.
38
+ # @param [String] entropy an entropy with hex format.
39
+ # @return [Array] the array of mnemonic word.
40
+ def to_mnemonic(entropy)
41
+ raise ArgumentError, 'entropy is empty.' if entropy.nil? || entropy.empty?
42
+ e = entropy.htb.unpack('B*').first
43
+ seed = e + checksum(e)
44
+ mnemonic_index = seed.chars.each_slice(11).map{|i|i.join.to_i(2)}
45
+ word_master = load_words
46
+ mnemonic_index.map{|i|word_master[i]}
47
+ end
48
+
49
+ # generate seed from mnemonic
50
+ # if mnemonic protected with passphrase, specify that passphrase.
51
+ # @param [Array] mnemonic a array of mnemonic word.
52
+ # @param [String] passphrase a passphrase which protects mnemonic. the default value is an empty string.
53
+ # @return [String] seed
54
+ def to_seed(mnemonic, passphrase: '')
55
+ to_entropy(mnemonic)
56
+ OpenSSL::PKCS5.pbkdf2_hmac(mnemonic.join(' ').downcase,
57
+ 'mnemonic' + passphrase, 2048, 64, OpenSSL::Digest::SHA512.new).bth
58
+ end
59
+
60
+ # calculate entropy checksum
61
+ # @param [String] entropy an entropy with bit string format
62
+ # @return [String] an entropy checksum with bit string format
63
+ def checksum(entropy)
64
+ b = Tapyrus.sha256([entropy].pack('B*')).unpack('B*').first
65
+ b.slice(0, (entropy.length/32))
66
+ end
67
+
68
+ private
69
+
70
+ # load word list contents
71
+ def load_words
72
+ File.readlines("#{WORD_DIR}/#{word_list}.txt").map(&:strip)
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,73 @@
1
+ module Tapyrus
2
+
3
+ module Network
4
+
5
+ # Basic Bitcoin P2P connection class
6
+ class Connection < EM::Connection
7
+
8
+ include MessageHandler
9
+
10
+ attr_reader :peer, :logger
11
+
12
+ # remote peer version.
13
+ attr_accessor :version
14
+
15
+ # if true, this peer send new block announcements using a headers message rather than an inv message.
16
+ attr_accessor :sendheaders
17
+
18
+ # minimum fee(in satoshis per kilobyte) for relay tx
19
+ attr_accessor :fee_rate
20
+
21
+ def initialize(peer)
22
+ @peer = peer
23
+ @logger = peer.logger
24
+ @sendheaders = false
25
+ @attr_accessor = 0
26
+ @message = ''
27
+ self.pending_connect_timeout = 5.0
28
+ end
29
+
30
+ def post_init
31
+ logger.info "connected. #{addr}"
32
+ peer.conn_time = Time.now.to_i
33
+ begin_handshake
34
+ end
35
+
36
+ # handle receiving data from remote node.
37
+ def receive_data(data)
38
+ handle(data)
39
+ end
40
+
41
+ def post_handshake
42
+ peer.post_handshake
43
+ end
44
+
45
+ def addr
46
+ peer.addr
47
+ end
48
+
49
+ # close network connection.
50
+ def close(msg = '')
51
+ logger.info "close connection with #{addr}. #{msg}"
52
+ close_connection_after_writing
53
+ end
54
+
55
+ def handle_error(e)
56
+ peer.handle_error(e)
57
+ end
58
+
59
+ def unbind
60
+ logger.info "unbind. #{addr}"
61
+ peer.unbind
62
+ end
63
+
64
+ private
65
+
66
+ # start handshake
67
+ def begin_handshake
68
+ logger.info "begin handshake with #{addr}"
69
+ send_message(peer.local_version)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,241 @@
1
+ module Tapyrus
2
+ module Network
3
+
4
+ # P2P message handler used by peer connection class.
5
+ module MessageHandler
6
+
7
+ # handle p2p message.
8
+ def handle(message)
9
+ peer.last_recv = Time.now.to_i
10
+ peer.bytes_recv += message.bytesize
11
+ begin
12
+ parse(message)
13
+ rescue Tapyrus::Message::Error => e
14
+ logger.error("invalid header magic. #{e.message}")
15
+ close
16
+ end
17
+ end
18
+
19
+ def parse(message)
20
+ @message += message
21
+ command, payload, rest = parse_header
22
+ return unless command
23
+
24
+ defer_handle_command(command, payload)
25
+ @message = ""
26
+ parse(rest) if rest && rest.bytesize > 0
27
+ end
28
+
29
+ def parse_header
30
+ head_magic = Tapyrus.chain_params.magic_head
31
+ return if @message.nil? || @message.size < MESSAGE_HEADER_SIZE
32
+
33
+ magic, command, length, checksum = @message.unpack('a4A12Va4')
34
+ raise Tapyrus::Message::Error, "invalid header magic. #{magic.bth}" unless magic.bth == head_magic
35
+
36
+ payload = @message[MESSAGE_HEADER_SIZE...(MESSAGE_HEADER_SIZE + length)]
37
+ return if payload.size < length
38
+ raise Tapyrus::Message::Error, "header checksum mismatch. #{checksum.bth}" unless Tapyrus.double_sha256(payload)[0...4] == checksum
39
+
40
+ rest = @message[(MESSAGE_HEADER_SIZE + length)..-1]
41
+ [command, payload, rest]
42
+ end
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
+
56
+ def handle_command(command, payload)
57
+ logger.info("[#{addr}] process command #{command}.")
58
+ case command
59
+ when Tapyrus::Message::Version::COMMAND
60
+ on_version(Tapyrus::Message::Version.parse_from_payload(payload))
61
+ when Tapyrus::Message::VerAck::COMMAND
62
+ on_ver_ack
63
+ when Tapyrus::Message::GetAddr::COMMAND
64
+ on_get_addr
65
+ when Tapyrus::Message::Addr::COMMAND
66
+ on_addr(Tapyrus::Message::Addr.parse_from_payload(payload))
67
+ when Tapyrus::Message::SendHeaders::COMMAND
68
+ on_send_headers
69
+ when Tapyrus::Message::FeeFilter::COMMAND
70
+ on_fee_filter(Tapyrus::Message::FeeFilter.parse_from_payload(payload))
71
+ when Tapyrus::Message::Ping::COMMAND
72
+ on_ping(Tapyrus::Message::Ping.parse_from_payload(payload))
73
+ when Tapyrus::Message::Pong::COMMAND
74
+ on_pong(Tapyrus::Message::Pong.parse_from_payload(payload))
75
+ when Tapyrus::Message::GetHeaders::COMMAND
76
+ on_get_headers(Tapyrus::Message::GetHeaders.parse_from_payload(payload))
77
+ when Tapyrus::Message::Headers::COMMAND
78
+ on_headers(Tapyrus::Message::Headers.parse_from_payload(payload))
79
+ when Tapyrus::Message::Block::COMMAND
80
+ on_block(Tapyrus::Message::Block.parse_from_payload(payload))
81
+ when Tapyrus::Message::Tx::COMMAND
82
+ on_tx(Tapyrus::Message::Tx.parse_from_payload(payload))
83
+ when Tapyrus::Message::NotFound::COMMAND
84
+ on_not_found(Tapyrus::Message::NotFound.parse_from_payload(payload))
85
+ when Tapyrus::Message::MemPool::COMMAND
86
+ on_mem_pool
87
+ when Tapyrus::Message::Reject::COMMAND
88
+ on_reject(Tapyrus::Message::Reject.parse_from_payload(payload))
89
+ when Tapyrus::Message::SendCmpct::COMMAND
90
+ on_send_cmpct(Tapyrus::Message::SendCmpct.parse_from_payload(payload))
91
+ when Tapyrus::Message::Inv::COMMAND
92
+ on_inv(Tapyrus::Message::Inv.parse_from_payload(payload))
93
+ when Tapyrus::Message::MerkleBlock::COMMAND
94
+ on_merkle_block(Tapyrus::Message::MerkleBlock.parse_from_payload(payload))
95
+ when Tapyrus::Message::CmpctBlock::COMMAND
96
+ on_cmpct_block(Tapyrus::Message::CmpctBlock.parse_from_payload(payload))
97
+ when Tapyrus::Message::GetData::COMMAND
98
+ on_get_data(Tapyrus::Message::GetData.parse_from_payload(payload))
99
+ else
100
+ logger.warn("unsupported command received. command: #{command}, payload: #{payload.bth}")
101
+ close("with command #{command}")
102
+ end
103
+ end
104
+
105
+ def send_message(msg)
106
+ logger.info "send message #{msg.class::COMMAND}"
107
+ pkt = msg.to_pkt
108
+ peer.last_send = Time.now.to_i
109
+ peer.bytes_sent = pkt.bytesize
110
+ send_data(pkt)
111
+ end
112
+
113
+ def handshake_done
114
+ return unless @incomming_handshake && @outgoing_handshake
115
+ logger.info 'handshake finished.'
116
+ @connected = true
117
+ post_handshake
118
+ end
119
+
120
+ def on_version(version)
121
+ logger.info("receive version message. #{version.build_json}")
122
+ @version = version
123
+ send_message(Tapyrus::Message::VerAck.new)
124
+ @incomming_handshake = true
125
+ handshake_done
126
+ end
127
+
128
+ def on_ver_ack
129
+ logger.info('receive verack message.')
130
+ @outgoing_handshake = true
131
+ handshake_done
132
+ end
133
+
134
+ def on_get_addr
135
+ logger.info('receive getaddr message.')
136
+ peer.send_addrs
137
+ end
138
+
139
+ def on_addr(addr)
140
+ logger.info("receive addr message. #{addr.build_json}")
141
+ # TODO
142
+ end
143
+
144
+ def on_send_headers
145
+ logger.info('receive sendheaders message.')
146
+ @sendheaders = true
147
+ end
148
+
149
+ def on_fee_filter(fee_filter)
150
+ logger.info("receive feefilter message. #{fee_filter.build_json}")
151
+ @fee_rate = fee_filter.fee_rate
152
+ end
153
+
154
+ def on_ping(ping)
155
+ logger.info("receive ping message. #{ping.build_json}")
156
+ send_message(ping.to_response)
157
+ end
158
+
159
+ def on_pong(pong)
160
+ logger.info("receive pong message. #{pong.build_json}")
161
+ if pong.nonce == peer.last_ping_nonce
162
+ peer.last_ping_nonce = nil
163
+ peer.last_pong = Time.now.to_i
164
+ else
165
+ logger.debug "The remote peer sent the wrong nonce (#{pong.nonce})."
166
+ end
167
+ end
168
+
169
+ def on_get_headers(headers)
170
+ logger.info('receive getheaders message.')
171
+ # TODO
172
+ end
173
+
174
+ def on_headers(headers)
175
+ logger.info('receive headers message.')
176
+ peer.handle_headers(headers)
177
+ end
178
+
179
+ def on_block(block)
180
+ logger.info('receive block message.')
181
+ # TODO
182
+ end
183
+
184
+ def on_tx(tx)
185
+ logger.info("receive tx message. #{tx.build_json}")
186
+ peer.handle_tx(tx)
187
+ end
188
+
189
+ def on_not_found(not_found)
190
+ logger.info("receive notfound message. #{not_found.build_json}")
191
+ # TODO
192
+ end
193
+
194
+ def on_mem_pool
195
+ logger.info('receive mempool message.')
196
+ # TODO return mempool tx
197
+ end
198
+
199
+ def on_reject(reject)
200
+ logger.warn("receive reject message. #{reject.build_json}")
201
+ # TODO
202
+ end
203
+
204
+ def on_send_cmpct(cmpct)
205
+ logger.info("receive sendcmpct message. #{cmpct.build_json}")
206
+ # TODO if mode is high and version is 1, relay block with cmpctblock message
207
+ end
208
+
209
+ def on_inv(inv)
210
+ logger.info('receive inv message.')
211
+ blocks = []
212
+ txs = []
213
+ inv.inventories.each do |i|
214
+ case i.identifier
215
+ when Tapyrus::Message::Inventory::MSG_TX
216
+ txs << i.hash
217
+ when Tapyrus::Message::Inventory::MSG_BLOCK
218
+ blocks << i.hash
219
+ else
220
+ logger.warn("[#{addr}] peer sent unknown inv type: #{i.identifier}")
221
+ end
222
+ end
223
+ logger.info("receive block= #{blocks.size}, txs: #{txs.size}")
224
+ peer.handle_block_inv(blocks) unless blocks.empty?
225
+ end
226
+
227
+ def on_merkle_block(merkle_block)
228
+ logger.info("receive merkle block message. #{merkle_block.build_json}")
229
+ peer.handle_merkle_block(merkle_block)
230
+ end
231
+
232
+ def on_cmpct_block(cmpct_block)
233
+ logger.info("receive cmpct_block message. #{cmpct_block.build_json}")
234
+ end
235
+
236
+ def on_get_data(get_data)
237
+ logger.info("receive get data message. #{get_data.build_json}")
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,223 @@
1
+ module Tapyrus
2
+ module Network
3
+
4
+ # remote peer class.
5
+ class Peer
6
+
7
+ # Interval for pinging peers.
8
+ PING_INTERVAL = 2 * 60
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
25
+ # remote peer info
26
+ attr_reader :host
27
+ attr_reader :port
28
+ # remote peer connection
29
+ attr_accessor :conn
30
+ attr_accessor :connected
31
+ attr_accessor :primary
32
+ # parent pool
33
+ attr_reader :pool
34
+ attr_reader :chain
35
+ attr_accessor :fee_rate
36
+
37
+ def initialize(host, port, pool, configuration)
38
+ @host = host
39
+ @port = port
40
+ @pool = pool
41
+ @chain = pool.chain
42
+ @connected = false
43
+ @primary = false
44
+ @logger = Tapyrus::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
+ @relay = configuration.conf[:relay]
52
+ current_height = @chain.latest_block.height
53
+ 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
+ end
56
+
57
+ def connect
58
+ self.conn ||= EM.connect(host, port, Tapyrus::Network::Connection, self)
59
+ end
60
+
61
+ def connected?
62
+ @connected
63
+ end
64
+
65
+ def outbound?
66
+ @outbound
67
+ end
68
+
69
+ def addr
70
+ "#{host}:#{port}"
71
+ end
72
+
73
+ # calculate ping-pong time.
74
+ def ping_time
75
+ last_pong ? (last_pong - last_ping) / 1e6 : -1
76
+ end
77
+
78
+ # set last pong
79
+ def last_pong=(time)
80
+ @last_pong = time
81
+ @min_ping = ping_time if min_ping == -1 || ping_time < min_ping
82
+ end
83
+
84
+ def post_handshake
85
+ @connected = true
86
+ pool.handle_new_peer(self)
87
+ # require remote peer to use headers message instead fo inv message.
88
+ conn.send_message(Tapyrus::Message::SendHeaders.new)
89
+ EM.add_periodic_timer(PING_INTERVAL) {send_ping}
90
+ end
91
+
92
+ # start block header download
93
+ def start_block_header_download
94
+ 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])
97
+ conn.send_message(get_headers)
98
+ end
99
+
100
+ # broadcast tx.
101
+ def broadcast_tx(tx)
102
+ conn.send_message(Tapyrus::Message::Tx.new(tx, support_witness?))
103
+ end
104
+
105
+ # check the remote peer support witness.
106
+ def support_witness?
107
+ return false unless remote_version
108
+ remote_version.services & Tapyrus::Message::SERVICE_FLAGS[:witness] > 0
109
+ end
110
+
111
+ # check the remote peer supports compact block.
112
+ def support_cmpct?
113
+ return false if remote_version.version < Tapyrus::Message::VERSION[:compact]
114
+ return true unless local_version.services & Tapyrus::Message::SERVICE_FLAGS[:witness] > 0
115
+ return false unless support_witness?
116
+ remote_version.version >= Tapyrus::Message::VERSION[:compact_witness]
117
+ end
118
+
119
+ # get peer's block type.
120
+ def block_type
121
+ Tapyrus::Message::Inventory::MSG_FILTERED_BLOCK # TODO need other implementation
122
+ end
123
+
124
+ # get remote peer's version message.
125
+ # @return [Tapyrus::Message::Version]
126
+ def remote_version
127
+ conn.version
128
+ end
129
+
130
+ # Whether to try and download blocks and transactions from this peer.
131
+ def primary?
132
+ primary
133
+ end
134
+
135
+ # handle headers message
136
+ # @params [Tapyrus::Message::Headers]
137
+ def handle_headers(headers)
138
+ headers.headers.each do |header|
139
+ break unless header.valid?
140
+ entry = chain.append_header(header)
141
+ next unless entry
142
+ @best_hash = entry.block_hash
143
+ @best_height = entry.height
144
+ end
145
+ pool.changed
146
+ pool.notify_observers(:header, {hash: @best_hash, height: @best_height})
147
+ start_block_header_download if headers.headers.size > 0 # next header download
148
+ end
149
+
150
+ # handle error
151
+ def handle_error(e)
152
+ pool.handle_error(e)
153
+ end
154
+
155
+ def unbind
156
+ pool.handle_close_peer(self)
157
+ end
158
+
159
+ # close peer connection.
160
+ def close(msg = '')
161
+ conn.close(msg)
162
+ end
163
+
164
+ # generate Tapyrus::Message::NetworkAddr object from this peer info.
165
+ # @return [Tapyrus::Message::NetworkAddr]
166
+ def to_network_addr
167
+ v = remote_version
168
+ Tapyrus::Message::NetworkAddr.new(ip: host, port: port, services: v.services, time: v.timestamp)
169
+ end
170
+
171
+ # send +addr+ message to remote peer
172
+ def send_addrs
173
+ addrs = pool.peers.select{|p|p != self}.map(&:to_network_addr)
174
+ conn.send_message(Tapyrus::Message::Addr.new(addrs))
175
+ end
176
+
177
+ # handle block inv message.
178
+ def handle_block_inv(hashes)
179
+ getdata = Tapyrus::Message::GetData.new(
180
+ hashes.map{|h|Tapyrus::Message::Inventory.new(block_type, h)})
181
+ conn.send_message(getdata)
182
+ end
183
+
184
+ def handle_tx(tx)
185
+ pool.changed
186
+ pool.notify_observers(:tx, tx)
187
+ end
188
+
189
+ def handle_merkle_block(merkle_block)
190
+ pool.changed
191
+ pool.notify_observers(:merkleblock, merkle_block)
192
+ end
193
+
194
+ # send ping message.
195
+ def send_ping
196
+ ping = Tapyrus::Message::Ping.new
197
+ @last_ping = Time.now.to_i
198
+ @last_pong = -1
199
+ @last_ping_nonce = ping.nonce
200
+ conn.send_message(ping)
201
+ end
202
+
203
+ # send filterload message.
204
+ def send_filter_load(bloom)
205
+ filter_load = Tapyrus::Message::FilterLoad.new(
206
+ bloom, Tapyrus::Message::FilterLoad::BLOOM_UPDATE_ALL)
207
+ conn.send_message(filter_load)
208
+ end
209
+
210
+ # send filteradd message.
211
+ def send_filter_add(element)
212
+ filter_add = Tapyrus::Message::FilterAdd.new(element)
213
+ conn.send_message(filter_add)
214
+ end
215
+
216
+ # send filterclear message.
217
+ def send_filter_clear
218
+ filter_clear = Tapyrus::Message::FilterClear.new
219
+ conn.send_message(filter_clear)
220
+ end
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,42 @@
1
+ module Tapyrus
2
+ module Network
3
+
4
+ class PeerDiscovery
5
+
6
+ attr_reader :logger, :configuration
7
+
8
+ def initialize(configuration)
9
+ @logger = Tapyrus::Logger.create(:debug)
10
+ @configuration = configuration
11
+ end
12
+
13
+ # get peer addresses, from DNS seeds.
14
+ def peers
15
+ # TODO add find from previous connected peer at first.
16
+ (find_from_dns_seeds + seeds).uniq
17
+ end
18
+
19
+ private
20
+
21
+ def dns_seeds
22
+ Tapyrus.chain_params.dns_seeds || []
23
+ end
24
+
25
+ def seeds
26
+ [*configuration.conf[:connect]]
27
+ end
28
+
29
+ def find_from_dns_seeds
30
+ 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
37
+ end
38
+ }.flatten.compact
39
+ end
40
+ end
41
+ end
42
+ end