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.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/CONTRIBUTING.md +60 -0
- data/bin/scc +70 -11
- data/examples/cross_host_simple_payment.rb +5 -5
- data/examples/history_generate_and_catchup.rb +26 -0
- data/examples/history_testnet_catchup.rb +15 -0
- data/examples/inflation.rb +8 -0
- data/examples/load_generation.rb +15 -0
- data/examples/load_generation_auto.rb +15 -0
- data/examples/multi_host_simple_payment.rb +12 -5
- data/examples/set_options.rb +23 -0
- data/examples/simple_payment.rb +3 -1
- data/examples/version_mix_consensus.rb +46 -0
- data/lib/stellar_core_commander.rb +1 -0
- data/lib/stellar_core_commander/commander.rb +50 -16
- data/lib/stellar_core_commander/convert.rb +1 -5
- data/lib/stellar_core_commander/docker_process.rb +220 -44
- data/lib/stellar_core_commander/local_process.rb +82 -41
- data/lib/stellar_core_commander/operation_builder.rb +128 -59
- data/lib/stellar_core_commander/process.rb +478 -49
- data/lib/stellar_core_commander/transactor.rb +222 -85
- data/lib/stellar_core_commander/version.rb +1 -1
- data/stellar_core_commander.gemspec +1 -1
- metadata +14 -6
@@ -3,7 +3,7 @@ module StellarCoreCommander
|
|
3
3
|
class OperationBuilder
|
4
4
|
include Contracts
|
5
5
|
|
6
|
-
|
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:
|
17
|
-
{buy:
|
16
|
+
{sell:Asset, for: Asset},
|
17
|
+
{buy:Asset, with: Asset},
|
18
18
|
]
|
19
19
|
|
20
|
-
|
20
|
+
Byte = And[Num, lambda{|n| (0..255).include? n}]
|
21
|
+
ThresholdByte = Byte
|
22
|
+
MasterWeightByte = Byte
|
21
23
|
|
22
24
|
Thresholds = {
|
23
|
-
low:
|
24
|
-
medium:
|
25
|
-
high:
|
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[
|
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|
|
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=
|
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,
|
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:
|
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
|
-
|
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
|
-
|
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
|
-
|
114
|
-
|
126
|
+
buying = make_asset currencies[:for]
|
127
|
+
selling = make_asset currencies[:sell]
|
115
128
|
else
|
116
|
-
|
117
|
-
|
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
|
-
|
126
|
-
|
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
|
-
|
138
|
-
|
150
|
+
buying = make_asset currencies[:for]
|
151
|
+
selling = make_asset currencies[:sell]
|
139
152
|
else
|
140
|
-
|
141
|
-
|
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
|
-
|
150
|
-
|
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
|
-
|
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
|
-
|
173
|
+
params = {
|
167
174
|
account: account,
|
168
175
|
sequence: next_sequence(account),
|
169
|
-
|
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
|
179
|
-
|
180
|
-
|
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
|
-
|
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
|
248
|
+
set_options account, thresholds: thresholds
|
249
|
+
end
|
195
250
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
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
|
-
|
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
|
225
|
-
def
|
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
|
-
[:
|
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 = [:
|
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:
|
20
|
-
working_dir:
|
21
|
-
name:
|
22
|
-
base_port:
|
23
|
-
identity:
|
24
|
-
quorum:
|
25
|
-
|
26
|
-
manual_close:
|
27
|
-
|
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
|
32
|
-
@working_dir
|
33
|
-
@name
|
34
|
-
@base_port
|
35
|
-
@identity
|
36
|
-
@quorum
|
37
|
-
@
|
38
|
-
@manual_close
|
39
|
-
@
|
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
|
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
|
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
|
64
|
-
@
|
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 =>
|
86
|
-
def
|
87
|
-
|
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
|
-
|
183
|
+
Contract None => URI::Generic
|
184
|
+
def database_uri
|
185
|
+
URI.parse(database_url)
|
186
|
+
end
|
90
187
|
|
91
|
-
|
92
|
-
|
188
|
+
Contract None => Maybe[String]
|
189
|
+
def database_host
|
190
|
+
database_uri.host
|
191
|
+
end
|
93
192
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
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
|
-
|
100
|
-
|
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
|
147
|
-
def
|
148
|
-
metrics
|
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
|
-
|
177
|
-
|
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
|
-
|
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
|