stellar_core_commander 0.0.11 → 0.0.12

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.
@@ -3,7 +3,7 @@ module StellarCoreCommander
3
3
  class OperationBuilder
4
4
  include Contracts
5
5
 
6
- Currency = Or[
6
+ Asset = Or[
7
7
  [String, Symbol],
8
8
  :native,
9
9
  ]
@@ -13,25 +13,38 @@ module StellarCoreCommander
13
13
  ]
14
14
 
15
15
  OfferCurrencies = Or[
16
- {sell:Currency, for: Currency},
17
- {buy:Currency, with: Currency},
16
+ {sell:Asset, for: Asset},
17
+ {buy:Asset, with: Asset},
18
18
  ]
19
19
 
20
- ThresholdByte = And[Num, lambda{|n| (0..255).include? n}]
20
+ Byte = And[Num, lambda{|n| (0..255).include? n}]
21
+ ThresholdByte = Byte
22
+ MasterWeightByte = Byte
21
23
 
22
24
  Thresholds = {
23
- low: ThresholdByte,
24
- medium: ThresholdByte,
25
- high: ThresholdByte,
26
- master_weight: ThresholdByte
25
+ low: Maybe[ThresholdByte],
26
+ medium: Maybe[ThresholdByte],
27
+ high: Maybe[ThresholdByte],
27
28
  }
28
29
 
30
+ SetOptionsArgs = {
31
+ inflation_dest: Maybe[Symbol],
32
+ clear_flags: Maybe[ArrayOf[Symbol]],
33
+ set_flags: Maybe[ArrayOf[Symbol]],
34
+ thresholds: Maybe[Thresholds],
35
+ master_weight: Maybe[MasterWeightByte],
36
+ home_domain: Maybe[String],
37
+ signer: Maybe[Stellar::Signer],
38
+ }
39
+
40
+ MAX_LIMIT= BigDecimal.new((2**63)-1) / Stellar::ONE
41
+
29
42
  Contract Transactor => Any
30
43
  def initialize(transactor)
31
44
  @transactor = transactor
32
45
  end
33
46
 
34
- Contract Symbol, Symbol, Amount, Or[{}, {path: ArrayOf[Currency], with:Amount}] => Any
47
+ Contract Symbol, Symbol, Amount, Or[{}, {path: ArrayOf[Asset], with:Amount}] => Any
35
48
  def payment(from, to, amount, options={})
36
49
  from = get_account from
37
50
  to = get_account to
@@ -45,7 +58,7 @@ module StellarCoreCommander
45
58
 
46
59
  tx = if options[:with]
47
60
  attrs[:with] = normalize_amount(options[:with])
48
- attrs[:path] = options[:path].map{|p| make_currency p}
61
+ attrs[:path] = options[:path].map{|p| make_asset p}
49
62
  Stellar::Transaction.path_payment(attrs)
50
63
  else
51
64
  Stellar::Transaction.payment(attrs)
@@ -55,7 +68,7 @@ module StellarCoreCommander
55
68
  end
56
69
 
57
70
  Contract Symbol, Symbol, Num => Any
58
- def create_account(account, funder=:master, starting_balance=1000_0000000)
71
+ def create_account(account, funder=:master, starting_balance=1000)
59
72
  account = get_account account
60
73
  funder = get_account funder
61
74
 
@@ -69,7 +82,7 @@ module StellarCoreCommander
69
82
 
70
83
  Contract Symbol, Symbol, String => Any
71
84
  def trust(account, issuer, code)
72
- change_trust account, issuer, code, (2**63)-1
85
+ change_trust account, issuer, code, MAX_LIMIT
73
86
  end
74
87
 
75
88
  Contract Symbol, Symbol, String, Num => Any
@@ -79,14 +92,14 @@ module StellarCoreCommander
79
92
  Stellar::Transaction.change_trust({
80
93
  account: account,
81
94
  sequence: next_sequence(account),
82
- line: make_currency([code, issuer]),
95
+ line: make_asset([code, issuer]),
83
96
  limit: limit
84
97
  }).to_envelope(account)
85
98
  end
86
99
 
87
100
  Contract Symbol, Symbol, String, Bool => Any
88
101
  def allow_trust(account, trustor, code, authorize=true)
89
- currency = make_currency([code, account])
102
+ asset = make_asset([code, account])
90
103
  account = get_account account
91
104
  trustor = get_account trustor
92
105
 
