stellar_core_commander 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 57e0dc80ca8af0d8ffb64901553182956be06de4
4
+ data.tar.gz: 47674fda6cbb673d5be6642d5b291093fa5de240
5
+ SHA512:
6
+ metadata.gz: 3dac295ce242278729cc17c41e9b20942bc5343a34062518c845a82d0753f6c5839775ef17f486232ab4231c165d74c8ee57dd18b4fb7579e68d44d9d5560408
7
+ data.tar.gz: de5bdb018fb12c3275791a521f277217382f728f58231f5a822feb2e517bba0afb688d2c5b3dcc8d35be705c908fd6d437bccb07f5b83a92f9f6959fb6558bb1
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ out.sql
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ # gem 'stellar-base', path: "~/src/stellar/ruby-stellar-base"
5
+ # gem 'xdr', path: "~/src/stellar/ruby-xdr"#
data/LICENSE.txt ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [2015] [Stellar Development Foundation]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # StellarCoreCommander
2
+
3
+ A helper gem for scripting a [stellar-core](https://github.com/stellar/stellar-core). This gem provides a system of creating isolated test networks into which you can play
4
+ transactions and record results.
5
+
6
+ The motivation for this project comes from the testing needs of [horizon](https://github.com/stellar/horizon). Horizon uses `scc` to record the various testing scenarios that its suite uses.
7
+
8
+
9
+ ## Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'stellar_core_commander'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install stellar_core_commander
24
+
25
+ ## Usage As Command Line Tool
26
+
27
+ Installing `stellar_core_commander` installs the command line tool `scc`. `scc`
28
+ takes a recipe file, spins up a test network, plays the defined transactions against it, then dumps the ledger database to stdout. `scc`'s usage is like so:
29
+
30
+ ```bash
31
+ $ scc -r my_recipe.rb > out.sql
32
+ ```
33
+
34
+ The above command will play the recipe written in `my_recipe.rb`, verify that all transactions within the recipe have succeeded, then dump the ledger database to `out.sql`
35
+
36
+ ## Usage as a Library
37
+
38
+ TODO
39
+
40
+ ## Writing Recipes
41
+
42
+ The heart of `scc` is a recipe, which is just a ruby script that executes in a context
43
+ that makes it easy to play transactions against the isolated test network. Lets look at a simple recipe:
44
+
45
+ ```ruby
46
+ account :scott
47
+ payment :master, :scott, [:native, 1000_000000]
48
+ ```
49
+
50
+ Let's look at each statement in turn. `account :scott` declares a new unfunded account and binds it to the name `:scott`, which we will use in the next statement.
51
+
52
+ The next statement is more complex: `payment :master, :scott, [:native, 1000_000000]`. This statement encodes "Send 1000 lumens from the :master account to the :scott account".
53
+
54
+ `:master` (sometimes also called the "root" account) is a special account that is created when a new ledger is initialized. We often use it in our recipes to fund other accounts, since in a ledgers initial state the :master account has all 100 billion lumen.
55
+
56
+ ### Recipe Reference
57
+
58
+ All recipe execute within the context of a `StellarCoreCommander::Transactor`. [See the code for all available methods](lib/stellar_core_commander/transactor.rb).
59
+
60
+ ## Example Recipes
61
+
62
+ See [examples](examples).
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it ( https://github.com/[my-github-username]/stellar_core_commander/fork )
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
data/bin/scc ADDED
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'stellar_core_commander'
4
+ require 'slop'
5
+
6
+ def run
7
+ $opts = Slop.parse do
8
+ banner 'Usage: scc -r RECIPE'
9
+
10
+ on 'stellar-core-bin', 'a path to a stellar-core executable (default to `which stellar-core`)', argument: true
11
+ on 'r', 'recipe', 'a recipe file', argument: true #, required: true
12
+ end
13
+
14
+ recipe = load_recipe
15
+ commander = make_commander
16
+ process = commander.make_process
17
+
18
+ #run recipe
19
+ transactor = StellarCoreCommander::Transactor.new(process)
20
+ process.run
21
+ process.wait_for_ready
22
+ transactor.run_recipe recipe
23
+ transactor.close_ledger
24
+
25
+ output_results(process)
26
+ end
27
+
28
+
29
+ def make_commander
30
+ stellar_core_bin = $opts[:"stellar-core-bin"]
31
+
32
+ if stellar_core_bin.blank?
33
+ search = `which stellar-core`.strip
34
+
35
+ if $?.success?
36
+ stellar_core_bin = search
37
+ else
38
+ $stderr.puts "Could not find a `stellar-core` binary, please use --stellar-core-bin to specify"
39
+ exit 1
40
+ end
41
+ end
42
+
43
+ StellarCoreCommander::Commander.new(stellar_core_bin).tap do |c|
44
+ c.cleanup_at_exit!
45
+ end
46
+ end
47
+
48
+ def load_recipe
49
+ recipe = $opts[:recipe]
50
+
51
+ if recipe.blank?
52
+ $stderr.puts $opts
53
+ exit 1
54
+ end
55
+
56
+ unless File.exist?(recipe)
57
+ $stderr.puts "not found: #{recipe}"
58
+ exit 1
59
+ end
60
+
61
+ recipe
62
+ end
63
+
64
+ def output_results(process)
65
+ $stdout.puts process.dump_database
66
+ end
67
+
68
+ run
69
+
70
+
@@ -0,0 +1,20 @@
1
+ account :usd_gateway
2
+ account :scott
3
+ account :andrew
4
+
5
+ payment :master, :usd_gateway, [:native, 1000_000000]
6
+ payment :master, :scott, [:native, 1000_000000]
7
+ payment :master, :andrew, [:native, 1000_000000]
8
+
9
+ close_ledger
10
+
11
+ trust :scott, :usd_gateway, "USD"
12
+ trust :andrew, :usd_gateway, "USD"
13
+
14
+ close_ledger
15
+
16
+ payment :usd_gateway, :scott, ["USD", :usd_gateway, 1000_000000]
17
+
18
+ close_ledger
19
+
20
+ payment :scott, :andrew, ["USD", :usd_gateway, 500_000000]
@@ -0,0 +1,34 @@
1
+ account :usd_gateway
2
+ account :eur_gateway
3
+ account :scott
4
+ account :bartek
5
+ account :andrew
6
+
7
+ payment :master, :usd_gateway, [:native, 1000_000000]
8
+ payment :master, :eur_gateway, [:native, 1000_000000]
9
+
10
+ payment :master, :scott, [:native, 1000_000000]
11
+ payment :master, :bartek, [:native, 1000_000000]
12
+ payment :master, :andrew, [:native, 1000_000000]
13
+
14
+ close_ledger
15
+
16
+ trust :scott, :usd_gateway, "USD"
17
+ trust :bartek, :eur_gateway, "EUR"
18
+ trust :andrew, :usd_gateway, "USD"
19
+ trust :andrew, :eur_gateway, "EUR"
20
+
21
+ close_ledger
22
+
23
+ payment :usd_gateway, :scott, ["USD", :usd_gateway, 1000_000000]
24
+ payment :usd_gateway, :andrew, ["USD", :usd_gateway, 200_000000]
25
+ payment :eur_gateway, :andrew, ["EUR", :eur_gateway, 200_000000]
26
+ payment :eur_gateway, :bartek, ["EUR", :eur_gateway, 1000_000000]
27
+
28
+ close_ledger
29
+
30
+ offer :offer_1, :andrew, ["EUR", :eur_gateway], ["USD", :usd_gateway], 200_000000, 1.0
31
+
32
+ close_ledger
33
+
34
+ payment :scott, :bartek, ["EUR", :eur_gateway, 10], path: [["USD", :usd_gateway], ["EUR", :eur_gateway]]
@@ -0,0 +1,3 @@
1
+ account :scott
2
+
3
+ payment :master, :scott, [:native, 1000_000000]
@@ -0,0 +1,42 @@
1
+ require 'fileutils'
2
+ module StellarCoreCommander
3
+ class Commander
4
+ include Contracts
5
+
6
+ #
7
+ # Creates a new core commander
8
+ #
9
+ Contract String => Any
10
+ def initialize(stellar_core_bin)
11
+ @stellar_core_bin = stellar_core_bin
12
+ raise "no file at #{stellar_core_bin}" unless File.exist?(stellar_core_bin)
13
+
14
+ @processes = []
15
+ end
16
+
17
+ Contract None => Process
18
+ def make_process
19
+ tmpdir = Dir.mktmpdir
20
+ identity = Stellar::KeyPair.random
21
+ base_port = 39132
22
+
23
+ FileUtils.cp(@stellar_core_bin, "#{tmpdir}/stellar-core")
24
+ Process.new(tmpdir, base_port, identity).tap do |p|
25
+ p.setup
26
+ @processes << p
27
+ end
28
+ end
29
+
30
+ def cleanup
31
+ @processes.each(&:cleanup)
32
+ end
33
+
34
+ def cleanup_at_exit!
35
+ at_exit do
36
+ $stderr.puts "cleaning up #{@processes.length} processes"
37
+ cleanup
38
+ end
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,30 @@
1
+ module StellarCoreCommander
2
+ #
3
+ # Generic format conversion module
4
+ #
5
+ module Convert
6
+ require 'base64'
7
+
8
+ def to_hex(string)
9
+ string.unpack("H*").first
10
+ end
11
+
12
+ def from_hex(hex_string)
13
+ [hex_string].pack("H*")
14
+ end
15
+
16
+ def to_base64(string)
17
+ Base64.strict_encode64(string)
18
+ end
19
+
20
+ def from_base64(base64_string)
21
+ Base64.strict_decode64(base64_string)
22
+ end
23
+
24
+ def base58
25
+ Stellar::Util::Base58.stellar
26
+ end
27
+
28
+ extend self
29
+ end
30
+ end
@@ -0,0 +1,272 @@
1
+ module StellarCoreCommander
2
+
3
+ class Process
4
+ include Contracts
5
+
6
+ attr_reader :working_dir
7
+ attr_reader :base_port
8
+ attr_reader :identity
9
+ attr_reader :pid
10
+ attr_reader :wait
11
+
12
+ def initialize(working_dir, base_port, identity)
13
+ @working_dir = working_dir
14
+ @base_port = base_port
15
+ @identity = identity
16
+
17
+ @server = Faraday.new(url: "http://127.0.0.1:#{http_port}") do |conn|
18
+ conn.request :url_encoded
19
+ conn.adapter Faraday.default_adapter
20
+ end
21
+ end
22
+
23
+ Contract None => Any
24
+ def forcescp
25
+ run_cmd "./stellar-core", ["--forcescp"]
26
+ raise "Could not set --forcescp" unless $?.success?
27
+ end
28
+
29
+ Contract None => Any
30
+ def initialize_history
31
+ run_cmd "./stellar-core", ["--newhist", "main"]
32
+ raise "Could not initialize history" unless $?.success?
33
+ end
34
+
35
+ Contract None => Any
36
+ def initialize_database
37
+ run_cmd "./stellar-core", ["--newdb"]
38
+ raise "Could not initialize db" unless $?.success?
39
+ end
40
+
41
+ Contract None => Any
42
+ def create_database
43
+ run_cmd "createdb", [database_name]
44
+ raise "Could not create db: #{database_name}" unless $?.success?
45
+ end
46
+
47
+ Contract None => Any
48
+ def drop_database
49
+ run_cmd "dropdb", [database_name]
50
+ raise "Could not drop db: #{database_name}" unless $?.success?
51
+ end
52
+
53
+ Contract None => Any
54
+ def write_config
55
+ IO.write("#{@working_dir}/stellar-core.cfg", config)
56
+ end
57
+
58
+ Contract None => Any
59
+ def rm_working_dir
60
+ FileUtils.rm_rf @working_dir
61
+ end
62
+
63
+ Contract None => Any
64
+ def setup
65
+ write_config
66
+ create_database
67
+ initialize_history
68
+ initialize_database
69
+ end
70
+
71
+ Contract None => Num
72
+ def run
73
+ raise "already running!" if running?
74
+
75
+ forcescp
76
+ launch_stellar_core
77
+ end
78
+
79
+
80
+ Contract None => Any
81
+ def wait_for_ready
82
+ loop do
83
+
84
+ response = @server.get("/info") rescue false
85
+
86
+ if response
87
+ body = ActiveSupport::JSON.decode(response.body)
88
+
89
+ break if body["info"]["state"] == "Synced!"
90
+ end
91
+
92
+ $stderr.puts "waiting until stellar-core is synced"
93
+ sleep 1
94
+ end
95
+ end
96
+
97
+ Contract None => Bool
98
+ def running?
99
+ return false unless @pid
100
+ ::Process.kill 0, @pid
101
+ true
102
+ rescue Errno::ESRCH
103
+ false
104
+ end
105
+
106
+ Contract Bool => Bool
107
+ def shutdown(graceful=true)
108
+ return true if !running?
109
+
110
+ if graceful
111
+ ::Process.kill "INT", @pid
112
+ else
113
+ ::Process.kill "KILL", @pid
114
+ end
115
+
116
+ @wait.value.success?
117
+ end
118
+
119
+ Contract None => Bool
120
+ def close_ledger
121
+ prev_ledger = latest_ledger
122
+ next_ledger = prev_ledger + 1
123
+
124
+ @server.get("manualclose")
125
+
126
+ Timeout.timeout(5.0) do
127
+ loop do
128
+ current_ledger = latest_ledger
129
+
130
+ case
131
+ when current_ledger == next_ledger
132
+ break
133
+ when current_ledger > next_ledger
134
+ raise "whoa! we jumped two ledgers, from #{prev_ledger} to #{current_ledger}"
135
+ else
136
+ $stderr.puts "waiting for ledger #{next_ledger}"
137
+ sleep 0.5
138
+ end
139
+ end
140
+ end
141
+
142
+ true
143
+ end
144
+
145
+ Contract String => Stellar::TransactionResult
146
+ def submit_transaction(envelope_hex)
147
+ response = @server.get("tx", blob: envelope_hex)
148
+ body = ActiveSupport::JSON.decode(response.body)
149
+
150
+ unless body["wasReceived"] == true
151
+ raise "transaction failed: #{body["result"]}"
152
+ end
153
+
154
+ hex_tr = body["result"]
155
+ raw_tr = Convert.from_hex(hex_tr)
156
+ Stellar::TransactionResult.from_xdr(raw_tr)
157
+ end
158
+
159
+
160
+ Contract Stellar::KeyPair => Num
161
+ def sequence_for(account)
162
+ row = database[:accounts].where(:accountid => account.address).first
163
+ row[:seqnum]
164
+ end
165
+
166
+
167
+ Contract None => Num
168
+ def latest_ledger
169
+ database[:ledgerheaders].max(:ledgerseq)
170
+ end
171
+
172
+ Contract String => String
173
+ def transaction_result(hex_hash)
174
+ row = database[:txhistory].where(txid:hex_hash).first
175
+ row[:txresult]
176
+ end
177
+
178
+ Contract None => Any
179
+ def cleanup
180
+ database.disconnect
181
+ shutdown
182
+ drop_database
183
+ rm_working_dir
184
+ end
185
+
186
+ Contract None => Any
187
+ def dump_database
188
+ Dir.chdir(@working_dir) do
189
+ `pg_dump #{database_name} --clean --no-owner`
190
+ end
191
+ end
192
+
193
+
194
+ Contract None => Sequel::Database
195
+ def database
196
+ @database ||= Sequel.postgres(database_name)
197
+ end
198
+
199
+ Contract None => String
200
+ def database_name
201
+ "stellar_core_tmp_#{basename}"
202
+ end
203
+
204
+ Contract None => String
205
+ def dsn
206
+ "postgresql://dbname=#{database_name}"
207
+ end
208
+
209
+ Contract None => Num
210
+ def http_port
211
+ @base_port
212
+ end
213
+
214
+ Contract None => Num
215
+ def peer_port
216
+ @base_port + 1
217
+ end
218
+
219
+ private
220
+ Contract None => String
221
+ def basename
222
+ File.basename(@working_dir)
223
+ end
224
+
225
+ Contract String, ArrayOf[String] => Maybe[Bool]
226
+ def run_cmd(cmd, args)
227
+ args += [{
228
+ out: "stellar-core.log",
229
+ err: "stellar-core.log",
230
+ }]
231
+
232
+ Dir.chdir @working_dir do
233
+ system(cmd, *args)
234
+ end
235
+ end
236
+
237
+ def launch_stellar_core
238
+ Dir.chdir @working_dir do
239
+ sin, sout, serr, wait = Open3.popen3("./stellar-core")
240
+
241
+ # throwaway stdout, stderr (the logs will record any output)
242
+ Thread.new{ until (line = sout.gets).nil? ; end }
243
+ Thread.new{ until (line = serr.gets).nil? ; end }
244
+
245
+ @wait = wait
246
+ @pid = wait.pid
247
+ end
248
+ end
249
+
250
+ Contract None => String
251
+ def config
252
+ <<-EOS.strip_heredoc
253
+ MANUAL_CLOSE=true
254
+ PEER_PORT=#{peer_port}
255
+ RUN_STANDALONE=false
256
+ HTTP_PORT=#{http_port}
257
+ PUBLIC_HTTP_PORT=false
258
+ PEER_SEED="#{@identity.seed}"
259
+ VALIDATION_SEED="#{@identity.seed}"
260
+ QUORUM_THRESHOLD=1
261
+ QUORUM_SET=["#{@identity.address}"]
262
+ DATABASE="#{dsn}"
263
+
264
+ [HISTORY.main]
265
+ get="cp history/main/{0} {1}"
266
+ put="cp {0} history/main/{1}"
267
+ mkdir="mkdir -p history/main/{0}"
268
+ EOS
269
+ end
270
+
271
+ end
272
+ end
@@ -0,0 +1,221 @@
1
+ require 'fileutils'
2
+ module StellarCoreCommander
3
+
4
+
5
+ #
6
+ # A transactor plays transactions against a stellar-core test node.
7
+ #
8
+ #
9
+ class Transactor
10
+ include Contracts
11
+
12
+ class FailedTransaction < StandardError ; end
13
+
14
+ Currency = [String, Symbol]
15
+ Amount = Any #TODO
16
+
17
+ Contract Process => Any
18
+ def initialize(process)
19
+ @process = process
20
+ @named = {}.with_indifferent_access
21
+ @unverified = []
22
+ account :master, Stellar::KeyPair.from_raw_seed("allmylifemyhearthasbeensearching")
23
+ end
24
+
25
+ Contract String => Any
26
+
27
+ #
28
+ # Runs the provided recipe against the process identified by @process
29
+ #
30
+ # @param recipe_path [String] path to the recipe file
31
+ #
32
+ def run_recipe(recipe_path)
33
+ raise "stellar-core not running" unless @process.running?
34
+
35
+ recipe_content = IO.read(recipe_path)
36
+ instance_eval recipe_content
37
+ end
38
+
39
+
40
+ Contract Symbol, Stellar::KeyPair => Any
41
+ #
42
+ # Registered an account for this scenario. Future calls may refer to
43
+ # the name provided.
44
+ #
45
+ # @param name [Symbol] the name to register the keypair at
46
+ # @param keypair=Stellar::KeyPair.random [Stellar::KeyPair] the keypair to use for this account
47
+ #
48
+ def account(name, keypair=Stellar::KeyPair.random)
49
+ unless keypair.is_a?(Stellar::KeyPair)
50
+ raise ArgumentError, "`#{keypair.class.name}` is not `Stellar::KeyPair`"
51
+ end
52
+
53
+ add_named name, keypair
54
+ end
55
+
56
+ Contract Symbol, Symbol, Amount, Or[{}, {path: Any}] => Any
57
+ def payment(from, to, amount, options={})
58
+ from = get_account from
59
+ to = get_account to
60
+
61
+ if amount.first != :native
62
+ amount = [:iso4217] + amount
63
+ amount[2] = get_account(amount[2])
64
+ amount[1] = amount[1].ljust(4, "\x00")
65
+ end
66
+
67
+ attrs = {
68
+ account: from,
69
+ destination: to,
70
+ sequence: next_sequence(from),
71
+ amount: amount,
72
+ }
73
+
74
+ if options[:path]
75
+ attrs[:path] = options[:path].map{|p| make_currency p}
76
+ end
77
+ envelope = Stellar::Transaction.payment(attrs).to_envelope(from)
78
+
79
+ submit_transaction envelope do |result|
80
+ payment_result = result.result.results!.first.tr!.payment_result!
81
+
82
+ raise FailedTransaction unless payment_result.code.value >= 0
83
+ end
84
+ end
85
+
86
+ Contract Symbol, Symbol, String => Any
87
+ def trust(account, issuer, code)
88
+ change_trust account, issuer, code, (2**63)-1
89
+ end
90
+
91
+ Contract Symbol, Symbol, String, Num => Any
92
+ def change_trust(account, issuer, code, limit)
93
+ account = get_account account
94
+
95
+ tx = Stellar::Transaction.change_trust({
96
+ account: account,
97
+ sequence: next_sequence(account),
98
+ line: make_currency([code, issuer]),
99
+ limit: limit
100
+ })
101
+
102
+ envelope = tx.to_envelope(account)
103
+
104
+ submit_transaction envelope
105
+ end
106
+
107
+ Contract Symbol, Symbol, Currency, Currency, Num, Num => Any
108
+ def offer(name, account, taker_gets, taker_pays, amount, price)
109
+ account = get_account account
110
+ taker_gets = make_currency taker_gets
111
+ taker_pays = make_currency taker_pays
112
+
113
+ tx = Stellar::Transaction.create_offer({
114
+ account: account,
115
+ sequence: next_sequence(account),
116
+ taker_gets: taker_gets,
117
+ taker_pays: taker_pays,
118
+ amount: amount,
119
+ price: price,
120
+ })
121
+
122
+ envelope = tx.to_envelope(account)
123
+
124
+ submit_transaction envelope do |result|
125
+ offer = begin
126
+ co_result = result.result.results!.first.tr!.create_offer_result!
127
+ co_result.success!.offer.offer!
128
+ rescue
129
+ raise FailedTransaction, "Could not extract offer from result:#{result.to_xdr(:base64)}"
130
+ end
131
+
132
+ add_named name, offer
133
+ end
134
+ end
135
+
136
+ Contract None => Any
137
+ #
138
+ # Triggers a ledger close. Any unvalidated transaction will
139
+ # be validated, which will trigger an error if any fail to be validated
140
+ #
141
+ def close_ledger
142
+ @process.close_ledger
143
+
144
+ @unverified.each do |eb|
145
+ begin
146
+ envelope, after_confirmation = *eb
147
+ result = validate_transaction envelope
148
+ after_confirmation.call(result) if after_confirmation
149
+ rescue FailedTransaction
150
+ require 'pry'; binding.pry
151
+ $stderr.puts "Failed to validate tx: #{Convert.to_hex envelope.tx.hash}"
152
+ exit 1
153
+ end
154
+ end
155
+
156
+ # TODO: validate in-flight transactions
157
+ @unverified.clear
158
+ end
159
+
160
+ private
161
+ Contract Symbol, Any => Any
162
+ def add_named(name, object)
163
+ if @named.has_key?(name)
164
+ raise ArgumentError, "#{name} is already registered"
165
+ end
166
+
167
+ @named[name] = object
168
+ end
169
+
170
+ Contract Stellar::TransactionEnvelope, Or[nil, Proc] => Any
171
+ def submit_transaction(envelope, &after_confirmation)
172
+ hex = envelope.to_xdr(:hex)
173
+ @process.submit_transaction hex
174
+
175
+ # submit to process
176
+ @unverified << [envelope, after_confirmation]
177
+ end
178
+
179
+ Contract Symbol => Stellar::KeyPair
180
+ def get_account(name)
181
+ @named[name].tap do |found|
182
+ unless found.is_a?(Stellar::KeyPair)
183
+ raise ArgumentError, "#{name.inspect} is not account"
184
+ end
185
+ end
186
+ end
187
+
188
+ Contract Stellar::KeyPair => Num
189
+ def next_sequence(account)
190
+ base_sequence = @process.sequence_for(account)
191
+ inflight_count = @unverified.select{|e| e.first.tx.source_account == account.public_key}.length
192
+
193
+ base_sequence + inflight_count + 1
194
+ end
195
+
196
+ Contract Currency => [Symbol, String, Stellar::KeyPair]
197
+ def make_currency(input)
198
+ code, issuer = *input
199
+ code = code.ljust(4, "\x00")
200
+ issuer = get_account issuer
201
+
202
+ [:iso4217, code, issuer]
203
+ end
204
+
205
+ Contract Stellar::TransactionEnvelope => Stellar::TransactionResult
206
+ def validate_transaction(envelope)
207
+ raw_hash = envelope.tx.hash
208
+ hex_hash = Convert.to_hex(raw_hash)
209
+
210
+ base64_result = @process.transaction_result(hex_hash)
211
+
212
+ raise "couldn't fine result for #{hex_hash}" if base64_result.blank?
213
+
214
+ raw_result = Convert.from_base64(base64_result)
215
+
216
+ pair = Stellar::TransactionResultPair.from_xdr(raw_result)
217
+ pair.result
218
+ end
219
+
220
+ end
221
+ end
@@ -0,0 +1,3 @@
1
+ module StellarCoreCommander
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,20 @@
1
+ require "stellar_core_commander/version"
2
+ require "active_support/all"
3
+ require "stellar-base"
4
+ require "contracts"
5
+ require "faraday"
6
+ require "faraday_middleware"
7
+ require "fileutils"
8
+ require "open3"
9
+ require "sequel"
10
+ require "pg"
11
+
12
+ module StellarCoreCommander
13
+ extend ActiveSupport::Autoload
14
+
15
+ autoload :Commander
16
+ autoload :Process
17
+ autoload :Transactor
18
+
19
+ autoload :Convert
20
+ end
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'stellar_core_commander/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "stellar_core_commander"
8
+ spec.version = StellarCoreCommander::VERSION
9
+ spec.authors = ["Scott Fleckenstein"]
10
+ spec.email = ["nullstyle@gmail.com"]
11
+ spec.summary = %q{A helper gem for scripting stellar-core}
12
+ spec.homepage = ""
13
+ spec.license = "Apache 2.0"
14
+
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
+ spec.require_paths = ["lib"]
19
+
20
+ spec.add_dependency "stellar-base", "~> 0.0.1"
21
+ spec.add_dependency "slop", "~> 3.6.0"
22
+ spec.add_dependency "faraday", "~> 0.9.1"
23
+ spec.add_dependency "faraday_middleware", "~> 0.9.1"
24
+ spec.add_dependency "pg", "~> 0.18.1"
25
+ spec.add_dependency "sequel", "~> 4.21.0"
26
+ spec.add_dependency "activesupport", ">= 4.0.0"
27
+ spec.add_dependency "contracts", "~> 0.9"
28
+
29
+ spec.add_development_dependency "bundler", "~> 1.7"
30
+ spec.add_development_dependency "rake", "~> 10.0"
31
+ spec.add_development_dependency "pry"
32
+ end
data/test.rb ADDED
@@ -0,0 +1,11 @@
1
+ require 'stellar_core_commander'
2
+ require 'pry'
3
+
4
+ bin = File.expand_path("~/src/stellar/stellar-core/bin/stellar-core")
5
+ cmd = StellarCoreCommander::Commander.new(bin)
6
+
7
+ cmd.cleanup_at_exit!
8
+ p1 = cmd.make_process
9
+
10
+
11
+ binding.pry
metadata ADDED
@@ -0,0 +1,217 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: stellar_core_commander
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Scott Fleckenstein
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-04-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: stellar-base
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.0.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.0.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: slop
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 3.6.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 3.6.0
41
+ - !ruby/object:Gem::Dependency
42
+ name: faraday
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.9.1
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.9.1
55
+ - !ruby/object:Gem::Dependency
56
+ name: faraday_middleware
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.9.1
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.9.1
69
+ - !ruby/object:Gem::Dependency
70
+ name: pg
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.18.1
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 0.18.1
83
+ - !ruby/object:Gem::Dependency
84
+ name: sequel
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 4.21.0
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 4.21.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: activesupport
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 4.0.0
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: 4.0.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: contracts
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '0.9'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '0.9'
125
+ - !ruby/object:Gem::Dependency
126
+ name: bundler
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '1.7'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '1.7'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rake
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '10.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '10.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: pry
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description:
168
+ email:
169
+ - nullstyle@gmail.com
170
+ executables:
171
+ - scc
172
+ extensions: []
173
+ extra_rdoc_files: []
174
+ files:
175
+ - ".gitignore"
176
+ - Gemfile
177
+ - LICENSE.txt
178
+ - README.md
179
+ - Rakefile
180
+ - bin/scc
181
+ - examples/non_native_payment.rb
182
+ - examples/pathed_payment.rb
183
+ - examples/simple_payment.rb
184
+ - lib/stellar_core_commander.rb
185
+ - lib/stellar_core_commander/commander.rb
186
+ - lib/stellar_core_commander/convert.rb
187
+ - lib/stellar_core_commander/process.rb
188
+ - lib/stellar_core_commander/transactor.rb
189
+ - lib/stellar_core_commander/version.rb
190
+ - stellar_core_commander.gemspec
191
+ - test.rb
192
+ homepage: ''
193
+ licenses:
194
+ - Apache 2.0
195
+ metadata: {}
196
+ post_install_message:
197
+ rdoc_options: []
198
+ require_paths:
199
+ - lib
200
+ required_ruby_version: !ruby/object:Gem::Requirement
201
+ requirements:
202
+ - - ">="
203
+ - !ruby/object:Gem::Version
204
+ version: '0'
205
+ required_rubygems_version: !ruby/object:Gem::Requirement
206
+ requirements:
207
+ - - ">="
208
+ - !ruby/object:Gem::Version
209
+ version: '0'
210
+ requirements: []
211
+ rubyforge_project:
212
+ rubygems_version: 2.2.2
213
+ signing_key:
214
+ specification_version: 4
215
+ summary: A helper gem for scripting stellar-core
216
+ test_files: []
217
+ has_rdoc: