xrbp 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +103 -5
  3. data/examples/accounts.rb +13 -0
  4. data/examples/autoconnect_timeout.rb +5 -1
  5. data/examples/autorety.rb +7 -0
  6. data/examples/crawl_nodes.rb +32 -0
  7. data/examples/dsl/account.rb +16 -0
  8. data/examples/dsl/ledger.rb +7 -0
  9. data/examples/dsl/ledger_subscribe.rb +18 -0
  10. data/examples/dsl/validators.rb +8 -0
  11. data/examples/gateways.rb +8 -0
  12. data/examples/latest_account.rb +4 -0
  13. data/examples/ledger_multi_subscribe.rb +1 -1
  14. data/examples/ledger_subscribe.rb +2 -2
  15. data/examples/market.rb +13 -0
  16. data/examples/username.rb +12 -0
  17. data/examples/validator.rb +8 -0
  18. data/lib/xrbp.rb +5 -1
  19. data/lib/xrbp/common.rb +10 -0
  20. data/lib/xrbp/core_ext.rb +24 -0
  21. data/lib/xrbp/dsl.rb +25 -0
  22. data/lib/xrbp/dsl/accounts.rb +13 -0
  23. data/lib/xrbp/dsl/ledgers.rb +23 -0
  24. data/lib/xrbp/dsl/validators.rb +10 -0
  25. data/lib/xrbp/dsl/webclient.rb +8 -0
  26. data/lib/xrbp/dsl/websocket.rb +28 -0
  27. data/lib/xrbp/model.rb +4 -0
  28. data/lib/xrbp/model/account.rb +142 -1
  29. data/lib/xrbp/model/base.rb +1 -0
  30. data/lib/xrbp/model/gateway.rb +24 -0
  31. data/lib/xrbp/model/ledger.rb +30 -1
  32. data/lib/xrbp/model/market.rb +52 -0
  33. data/lib/xrbp/model/node.rb +131 -0
  34. data/lib/xrbp/model/parsers/account.rb +44 -0
  35. data/lib/xrbp/model/parsers/gateway.rb +40 -0
  36. data/lib/xrbp/model/parsers/market.rb +28 -0
  37. data/lib/xrbp/model/parsers/node.rb +19 -0
  38. data/lib/xrbp/model/parsers/quote.rb +47 -0
  39. data/lib/xrbp/model/parsers/validator.rb +25 -0
  40. data/lib/xrbp/model/validator.rb +24 -0
  41. data/lib/xrbp/plugins.rb +6 -0
  42. data/lib/xrbp/plugins/base.rb +10 -0
  43. data/lib/xrbp/plugins/has_plugin.rb +45 -0
  44. data/lib/xrbp/plugins/has_result_parsers.rb +27 -0
  45. data/lib/xrbp/plugins/plugin_registry.rb +20 -0
  46. data/lib/xrbp/plugins/result_parser.rb +19 -0
  47. data/lib/xrbp/terminatable.rb +19 -0
  48. data/lib/xrbp/thread_registry.rb +22 -0
  49. data/lib/xrbp/version.rb +1 -1
  50. data/lib/xrbp/webclient.rb +2 -0
  51. data/lib/xrbp/webclient/connection.rb +100 -0
  52. data/lib/xrbp/webclient/plugins.rb +8 -0
  53. data/lib/xrbp/webclient/plugins/autoretry.rb +54 -0
  54. data/lib/xrbp/webclient/plugins/result_parser.rb +23 -0
  55. data/lib/xrbp/websocket/client.rb +85 -24
  56. data/lib/xrbp/websocket/cmds/account_info.rb +4 -0
  57. data/lib/xrbp/websocket/cmds/account_lines.rb +5 -0
  58. data/lib/xrbp/websocket/cmds/account_objects.rb +4 -0
  59. data/lib/xrbp/websocket/cmds/account_offers.rb +5 -0
  60. data/lib/xrbp/websocket/cmds/account_tx.rb +4 -0
  61. data/lib/xrbp/websocket/cmds/book_offers.rb +4 -0
  62. data/lib/xrbp/websocket/cmds/ledger.rb +3 -0
  63. data/lib/xrbp/websocket/cmds/ledger_entry.rb +4 -0
  64. data/lib/xrbp/websocket/cmds/paginated.rb +4 -1
  65. data/lib/xrbp/websocket/cmds/server_info.rb +4 -0
  66. data/lib/xrbp/websocket/cmds/subscribe.rb +4 -0
  67. data/lib/xrbp/websocket/command.rb +11 -0
  68. data/lib/xrbp/websocket/connection.rb +75 -22
  69. data/lib/xrbp/websocket/message.rb +5 -2
  70. data/lib/xrbp/websocket/multi/fallback.rb +2 -4
  71. data/lib/xrbp/websocket/multi/multi_connection.rb +37 -2
  72. data/lib/xrbp/websocket/multi/parallel.rb +2 -4
  73. data/lib/xrbp/websocket/multi/prioritized.rb +2 -4
  74. data/lib/xrbp/websocket/multi/round_robin.rb +4 -0
  75. data/lib/xrbp/websocket/plugins.rb +1 -7
  76. data/lib/xrbp/websocket/plugins/autoconnect.rb +25 -5
  77. data/lib/xrbp/websocket/plugins/command_dispatcher.rb +5 -0
  78. data/lib/xrbp/websocket/plugins/command_paginator.rb +9 -8
  79. data/lib/xrbp/websocket/plugins/connection_timeout.rb +27 -16
  80. data/lib/xrbp/websocket/plugins/message_dispatcher.rb +60 -30
  81. data/lib/xrbp/websocket/plugins/result_parser.rb +19 -19
  82. data/lib/xrbp/websocket/socket.rb +23 -6
  83. metadata +118 -8
  84. data/lib/xrbp/network.rb +0 -6
  85. data/lib/xrbp/network/nodes.rb +0 -8
  86. data/lib/xrbp/websocket/has_plugin.rb +0 -30
