tapyrus 0.1.0

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