xrbp 0.0.1 → 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 (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