@@ -0,0 +1,10 @@
1
+ module XRBP
2
+ module DSL
3
+ # Return list of all validators
4
+ #
5
+ # @return [Array<Hash>] list of validators retrieved
6
+ def validators
7
+ Model::Validator.all :connection => webclient
8
+ end
9
+ end # module DSL
10
+ end # module XRBP
@@ -0,0 +1,8 @@
1
+ module XRBP
2
+ module DSL
3
+ # Client which may be used to access HTTP resources
4
+ def webclient
5
+ @webclient ||= WebClient::Connection.new
6
+ end
7
+ end # module DSL
8
+ end # module XRBP
@@ -0,0 +1,28 @@
1
+ module XRBP
2
+ module DSL
3
+ # Default websocket endpoints. Override to specify
4
+ # different ones.
5
+ def websocket_endpoints
6
+ ["wss://s1.ripple.com:443", "wss://s2.ripple.com:443"]
7
+ end
8
+
9
+ # Client which may be used to access websocket endpoints.
10
+ #
11
+ # By default a RoundRobin strategy will be used to cycle
12
+ # through specified endpoints.
13
+ def websocket
14
+ @websocket ||= WebSocket::RoundRobin.new *websocket_endpoints
15
+ end
16
+
17
+ # Register a callback to be invoked when messages are received
18
+ # via websocket connections
19
+ def websocket_msg(&bl)
20
+ websocket.on :message, &bl
21
+ end
22
+
23
+ # Block until all websocket connections are closed
24
+ def websocket_wait
25
+ websocket.wait_for_close
26
+ end
27
+ end # module DSL
28
+ end # module XRBP
data/lib/xrbp/model.rb CHANGED
@@ -1,3 +1,7 @@
1
1
  require_relative './model/base'
2
2
  require_relative './model/ledger'
3
3
  require_relative './model/account'
4
+ require_relative './model/validator'
5
+ require_relative './model/gateway'
6
+ require_relative './model/market'
7
+ require_relative './model/node'
@@ -1,27 +1,168 @@
1
+ require 'fileutils'
2
+ require_relative './parsers/account'
3
+
1
4
  module XRBP
2
5
  module Model
3
6
  class Account < Base
4
7
  extend Base::ClassMethods
5
8
 
9
+ DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
10
+
6
11
  attr_accessor :id
7
12
 
8
- def self.create(opts={})
13
+ # Local data cache location
14
+ def self.cache
15
+ @cache ||= File.expand_path("~/.xrbp/accounts/")
16
+ end
17
+
18
+ # All cached accounts
19
+ def self.cached
20
+ Dir.glob("#{cache}/*").collect { |f|
21
+ next nil if f == "#{cache}marker" ||
22
+ f == "#{cache}start"
23
+ begin
24
+ JSON.parse(File.read(f))
25
+ rescue
26
+ nil
27
+ end
28
+ }.flatten.compact
9
29
  end
10
30
 
