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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +12 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +21 -0
- data/README.md +100 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/exe/tapyrusrb-cli +5 -0
- data/exe/tapyrusrbd +41 -0
- data/lib/openassets/marker_output.rb +20 -0
- data/lib/openassets/payload.rb +54 -0
- data/lib/openassets/util.rb +28 -0
- data/lib/openassets.rb +9 -0
- data/lib/tapyrus/base58.rb +38 -0
- data/lib/tapyrus/block.rb +77 -0
- data/lib/tapyrus/block_header.rb +88 -0
- data/lib/tapyrus/bloom_filter.rb +78 -0
- data/lib/tapyrus/chain_params.rb +90 -0
- data/lib/tapyrus/chainparams/mainnet.yml +41 -0
- data/lib/tapyrus/chainparams/regtest.yml +38 -0
- data/lib/tapyrus/chainparams/testnet.yml +41 -0
- data/lib/tapyrus/constants.rb +195 -0
- data/lib/tapyrus/descriptor.rb +147 -0
- data/lib/tapyrus/ext_key.rb +337 -0
- data/lib/tapyrus/key.rb +296 -0
- data/lib/tapyrus/key_path.rb +26 -0
- data/lib/tapyrus/logger.rb +42 -0
- data/lib/tapyrus/merkle_tree.rb +149 -0
- data/lib/tapyrus/message/addr.rb +35 -0
- data/lib/tapyrus/message/base.rb +28 -0
- data/lib/tapyrus/message/block.rb +46 -0
- data/lib/tapyrus/message/block_transaction_request.rb +45 -0
- data/lib/tapyrus/message/block_transactions.rb +31 -0
- data/lib/tapyrus/message/block_txn.rb +27 -0
- data/lib/tapyrus/message/cmpct_block.rb +42 -0
- data/lib/tapyrus/message/error.rb +10 -0
- data/lib/tapyrus/message/fee_filter.rb +27 -0
- data/lib/tapyrus/message/filter_add.rb +28 -0
- data/lib/tapyrus/message/filter_clear.rb +17 -0
- data/lib/tapyrus/message/filter_load.rb +39 -0
- data/lib/tapyrus/message/get_addr.rb +17 -0
- data/lib/tapyrus/message/get_block_txn.rb +27 -0
- data/lib/tapyrus/message/get_blocks.rb +29 -0
- data/lib/tapyrus/message/get_data.rb +21 -0
- data/lib/tapyrus/message/get_headers.rb +28 -0
- data/lib/tapyrus/message/header_and_short_ids.rb +57 -0
- data/lib/tapyrus/message/headers.rb +35 -0
- data/lib/tapyrus/message/headers_parser.rb +24 -0
- data/lib/tapyrus/message/inv.rb +21 -0
- data/lib/tapyrus/message/inventories_parser.rb +23 -0
- data/lib/tapyrus/message/inventory.rb +51 -0
- data/lib/tapyrus/message/mem_pool.rb +17 -0
- data/lib/tapyrus/message/merkle_block.rb +42 -0
- data/lib/tapyrus/message/network_addr.rb +63 -0
- data/lib/tapyrus/message/not_found.rb +21 -0
- data/lib/tapyrus/message/ping.rb +30 -0
- data/lib/tapyrus/message/pong.rb +26 -0
- data/lib/tapyrus/message/prefilled_tx.rb +29 -0
- data/lib/tapyrus/message/reject.rb +46 -0
- data/lib/tapyrus/message/send_cmpct.rb +43 -0
- data/lib/tapyrus/message/send_headers.rb +16 -0
- data/lib/tapyrus/message/tx.rb +30 -0
- data/lib/tapyrus/message/ver_ack.rb +17 -0
- data/lib/tapyrus/message/version.rb +69 -0
- data/lib/tapyrus/message.rb +70 -0
- data/lib/tapyrus/mnemonic/wordlist/chinese_simplified.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/chinese_traditional.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/english.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/french.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/italian.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/japanese.txt +2048 -0
- data/lib/tapyrus/mnemonic/wordlist/spanish.txt +2048 -0
- data/lib/tapyrus/mnemonic.rb +77 -0
- data/lib/tapyrus/network/connection.rb +73 -0
- data/lib/tapyrus/network/message_handler.rb +241 -0
- data/lib/tapyrus/network/peer.rb +223 -0
- data/lib/tapyrus/network/peer_discovery.rb +42 -0
- data/lib/tapyrus/network/pool.rb +135 -0
- data/lib/tapyrus/network.rb +13 -0
- data/lib/tapyrus/node/cli.rb +112 -0
- data/lib/tapyrus/node/configuration.rb +38 -0
- data/lib/tapyrus/node/spv.rb +79 -0
- data/lib/tapyrus/node.rb +7 -0
- data/lib/tapyrus/opcodes.rb +178 -0
- data/lib/tapyrus/out_point.rb +44 -0
- data/lib/tapyrus/rpc/http_server.rb +65 -0
- data/lib/tapyrus/rpc/request_handler.rb +150 -0
- data/lib/tapyrus/rpc/tapyrus_core_client.rb +72 -0
- data/lib/tapyrus/rpc.rb +7 -0
- data/lib/tapyrus/script/multisig.rb +92 -0
- data/lib/tapyrus/script/script.rb +551 -0
- data/lib/tapyrus/script/script_error.rb +111 -0
- data/lib/tapyrus/script/script_interpreter.rb +668 -0
- data/lib/tapyrus/script/tx_checker.rb +81 -0
- data/lib/tapyrus/script_witness.rb +38 -0
- data/lib/tapyrus/secp256k1/native.rb +174 -0
- data/lib/tapyrus/secp256k1/ruby.rb +123 -0
- data/lib/tapyrus/secp256k1.rb +12 -0
- data/lib/tapyrus/slip39/share.rb +122 -0
- data/lib/tapyrus/slip39/sss.rb +245 -0
- data/lib/tapyrus/slip39/wordlist/english.txt +1024 -0
- data/lib/tapyrus/slip39.rb +93 -0
- data/lib/tapyrus/store/chain_entry.rb +67 -0
- data/lib/tapyrus/store/db/level_db.rb +98 -0
- data/lib/tapyrus/store/db.rb +9 -0
- data/lib/tapyrus/store/spv_chain.rb +101 -0
- data/lib/tapyrus/store.rb +9 -0
- data/lib/tapyrus/tx.rb +347 -0
- data/lib/tapyrus/tx_in.rb +89 -0
- data/lib/tapyrus/tx_out.rb +74 -0
- data/lib/tapyrus/util.rb +133 -0
- data/lib/tapyrus/validation.rb +115 -0
- data/lib/tapyrus/version.rb +3 -0
- data/lib/tapyrus/wallet/account.rb +151 -0
- data/lib/tapyrus/wallet/base.rb +162 -0
- data/lib/tapyrus/wallet/db.rb +81 -0
- data/lib/tapyrus/wallet/master_key.rb +110 -0
- data/lib/tapyrus/wallet.rb +8 -0
- data/lib/tapyrus.rb +219 -0
- data/tapyrusrb.conf.sample +0 -0
- data/tapyrusrb.gemspec +47 -0
- 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
|