stellar_core_commander 0.0.11 → 0.0.12

Sign up to get free protection for your applications and to get access to all the features.
@@ -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