ciri 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require 'ciri/rlp/serializable'
25
+
26
+ module Ciri
27
+ module DevP2P
28
+ module RLPX
29
+
30
+ # RLPX message
31
+ class Message
32
+ include Ciri::RLP::Serializable
33
+
34
+ schema [
35
+ {code: Integer},
36
+ {size: Integer},
37
+ :payload,
38
+ :received_at
39
+ ]
40
+ default_data(received_at: nil)
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require 'ciri/key'
25
+
26
+ module Ciri
27
+ module DevP2P
28
+ module RLPX
29
+
30
+ # present node id
31
+ class NodeID
32
+
33
+ class << self
34
+ def from_raw_id(raw_id)
35
+ NodeID.new(Ciri::Key.new(raw_public_key: "\x04".b + raw_id))
36
+ end
37
+ end
38
+
39
+ attr_reader :public_key
40
+
41
+ alias key public_key
42
+
43
+ def initialize(public_key)
44
+ unless public_key.is_a?(Ciri::Key)
45
+ raise TypeError.new("expect Ciri::Key but get #{public_key.class}")
46
+ end
47
+ @public_key = public_key
48
+ end
49
+
50
+ def id
51
+ @id ||= key.raw_public_key[1..-1]
52
+ end
53
+
54
+ def == (other)
55
+ self.class == other.class && id == other.id
56
+ end
57
+ end
58
+
59
+ class Node
60
+ attr_reader :node_id, :ip, :udp_port, :tcp_port, :added_at
61
+
62
+ def initialize(node_id:, ip:, udp_port:, tcp_port:, added_at: nil)
63
+ @node_id = node_id
64
+ @ip = ip
65
+ @udp_port = udp_port
66
+ @tcp_port = tcp_port
67
+ @added_at = added_at
68
+ end
69
+
70
+ def == (other)
71
+ self.class == other.class && node_id == other.node_id
72
+ end
73
+ end
74
+
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require 'ciri/rlp/serializable'
25
+
26
+ module Ciri
27
+ module DevP2P
28
+ module RLPX
29
+
30
+ class Cap
31
+ include Ciri::RLP::Serializable
32
+
33
+ schema [
34
+ :name,
35
+ {version: Integer}
36
+ ]
37
+ end
38
+
39
+ # handle protocol handshake
40
+ class ProtocolHandshake
41
+ include Ciri::RLP::Serializable
42
+
43
+ schema [
44
+ {version: Integer},
45
+ :name,
46
+ {caps: [Cap]},
47
+ {listen_port: Integer},
48
+ :id
49
+ ]
50
+ default_data(listen_port: 0)
51
+ end
52
+
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+
24
+ require 'ciri/key'
25
+ require 'ciri/rlp/serializable'
26
+
27
+ module Ciri
28
+ module DevP2P
29
+ module RLPX
30
+ MESSAGES = {
31
+ handshake: 0x00,
32
+ discover: 0x01,
33
+ ping: 0x02,
34
+ pong: 0x03
35
+ }.freeze
36
+
37
+ BASE_PROTOCOL_VERSION = 5
38
+ BASE_PROTOCOL_LENGTH = 16
39
+ BASE_PROTOCOL_MAX_MSG_SIZE = 2 * 1024
40
+ SNAPPY_PROTOCOL_VERSION = 5
41
+
42
+ ### messages
43
+
44
+ class AuthMsgV4
45
+ include Ciri::RLP::Serializable
46
+
47
+ schema [
48
+ :signature,
49
+ :initiator_pubkey,
50
+ :nonce,
51
+ {version: Integer}
52
+ ]
53
+
54
+ # keep this field let client known how to format(plain or eip8)
55
+ attr_accessor :got_plain
56
+ end
57
+
58
+ class AuthRespV4
59
+ include Ciri::RLP::Serializable
60
+
61
+ schema [
62
+ :random_pubkey,
63
+ :nonce,
64
+ {version: Integer}
65
+ ]
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
4
+ #
5
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ # of this software and associated documentation files (the "Software"), to deal
7
+ # in the Software without restriction, including without limitation the rights
8
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ # copies of the Software, and to permit persons to whom the Software is
10
+ # furnished to do so, subject to the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be included in
13
+ # all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ # THE SOFTWARE.
22
+
23
+
24
+ module Ciri
25
+ module DevP2P
26
+ module RLPX
27
+
28
+ # class used to store rplx protocol secrets
29
+ class Secrets
30
+ attr_reader :remote_id, :aes, :mac
31
+ attr_accessor :egress_mac, :ingress_mac
32
+
33
+ def initialize(remote_id: nil, aes:, mac:)
34
+ @remote_id = remote_id
35
+ @aes = aes
36
+ @mac = mac
37
+ end
38
+
39
+ def ==(other)
40
+ self.class == other.class &&
41
+ remote_id == other.remote &&
42
+ aes == other.aes &&
43
+ mac == other.mac
44
+ end
45
+ end
46
+
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,207 @@
1
+ # frozen_string_literal: true
2
+
3
+
4
+ # Copyright (c) 2018, by Jiang Jinyang. <https://justjjy.com>
5
+ #
6
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ # of this software and associated documentation files (the "Software"), to deal
8
+ # in the Software without restriction, including without limitation the rights
9
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ # copies of the Software, and to permit persons to whom the Software is
11
+ # furnished to do so, subject to the following conditions:
12
+ #
13
+ # The above copyright notice and this permission notice shall be included in
14
+ # all copies or substantial portions of the Software.
15
+ #
16
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
+ # THE SOFTWARE.
23
+
24
+
25
+ require 'concurrent'
26
+ require 'forwardable'
27
+ require_relative 'rlpx/connection'
28
+ require_relative 'rlpx/protocol_handshake'
29
+ require_relative 'peer'
30
+ require_relative 'actor'
31
+
32
+ module Ciri
33
+ module DevP2P
34
+
35
+ # DevP2P Server
36
+ # maintain connection, node discovery, rlpx handshake and protocols
37
+ class Server
38
+ include RLPX
39
+
40
+ MAX_ACTIVE_DIAL_TASKS = 16
41
+ DEFAULT_MAX_PENDING_PEERS = 50
42
+ DEFAULT_DIAL_RATIO = 3
43
+
44
+ class Error < StandardError
45
+ end
46
+ class UselessPeerError < Error
47
+ end
48
+
49
+ attr_reader :handshake, :dial, :scheduler, :protocol_manage, :protocols
50
+ attr_accessor :logger, :bootstrap_nodes
51
+
52
+ def initialize(private_key:, protocol_manage:, executor:)
53
+ @private_key = private_key
54
+ @name = 'ciri'
55
+ @scheduler = Scheduler.new(self, executor: executor)
56
+ @protocol_manage = protocol_manage
57
+ @protocols = protocol_manage.protocols
58
+ end
59
+
60
+ def start
61
+ #TODO start dialer, discovery nodes and connect to them
62
+ #TODO listen udp, for discovery protocol
63
+
64
+ server_node_id = NodeID.new(@private_key)
65
+ caps = [Cap.new(name: 'eth', version: 63)]
66
+ @handshake = ProtocolHandshake.new(version: BASE_PROTOCOL_VERSION, name: @name, id: server_node_id.id, caps: caps)
67
+ # start listen tcp
68
+ @dial = Dial.new(self)
69
+ @protocol_manage.executor ||= @scheduler.executor
70
+ @protocol_manage.start
71
+ @scheduler.start
72
+ end
73
+
74
+ def setup_connection(node)
75
+ socket = TCPSocket.new(node.ip, node.tcp_port)
76
+ c = Connection.new(socket)
77
+ c.encryption_handshake!(private_key: @private_key, node_id: node.node_id)
78
+ remote_handshake = c.protocol_handshake!(handshake)
79
+ scheduler << [:add_peer, c, remote_handshake]
80
+ end
81
+
82
+ def protocol_handshake_checks(handshake)
83
+ if !protocols.empty? && count_matching_protocols(protocols, handshake.caps) == 0
84
+ raise UselessPeerError.new('discovery useless peer')
85
+ end
86
+ end
87
+
88
+ def count_matching_protocols(protocols, caps)
89
+ #TODO implement this
90
+ 1
91
+ end
92
+
93
+ # scheduler task
94
+ class Task
95
+ attr_reader :name
96
+
97
+ def initialize(name:, &blk)
98
+ @name = name
99
+ @blk = blk
100
+ end
101
+
102
+ def call(*args)
103
+ @blk.call(*args)
104
+ end
105
+ end
106
+
107
+ # Discovery and dial new nodes
108
+ class Dial
109
+ def initialize(server)
110
+ @server = server
111
+ @cache = []
112
+ end
113
+
114
+ # return new tasks to find peers
115
+ def find_peer_tasks(running_count, peers, now)
116
+ node = @server.bootstrap_nodes[0]
117
+ return [] if @cache.include?(node)
118
+ @cache << node
119
+ [Task.new(name: 'find peer') {
120
+ @server.setup_connection(node)
121
+ }]
122
+ end
123
+ end
124
+
125
+ class Scheduler
126
+ include Actor
127
+
128
+ extend Forwardable
129
+
130
+ attr_reader :server
131
+ def_delegators :server, :logger
132
+
133
+ def initialize(server, executor:)
134
+ @server = server
135
+ @queued_tasks = []
136
+ @running_tasks = []
137
+ @peers = {}
138
+ # init actor
139
+ super(executor: executor)
140
+ end
141
+
142
+ # called by actor loop
143
+ def loop_callback
144
+ schedule_tasks
145
+ yield(wait_message: false)
146
+ end
147
+
148
+ def start_tasks(tasks)
149
+ tasks = tasks.dup
150
+ while @running_tasks.size < MAX_ACTIVE_DIAL_TASKS
151
+ break unless (task = tasks.pop)
152
+ executor.post(task) do |task|
153
+ task.call
154
+ self << [:task_done, task]
155
+ end
156
+ @running_tasks << task
157
+ end
158
+ tasks
159
+ end
160
+
161
+ # invoke tasks, and prepare search peer tasks
162
+ def schedule_tasks
163
+ @queued_tasks = start_tasks(@queued_tasks)
164
+ if @queued_tasks.size < MAX_ACTIVE_DIAL_TASKS
165
+ tasks = server.dial.find_peer_tasks(@running_tasks.size + @queued_tasks.size, @peers, Time.now)
166
+ @queued_tasks += tasks
167
+ end
168
+ end
169
+
170
+ private
171
+ def add_peer(connection, handshake)
172
+ server.protocol_handshake_checks(handshake)
173
+ peer = Peer.new(connection, handshake, server.protocols)
174
+ # set actor executor
175
+ peer.executor = executor
176
+ @peers[peer.node_id] = peer
177
+ # run peer logic
178
+ # do sub protocol handshake...
179
+ executor.post {
180
+ peer.start
181
+
182
+ exit_error = nil
183
+ begin
184
+ peer.wait
185
+ rescue StandardError => e
186
+ exit_error = e
187
+ end
188
+ # remove peer
189
+ self << [:remove_peer, peer, exit_error]
190
+ }
191
+ logger.debug("add peer: #{peer}")
192
+ end
193
+
194
+ def remove_peer(peer, *args)
195
+ error, * = args
196
+ logger.debug("remove peer: #{peer}, error: #{error}")
197
+ end
198
+
199
+ def task_done(task, *args)
200
+ logger.debug("task done: #{task.name}")
201
+ end
202
+
203
+ end
204
+
205
+ end
206
+ end
207
+ end