devp2p 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -0
  3. data/README.md +22 -0
  4. data/lib/devp2p.rb +57 -0
  5. data/lib/devp2p/app_helper.rb +85 -0
  6. data/lib/devp2p/base_app.rb +80 -0
  7. data/lib/devp2p/base_protocol.rb +136 -0
  8. data/lib/devp2p/base_service.rb +55 -0
  9. data/lib/devp2p/command.rb +82 -0
  10. data/lib/devp2p/configurable.rb +32 -0
  11. data/lib/devp2p/connection_monitor.rb +77 -0
  12. data/lib/devp2p/control.rb +32 -0
  13. data/lib/devp2p/crypto.rb +73 -0
  14. data/lib/devp2p/crypto/ecc_x.rb +133 -0
  15. data/lib/devp2p/crypto/ecies.rb +134 -0
  16. data/lib/devp2p/discovery.rb +118 -0
  17. data/lib/devp2p/discovery/address.rb +83 -0
  18. data/lib/devp2p/discovery/kademlia_protocol_adapter.rb +11 -0
  19. data/lib/devp2p/discovery/node.rb +32 -0
  20. data/lib/devp2p/discovery/protocol.rb +342 -0
  21. data/lib/devp2p/discovery/transport.rb +105 -0
  22. data/lib/devp2p/exception.rb +30 -0
  23. data/lib/devp2p/frame.rb +197 -0
  24. data/lib/devp2p/kademlia.rb +48 -0
  25. data/lib/devp2p/kademlia/k_bucket.rb +178 -0
  26. data/lib/devp2p/kademlia/node.rb +40 -0
  27. data/lib/devp2p/kademlia/protocol.rb +284 -0
  28. data/lib/devp2p/kademlia/routing_table.rb +131 -0
  29. data/lib/devp2p/kademlia/wire_interface.rb +30 -0
  30. data/lib/devp2p/multiplexed_session.rb +110 -0
  31. data/lib/devp2p/multiplexer.rb +358 -0
  32. data/lib/devp2p/p2p_protocol.rb +170 -0
  33. data/lib/devp2p/packet.rb +35 -0
  34. data/lib/devp2p/peer.rb +329 -0
  35. data/lib/devp2p/peer_errors.rb +35 -0
  36. data/lib/devp2p/peer_manager.rb +274 -0
  37. data/lib/devp2p/rlpx_session.rb +434 -0
  38. data/lib/devp2p/sync_queue.rb +76 -0
  39. data/lib/devp2p/utils.rb +106 -0
  40. data/lib/devp2p/version.rb +13 -0
  41. data/lib/devp2p/wired_service.rb +30 -0
  42. metadata +227 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: c9730d697b44cd5f35301e1010636cfa941e30db