@@ -94,7 +107,7 @@ module StellarCoreCommander
94
107
  Stellar::Transaction.allow_trust({
95
108
  account: account,
96
109
  sequence: next_sequence(account),
97
- currency: currency,
110
+ asset: asset,
98
111
  trustor: trustor,
99
112
  authorize: authorize,
100
113
  }).to_envelope(account)
@@ -110,11 +123,11 @@ module StellarCoreCommander
110
123
  account = get_account account
111
124
 
112
125
  if currencies.has_key?(:sell)
113
- taker_pays = make_currency currencies[:for]
114
- taker_gets = make_currency currencies[:sell]
126
+ buying = make_asset currencies[:for]
127
+ selling = make_asset currencies[:sell]
115
128
  else
116
- taker_pays = make_currency currencies[:buy]
117
- taker_gets = make_currency currencies[:with]
129
+ buying = make_asset currencies[:buy]
130
+ selling = make_asset currencies[:with]
118
131
  price = 1 / price
119
132
  amount = (amount * price).floor
120
133
  end
@@ -122,8 +135,8 @@ module StellarCoreCommander
122
135
  Stellar::Transaction.manage_offer({
123
136
  account: account,
124
137
  sequence: next_sequence(account),
125
- taker_gets: taker_gets,
126
- taker_pays: taker_pays,
138
+ selling: selling,
139
+ buying: buying,
127
140
  amount: amount,
128
141
  price: price,
129
142
  }).to_envelope(account)
@@ -134,11 +147,11 @@ module StellarCoreCommander
134
147
  account = get_account account
135
148
 
136
149
  if currencies.has_key?(:sell)
137
- taker_pays = make_currency currencies[:for]
138
- taker_gets = make_currency currencies[:sell]
150
+ buying = make_asset currencies[:for]
151
+ selling = make_asset currencies[:sell]
139
152
  else
140
- taker_pays = make_currency currencies[:buy]
141
- taker_gets = make_currency currencies[:with]
153
+ buying = make_asset currencies[:buy]
154
+ selling = make_asset currencies[:with]
142
155
  price = 1 / price
143
156
  amount = (amount * price).floor
144
157
  end
@@ -146,60 +159,103 @@ module StellarCoreCommander
146
159
  Stellar::Transaction.create_passive_offer({
147
160
  account: account,
148
161
  sequence: next_sequence(account),
149
- taker_gets: taker_gets,
150
- taker_pays: taker_pays,
162
+ selling: selling,
163
+ buying: buying,
151
164
  amount: amount,
152
165
  price: price,
153
166
  }).to_envelope(account)
154
167
  end
155
168
 
156
-
157
- Contract Symbol => Any
158
- def require_trust_auth(account)
159
- set_flags account, [:auth_required_flag]
160
- end
161
-
162
- Contract Symbol, ArrayOf[Symbol] => Any
163
- def set_flags(account, flags)
169
+ Contract Symbol, SetOptionsArgs => Any
170
+ def set_options(account, args)
164
171
  account = get_account account
165
172
 
166
- tx = Stellar::Transaction.set_options({
173
+ params = {
167
174
  account: account,
168
175
  sequence: next_sequence(account),
169
- set: make_account_flags(flags),
170
- })
176
+ }
177
+
178
+ if args[:inflation_dest].present?
179
+ params[:inflation_dest] = get_account args[:inflation_dest]
180
+ end
171
181
 
182
+ if args[:set_flags].present?
183
+ params[:set] = make_account_flags(args[:set_flags])
184
+ end
185
+
186
+ if args[:clear_flags].present?
187
+ params[:clear] = make_account_flags(args[:clear_flags])
188
+ end
189
+
190
+ if args[:master_weight].present?
191
+ params[:master_weight] = args[:master_weight]
192
+ end
193
+
194
+ if args[:thresholds].present?
195
+ params[:low_threshold] = args[:thresholds][:low]
196
+ params[:med_threshold] = args[:thresholds][:medium]
197
+ params[:high_threshold] = args[:thresholds][:high]
198
+ end
199
+
200
+ if args[:home_domain].present?
201
+ params[:home_domain] = args[:home_domain]
202
+ end
203
+
204
+ if args[:signer].present?
205
+ params[:signer] = args[:signer]
206
+ end
207
+
208
+ tx = Stellar::Transaction.set_options(params)
172
209
  tx.to_envelope(account)
173
210
  end
174
211
 
175
212
 