31
+ # TODO invoke gen account command via websocket
32
+ #def self.create(opts={})
33
+ #end
34
+
35
+ # TODO 'parallel accounts' method,
36
+ # - split period between Genesis and Time.now into
37
+ # N-segments (of configurable length: hours, days,
38
+ # months, etc)
39
+ # - parallel retrieve segments specifying start param
40
+ # & following markers until data is no longer
41
+ # in-domain
42
+ # - emit :account signal w/ each account during process
43
+ # & reassemble complete set after
44
+
45
+ # Retrieve all accounts via WebClient::Connection
46
+ #
47
+ # @param opts [Hash] options to retrieve accounts with
48
+ # @option opts [WebClient::Connection] :connection Connection
49
+ # to use to retrieve accounts
50
+ def self.all(opts={})
51
+ set_opts(opts)
52
+ FileUtils.mkdir_p(cache) unless File.exist?(cache)
53
+
54
+ # start at last marker
55
+ marker = File.exist?("#{cache}/marker") ?
56
+ File.read("#{cache}/marker") : nil
57
+
58
+ # load start time, if set
59
+ start = File.exist?("#{cache}/start") ?
60
+ File.read("#{cache}/start") :
61
+ GENESIS_TIME.strftime(DATE_FORMAT)
62
+
63
+ # Parse results
64
+ connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
65
+ connection.add_plugin Parsers::AccountInfo unless connection.plugin?(Parsers::AccountInfo)
66
+
67
+ # Retrieve data until complete
68
+ accounts = []
69
+ finished = false
70
+ until finished || connection.force_quit?
71
+ # HTTP request
72
+ connection.url = "https://data.ripple.com/v2/accounts/?"\
73
+ "start=#{start}&limit=1000&marker=#{marker}"
74
+ res = connection.perform
75
+
76
+ # Cache data
77
+ cache_file = "#{cache}/#{marker || "genesis"}"
78
+ File.write(cache_file, res[:accounts].to_json)
79
+
80
+ # Emit signal
81
+ res[:accounts].each { |acct|
82
+ break if connection.force_quit?
83
+ connection.emit :account, acct
84
+ }
85
+
86
+ break if connection.force_quit?
87
+
88
+ marker = res[:marker]
89
+ accounts += res[:accounts]
90
+
91
+ # Store state, eval exit condition
92
+ File.write("#{cache}/marker", marker.to_s)
93
+ finished = !marker
94
+ end
95
+
96
+ # Store state for next run
97
+ File.write("#{cache}/start",
98
+ accounts.last[:inception]) unless marker
99
+
100
+ accounts
101
+ end
102
+
103
+ # Retrieve latest account using specified WebClient::Connection
104
+ #
105
+ # @param opts [Hash] options to retrieve account with
106
+ # @option opts [WebClient::Connection] :connection Connection
107
+ # to use to retrieve account
108
+ def self.latest(opts={})
109
+ set_opts(opts)
110
+
111
+ connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
112
+ connection.add_plugin Parsers::AccountInfo unless connection.plugin?(Parsers::AccountInfo)
113
+
114
+ connection.url = "https://data.ripple.com/v2/accounts/?"\
115
+ "descending=true&limit=1000"
116
+ res = connection.perform
117
+ res[:accounts].first
118
+ end
119
+
120
+ ###
121
+
122
+ # Initialize new Account instance
123
+ #
124
+ # @param opts [Hash] options to initialize account with
125
+ # @option opts [String] :id id of account
126
+ # to use to retrieve account
11
127
  def initialize(opts={})
12
128
  @id = opts[:id]
13
129
  super(opts)
14
130
  end
15
131
 
132
+ # Retrieve account info via WebSocket::Connection
133
+ #
134
+ # @param opts [Hash] options to retrieve account info with
135
+ # @option opts [WebSocket::Connection] :connection Connection
136
+ # to use to retrieve account info
16
137
  def info(opts={}, &bl)
17
138
  set_opts(opts)
18
139
  connection.cmd(WebSocket::Cmds::AccountInfo.new(id, full_opts), &bl)
19
140
  end
20
141
 
142
+ # Retrieve account objects via WebSocket::Connection
143
+ #
144
+ # @param opts [Hash] options to retrieve account objects with
145
+ # @option opts [WebSocket::Connection] :connection Connection
146
+ # to use to retrieve account objects
21
147
  def objects(opts={}, &bl)
22
148
  set_opts(opts)
23
149
  connection.cmd(WebSocket::Cmds::AccountObjects.new(id, full_opts), &bl)
24
150
  end
151
+
152
+ # Retrieve account username via WebClient::Connection
153
+ #
154
+ # @param opts [Hash] options to retrieve account username with
155
+ # @option opts [WebClient::Connection] :connection Connection
156
+ # to use to retrieve account username
157
+ def username(opts={}, &bl)
158
+ set_opts(opts)
159
+ connection.url = "https://id.ripple.com/v1/authinfo?username=#{id}"
160
+
161
+ connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
162
+ connection.add_plugin Parsers::AccountUsername unless connection.plugin?(Parsers::AccountUsername)
163
+
164
+ connection.perform
165
+ end
25
166
  end # class Account