4
+ data.tar.gz: dc7bed10ffed00dc8353e459906014d541738fcc
5
+ SHA512:
6
+ metadata.gz: 3b8635533c9b4e6211972ea1d0e3973317553b991dcfd664823ed3cbee5c627b7f68e72d411974d47ceafe8eb241e9137563af6b944f09549b18b56f178c3e45
7
+ data.tar.gz: 974eae8a51cc88d2668b07c4baa0f75aa305ab59b283307abea19c7edccc9cf5f84e2567dceb223f96739c820394e597fd0c5deb853172d96fab951de00812c1
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Jan Xie
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 all
13
+ 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 THE
21
+ SOFTWARE.
@@ -0,0 +1,22 @@
1
+ # ruby-devp2p
2
+
3
+ [![MIT License](https://img.shields.io/packagist/l/doctrine/orm.svg)](LICENSE)
4
+ [![travis build status](https://travis-ci.org/janx/ruby-devp2p.svg?branch=master)](https://travis-ci.org/janx/ruby-devp2p)
5
+
6
+ A ruby implementation of Ethereum's DEVp2p framework.
7
+
8
+ ## Fiber Stack Size
9
+
10
+ DEVp2p is build on [Celluloid](https://github.com/celluloid/celluloid/), which
11
+ uses fibers to schedule tasks. Ruby's default limit on fiber stack size is quite
12
+ small, which need to be increased by setting environment variables:
13
+
14
+ ```
15
+ export RUBY_FIBER_VM_STACK_SIZE=104857600 # 100MB
16
+ export RUBY_FIBER_MACHINE_STACK_SIZE=1048576000
17
+ ```
18
+
19
+ ## Resources
20
+
21
+ * [DEVp2p Whitepaper](https://github.com/ethereum/wiki/wiki/libp2p-Whitepaper)
22
+ * [RLPx](https://github.com/ethereum/devp2p/blob/master/rlpx.md)
@@ -0,0 +1,57 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ require 'block_logger'
4
+ require 'celluloid/io'
5
+
6
+ require 'rlp'
7
+
8
+ #Celluloid.exception_handler do |ex|
9
+ # puts "!!!!! Exception raised in celluloid actor:"
10
+ # puts ex
11
+ # puts ex.backtrace[0,20].join("\n")
12
+ #end
13
+
14
+ module DEVp2p
15
+ Logger = BlockLogger
16
+
17
+ TT16 = 2**16
18
+ TT256 = 2**256
19
+
20
+ NODE_URI_SCHEME = 'enode://'.freeze
21
+ end
22
+
23
+ require 'devp2p/version'
24
+
25
+ require 'devp2p/configurable'
26
+ require 'devp2p/utils'
27
+ require 'devp2p/exception'
28
+
29
+ require 'devp2p/control'
30
+ require 'devp2p/base_service'
31
+ require 'devp2p/wired_service'
32
+
33
+ require 'devp2p/kademlia'
34
+ require 'devp2p/discovery'
35
+
36
+ require 'devp2p/sync_queue'
37
+
38
+ require 'devp2p/packet'
39
+ require 'devp2p/frame'
40
+ require 'devp2p/multiplexer'
41
+
42
+ require 'devp2p/crypto'
43
+ require 'devp2p/rlpx_session'
44
+ require 'devp2p/multiplexed_session'
45
+
46
+ require 'devp2p/command'
47
+ require 'devp2p/base_protocol'
48
+ require 'devp2p/connection_monitor'
49
+ require 'devp2p/p2p_protocol'
50
+
51
+ require 'devp2p/peer'
52
+ require 'devp2p/peer_errors'
53
+ require 'devp2p/peer_manager'
54
+
55
+ require 'devp2p/base_app'
56
+ require 'devp2p/app_helper'
57
+
@@ -0,0 +1,85 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module DEVp2p
4
+ class AppHelper
5
+
6
+ def run(app_class, service_class, num_nodes: 3, seed: 0, min_peers: 2, max_peers: 2, random_port: false)
7
+ base_port = random_port ? SecureRandom.random_number(50000) + 10000 : 29870
8
+
9
+ bootstrap_node_privkey = Crypto.mk_privkey "#{seed}:udp:0"
10
+ bootstrap_node_pubkey = Crypto.privtopub bootstrap_node_privkey
11
+ enode = Utils.host_port_pubkey_to_uri "0.0.0.0", base_port, bootstrap_node_pubkey
12
+
13
+ services = [Discovery::Transport, PeerManager]#, service_class]
14
+
15
+ base_config = {}
16
+ services.each {|s| Utils.update_config_with_defaults base_config, s.default_config }
17
+
18
+ base_config[:discovery][:bootstrap_nodes] = [enode]
19
+ base_config[:seed] = seed
20
+ base_config[:base_port] = base_port
21
+ base_config[:num_nodes] = num_nodes
22
+ base_config[:min_peers] = min_peers
23
+ base_config[:max_peers] = max_peers
24
+
25
+ apps = []
26
+ num_nodes.times do |node_num|
27
+ app = create_app node_num, base_config, services, app_class
28
+ apps.push app
29
+ end
30
+
31
+ serve_until_stopped apps
32
+ end
33
+
34
+ def create_app(node_num, config, services, app_class)
35
+ num_nodes = config[:num_nodes]
36
+ base_port = config[:base_port]
37
+ seed = config[:seed]
38
+ min_peers = config[:min_peers]
39
+ max_peers = config[:max_peers]
40
+
41
+ raise "invalid node_num" unless node_num < num_nodes
42
+ #raise "invalid min/max peers" unless min_peers <= max_peers && max_peers < num_nodes
43
+
44
+ config = Marshal.load Marshal.dump(config)
45
+ config[:node_num] = node_num
46
+
47
+ config[:node][:privkey_hex] = Utils.encode_hex Crypto.mk_privkey("#{seed}:udp:#{node_num}")
48
+ config[:discovery][:listen_port] = base_port + node_num
49
+ config[:p2p][:listen_port] = base_port + node_num
50
+ config[:p2p][:min_peers] = [min_peers, 10].min
51
+ config[:p2p][:max_peers] = max_peers
52
+ config[:client_version_string] = "NODE#{node_num}"
53
+
54
+ app = app_class.new config
55
+
56
+ services.each do |service|
57
+ raise "invalid service" unless service.instance_of?(Class) && service < BaseService
58
+
59
+ if !app.config[:deactivated_services].include?(service.name)
60
+ raise "service should not be active" if app.services.has_key?(service.name)
61
+ service.register_with_app app
62
+ raise "servier should be active" unless app.services.has_key?(service.name)
63
+ end
64
+ end
65
+
66
+ app
67
+ end
68
+
69
+ def serve_until_stopped(apps)
70
+ apps.each do |app|
71
+ app.start
72
+
73
+ if app.config[:post_app_start_callback]
74
+ app.config[:post_app_start_callback].call(app)
75
+ end
76
+ end
77
+
78
+ apps.each(&:join)
79
+
80
+ # finally stop
81
+ apps.each(&:stop)
82
+ end
83
+
84
+ end
85
+ end
@@ -0,0 +1,80 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+ require 'hashie'
3
+
4
+ module DEVp2p
5
+ class BaseApp
6
+
7
+ extend Configurable
8
+ add_config(
9
+ default_config: {
10
+ client_version_string: "ruby-devp2p #{VersionString}",
11
+ deactivated_services: []
12
+ }
13
+ )
14
+
15
+ attr :config, :services
16
+
17
+ def initialize(config=default_config)
18
+ @config = Utils.update_config_with_defaults config, default_config
19
+ @registry = Celluloid::Supervision::Configuration.new
20
+ @services = Hashie::Mash.new
21
+ end
22
+
23
+ ##
24
+ # Registers protocol with app, which will be accessible as
25
+ # `app.services.<protocol_name>` (e.g. `app.services.p2p` or
26
+ # `app.services.eth`)
27
+ #
28
+ def register_service(klass, *args)
29
+ raise ArgumentError, "service must be instance of BaseService" unless klass.instance_of?(Class) && klass < BaseService
30
+ raise ArgumentError, "service #{klass.name} already registered" if services.has_key?(klass.name)
31
+
32
+ logger.info "registering service", service: klass.name
33
+
34
+ registry_name = "#{object_id}__#{klass.name}"
35
+ @registry.define type: klass, as: registry_name, args: args
36
+ services[klass.name] = nil
37
+
38
+ klass
39
+ end
40
+
41
+ def deregister_service(service)
42
+ raies NotImplemented
43
+ #raise ArgumentError, "service must be instance of BaseService" unless service.is_a?(BaseService)
44
+ #services.delete(service.name)
45
+ #unlink service
46
+ end
47
+
48
+ def start
49
+ @registry.deploy
50
+
51
+ services.keys.each do |k|
52
+ registry_name = "#{object_id}__#{k}"
53
+ services[k] = Celluloid::Actor[registry_name]
54
+ services[k].start
55
+ end
56
+ end
57
+
58
+ def stop
59
+ services.keys.each do |k|
60
+ services[k].stop if services[k].alive?
61
+ services[k] = nil
62
+ end
63
+
64
+ @registry.shutdown
65
+ end
66
+
67
+ def join
68
+ @services.each_value do |service|
69
+ Celluloid::Actor.join service
70
+ end
71
+ end
72
+
73
+ private
74
+
75
+ def logger
76
+ @logger ||= Logger.new 'app'
77
+ end
78
+
79
+ end
80
+ end
@@ -0,0 +1,136 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+
3
+ module DEVp2p
4
+
5
+ ##
6
+ # A protocol mediates between the network and the service. It implements a
7
+ # collection of commands.
8
+ #
9
+ # For each command X the following methods are created at initialization:
10
+ #
11
+ # * `packet = protocol.create_X(*args, **kwargs)`
12
+ # * `protocol.send_X(*args, **kwargs)`, which is a shortcut for `send_packet`
13
+ # plus `create_X`.
14
+ # * `protocol.receive_X(data)`
15
+ #
16
+ # On `protocol.receive_packet`, the packet is deserialized according to the
17
+ # `command.structure` and the `command.receive` method called with a hash
18
+ # containing the received data.
19
+ #
20
+ # The default implementation of `command.receive` calls callbacks which can
21
+ # be registered in a list which is available as: `protocol.receive_X_callbacks`.
22
+ #
23
+ class BaseProtocol
24
+ include Celluloid
25
+ include Control
26
+
27
+ extend Configurable
28
+ add_config(
29
+ name: '',
30
+ protocol_id: 0,
31
+ version: 0,
32
+ max_cmd_id: 0 # reserved cmd space
33
+ )
34
+
35
+ attr :peer, :service, :cmd_by_id
36
+
37
+ def initialize(peer, service)
38
+ raise ArgumentError, 'service must be WiredService' unless service.is_a?(WiredService)
39
+ raise ArgumentError, 'peer.send_packet must be callable' unless peer.respond_to?(:send_packet)
40
+
41
+ @peer = peer
42
+ @service = service
43
+
44
+ initialize_control
45
+
46
+ setup
47
+ end
48
+
49
+ def start
50
+ logger.debug 'starting', proto: Actor.current
51
+ service.on_wire_protocol_start Actor.current
52
+ super
53
+ end
54
+
55
+ def stop
56
+ logger.debug 'stopping', proto: Actor.current
57
+ service.on_wire_protocol_stop Actor.current
58
+ super
59
+ end
60
+
61
+ def _run
62
+ # pass
63
+ end
64
+
65
+ def receive_packet(packet)
66
+ cmd_name = @cmd_by_id[packet.cmd_id]
67
+ cmd = "receive_#{cmd_name}"
68
+ send cmd, packet
69
+ rescue ProtocolError => e
70
+ logger.warn "protocol exception, stopping", error: e
71
+ stop
72
+ end
73
+
74
+ def send_packet(packet)
75
+ peer.send_packet packet
76
+ end
77
+
78
+ def to_s
79
+ "<#{name} #{peer}>"
80
+ end
81
+ alias :inspect :to_s
82
+
83
+ private
84
+
85
+ def logger
86
+ @logger ||= Logger.new('protocol')
87
+ end
88
+
89
+ def setup
90
+ klasses = []
91
+ self.class.constants.each do |name|
92
+ c = self.class.const_get name
93
+ klasses.push(c) if c.instance_of?(Class) && c < Command
94
+ end
95
+
96
+ raise DuplicatedCommand unless klasses.map(&:cmd_id).uniq.size == klasses.size
97
+
98
+ proto = Actor.current
99
+
100
+ klasses.each do |klass|
101
+ instance = klass.new
102
+
103
+ # decode rlp, create hash, call receive
104
+ receive = lambda do |packet|
105
+ raise ArgumentError unless packet.is_a?(Packet)
106
+ instance.receive proto, klass.decode_payload(packet.payload)
107
+ end
108
+
109
+ # get data, rlp encode, return packet
110
+ create = lambda do |*args|
111
+ res = instance.create(proto, *args)
112
+ payload = klass.encode_payload res
113
+ Packet.new protocol_id, klass.cmd_id, payload
114
+ end
115
+
116
+ # create and send packet
117
+ send_packet = lambda do |*args|
118
+ packet = create.call *args
119
+ send_packet packet
120
+ end
121
+
122
+ name = klass.name.split('::').last.downcase
123
+ singleton_class.send(:define_method, "receive_#{name}", &receive)
124
+ singleton_class.send(:define_method, "create_#{name}", &create)
125
+ singleton_class.send(:define_method, "send_#{name}", &send_packet)
126
+ singleton_class.send(:define_method, "receive_#{name}_callbacks") do
127
+ instance.receive_callbacks
128
+ end
129
+ end
130
+
131
+ @cmd_by_id = klasses.map {|k| [k.cmd_id, Utils.underscore(k.name)] }.to_h
132
+ end
133
+
134
+ end
135
+
136
+ end
@@ -0,0 +1,55 @@
1
+ # -*- encoding : ascii-8bit -*-
2
+ module DEVp2p
3
+
4
+ ##
5
+ # Service instances are added to the application under
6
+ # `app.services.<service_name>`.
7
+ #
8
+ # App should be passed to the service in order to query other services.
9
+ #
10
+ # Services must be an actor. If a service spawns additional services, it's
11
+ # responsible to stop them.
12
+ #
13
+ class BaseService
14
+ include Celluloid
15
+ #finalizer :stop
16
+ include Control
17
+
18
+ extend Configurable
19
+ add_config(
20
+ name: '',
21
+ default_config: {name: {}},
22
+ required_services: []
23
+ )
24
+
25
+ class <<self
26
+ ##
27
+ # Services know best how to initiate themselves. Create a service
28
+ # instance, probably based on `app.config` and `app.services`.
29
+ #
30
+ def register_with_app(app)
31
+ app.register_service self, app
32
+ end
33
+ end
34
+
35
+ attr :app, :config
36
+
37
+ def initialize(app)
38
+ @app = app
39
+ @config = Utils.update_config_with_defaults app.config, default_config
40
+
41
+ initialize_control
42
+
43
+ available_services = app.services.each_value.map(&:class)
44
+ required_services.each do |r|
45
+ raise MissingRequiredServiceError, "require service #{r}" unless available_services.include?(r)
46
+ end
47
+ end
48
+
49
+ def to_s
50
+ "<Service #{name}##{object_id}>"
51
+ end
52
+ alias inspect to_s
53
+
54
+ end
55
+ end