devp2p 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 (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