26
167
  end # module Model
27
168
  end # module XRBP
@@ -1,5 +1,6 @@
1
1
  module XRBP
2
2
  module Model
3
+ # Base model definition, provides common logic to set connection & opts.
3
4
  class Base
4
5
  module ClassMethods
5
6
  attr_accessor :opts, :connection
@@ -0,0 +1,24 @@
1
+ require_relative './parsers/gateway'
2
+
3
+ module XRBP
4
+ module Model
5
+ class Gateway < Base
6
+ extend Base::ClassMethods
7
+
8
+ # Retrieve list of gateways provided WebClient::Connection.
9
+ #
10
+ # @param opts [Hash] options to retrieve gateway list with
11
+ # @option opts [WebClient::Connection] :connection Connection
12
+ # to use to retrieve gateway list
13
+ def self.all(opts={})
14
+ set_opts(opts)
15
+ connection.url = "https://data.ripple.com/v2/gateways"
16
+
17
+ connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
18
+ connection.add_plugin Parsers::Gateway unless connection.plugin?(Parsers::Gateway)
19
+
20
+ connection.perform
21
+ end
22
+ end # class Gateway
23
+ end # module Model
24
+ end # module XRBP
@@ -10,14 +10,43 @@ module XRBP
10
10
  super(opts)
11
11
  end
12
12
 
13
+ # Retreive specified ledger via WebSocket::Connection
14
+ #
15
+ # @param opts [Hash] options to retrieve ledger with
16
+ # @option opts [WebSocket::Connection] :connection Connection
17
+ # to use to retrieve ledger
13
18
  def sync(opts={}, &bl)
14
19
  set_opts(opts)
15
20
  connection.cmd(WebSocket::Cmds::Ledger.new(id, full_opts), &bl)
16
21
  end
17
22
 
23
+ # Subscribe to ledger stream via WebSocket::Connection
24
+ #
25
+ # @param opts [Hash] options to subscribe to ledger stream with
26
+ # @option opts [WebSocket::Connection] :connection Connection
27
+ # to use to subscribe to ledger stream
18
28
  def self.subscribe(opts={}, &bl)
19
29
  set_opts(opts)
20
- connection.cmd(WebSocket::Cmds::Subscribe.new(:streams => ["ledger"]), &bl)
30
+ conn = connection
31
+ conn.cmd(WebSocket::Cmds::Subscribe.new(:streams => ["ledger"]), &bl)
32
+ conn.on :message do |*args|
33
+ c,msg = args.size > 1 ? [args[0], args[1]] :
34
+ [nil, args[0]]
35
+
36
+ begin
37
+ i = JSON.parse(msg.to_s)
38
+ if i["ledger_hash"] &&
39
+ i["ledger_index"]
40
+ if c
41
+ conn.emit :ledger, c, i
42
+ else
43
+ conn.emit :ledger, i
44
+ conn.parent.emit :ledger, conn, i if conn.parent
45
+ end
46
+ end
47
+ rescue
48
+ end
49
+ end
21
50
  end
22
51
  end # class Ledger
23
52
  end # module Model