213
+ Contract Symbol, ArrayOf[Symbol] => Any
214
+ def set_flags(account, flags)
215
+ set_options account, set_flags: flags
216
+ end
217
+
218
+ Contract Symbol, ArrayOf[Symbol] => Any
219
+ def clear_flags(account, flags)
220
+ set_options account, clear_flags: flags
221
+ end
222
+
223
+ Contract Symbol => Any
224
+ def require_trust_auth(account)
225
+ set_flags account, [:auth_required_flag]
226
+ end
227
+
176
228
  Contract Symbol, Stellar::KeyPair, Num => Any
177
229
  def add_signer(account, key, weight)
178
- account = get_account account
179
-
180
- tx = Stellar::Transaction.set_options({
181
- account: account,
182
- sequence: next_sequence(account),
183
- signer: Stellar::Signer.new({
184
- pub_key: key.public_key,
185
- weight: weight
186
- }),
230
+ set_options account, signer: Stellar::Signer.new({
231
+ pub_key: key.public_key,
232
+ weight: weight
187
233
  })
234
+ end
188
235
 
189
- tx.to_envelope(account)
236
+ Contract Symbol, Stellar::KeyPair => Any
237
+ def remove_signer(account, key)
238
+ add_signer account, key, 0
239
+ end
240
+
241
+ Contract(Symbol, MasterWeightByte => Any)
242
+ def set_master_signer_weight(account, weight)
243
+ set_options account, master_weight: weight
190
244
  end
191
245
 
192
246
  Contract(Symbol, Thresholds => Any)
193
247
  def set_thresholds(account, thresholds)
194
- account = get_account account
248
+ set_options account, thresholds: thresholds
249
+ end
195
250
 
196
- tx = Stellar::Transaction.set_options({
197
- account: account,
198
- sequence: next_sequence(account),
199
- thresholds: make_thresholds_word(thresholds),
200
- })
251
+ Contract(Symbol, Symbol => Any)
252
+ def set_inflation_dest(account, destination)
253
+ set_options account, inflation_dest: destination
254
+ end
201
255
 
202
- tx.to_envelope(account)
256
+ Contract(Symbol, String => Any)
257
+ def set_home_domain(account, domain)
258
+ set_options account, home_domain: domain
203
259
  end
204
260
 
205
261
  Contract Symbol, Symbol => Any
@@ -216,13 +272,26 @@ module StellarCoreCommander
216
272
  tx.to_envelope(account)
217
273
  end
218
274
 
275
+
276
+ Contract None => Any
277
+ def inflation(account=:master)
278
+ account = get_account account
279
+
280
+ tx = Stellar::Transaction.inflation({
281
+ account: account,
282
+ sequence: next_sequence(account),
283
+ })
284
+
285
+ tx.to_envelope(account)
286
+ end
287
+
219
288
  private
220
289
 
221
290
  delegate :get_account, to: :@transactor
222
291
  delegate :next_sequence, to: :@transactor
223
292
 
224
- Contract Currency => Or[[Symbol, String, Stellar::KeyPair], [:native]]
225
- def make_currency(input)
293
+ Contract Asset => Or[[Symbol, String, Stellar::KeyPair], [:native]]
294
+ def make_asset(input)
226
295
  if input == :native
227
296
  return [:native]
228
297
  end
@@ -230,7 +299,7 @@ module StellarCoreCommander
230
299
  code, issuer = *input
231
300
  issuer = get_account issuer
232
301
 
233
- [:alphanum, code, issuer]
302
+ [:alphanum4, code, issuer]
234
303
  end
235
304
 
236
305
  def make_account_flags(flags=nil)
@@ -247,7 +316,7 @@ module StellarCoreCommander
247
316
  def normalize_amount(amount)
248
317
  return amount if amount.first == :native
249
318
 
250
- amount = [:alphanum] + amount
319
+ amount = [:alphanum4] + amount
251
320
  amount[2] = get_account(amount[2]) # translate issuer to account
252
321
 
253
322
  amount
@@ -1,8 +1,22 @@
1
1
  module StellarCoreCommander
2
2
 
3
+ class UnexpectedDifference < StandardError
4
+ def initialize(kind, x, y)
5
+ @kind = kind
6
+ @x = x
7
+ @y = y
8
+ end
9
+ def message
10
+ "Unexpected difference in #{@kind}: #{@x} != #{@y}"
11
+ end
12
+ end
13
+
3
14
  class Process
4
15
  include Contracts
5
16
 
17
+ class Crash < StandardError ; end
18
+ class AlreadyRunning < StandardError ; end
19
+
6
20
  attr_reader :transactor
7
21
  attr_reader :working_dir
8
22
  attr_reader :name
@@ -10,40 +24,94 @@ module StellarCoreCommander
10
24
  attr_reader :identity
11
25
  attr_reader :server
12
26
  attr_accessor :unverified
13
- attr_reader :threshold
14
27
  attr_reader :host
28
+ attr_reader :atlas
29
+ attr_reader :atlas_interval
30
+ attr_reader :network_passphrase
15
31
 
16
32
  DEFAULT_HOST = '127.0.0.1'
17
33
 
34
+ SPECIAL_PEERS = {
35
+ :testnet1 => {
36
+ :dns => "core-testnet1.stellar.org",
37
+ :key => "GDKXE2OZMJIPOSLNA6N6F2BVCI3O777I2OOC4BV7VOYUEHYX7RTRYA7Y",
38
+ :name => "core-testnet-001",
39
+ :get => "wget -q https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/%s/{0} -O {1}"
40
+ },
41
+ :testnet2 => {
42
+ :dns => "core-testnet2.stellar.org",
43
+ :key => "GCUCJTIYXSOXKBSNFGNFWW5MUQ54HKRPGJUTQFJ5RQXZXNOLNXYDHRAP",
44
+ :name => "core-testnet-002",
45
+ :get => "wget -q https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/%s/{0} -O {1}"
46
+ },
47
+ :testnet3 => {
48
+ :dns => "core-testnet3.stellar.org",
49
+ :key => "GC2V2EFSXN6SQTWVYA5EPJPBWWIMSD2XQNKUOHGEKB535AQE2I6IXV2Z",
50
+ :name => "core-testnet-003",
51
+ :get => "wget -q https://s3-eu-west-1.amazonaws.com/history.stellar.org/prd/core-testnet/%s/{0} -O {1}"
52
+ }
53
+ }
54
+
18
55
  Contract({
19
- transactor: Transactor,
20
- working_dir: String,
21
- name: Symbol,
22
- base_port: Num,
23
- identity: Stellar::KeyPair,
24
- quorum: ArrayOf[Symbol],
25
- threshold: Num,
26
- manual_close: Or[Bool, nil],
27
- host: Or[String, nil]
56
+ transactor: Transactor,
57
+ working_dir: String,
58
+ name: Symbol,
59
+ base_port: Num,
60
+ identity: Stellar::KeyPair,
61
+ quorum: ArrayOf[Symbol],
62
+ peers: Maybe[ArrayOf[Symbol]],
63
+ manual_close: Maybe[Bool],
64
+ await_sync: Maybe[Bool],
65
+ accelerate_time: Maybe[Bool],
66
+ catchup_complete: Maybe[Bool],
67
+ forcescp: Maybe[Bool],
68
+ validate: Maybe[Bool],
69
+ host: Maybe[String],
70
+ atlas: Maybe[String],
71
+ atlas_interval: Num,
72
+ use_s3: Bool,
73
+ s3_history_prefix: String,
74
+ s3_history_region: String,
75
+ database_url: Maybe[String],
76
+ keep_database: Maybe[Bool],
77
+ debug: Maybe[Bool],
78
+ network_passphrase: Maybe[String],
28
79
  } => Any)
29
80
  def initialize(params)
30
81
  #config
31
- @transactor = params[:transactor]
32
- @working_dir = params[:working_dir]
33
- @name = params[:name]
34
- @base_port = params[:base_port]
35
- @identity = params[:identity]
36
- @quorum = params[:quorum]
37
- @threshold = params[:threshold]
38
- @manual_close = params[:manual_close] || false
39
- @host = params[:host]
82
+ @transactor = params[:transactor]
83
+ @working_dir = params[:working_dir]
84
+ @name = params[:name]
85
+ @base_port = params[:base_port]
86
+ @identity = params[:identity]
87
+ @quorum = params[:quorum]
88
+ @peers = params[:peers] || params[:quorum]
89
+ @manual_close = params[:manual_close] || false
90
+ @await_sync = params.fetch(:await_sync, true)
91
+ @accelerate_time = params[:accelerate_time] || false
92
+ @catchup_complete = params[:catchup_complete] || false
93
+ @forcescp = params.fetch(:forcescp, true)
94
+ @validate = params.fetch(:validate, true)
95
+ @host = params[:host]
96
+ @atlas = params[:atlas]
97
+ @atlas_interval = params[:atlas_interval]
98
+ @use_s3 = params[:use_s3]
99
+ @s3_history_region = params[:s3_history_region]
100
+ @s3_history_prefix = params[:s3_history_prefix]
101
+ @database_url = params[:database_url]
102
+ @keep_database = params[:keep_database]
103
+ @debug = params[:debug]
104
+ @network_passphrase = params[:network_passphrase] || Stellar::Networks::TESTNET
40
105
 
41
106
  # state
42
107
  @unverified = []
43
108
 
44
-
45
109
  if not @quorum.include? @name
46
- @quorum << @name
110
+ @quorum = @quorum + [@name]
111
+ end
112
+
113
+ if not @peers.include? @name
114
+ @peers = @peers + [@name]
47
115
  end
48
116
 
49
117
  @server = Faraday.new(url: "http://#{hostname}:#{http_port}") do |conn|
@@ -52,16 +120,42 @@ module StellarCoreCommander
52
120
  end
53
121
  end
54
122
 
123
+ Contract None => Bool
124
+ def has_special_peers?
125
+ @peers.any? {|q| SPECIAL_PEERS.has_key? q}
126
+ end
127
+
128
+ Contract ArrayOf[Symbol], Symbol, Bool, Proc => ArrayOf[String]
129
+ def node_map_or_special_field(nodes, field, include_self)
130
+ specials = nodes.select {|q| SPECIAL_PEERS.has_key? q}
131
+ if specials.empty?
132
+ (nodes.map do |q|
133
+ if q != @name or include_self
134
+ yield q
135
+ end
136
+ end).compact
137
+ else
138
+ specials.map {|q| SPECIAL_PEERS[q][field]}
139
+ end
140
+ end
141
+
55
142
  Contract None => ArrayOf[String]
56
143
  def quorum
57
- @quorum.map do |q|
144
+ node_map_or_special_field @quorum, :key, @validate do |q|
58
145
  @transactor.get_process(q).identity.address
59
146
  end
60
147
  end
61
148
 
62
149
  Contract None => ArrayOf[String]
63
- def peers
64
- @quorum.map do |q|
150
+ def peer_names
151
+ node_map_or_special_field @peers, :name, true do |q|
152
+ q.to_s
153
+ end
154
+ end
155
+
156
+ Contract None => ArrayOf[String]
157
+ def peer_connections
158
+ node_map_or_special_field @peers, :dns, false do |q|
65
159
  p = @transactor.get_process(q)
66
160
  "#{p.hostname}:#{p.peer_port}"
67
161
  end
@@ -72,32 +166,73 @@ module StellarCoreCommander
72
166
  2
73
167
  end
74
168
 
75
- Contract None => Any
76
- def rm_working_dir
77
- FileUtils.rm_rf working_dir
78
- end
79
-
80
169
  Contract None => String
81
170
  def idname
82
171
  "#{@name}-#{@base_port}-#{@identity.address[0..5]}"
83
172
  end
84
173
 
85
- Contract None => Any
86
- def wait_for_ready
87
- loop do
174
+ Contract None => String
175
+ def database_url
176
+ if @database_url.present?
177
+ @database_url.strip
178
+ else
179
+ default_database_url
180
+ end
181
+ end
88
182
 
89
- response = server.get("/info") rescue false
183
+ Contract None => URI::Generic
184
+ def database_uri
185
+ URI.parse(database_url)
186
+ end
90
187
 
91
- if response
92
- body = ActiveSupport::JSON.decode(response.body)
188
+ Contract None => Maybe[String]
189
+ def database_host
190
+ database_uri.host
191
+ end
93
192
 
94
- state = body["info"]["state"]
95
- $stderr.puts "state: #{state}"
96
- break if state == "Synced!"
97
- end
193
+ Contract None => String
194
+ def database_name
195
+ database_uri.path[1..-1]
196
+ end
197
+
198
+ Contract None => Sequel::Database
199
+ def database
200
+ @database ||= Sequel.connect(database_url)
201
+ end
202
+
203
+ Contract None => Maybe[String]
204
+ def database_user
205
+ database_uri.user
206
+ end
98
207
 
99
- $stderr.puts "waiting until stellar-core #{idname} is synced"
100
- sleep 1
208
+ Contract None => Maybe[String]
209
+ def database_password
210
+ database_uri.password
211
+ end
212
+
213
+ Contract None => String
214
+ def database_port
215
+ database_uri.port || "5432"
216
+ end
217
+
218
+ Contract None => String
219
+ def dsn
220
+ base = "postgresql://dbname=#{database_name} "
221
+ base << " user=#{database_user}" if database_user.present?
222
+ base << " password=#{database_password}" if database_password.present?
223
+ base << " host=#{database_host} port=#{database_port}" if database_host.present?
224
+
225
+ base
226
+ end
227
+
228
+ Contract None => Any
229
+ def wait_for_ready
230
+ Timeout.timeout(sync_timeout) do
231
+ loop do
232
+ break if synced?
233
+ $stderr.puts "waiting until stellar-core #{idname} is synced (state: #{info_field 'state'}, quorum heard: #{scp_quorum_heard})"
234
+ sleep 1
235
+ end
101
236
  end
102
237
  end
103
238
 
@@ -134,6 +269,45 @@ module StellarCoreCommander
134
269
  true
135
270
  end
136
271
 
272
+ Contract Num, Symbol => Any
273
+ def catchup(ledger, mode)
274
+ server.get("/catchup?ledger=#{ledger}&mode=#{mode}")
275
+ end
276
+
277
+ Contract None => Hash
278
+ def info
279
+ response = server.get("/info")
280
+ body = ActiveSupport::JSON.decode(response.body)
281
+ body["info"]
282
+ rescue
283
+ {}
284
+ end
285
+
286
+ Contract String => Any
287
+ def info_field(k)
288
+ i = info
289
+ i[k]
290
+ rescue
291
+ false
292
+ end
293
+
294
+ Contract None => Bool
295
+ def synced?
296
+ (info_field "state") == "Synced!"
297
+ end
298
+
299
+ Contract None => Num
300
+ def ledger_num
301
+ (info_field "ledger")["num"]
302
+ rescue
303
+ 0
304
+ end
305
+
306
+ Contract None => Bool
307
+ def await_sync?
308
+ @await_sync
309
+ end
310
+
137
311
  Contract None => Hash
138
312
  def metrics
139
313
  response = server.get("/metrics")
@@ -143,13 +317,111 @@ module StellarCoreCommander
143
317
  {}
144
318
  end
145
319
 
146
- Contract None => Num
147
- def scp_ballots_prepared
148
- metrics["scp.ballot.prepare"]["count"]
320
+ Contract String => Num
321
+ def metrics_count(k)
322
+ m = metrics
323
+ m[k]["count"]
324
+ rescue
325
+ 0
326
+ end
327
+
328
+ Contract String => Num
329
+ def metrics_1m_rate(k)
330
+ m = metrics
331
+ m[k]["1_min_rate"]
149
332
  rescue
150
333
  0
151
334
  end
152
335
 
336
+ Contract String => Any
337
+ def dump_server_query(s)
338
+ fname = "#{working_dir}/#{s}-#{Time.now.to_i}-#{rand 100000}.json"
339
+ $stderr.puts "dumping server query #{fname}"
340
+ response = server.get("/#{s}")
341
+ File.open(fname, 'w') {|f| f.write(response.body) }
342
+ rescue
343
+ nil
344
+ end
345
+
346
+ Contract None => Any
347
+ def dump_metrics
348
+ dump_server_query("metrics")
349
+ end
350
+
351
+ Contract None => Any
352
+ def dump_info
353
+ dump_server_query("info")
354
+ end
355
+
356
+ Contract None => Any
357
+ def dump_scp_state
358
+ dump_server_query("scp")
359
+ end
360
+
361
+ Contract None => Num
362
+ def scp_ballots_prepared
363
+ metrics_count "scp.ballot.prepare"
364
+ end
365
+
366
+ Contract None => Num
367
+ def scp_quorum_heard
368
+ metrics_count "scp.quorum.heard"
369
+ end
370
+
371
+ Contract None => Bool
372
+ def check_no_error_metrics
373
+ m = metrics
374
+ for metric in ["scp.envelope.invalidsig",
375
+ "history.publish.failure",
376
+ "history.catchup.failure"]
377
+ c = m[metric]["count"] rescue 0
378
+ if c != 0
379
+ raise "nonzero metrics count for #{metric}: #{c}"
380
+ end
381
+ end
382
+ true
383
+ end
384
+
385
+ Contract Num, Num, Or[Symbol, Num] => Any
386
+ def start_load_generation(accounts, txs, txrate)
387
+ server.get("/generateload?accounts=#{accounts}&txs=#{txs}&txrate=#{txrate}")
388
+ end
389
+
390
+ Contract None => Num
391
+ def load_generation_runs
392
+ metrics_count "loadgen.run.complete"
393
+ end
394
+
395
+ Contract None => Num
396
+ def transactions_applied
397
+ metrics_count "ledger.transaction.apply"
398
+ end
399
+
400
+ Contract None => Num
401
+ def transactions_per_second
402
+ metrics_1m_rate "ledger.transaction.apply"
403
+ end
404
+
405
+ Contract None => Num
406
+ def operations_per_second
407
+ metrics_1m_rate "transaction.op.apply"
408
+ end
409
+
410
+ Contract None => Any
411
+ def start_checkdb
412
+ server.get("/checkdb")
413
+ end
414
+
415
+ Contract None => Num
416
+ def checkdb_runs
417
+ metrics_count "bucket.checkdb.execute"
418
+ end
419
+
420
+ Contract None => Num
421
+ def objects_checked
422
+ metrics_count "bucket.checkdb.object-compare"
423
+ end
424
+
153
425
  Contract None => Num
154
426
  def http_port
155
427
  base_port
@@ -171,10 +443,21 @@ module StellarCoreCommander
171
443
 
172
444
  end
173
445
 
446
+ Contract Stellar::KeyPair => Any
447
+ def account_row(account)
448
+ row = database[:accounts].where(:accountid => account.address).first
449
+ raise "Missing account in #{idname}'s database: #{account.address}" unless row
450
+ row
451
+ end
452
+
174
453
  Contract Stellar::KeyPair => Num
175
454
  def sequence_for(account)
176
- row = database[:accounts].where(:accountid => account.address).first
177
- row[:seqnum]
455
+ (account_row account)[:seqnum]
456
+ end
457
+
458
+ Contract Stellar::KeyPair => Num
459
+ def balance_for(account)
460
+ (account_row account)[:balance]
178
461
  end
179
462
 
180
463
  Contract None => Num
@@ -182,6 +465,87 @@ module StellarCoreCommander
182
465
  database[:ledgerheaders].max(:ledgerseq)
183
466
  end
184
467
 
468
+ Contract String => Any
469
+ def db_store_state(name)
470
+ database.select(:state).from(:storestate).filter(statename: name).first[:state]
471
+ end
472
+
473
+ Contract None => String
474
+ def latest_ledger_hash
475
+ s_lcl = db_store_state("lastclosedledger")
476
+ t_lcl = database.select(:ledgerhash)
477
+ .from(:ledgerheaders)
478
+ .filter(:ledgerseq=>latest_ledger).first[:ledgerhash]
479
+ raise "inconsistent last-ledger hashes in db: #{t_lcl} vs. #{s_lcl}" if t_lcl != s_lcl
480
+ s_lcl
481
+ end
482
+
483
+ Contract None => Any
484
+ def history_archive_state
485
+ ActiveSupport::JSON.decode(db_store_state("historyarchivestate"))
486
+ end
487
+
488
+ Contract None => Num
489
+ def account_count
490
+ database.fetch("SELECT count(*) FROM accounts").first[:count]
491
+ end
492
+
493
+ Contract None => Num
494
+ def trustline_count
495
+ database.fetch("SELECT count(*) FROM trustlines").first[:count]
496
+ end
497
+
498
+ Contract None => Num
499
+ def offer_count
500
+ database.fetch("SELECT count(*) FROM offers").first[:count]
501
+ end
502
+
503
+ Contract None => ArrayOf[Any]
504
+ def ten_accounts
505
+ database.fetch("SELECT * FROM accounts ORDER BY accountid LIMIT 10").all
506
+ end
507
+
508
+ Contract None => ArrayOf[Any]
509
+ def ten_offers
510
+ database.fetch("SELECT * FROM offers ORDER BY sellerid LIMIT 10").all
511
+ end
512
+
513
+ Contract None => ArrayOf[Any]
514
+ def ten_trustlines
515
+ database.fetch("SELECT * FROM trustlines ORDER BY accountid, issuer, assetcode LIMIT 10").all
516
+ end
517
+
518
+ Contract String, Any, Any => nil
519
+ def check_equal(kind, x, y)
520
+ raise UnexpectedDifference.new(kind, x, y) if x != y
521
+ end
522
+
523
+ Contract Process => nil
524
+ def check_equal_ledger_objects(other)
525
+ check_equal "account count", account_count, other.account_count
526
+ check_equal "trustline count", trustline_count, other.trustline_count
527
+ check_equal "offer count", offer_count, other.offer_count
528
+
529
+ check_equal "ten accounts", ten_accounts, other.ten_accounts
530
+ check_equal "ten trustlines", ten_trustlines, other.ten_trustlines
531
+ check_equal "ten offers", ten_offers, other.ten_offers
532
+ end
533
+
534
+ Contract Process => Any
535
+ def check_ledger_sequence_is_prefix_of(other)
536
+ q = "SELECT ledgerseq, ledgerhash FROM ledgerheaders ORDER BY ledgerseq"
537
+ our_headers = other.database.fetch(q).all
538
+ other_headers = other.database.fetch(q).all
539
+ our_hash = {}
540
+ other_hash = {}
541
+ other_headers.each do |row|
542
+ other_hash[row[:ledgerseq]] = row[:ledgerhash]
543
+ end
544
+ our_headers.each do |row|
545
+ check_equal "ledger hashes", other_hash[row[:ledgerseq]], row[:ledgerhash]
546
+ end
547
+ end
548
+
185
549
  Contract String => Maybe[String]
186
550
  def transaction_result(hex_hash)
187
551
  row = database[:txhistory].where(txid:hex_hash).first
@@ -196,14 +560,32 @@ module StellarCoreCommander
196
560
 
197
561
  Contract None => Num
198
562
  def close_timeout
199
- 15.0
563
+ 150.0
564
+ end
565
+
566
+ Contract None => Num
567
+ def sync_timeout
568
+ if has_special_peers? and @catchup_complete
569
+ # catchup-complete can take quite a while on testnet; for now,
570
+ # give such tests an hour. May require a change in strategy later.
571
+ 3600.0
572
+ else
573
+ # Checkpoints are made every 64 ledgers = 320s on a normal network,
574
+ # or every 8 ledgers = 8s on an accelerated-time network; we give you
575
+ # 3 checkpoints to make it to a sync (~16min) before giving up. The
576
+ # accelerated-time variant tends to need more tries due to S3 not
577
+ # admitting writes instantaneously, so we do not use a tighter bound
578
+ # for that case, just use the same 16min value, despite commonly
579
+ # succeeding in 20s or less.
580
+ 320.0 * 3
581
+ end
200
582
  end
201
583
 
202
584
  Contract String, ArrayOf[String] => Maybe[Bool]
203
585
  def run_cmd(cmd, args)
204
586
  args += [{
205
- out: "stellar-core.log",
206
- err: "stellar-core.log",
587
+ out: ["stellar-core.log", "a"],
588
+ err: ["stellar-core.log", "a"],
207
589
  }]
208
590
 
209
591
  Dir.chdir working_dir do
@@ -211,5 +593,52 @@ module StellarCoreCommander
211
593
  end
212
594
  end
213
595
 
596
+ Contract None => Bool
597
+ def stopped?
598
+ !running?
599
+ end
600
+
601
+ Contract None => Bool
602
+ def launched?
603
+ !!@launched
604
+ end
605
+
606
+ Contract None => Bool
607
+ def crashed?
608
+ launched? && stopped?
609
+ end
610
+
611
+ Contract None => Any
612
+ def prepare
613
+ # noop by default, implement in subclass to customize behavior
614
+ nil
615
+ end
616
+
617
+ Contract None => Any
618
+ def run
619
+ raise Process::AlreadyRunning, "already running!" if running?
620
+ raise Process::Crash, "process #{name} has crashed. cannot run process again" if crashed?
621
+
622
+ setup
623
+ launch_process
624
+ @launched = true
625
+ end
626
+
627
+ # Dumps the database of the process to the working directory, returning the path to the file written to
628
+ Contract None => String
629
+ def dump_database
630
+ raise NotImplementedError, "implement in subclass"
631
+ end
632
+
633
+ private
634
+ Contract None => Any
635
+ def launch_process
636
+ raise NotImplementedError, "implement in subclass"
637
+ end
638
+
639
+ Contract None => Any
640
+ def setup
641
+ raise NotImplementedError, "implement in subclass"
642
+ end
214
643
  end
215
644
  end