@@ -0,0 +1,52 @@
1
+ require_relative './parsers/market'
2
+ require_relative './parsers/quote'
3
+
4
+ module XRBP
5
+ module Model
6
+ class Market < Base
7
+ extend Base::ClassMethods
8
+ # TODO plugabble system to pull in markets from other sources
9
+
10
+ # Retrieve list of markets via WebClient::Connection
11
+ #
12
+ # @param opts [Hash] options to retrieve market list with
13
+ # @option opts [WebClient::Connection] :connection Connection
14
+ # to use to retrieve market list
15
+ def self.all(opts={})
16
+ set_opts(opts)
17
+ connection.url = "https://api.cryptowat.ch/assets/xrp"
18
+
19
+ connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
20
+ connection.add_plugin Parsers::Market unless connection.plugin?(Parsers::Market)
21
+
22
+ connection.perform
23
+ end
24
+
25
+ attr_accessor :route
26
+
27
+ def initialize(opts={})
28
+ set_opts(opts)
29
+ end
30
+
31
+ def set_opts(opts={})
32
+ super opts
33
+ @route = opts[:route] if opts[:route]
34
+ end
35
+
36
+ # Retrieve list of quotes for market via WebClient::Connection
37
+ #
38
+ # @param opts [Hash] options to retrieve quotes with
39
+ # @option opts [WebClient::Connection] :connection Connection
40
+ # to use to retrieve quotes
41
+ def quotes(opts={})
42
+ set_opts(opts)
43
+ connection.url = self.route
44
+
45
+ connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
46
+ connection.add_plugin Parsers::Quote unless connection.plugin?(Parsers::Quote)
47
+
48
+ connection.perform
49
+ end
50
+ end # class Market
51
+ end # module Model
52
+ end # module XRBP
@@ -0,0 +1,131 @@
1
+ require 'uri'
2
+ require 'resolv'
3
+
4
+ require_relative './parsers/node'
5
+
6
+ module XRBP
7
+ module Model
8
+ class Node < Base
9
+ extend Base::ClassMethods
10
+
11
+ DEFAULT_CRAWL_PORT = 51235
12
+
13
+ attr_accessor :ip, :port
14
+ attr_accessor :addr, :version, :uptime, :type
15
+
16
+ # Return unique node id
17
+ def id
18
+ "#{ip}:#{port}"
19
+ end
20
+
21
+ # Return node url
22
+ def url
23
+ "https://#{ip}:#{port}/crawl"
24
+ end
25
+
26
+ # Return bool indicating if this node is valid for crawling
27
+ def valid?
28
+ return false unless ip && port
29
+
30
+ # ensure no parsing errs
31
+ begin
32
+ # FIXME URI.parse is limiting our ability to traverse entire node-set,
33
+ # some nodes are represented as IPv6 addresses which is throwing
34
+ # things off.
35
+ URI.parse(url)
36
+ rescue
37
+ false
38
+ end
39
+
40
+ true
41
+ end
42
+
43
+ def ==(o)
44
+ ip == o.ip && port == o.port
45
+ end
46
+
47
+ # Return new node from the specified url
48
+ #
49
+ # @param url [String] node url
50
+ # @return [Node] new node instance
51
+ def self.parse_url(url)
52
+ n = new
53
+
54
+ uri = URI.parse(url)
55
+ n.ip = Resolv.getaddress(uri.host)
56
+ n.port = uri.port
57
+
58
+ n
59
+ end
60
+
61
+ # Return new node from the specified peer object
62
+ #
63
+ # @param p [Hash] peer data
64
+ # @return [Node] new node instance
65
+ def self.from_peer(p)
66
+ n = new
67
+
68
+ n.addr = p["public_key"]
69
+ n.ip = p["ip"]
70
+ n.port = p["port"] || DEFAULT_CRAWL_PORT
71
+ n.version = p["version"].split("-").last
72
+ n.uptime = p["uptime"]
73
+ n.type = p["type"]
74
+
75
+ n
76
+ end
77
+
78
+ # Crawl nodes via WebClient::Connection
79
+ #
80
+ # @param opts [Hash] options to crawl nodes with
81
+ # @option opts [WebSocket::Connection] :connection Connection
82
+ # to use to crawl nodes
83
+ # @option opts [Integer] :delay optional delay to wait between
84
+ # crawl iterations
85
+ def self.crawl(start, opts={})
86
+ set_opts(opts)
87
+ delay = opts[:delay] || 1
88
+
89
+ queue = Array.new
90
+ queue << start
91
+
92
+ connection.add_plugin :result_parser unless connection.plugin?(:result_parser)
93
+ connection.add_plugin Parsers::NodePeers unless connection.plugin?(Parsers::NodePeers)
94
+
95
+ connection.ssl_verify_peer = false
96
+ connection.ssl_verify_host = false
97
+
98
+ until connection.force_quit?
99
+ node = queue.shift
100
+ node = parse_url node unless node.is_a?(Node)
101
+
102
+ connection.emit :precrawl, node
103
+ connection.url = node.url
104
+
105
+ peers = connection.perform
106
+ if peers.nil? || peers.empty?
107
+ queue << node
108
+ connection.emit :crawlerr, node
109
+ connection.rsleep(delay) unless connection.force_quit?
110
+ next
111
+ end
112
+
113
+ connection.emit :peers, node, peers
114
+ peers.each { |peer|
115
+ break if connection.force_quit?
116
+
117
+ peer = Node.from_peer peer
118
+ next unless peer.valid? # skip unless valid
119
+
120
+ connection.emit :peer, node, peer
121
+ queue << peer unless queue.include?(peer)
122
+ }
123
+
124
+ queue << node
125
+ connection.emit :postcrawl, node
126
+ connection.rsleep(delay) unless connection.force_quit?
127
+ end
128
+ end
129
+ end
130
+ end # module Model
131
+ end # module XRBP