diff --git a/Builds/VisualStudio/stellar-core.vcxproj b/Builds/VisualStudio/stellar-core.vcxproj
index c2b9076e83..cae0385a6d 100644
--- a/Builds/VisualStudio/stellar-core.vcxproj
+++ b/Builds/VisualStudio/stellar-core.vcxproj
@@ -319,6 +319,7 @@ exit /b 0
+
@@ -412,7 +413,10 @@ exit /b 0
+
+
+
@@ -595,6 +599,7 @@ exit /b 0
+
@@ -652,8 +657,11 @@ exit /b 0
+
+
+
@@ -933,4 +941,4 @@ exit /b 0
-
\ No newline at end of file
+
diff --git a/Builds/VisualStudio/stellar-core.vcxproj.filters b/Builds/VisualStudio/stellar-core.vcxproj.filters
index 92595f2703..2cc57023e3 100644
--- a/Builds/VisualStudio/stellar-core.vcxproj.filters
+++ b/Builds/VisualStudio/stellar-core.vcxproj.filters
@@ -141,6 +141,15 @@
main
+
+ main
+
+
+ main
+
+
+ main
+
main
@@ -720,6 +729,9 @@
history
+
+ history
+
history
@@ -887,6 +899,15 @@
main
+
+ main
+
+
+ main
+
+
+ main
+
overlay
@@ -1415,6 +1436,9 @@
history
+
+ history
+
history
@@ -1637,4 +1661,4 @@
-
\ No newline at end of file
+
diff --git a/docs/software/admin.md b/docs/software/admin.md
index fb16c3032d..cdf89ab326 100644
--- a/docs/software/admin.md
+++ b/docs/software/admin.md
@@ -183,7 +183,7 @@ Before attempting to configure stellar-core, it is highly recommended to first t
All configuration for stellar-core is done with a TOML file. By default
stellar-core loads `./stellar-core.cfg`, but you can specify a different file to load on the command line:
-`$ stellar-core --conf betterfile.cfg`
+`$ stellar-core --conf betterfile.cfg`
The [example config](https://github.com/stellar/stellar-core/blob/master/docs/stellar-core_example.cfg) is not a real configuration, but documents all possible configuration elements as well as their default values.
@@ -206,7 +206,7 @@ messages will look like they came from you.
Generate a key pair like this:
-`$ stellar-core --genseed`
+`$ stellar-core gen-seed`
the output will look something like
```
Secret seed: SBAAOHEU4WSWX6GBZ3VOXEGQGWRBJ72ZN3B3MFAJZWXRYGDIWHQO37SY
@@ -352,7 +352,7 @@ Cross reference your validator settings, in particular:
After configuring your [database](#database) and [buckets](#buckets) settings, when running stellar-core for the first time, you must initialize the database:
-`$ stellar-core --newdb`
+`$ stellar-core new-db`
This command will initialize the database as well as the bucket directory and then exit.
@@ -414,7 +414,7 @@ Archive sections can also be configured with `put` and `mkdir` commands to
cause the instance to publish to that archive (for nodes configured as [archiver nodes](#archiver-nodes) or [full validators](#full-validators)).
The very first time you want to use your archive *before starting your node* you need to initialize it with:
-`$ stellar-core --newhist `
+`$ stellar-core new-hist `
**IMPORTANT:**
* make sure that you configure both `put` and `mkdir` if `put` doesn't
@@ -437,7 +437,7 @@ After having configured your node and its environment, you're ready to start ste
This can be done with a command equivalent to
-`$ stellar-core`
+`$ stellar-core run`
At this point you're ready to observe core's activity as it joins the network.
@@ -448,7 +448,7 @@ While running, interaction with stellar-core is done via an administrative
HTTP endpoint. Commands can be submitted using command-line HTTP tools such
as `curl`, or by running a command such as
-`$ stellar-core -c `
+`$ stellar-core http-command `
The endpoint is [not intended to be exposed to the public internet](#interaction-with-other-internal-systems). It's typically accessed by administrators, or by a mid-tier application to submit transactions to the Stellar network.
@@ -526,14 +526,14 @@ configurable as `LOG_FILE_PATH`.
The log level can be controlled by configuration, the `-ll` command-line flag
or adjusted dynamically by administrative (HTTP) commands. Run:
-`$ stellar-core -c "ll?level=debug"`
+`$ stellar-core http-command "ll?level=debug"`
against a running system.
Log levels can also be adjusted on a partition-by-partition basis through the
administrative interface.
For example the history system can be set to DEBUG-level logging by running:
-`$ stellar-core -c "ll?level=debug&partition=history"`
+`$ stellar-core http-command "ll?level=debug&partition=history"`
against a running system.
The default log level is `INFO`, which is moderately verbose and should emit
@@ -545,7 +545,7 @@ against a running system.
Information provided here can be used for both human operators and programmatic access.
### General node information
-Run `$ stellar-core --c 'info'`
+Run `$ stellar-core http-command 'info'`
The output will look something like
```json
{
@@ -624,7 +624,7 @@ The `peers` command returns information on the peers the instance is connected t
This list is the result of both inbound connections from other peers and outbound connections from this node to other peers.
-`$ stellar-core --c 'peers'`
+`$ stellar-core http-command 'peers'`
```json
{
@@ -661,7 +661,7 @@ The `quorum` command allows to diagnose problems with the quorum set of the loca
Run
-`$ stellar-core --c 'quorum'`
+`$ stellar-core http-command 'quorum'`
The output looks something like:
```json
@@ -702,7 +702,7 @@ as a whole will not be able to reach consensus (and the opposite is true, the ne
may fail because of a different set of validators failing).
You can get a sense of the quorum set health of a different node by doing
-`$ stellar-core --c 'quorum?node=$sdf1` or `$ stellar-core --c 'quorum?node=@GABCDE`
+`$ stellar-core http-command 'quorum?node=$sdf1` or `$ stellar-core http-command 'quorum?node=@GABCDE`
Overall network health can be evaluated by walking through all nodes and looking at their health. Note that this is only an approximation as remote nodes may not have received the same messages (in particular: `missing` for other nodes is not reliable).
@@ -769,9 +769,9 @@ For more information look at [`docs/versioning.md`](../versioning.md).
Example here is to upgrade the protocol version to version 9 on January-31-2018.
-1. `$ stellar-core -c 'upgrades?mode=set&upgradetime=2018-01-31T20:00:00Z&protocolversion=9'`
+1. `$ stellar-core http-command 'upgrades?mode=set&upgradetime=2018-01-31T20:00:00Z&protocolversion=9'`
-2. `$ stellar-core -c info`
+2. `$ stellar-core http-command info`
At this point `info` will tell you that the node is setup to vote for this upgrade:
```json
"status" : [
diff --git a/docs/software/commands.md b/docs/software/commands.md
index e6b0aa47c7..954de1b831 100644
--- a/docs/software/commands.md
+++ b/docs/software/commands.md
@@ -4,79 +4,102 @@ title: Commands
stellar-core can be controlled via the following commands.
+## Common options
+* **--conf **: Specify a config file to use. You can use '-' and
+ provide the config file via STDIN. *default 'stellar-core.cfg'*
+* **--ll **: Set the log level. It is redundant with `htpp-command ll`
+ but we need this form if you want to change the log level during test runs.
+* **--metric **: Report metric METRIC on exit. Used for gathering
+ a metric cumulatively during a test run.
+
## Command line options
-* **--?** or **--help**: Print the available command line options and then exit..
-* **--c** Send an [HTTP command](#http-commands) to an already running local instance of stellar-core and then exit. For example:
-
-`$ stellar-core -c info`
-
-* **--conf FILE**: Specify a config file to use. You can use '-' and provide the config file via STDIN. *default 'stellar-core.cfg'*
-* **--convertid ID**: Will output the passed ID in all known forms and then exit. Useful for determining the public key that corresponds to a given private key. For example:
-
-`$ stellar-core --convertid SDQVDISRYN2JXBS7ICL7QJAEKB3HWBJFP2QECXG7GZICAHBK4UNJCWK2`
-
-* **--dumpxdr FILE**: Dumps the given XDR file and then exits.
-* **--loadxdr FILE**: Load an XDR bucket file, for testing.
-* **--forcescp**: This command is used to start a network from scratch or when a
-network has lost quorum because of failed nodes or otherwise. It sets a flag in
-the database. The next time stellar-core is run, stellar-core will start
-emitting SCP messages based on its last known ledger. Without this flag stellar-core waits to hear
-a ledger close from the network before starting SCP.
-forcescp doesn't change the requirements for quorum so although this node will emit SCP messages SCP won't complete until there are also a quorum of other nodes also emitting SCP messages on this same ledger.
-* **--fuzz FILE**: Run a single fuzz input and exit.
-* **--genfuzz FILE**: Generate a random fuzzer input file.
-* **--genseed**: Generate and print a random public/private key and then exit.
-* **--inferquorum**: Print a potential quorum set inferred from history.
-* **--checkquorum**: Check quorum intersection from history to ensure there is closure over all the validators in the network.
-* **--graphquorum**: Print a quorum set graph from history.
-* **--offlineinfo**: Returns an output similar to `--c info` for an offline instance
-* **--ll LEVEL**: Set the log level. It is redundant with `--c ll` but we need this form if you want to change the log level during test runs.
-* **--metric METRIC**: Report metric METRIC on exit. Used for gathering a metric cumulatively during a test run.
-* **--newdb**: Clears the local database and resets it to the genesis ledger. If you connect to the network after that it will catch up from scratch.
-* **--newhist ARCH**: Initialize the named history archive ARCH. ARCH should be one of the history archives you have specified in the stellar-core.cfg. This will write a `.well-known/stellar-history.json` file in the archive root.
-* **--printxdr FILE**: Pretty-print a binary file containing an XDR object. If FILE is "-", the XDR object is read from
- standard input.
-* **--filetype [auto|ledgerheader|meta|result|resultpair|tx|txfee]**: toggle for type used for printxdr (default: auto).
-* **--signtxn FILE**: Add a digital signature to a transaction
- envelope stored in binary format in FILE, and send the result to
- standard output (which should be redirected to a file or piped
- through a tool such as `base64`). The private signing key is read
- from standard input, unless FILE is "-" in which case the
- transaction envelope is read from standard input and the signing key
- is read from `/dev/tty`. In either event, if the signing key
- appears to be coming from a terminal, stellar-core disables echo.
- Note that if you do not have a STELLAR_NETWORK_ID environment
- variable, then before this argument you must specify the --netid
- option.
-* **--base64**: When preceding --printtxn or --signtxn, alters the
- behavior of the option to work on base64-encoded XDR rather than
+* **catchup **: Perform catchup from history
+ archives without connecting to network. This option will catchup to
+ DESTINATION-LEDGER keeping history from DESTINATION-LEDGER-LEDGER-COUNT or
+ from the last closed ledger (whichever value is biggest).
+* **check-quorum**: Check quorum intersection from history to ensure there is
+ closure over all the validators in the network.
+* **convert-id **: Will output the passed ID in all known forms and then
+ exit. Useful for determining the public key that corresponds to a given
+ private key. For example:
+
+`$ stellar-core convert-id SDQVDISRYN2JXBS7ICL7QJAEKB3HWBJFP2QECXG7GZICAHBK4UNJCWK2`
+
+* **dump-xdr **: Dumps the given XDR file and then exits.
+* **force-scp**: This command is used to start a network from scratch or when a
+ network has lost quorum because of failed nodes or otherwise. It sets a flag
+ in the database. The next time stellar-core is run, stellar-core will start
+ emitting SCP messages based on its last known ledger. Without this flag
+ stellar-core waits to hear a ledger close from the network before starting
+ SCP.
force-scp doesn't change the requirements for quorum so although
+ this node will emit SCP messages SCP won't complete until there are also a
+ quorum of other nodes also emitting SCP messages on this same ledger. Value
+ of force-scp can be reset with --reset flag.
+* **fuzz **: Run a single fuzz input and exit.
+* **gen-fuzz **: Generate a random fuzzer input file.
+* **gen-seed**: Generate and print a random public/private key and then exit.
+* **help**: Print the available command line options and then exit..
+* **http-command ** Send an [HTTP command](#http-commands) to an
+ already running local instance of stellar-core and then exit. For example:
+
+`$ stellar-core http-command info`
+
+* **infer-quorum**: Print a potential quorum set inferred from history.
+* **load-xdr **: Load an XDR bucket file, for testing.
+* **new-db**: Clears the local database and resets it to the genesis ledger. If
+ you connect to the network after that it will catch up from scratch.
+* **new-hist ...**: Initialize the named history archives
+ HISTORY-LABEL. HISTORY-LABEL should be one of the history archives you have
+ specified in the stellar-core.cfg. This will write a
+ `.well-known/stellar-history.json` file in the archive root.
+* **offline-info**: Returns an output similar to `--c info` for an offline
+ instance
+* **print-xdr **: Pretty-print a binary file containing an XDR
+ object. If FILE-NAME is "-", the XDR object is read from standard input.
+ Option --filetype [auto|ledgerheader|meta|result|resultpair|tx|txfee]**
+ controls type used for printing (default: auto).
+ Option --base64 alters the behavior to work on base64-encoded XDR rather than
raw XDR.
-* **--sec2pub**: Reads a secret key on standard input and outputs the
+* **report-last-history-checkpoint**: Download and report last history
+ checkpoint from a history archive.
+* **run**: Runs stellar-core service.
+* **sec-to-pub**: Reads a secret key on standard input and outputs the
corresponding public key. Both keys are in Stellar's standard
- base-32 ASCII format.
-* **--netid STRING**: The --signtxn option requires a particular
- network to sign for. For example, the production stellar network is
- "`Public Global Stellar Network ; September 2015`" while the test
- network is "`Test SDF Network ; September 2015`".
-* **--test**: Run all the unit tests.
+ base-32 ASCII format.
+* **sign-transaction **: Add a digital signature to a transaction
+ envelope stored in binary format in , and send the result to
+ standard output (which should be redirected to a file or piped through a tool
+ such as `base64`). The private signing key is read from standard input,
+ unless is "-" in which case the transaction envelope is read from
+ standard input and the signing key is read from `/dev/tty`. In either event,
+ if the signing key appears to be coming from a terminal, stellar-core
+ disables echo. Note that if you do not have a STELLAR_NETWORK_ID environment
+ variable, then before this argument you must specify the --netid option. For
+ example, the production stellar network is "`Public Global Stellar Network ;
+ September 2015`" while the test network is "`Test SDF Network ; September
+ 2015`".
+ Option --base64 alters the behavior to work on base64-encoded XDR rather than
+ raw XDR.
+* **test**: Run all the unit tests.
* Suboptions specific to stellar-core:
* `--all-versions` : run with all possible protocol versions
* `--version ` : run tests for protocol version N, can be specified
multiple times (default latest)
* `--base-instance ` : run tests with instance numbers offset by N,
used to run tests in parallel
- * For [further info](https://github.com/philsquared/Catch/blob/master/docs/command-line.md) on
- possible options for test.
+ * For [further info](https://github.com/philsquared/Catch/blob/master/docs/command-line.md)
+ on possible options for test.
* For example this will run just the tests tagged with `[tx]` using protocol
- versions 9 and 10 and stop after the first failure:
- `stellar-core --test -a --version 9 --version 10 "[tx]"`
-* **--version**: Print version info and then exit.
-
+ versions 9 and 10 and stop after the first failure:
+ `stellar-core --test -a --version 9 --version 10 "[tx]"`
+* **version**: Print version info and then exit.
+* **write-quorum**: Print a quorum set graph from history.
## HTTP Commands
By default stellar-core listens for connections from localhost on port 11626.
You can send commands to stellar-core via a web browser, curl, or using the --c
-command line option (see above). Most commands return their results in JSON format.
+command line option (see above). Most commands return their results in JSON
+format.
* **help**
Prints a list of currently supported commands.
@@ -90,7 +113,8 @@ command line option (see above). Most commands return their results in JSON form
Triggers the instance to perform a background check of the database's state.
* **checkpoint**
- Triggers the instance to write an immediate history checkpoint. And uploads it to the archive.
+ Triggers the instance to write an immediate history checkpoint. And uploads
+ it to the archive.
* **connect**
`/connect?peer=NAME&port=NNN`
@@ -98,29 +122,33 @@ command line option (see above). Most commands return their results in JSON form
* **dropcursor**
`/dropcursor?id=XYZ`
- deletes the tracking cursor with identified by `id`. See `setcursor` for more information.
+ deletes the tracking cursor with identified by `id`. See `setcursor` for
+ more information.
* **info**
- Returns information about the server in JSON format (sync
- state, connected peers, etc).
+ Returns information about the server in JSON format (sync state, connected
+ peers, etc).
* **ll**
`/ll?level=L[&partition=P]`
- Adjust the log level for partition P where P is one of Bucket, Database, Fs, Herder, History, Ledger, Overlay, Process, SCP, Tx (or all if no partition is specified).
- Level is one of FATAL, ERROR, WARNING, INFO, DEBUG, VERBOSE, TRACE
+ Adjust the log level for partition P where P is one of Bucket, Database, Fs,
+ Herder, History, Ledger, Overlay, Process, SCP, Tx (or all if no partition is
+ specified). Level is one of FATAL, ERROR, WARNING, INFO, DEBUG, VERBOSE,
+ TRACE.
* **maintenance**
- `/maintenance?[queue=true]`
+ `/maintenance?[queue=true]`
Performs maintenance tasks on the instance.
* `queue` performs deletion of queue data. See `setcursor` for more information.
* **metrics**
- Returns a snapshot of the metrics registry (for monitoring and
-debugging purpose).
+ Returns a snapshot of the metrics registry (for monitoring and debugging
+ purpose).
* **clearmetrics**
- `/clearmetrics?[domain=DOMAIN]`
- Clear metrics for a specified domain. If no domain specified, clear all metrics (for testing purposes).
+ `/clearmetrics?[domain=DOMAIN]`
+ Clear metrics for a specified domain. If no domain specified, clear all
+ metrics (for testing purposes).
* **peers**
Returns the list of known peers in JSON format.
@@ -128,25 +156,30 @@ debugging purpose).
* **quorum**
`/quorum?[node=NODE_ID][&compact=true]`
returns information about the quorum for node NODE_ID (this node by default).
- NODE_ID is either a full key (`GABCD...`), an alias (`$name`) or
- an abbreviated ID (`@GABCD`).
- If compact is set, only returns a summary version.
+ NODE_ID is either a full key (`GABCD...`), an alias (`$name`) or an
+ abbreviated ID (`@GABCD`). If compact is set, only returns a summary version.
* **setcursor**
- `/setcursor?id=ID&cursor=N`
- sets or creates a cursor identified by `ID` with value `N`. ID is an uppercase AlphaNum, N is an uint32 that represents the last ledger sequence number that the instance ID processed.
- Cursors are used by dependent services to tell stellar-core which data can be safely deleted by the instance.
- The data is historical data stored in the SQL tables such as txhistory or ledgerheaders. When all consumers processed the data for ledger sequence N the data can be safely removed by the instance.
- The actual deletion is performed by invoking the `maintenance` endpoint or on startup.
- See also `dropcursor`.
+ `/setcursor?id=ID&cursor=N`
+ sets or creates a cursor identified by `ID` with value `N`. ID is an
+ uppercase AlphaNum, N is an uint32 that represents the last ledger sequence
+ number that the instance ID processed. Cursors are used by dependent services
+ to tell stellar-core which data can be safely deleted by the instance. The
+ data is historical data stored in the SQL tables such as txhistory or
+ ledgerheaders. When all consumers processed the data for ledger sequence N
+ the data can be safely removed by the instance. The actual deletion is
+ performed by invoking the `maintenance` endpoint or on startup. See also
+ `dropcursor`.
* **getcursor**
- `/getcursor?[id=ID]`
- gets the cursor identified by `ID`. If ID is not defined then all cursors will be returned.
+ `/getcursor?[id=ID]`
+ gets the cursor identified by `ID`. If ID is not defined then all cursors
+ will be returned.
* **scp**
`/scp?[limit=n]`
- Returns a JSON object with the internal state of the SCP engine for the last n (default 2) ledgers.
+ Returns a JSON object with the internal state of the SCP engine for the last
+ n (default 2) ledgers.
* **tx**
`/tx?blob=Base64`
@@ -162,45 +195,50 @@ debugging purpose).
* **upgrades**
* `/upgrades?mode=get`
- retrieves the currently configured upgrade settings
+ retrieves the currently configured upgrade settings
* `/upgrades?mode=clear`
- clears any upgrade settings
+ clears any upgrade settings
* `/upgrades?mode=set&upgradetime=DATETIME&[basefee=NUM]&[basereserve=NUM]&[maxtxsize=NUM]&[protocolversion=NUM]`
* upgradetime is a required date (UTC) in the form `1970-01-01T00:00:00Z`.
It is the time the upgrade will be scheduled for. If it is in the past,
the upgrade will occur immediately.
- * fee (uint32) This is what you would prefer the base fee to be. It is
- in stroops
- * basereserve (uint32) This is what you would prefer the base reserve
- to be. It is in stroops.
- * maxtxsize (uint32) This defines the maximum number of transactions
- to include in a ledger. When too many transactions are pending,
- surge pricing is applied. The instance picks the top maxtxsize
- transactions locally to be considered in the next ledger. Where
- transactions are ordered by transaction fee(lower fee transactions
- are held for later).
+ * fee (uint32) This is what you would prefer the base fee to be. It is in
+ stroops
+ * basereserve (uint32) This is what you would prefer the base reserve to
+ be. It is in stroops.
+ * maxtxsize (uint32) This defines the maximum number of transactions to
+ include in a ledger. When too many transactions are pending, surge
+ pricing is applied. The instance picks the top maxtxsize transactions
+ locally to be considered in the next ledger. Where transactions are
+ ordered by transaction fee(lower fee transactions are held for later).
+
* protocolversion (uint32) defines the protocol version to upgrade to.
- When specified it must match one of the protocol versions supported
- by the node and should be greater than ledgerVersion from the
- current ledger
+ When specified it must match one of the protocol versions supported
+ by the node and should be greater than ledgerVersion from the current
+ ledger
### The following HTTP commands are exposed on test instances
* **generateload**
`/generateload[?mode=(create|pay)&accounts=N&offset=K&txs=M&txrate=(R|auto)&batchsize=L]`
- Artificially generate load for testing; must be used with `ARTIFICIALLY_GENERATE_LOAD_FOR_TESTING` set to true.
- Depending on the mode, either creates new accounts or generates payments on
- accounts specified (where number of accounts can be offset).
- Additionally, allows batching up to 100 account creations per transaction
- via 'batchsize'.
+ Artificially generate load for testing; must be used with
+ `ARTIFICIALLY_GENERATE_LOAD_FOR_TESTING` set to true. Depending on the mode,
+ either creates new accounts or generates payments on accounts specified
+ (where number of accounts can be offset). Additionally, allows batching up to
+ 100 account creations per transaction via 'batchsize'.
* **manualclose**
- If MANUAL_CLOSE is set to true in the .cfg file. This will cause the current ledger to close.
+ If MANUAL_CLOSE is set to true in the .cfg file. This will cause the current
+ ledger to close.
* **testacc**
- `/testacc?name=N`
- Returns basic information about the account identified by name. Note that N is a string used as seed, but "root" can be used as well to specify the root account used for the test instance.
+ `/testacc?name=N`
+ Returns basic information about the account identified by name. Note that N
+ is a string used as seed, but "root" can be used as well to specify the root
+ account used for the test instance.
* **testtx**
- `/testtx?from=F&to=T&amount=N&[create=true]`
- Injects a payment transaction (or a create transaction if "create" is specified) from the account F to the account T, sending N XLM to the account.
- Note that F and T are seed strings but can also be specified as "root" as a shorthand for the root account for the test instance.
+ `/testtx?from=F&to=T&amount=N&[create=true]`
+ Injects a payment transaction (or a create transaction if "create" is
+ specified) from the account F to the account T, sending N XLM to the account.
+ Note that F and T are seed strings but can also be specified as "root" as
+ shorthand for the root account for the test instance.
diff --git a/lib/clara.hpp b/lib/clara.hpp
new file mode 100644
index 0000000000..43568cee2a
--- /dev/null
+++ b/lib/clara.hpp
@@ -0,0 +1,1255 @@
+// Copyright 2017 Two Blue Cubes Ltd. All rights reserved.
+//
+// Distributed under the Boost Software License, Version 1.0. (See accompanying
+// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
+//
+// See https://github.com/philsquared/Clara for more details
+
+// Clara v1.1.4
+
+#ifndef CLARA_HPP_INCLUDED
+#define CLARA_HPP_INCLUDED
+
+#ifndef CLARA_CONFIG_CONSOLE_WIDTH
+#define CLARA_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH CLARA_CONFIG_CONSOLE_WIDTH
+#endif
+
+#ifndef CLARA_CONFIG_OPTIONAL_TYPE
+#ifdef __has_include
+#if __has_include() && __cplusplus >= 201703L
+#include
+#define CLARA_CONFIG_OPTIONAL_TYPE std::optional
+#endif
+#endif
+#endif
+
+
+// ----------- #included from clara_textflow.hpp -----------
+
+// TextFlowCpp
+//
+// A single-header library for wrapping and laying out basic text, by Phil Nash
+//
+// This work is licensed under the BSD 2-Clause license.
+// See the accompanying LICENSE file, or the one at https://opensource.org/licenses/BSD-2-Clause
+//
+// This project is hosted at https://github.com/philsquared/textflowcpp
+
+#ifndef CLARA_TEXTFLOW_HPP_INCLUDED
+#define CLARA_TEXTFLOW_HPP_INCLUDED
+
+#include
+#include
+#include
+#include
+
+#ifndef CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH
+#define CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH 80
+#endif
+
+
+namespace clara { namespace TextFlow {
+
+ inline auto isWhitespace( char c ) -> bool {
+ static std::string chars = " \t\n\r";
+ return chars.find( c ) != std::string::npos;
+ }
+ inline auto isBreakableBefore( char c ) -> bool {
+ static std::string chars = "[({<|";
+ return chars.find( c ) != std::string::npos;
+ }
+ inline auto isBreakableAfter( char c ) -> bool {
+ static std::string chars = "])}>.,:;*+-=&/\\";
+ return chars.find( c ) != std::string::npos;
+ }
+
+ class Columns;
+
+ class Column {
+ std::vector m_strings;
+ size_t m_width = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH;
+ size_t m_indent = 0;
+ size_t m_initialIndent = std::string::npos;
+
+ public:
+ class iterator {
+ friend Column;
+
+ Column const& m_column;
+ size_t m_stringIndex = 0;
+ size_t m_pos = 0;
+
+ size_t m_len = 0;
+ size_t m_end = 0;
+ bool m_suffix = false;
+
+ iterator( Column const& column, size_t stringIndex )
+ : m_column( column ),
+ m_stringIndex( stringIndex )
+ {}
+
+ auto line() const -> std::string const& { return m_column.m_strings[m_stringIndex]; }
+
+ auto isBoundary( size_t at ) const -> bool {
+ assert( at > 0 );
+ assert( at <= line().size() );
+
+ return at == line().size() ||
+ ( isWhitespace( line()[at] ) && !isWhitespace( line()[at-1] ) ) ||
+ isBreakableBefore( line()[at] ) ||
+ isBreakableAfter( line()[at-1] );
+ }
+
+ void calcLength() {
+ assert( m_stringIndex < m_column.m_strings.size() );
+
+ m_suffix = false;
+ auto width = m_column.m_width-indent();
+ m_end = m_pos;
+ while( m_end < line().size() && line()[m_end] != '\n' )
+ ++m_end;
+
+ if( m_end < m_pos + width ) {
+ m_len = m_end - m_pos;
+ }
+ else {
+ size_t len = width;
+ while (len > 0 && !isBoundary(m_pos + len))
+ --len;
+ while (len > 0 && isWhitespace( line()[m_pos + len - 1] ))
+ --len;
+
+ if (len > 0) {
+ m_len = len;
+ } else {
+ m_suffix = true;
+ m_len = width - 1;
+ }
+ }
+ }
+
+ auto indent() const -> size_t {
+ auto initial = m_pos == 0 && m_stringIndex == 0 ? m_column.m_initialIndent : std::string::npos;
+ return initial == std::string::npos ? m_column.m_indent : initial;
+ }
+
+ auto addIndentAndSuffix(std::string const &plain) const -> std::string {
+ return std::string( indent(), ' ' ) + (m_suffix ? plain + "-" : plain);
+ }
+
+ public:
+ explicit iterator( Column const& column ) : m_column( column ) {
+ assert( m_column.m_width > m_column.m_indent );
+ assert( m_column.m_initialIndent == std::string::npos || m_column.m_width > m_column.m_initialIndent );
+ calcLength();
+ if( m_len == 0 )
+ m_stringIndex++; // Empty string
+ }
+
+ auto operator *() const -> std::string {
+ assert( m_stringIndex < m_column.m_strings.size() );
+ assert( m_pos <= m_end );
+ if( m_pos + m_column.m_width < m_end )
+ return addIndentAndSuffix(line().substr(m_pos, m_len));
+ else
+ return addIndentAndSuffix(line().substr(m_pos, m_end - m_pos));
+ }
+
+ auto operator ++() -> iterator& {
+ m_pos += m_len;
+ if( m_pos < line().size() && line()[m_pos] == '\n' )
+ m_pos += 1;
+ else
+ while( m_pos < line().size() && isWhitespace( line()[m_pos] ) )
+ ++m_pos;
+
+ if( m_pos == line().size() ) {
+ m_pos = 0;
+ ++m_stringIndex;
+ }
+ if( m_stringIndex < m_column.m_strings.size() )
+ calcLength();
+ return *this;
+ }
+ auto operator ++(int) -> iterator {
+ iterator prev( *this );
+ operator++();
+ return prev;
+ }
+
+ auto operator ==( iterator const& other ) const -> bool {
+ return
+ m_pos == other.m_pos &&
+ m_stringIndex == other.m_stringIndex &&
+ &m_column == &other.m_column;
+ }
+ auto operator !=( iterator const& other ) const -> bool {
+ return !operator==( other );
+ }
+ };
+ using const_iterator = iterator;
+
+ explicit Column( std::string const& text ) { m_strings.push_back( text ); }
+
+ auto width( size_t newWidth ) -> Column& {
+ assert( newWidth > 0 );
+ m_width = newWidth;
+ return *this;
+ }
+ auto indent( size_t newIndent ) -> Column& {
+ m_indent = newIndent;
+ return *this;
+ }
+ auto initialIndent( size_t newIndent ) -> Column& {
+ m_initialIndent = newIndent;
+ return *this;
+ }
+
+ auto width() const -> size_t { return m_width; }
+ auto begin() const -> iterator { return iterator( *this ); }
+ auto end() const -> iterator { return { *this, m_strings.size() }; }
+
+ inline friend std::ostream& operator << ( std::ostream& os, Column const& col ) {
+ bool first = true;
+ for( auto line : col ) {
+ if( first )
+ first = false;
+ else
+ os << "\n";
+ os << line;
+ }
+ return os;
+ }
+
+ auto operator + ( Column const& other ) -> Columns;
+
+ auto toString() const -> std::string {
+ std::ostringstream oss;
+ oss << *this;
+ return oss.str();
+ }
+ };
+
+ class Spacer : public Column {
+
+ public:
+ explicit Spacer( size_t spaceWidth ) : Column( "" ) {
+ width( spaceWidth );
+ }
+ };
+
+ class Columns {
+ std::vector m_columns;
+
+ public:
+
+ class iterator {
+ friend Columns;
+ struct EndTag {};
+
+ std::vector const& m_columns;
+ std::vector m_iterators;
+ size_t m_activeIterators;
+
+ iterator( Columns const& columns, EndTag )
+ : m_columns( columns.m_columns ),
+ m_activeIterators( 0 )
+ {
+ m_iterators.reserve( m_columns.size() );
+
+ for( auto const& col : m_columns )
+ m_iterators.push_back( col.end() );
+ }
+
+ public:
+ explicit iterator( Columns const& columns )
+ : m_columns( columns.m_columns ),
+ m_activeIterators( m_columns.size() )
+ {
+ m_iterators.reserve( m_columns.size() );
+
+ for( auto const& col : m_columns )
+ m_iterators.push_back( col.begin() );
+ }
+
+ auto operator ==( iterator const& other ) const -> bool {
+ return m_iterators == other.m_iterators;
+ }
+ auto operator !=( iterator const& other ) const -> bool {
+ return m_iterators != other.m_iterators;
+ }
+ auto operator *() const -> std::string {
+ std::string row, padding;
+
+ for( size_t i = 0; i < m_columns.size(); ++i ) {
+ auto width = m_columns[i].width();
+ if( m_iterators[i] != m_columns[i].end() ) {
+ std::string col = *m_iterators[i];
+ row += padding + col;
+ if( col.size() < width )
+ padding = std::string( width - col.size(), ' ' );
+ else
+ padding = "";
+ }
+ else {
+ padding += std::string( width, ' ' );
+ }
+ }
+ return row;
+ }
+ auto operator ++() -> iterator& {
+ for( size_t i = 0; i < m_columns.size(); ++i ) {
+ if (m_iterators[i] != m_columns[i].end())
+ ++m_iterators[i];
+ }
+ return *this;
+ }
+ auto operator ++(int) -> iterator {
+ iterator prev( *this );
+ operator++();
+ return prev;
+ }
+ };
+ using const_iterator = iterator;
+
+ auto begin() const -> iterator { return iterator( *this ); }
+ auto end() const -> iterator { return { *this, iterator::EndTag() }; }
+
+ auto operator += ( Column const& col ) -> Columns& {
+ m_columns.push_back( col );
+ return *this;
+ }
+ auto operator + ( Column const& col ) -> Columns {
+ Columns combined = *this;
+ combined += col;
+ return combined;
+ }
+
+ inline friend std::ostream& operator << ( std::ostream& os, Columns const& cols ) {
+
+ bool first = true;
+ for( auto line : cols ) {
+ if( first )
+ first = false;
+ else
+ os << "\n";
+ os << line;
+ }
+ return os;
+ }
+
+ auto toString() const -> std::string {
+ std::ostringstream oss;
+ oss << *this;
+ return oss.str();
+ }
+ };
+
+ inline auto Column::operator + ( Column const& other ) -> Columns {
+ Columns cols;
+ cols += *this;
+ cols += other;
+ return cols;
+ }
+}} // namespace clara::TextFlow
+
+#endif // CLARA_TEXTFLOW_HPP_INCLUDED
+
+// ----------- end of #include from clara_textflow.hpp -----------
+// ........... back in clara.hpp
+
+
+#include
+#include
+#include
+
+#if !defined(CLARA_PLATFORM_WINDOWS) && ( defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) )
+#define CLARA_PLATFORM_WINDOWS
+#endif
+
+namespace clara {
+namespace detail {
+
+ // Traits for extracting arg and return type of lambdas (for single argument lambdas)
+ template
+ struct UnaryLambdaTraits : UnaryLambdaTraits {};
+
+ template
+ struct UnaryLambdaTraits {
+ static const bool isValid = false;
+ };
+
+ template
+ struct UnaryLambdaTraits {
+ static const bool isValid = true;
+ using ArgType = typename std::remove_const::type>::type;
+ using ReturnType = ReturnT;
+ };
+
+ class TokenStream;
+
+ // Transport for raw args (copied from main args, or supplied via init list for testing)
+ class Args {
+ friend TokenStream;
+ std::string m_exeName;
+ std::vector m_args;
+
+ public:
+ Args( int argc, char const* const* argv )
+ : m_exeName(argv[0]),
+ m_args(argv + 1, argv + argc) {}
+
+ Args( std::initializer_list args )
+ : m_exeName( *args.begin() ),
+ m_args( args.begin()+1, args.end() )
+ {}
+
+ auto exeName() const -> std::string {
+ return m_exeName;
+ }
+ };
+
+ // Wraps a token coming from a token stream. These may not directly correspond to strings as a single string
+ // may encode an option + its argument if the : or = form is used
+ enum class TokenType {
+ Option, Argument
+ };
+ struct Token {
+ TokenType type;
+ std::string token;
+ };
+
+ inline auto isOptPrefix( char c ) -> bool {
+ return c == '-'
+#ifdef CLARA_PLATFORM_WINDOWS
+ || c == '/'
+#endif
+ ;
+ }
+
+ // Abstracts iterators into args as a stream of tokens, with option arguments uniformly handled
+ class TokenStream {
+ using Iterator = std::vector::const_iterator;
+ Iterator it;
+ Iterator itEnd;
+ std::vector m_tokenBuffer;
+
+ void loadBuffer() {
+ m_tokenBuffer.resize( 0 );
+
+ // Skip any empty strings
+ while( it != itEnd && it->empty() )
+ ++it;
+
+ if( it != itEnd ) {
+ auto const &next = *it;
+ if( isOptPrefix( next[0] ) ) {
+ auto delimiterPos = next.find_first_of( " :=" );
+ if( delimiterPos != std::string::npos ) {
+ m_tokenBuffer.push_back( { TokenType::Option, next.substr( 0, delimiterPos ) } );
+ m_tokenBuffer.push_back( { TokenType::Argument, next.substr( delimiterPos + 1 ) } );
+ } else {
+ if( next[1] != '-' && next.size() > 2 ) {
+ std::string opt = "- ";
+ for( size_t i = 1; i < next.size(); ++i ) {
+ opt[1] = next[i];
+ m_tokenBuffer.push_back( { TokenType::Option, opt } );
+ }
+ } else {
+ m_tokenBuffer.push_back( { TokenType::Option, next } );
+ }
+ }
+ } else {
+ m_tokenBuffer.push_back( { TokenType::Argument, next } );
+ }
+ }
+ }
+
+ public:
+ explicit TokenStream( Args const &args ) : TokenStream( args.m_args.begin(), args.m_args.end() ) {}
+
+ TokenStream( Iterator it, Iterator itEnd ) : it( it ), itEnd( itEnd ) {
+ loadBuffer();
+ }
+
+ explicit operator bool() const {
+ return !m_tokenBuffer.empty() || it != itEnd;
+ }
+
+ auto count() const -> size_t { return m_tokenBuffer.size() + (itEnd - it); }
+
+ auto operator*() const -> Token {
+ assert( !m_tokenBuffer.empty() );
+ return m_tokenBuffer.front();
+ }
+
+ auto operator->() const -> Token const * {
+ assert( !m_tokenBuffer.empty() );
+ return &m_tokenBuffer.front();
+ }
+
+ auto operator++() -> TokenStream & {
+ if( m_tokenBuffer.size() >= 2 ) {
+ m_tokenBuffer.erase( m_tokenBuffer.begin() );
+ } else {
+ if( it != itEnd )
+ ++it;
+ loadBuffer();
+ }
+ return *this;
+ }
+ };
+
+
+ class ResultBase {
+ public:
+ enum Type {
+ Ok, LogicError, RuntimeError
+ };
+
+ protected:
+ ResultBase( Type type ) : m_type( type ) {}
+ virtual ~ResultBase() = default;
+
+ virtual void enforceOk() const = 0;
+
+ Type m_type;
+ };
+
+ template
+ class ResultValueBase : public ResultBase {
+ public:
+ auto value() const -> T const & {
+ enforceOk();
+ return m_value;
+ }
+
+ protected:
+ ResultValueBase( Type type ) : ResultBase( type ) {}
+
+ ResultValueBase( ResultValueBase const &other ) : ResultBase( other ) {
+ if( m_type == ResultBase::Ok )
+ new( &m_value ) T( other.m_value );
+ }
+
+ ResultValueBase( Type, T const &value ) : ResultBase( Ok ) {
+ new( &m_value ) T( value );
+ }
+
+ auto operator=( ResultValueBase const &other ) -> ResultValueBase & {
+ if( m_type == ResultBase::Ok )
+ m_value.~T();
+ ResultBase::operator=(other);
+ if( m_type == ResultBase::Ok )
+ new( &m_value ) T( other.m_value );
+ return *this;
+ }
+
+ ~ResultValueBase() override {
+ if( m_type == Ok )
+ m_value.~T();
+ }
+
+ union {
+ T m_value;
+ };
+ };
+
+ template<>
+ class ResultValueBase : public ResultBase {
+ protected:
+ using ResultBase::ResultBase;
+ };
+
+ template
+ class BasicResult : public ResultValueBase {
+ public:
+ template
+ explicit BasicResult( BasicResult const &other )
+ : ResultValueBase( other.type() ),
+ m_errorMessage( other.errorMessage() )
+ {
+ assert( type() != ResultBase::Ok );
+ }
+
+ template
+ static auto ok( U const &value ) -> BasicResult { return { ResultBase::Ok, value }; }
+ static auto ok() -> BasicResult { return { ResultBase::Ok }; }
+ static auto logicError( std::string const &message ) -> BasicResult { return { ResultBase::LogicError, message }; }
+ static auto runtimeError( std::string const &message ) -> BasicResult { return { ResultBase::RuntimeError, message }; }
+
+ explicit operator bool() const { return m_type == ResultBase::Ok; }
+ auto type() const -> ResultBase::Type { return m_type; }
+ auto errorMessage() const -> std::string { return m_errorMessage; }
+
+ protected:
+ void enforceOk() const override {
+
+ // Errors shouldn't reach this point, but if they do
+ // the actual error message will be in m_errorMessage
+ assert( m_type != ResultBase::LogicError );
+ assert( m_type != ResultBase::RuntimeError );
+ if( m_type != ResultBase::Ok )
+ std::abort();
+ }
+
+ std::string m_errorMessage; // Only populated if resultType is an error
+
+ BasicResult( ResultBase::Type type, std::string const &message )
+ : ResultValueBase(type),
+ m_errorMessage(message)
+ {
+ assert( m_type != ResultBase::Ok );
+ }
+
+ using ResultValueBase::ResultValueBase;
+ using ResultBase::m_type;
+ };
+
+ enum class ParseResultType {
+ Matched, NoMatch, ShortCircuitAll, ShortCircuitSame
+ };
+
+ class ParseState {
+ public:
+
+ ParseState( ParseResultType type, TokenStream const &remainingTokens )
+ : m_type(type),
+ m_remainingTokens( remainingTokens )
+ {}
+
+ auto type() const -> ParseResultType { return m_type; }
+ auto remainingTokens() const -> TokenStream { return m_remainingTokens; }
+
+ private:
+ ParseResultType m_type;
+ TokenStream m_remainingTokens;
+ };
+
+ using Result = BasicResult;
+ using ParserResult = BasicResult;
+ using InternalParseResult = BasicResult;
+
+ struct HelpColumns {
+ std::string left;
+ std::string right;
+ };
+
+ template
+ inline auto convertInto( std::string const &source, T& target ) -> ParserResult {
+ std::stringstream ss;
+ ss << source;
+ ss >> target;
+ if( ss.fail() )
+ return ParserResult::runtimeError( "Unable to convert '" + source + "' to destination type" );
+ else
+ return ParserResult::ok( ParseResultType::Matched );
+ }
+ inline auto convertInto( std::string const &source, std::string& target ) -> ParserResult {
+ target = source;
+ return ParserResult::ok( ParseResultType::Matched );
+ }
+ inline auto convertInto( std::string const &source, bool &target ) -> ParserResult {
+ std::string srcLC = source;
+ std::transform( srcLC.begin(), srcLC.end(), srcLC.begin(), []( char c ) { return static_cast( ::tolower(c) ); } );
+ if (srcLC == "y" || srcLC == "1" || srcLC == "true" || srcLC == "yes" || srcLC == "on")
+ target = true;
+ else if (srcLC == "n" || srcLC == "0" || srcLC == "false" || srcLC == "no" || srcLC == "off")
+ target = false;
+ else
+ return ParserResult::runtimeError( "Expected a boolean value but did not recognise: '" + source + "'" );
+ return ParserResult::ok( ParseResultType::Matched );
+ }
+#ifdef CLARA_CONFIG_OPTIONAL_TYPE
+ template
+ inline auto convertInto( std::string const &source, CLARA_CONFIG_OPTIONAL_TYPE& target ) -> ParserResult {
+ T temp;
+ auto result = convertInto( source, temp );
+ if( result )
+ target = std::move(temp);
+ return result;
+ }
+#endif // CLARA_CONFIG_OPTIONAL_TYPE
+
+ struct NonCopyable {
+ NonCopyable() = default;
+ NonCopyable( NonCopyable const & ) = delete;
+ NonCopyable( NonCopyable && ) = delete;
+ NonCopyable &operator=( NonCopyable const & ) = delete;
+ NonCopyable &operator=( NonCopyable && ) = delete;
+ };
+
+ struct BoundRef : NonCopyable {
+ virtual ~BoundRef() = default;
+ virtual auto isContainer() const -> bool { return false; }
+ virtual auto isFlag() const -> bool { return false; }
+ };
+ struct BoundValueRefBase : BoundRef {
+ virtual auto setValue( std::string const &arg ) -> ParserResult = 0;
+ };
+ struct BoundFlagRefBase : BoundRef {
+ virtual auto setFlag( bool flag ) -> ParserResult = 0;
+ virtual auto isFlag() const -> bool { return true; }
+ };
+
+ template
+ struct BoundValueRef : BoundValueRefBase {
+ T &m_ref;
+
+ explicit BoundValueRef( T &ref ) : m_ref( ref ) {}
+
+ auto setValue( std::string const &arg ) -> ParserResult override {
+ return convertInto( arg, m_ref );
+ }
+ };
+
+ template
+ struct BoundValueRef> : BoundValueRefBase {
+ std::vector &m_ref;
+
+ explicit BoundValueRef( std::vector &ref ) : m_ref( ref ) {}
+
+ auto isContainer() const -> bool override { return true; }
+
+ auto setValue( std::string const &arg ) -> ParserResult override {
+ T temp;
+ auto result = convertInto( arg, temp );
+ if( result )
+ m_ref.push_back( temp );
+ return result;
+ }
+ };
+
+ struct BoundFlagRef : BoundFlagRefBase {
+ bool &m_ref;
+
+ explicit BoundFlagRef( bool &ref ) : m_ref( ref ) {}
+
+ auto setFlag( bool flag ) -> ParserResult override {
+ m_ref = flag;
+ return ParserResult::ok( ParseResultType::Matched );
+ }
+ };
+
+ template
+ struct LambdaInvoker {
+ static_assert( std::is_same::value, "Lambda must return void or clara::ParserResult" );
+
+ template
+ static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult {
+ return lambda( arg );
+ }
+ };
+
+ template<>
+ struct LambdaInvoker {
+ template
+ static auto invoke( L const &lambda, ArgType const &arg ) -> ParserResult {
+ lambda( arg );
+ return ParserResult::ok( ParseResultType::Matched );
+ }
+ };
+
+ template
+ inline auto invokeLambda( L const &lambda, std::string const &arg ) -> ParserResult {
+ ArgType temp{};
+ auto result = convertInto( arg, temp );
+ return !result
+ ? result
+ : LambdaInvoker::ReturnType>::invoke( lambda, temp );
+ }
+
+
+ template
+ struct BoundLambda : BoundValueRefBase {
+ L m_lambda;
+
+ static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" );
+ explicit BoundLambda( L const &lambda ) : m_lambda( lambda ) {}
+
+ auto setValue( std::string const &arg ) -> ParserResult override {
+ return invokeLambda::ArgType>( m_lambda, arg );
+ }
+ };
+
+ template
+ struct BoundFlagLambda : BoundFlagRefBase {
+ L m_lambda;
+
+ static_assert( UnaryLambdaTraits::isValid, "Supplied lambda must take exactly one argument" );
+ static_assert( std::is_same::ArgType, bool>::value, "flags must be boolean" );
+
+ explicit BoundFlagLambda( L const &lambda ) : m_lambda( lambda ) {}
+
+ auto setFlag( bool flag ) -> ParserResult override {
+ return LambdaInvoker::ReturnType>::invoke( m_lambda, flag );
+ }
+ };
+
+ enum class Optionality { Optional, Required };
+
+ struct Parser;
+
+ class ParserBase {
+ public:
+ virtual ~ParserBase() = default;
+ virtual auto validate() const -> Result { return Result::ok(); }
+ virtual auto parse( std::string const& exeName, TokenStream const &tokens) const -> InternalParseResult = 0;
+ virtual auto cardinality() const -> size_t { return 1; }
+
+ auto parse( Args const &args ) const -> InternalParseResult {
+ return parse( args.exeName(), TokenStream( args ) );
+ }
+ };
+
+ template
+ class ComposableParserImpl : public ParserBase {
+ public:
+ template
+ auto operator|( T const &other ) const -> Parser;
+
+ template
+ auto operator+( T const &other ) const -> Parser;
+ };
+
+ // Common code and state for Args and Opts
+ template
+ class ParserRefImpl : public ComposableParserImpl {
+ protected:
+ Optionality m_optionality = Optionality::Optional;
+ std::shared_ptr m_ref;
+ std::string m_hint;
+ std::string m_description;
+
+ explicit ParserRefImpl( std::shared_ptr const &ref ) : m_ref( ref ) {}
+
+ public:
+ template
+ ParserRefImpl( T &ref, std::string const &hint )
+ : m_ref( std::make_shared>( ref ) ),
+ m_hint( hint )
+ {}
+
+ template
+ ParserRefImpl( LambdaT const &ref, std::string const &hint )
+ : m_ref( std::make_shared>( ref ) ),
+ m_hint(hint)
+ {}
+
+ auto operator()( std::string const &description ) -> DerivedT & {
+ m_description = description;
+ return static_cast( *this );
+ }
+
+ auto optional() -> DerivedT & {
+ m_optionality = Optionality::Optional;
+ return static_cast( *this );
+ };
+
+ auto required() -> DerivedT & {
+ m_optionality = Optionality::Required;
+ return static_cast( *this );
+ };
+
+ auto isOptional() const -> bool {
+ return m_optionality == Optionality::Optional;
+ }
+
+ auto cardinality() const -> size_t override {
+ if( m_ref->isContainer() )
+ return 0;
+ else
+ return 1;
+ }
+
+ auto hint() const -> std::string { return m_hint; }
+ };
+
+ class ExeName : public ComposableParserImpl {
+ std::shared_ptr m_name;
+ std::shared_ptr m_ref;
+
+ template
+ static auto makeRef(LambdaT const &lambda) -> std::shared_ptr {
+ return std::make_shared>( lambda) ;
+ }
+
+ public:
+ ExeName() : m_name( std::make_shared( "" ) ) {}
+
+ explicit ExeName( std::string &ref ) : ExeName() {
+ m_ref = std::make_shared>( ref );
+ }
+
+ template
+ explicit ExeName( LambdaT const& lambda ) : ExeName() {
+ m_ref = std::make_shared>( lambda );
+ }
+
+ // The exe name is not parsed out of the normal tokens, but is handled specially
+ auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {
+ return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
+ }
+
+ auto name() const -> std::string { return *m_name; }
+ auto set( std::string const& newName ) -> ParserResult {
+
+ auto lastSlash = newName.find_last_of( "\\/" );
+ auto filename = ( lastSlash == std::string::npos )
+ ? newName
+ : newName.substr( lastSlash+1 );
+
+ *m_name = filename;
+ if( m_ref )
+ return m_ref->setValue( filename );
+ else
+ return ParserResult::ok( ParseResultType::Matched );
+ }
+ };
+
+ class Arg : public ParserRefImpl {
+ public:
+ using ParserRefImpl::ParserRefImpl;
+
+ auto parse( std::string const &, TokenStream const &tokens ) const -> InternalParseResult override {
+ auto validationResult = validate();
+ if( !validationResult )
+ return InternalParseResult( validationResult );
+
+ auto remainingTokens = tokens;
+ auto const &token = *remainingTokens;
+ if( token.type != TokenType::Argument )
+ return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) );
+
+ assert( !m_ref->isFlag() );
+ auto valueRef = static_cast( m_ref.get() );
+
+ auto result = valueRef->setValue( remainingTokens->token );
+ if( !result )
+ return InternalParseResult( result );
+ else
+ return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );
+ }
+ };
+
+ inline auto normaliseOpt( std::string const &optName ) -> std::string {
+#ifdef CLARA_PLATFORM_WINDOWS
+ if( optName[0] == '/' )
+ return "-" + optName.substr( 1 );
+ else
+#endif
+ return optName;
+ }
+
+ class Opt : public ParserRefImpl {
+ protected:
+ std::vector m_optNames;
+
+ public:
+ template
+ explicit Opt( LambdaT const &ref ) : ParserRefImpl( std::make_shared>( ref ) ) {}
+
+ explicit Opt( bool &ref ) : ParserRefImpl( std::make_shared( ref ) ) {}
+
+ template
+ Opt( LambdaT const &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}
+
+ template
+ Opt( T &ref, std::string const &hint ) : ParserRefImpl( ref, hint ) {}
+
+ auto operator[]( std::string const &optName ) -> Opt & {
+ m_optNames.push_back( optName );
+ return *this;
+ }
+
+ auto getHelpColumns() const -> std::vector {
+ std::ostringstream oss;
+ bool first = true;
+ for( auto const &opt : m_optNames ) {
+ if (first)
+ first = false;
+ else
+ oss << ", ";
+ oss << opt;
+ }
+ if( !m_hint.empty() )
+ oss << " <" << m_hint << ">";
+ return { { oss.str(), m_description } };
+ }
+
+ auto isMatch( std::string const &optToken ) const -> bool {
+ auto normalisedToken = normaliseOpt( optToken );
+ for( auto const &name : m_optNames ) {
+ if( normaliseOpt( name ) == normalisedToken )
+ return true;
+ }
+ return false;
+ }
+
+ using ParserBase::parse;
+
+ auto parse( std::string const&, TokenStream const &tokens ) const -> InternalParseResult override {
+ auto validationResult = validate();
+ if( !validationResult )
+ return InternalParseResult( validationResult );
+
+ auto remainingTokens = tokens;
+ if( remainingTokens && remainingTokens->type == TokenType::Option ) {
+ auto const &token = *remainingTokens;
+ if( isMatch(token.token ) ) {
+ if( m_ref->isFlag() ) {
+ auto flagRef = static_cast( m_ref.get() );
+ auto result = flagRef->setFlag( true );
+ if( !result )
+ return InternalParseResult( result );
+ if( result.value() == ParseResultType::ShortCircuitAll )
+ return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) );
+ } else {
+ auto valueRef = static_cast( m_ref.get() );
+ ++remainingTokens;
+ if( !remainingTokens )
+ return InternalParseResult::runtimeError( "Expected argument following " + token.token );
+ auto const &argToken = *remainingTokens;
+ if( argToken.type != TokenType::Argument )
+ return InternalParseResult::runtimeError( "Expected argument following " + token.token );
+ auto result = valueRef->setValue( argToken.token );
+ if( !result )
+ return InternalParseResult( result );
+ if( result.value() == ParseResultType::ShortCircuitAll )
+ return InternalParseResult::ok( ParseState( result.value(), remainingTokens ) );
+ }
+ return InternalParseResult::ok( ParseState( ParseResultType::Matched, ++remainingTokens ) );
+ }
+ }
+ return InternalParseResult::ok( ParseState( ParseResultType::NoMatch, remainingTokens ) );
+ }
+
+ auto validate() const -> Result override {
+ if( m_optNames.empty() )
+ return Result::logicError( "No options supplied to Opt" );
+ for( auto const &name : m_optNames ) {
+ if( name.empty() )
+ return Result::logicError( "Option name cannot be empty" );
+#ifdef CLARA_PLATFORM_WINDOWS
+ if( name[0] != '-' && name[0] != '/' )
+ return Result::logicError( "Option name must begin with '-' or '/'" );
+#else
+ if( name[0] != '-' )
+ return Result::logicError( "Option name must begin with '-'" );
+#endif
+ }
+ return ParserRefImpl::validate();
+ }
+ };
+
+ struct Help : Opt {
+ Help( bool &showHelpFlag )
+ : Opt([&]( bool flag ) {
+ showHelpFlag = flag;
+ return ParserResult::ok( ParseResultType::ShortCircuitAll );
+ })
+ {
+ static_cast( *this )
+ ("display usage information")
+ ["-?"]["-h"]["--help"]
+ .optional();
+ }
+ };
+
+
+ struct Parser : ParserBase {
+
+ mutable ExeName m_exeName;
+ std::vector m_options;
+ std::vector m_args;
+
+ auto operator|=( ExeName const &exeName ) -> Parser & {
+ m_exeName = exeName;
+ return *this;
+ }
+
+ auto operator|=( Arg const &arg ) -> Parser & {
+ m_args.push_back(arg);
+ return *this;
+ }
+
+ auto operator|=( Opt const &opt ) -> Parser & {
+ m_options.push_back(opt);
+ return *this;
+ }
+
+ auto operator|=( Parser const &other ) -> Parser & {
+ m_options.insert(m_options.end(), other.m_options.begin(), other.m_options.end());
+ m_args.insert(m_args.end(), other.m_args.begin(), other.m_args.end());
+ return *this;
+ }
+
+ template
+ auto operator|( T const &other ) const -> Parser {
+ return Parser( *this ) |= other;
+ }
+
+ // Forward deprecated interface with '+' instead of '|'
+ template
+ auto operator+=( T const &other ) -> Parser & { return operator|=( other ); }
+ template
+ auto operator+( T const &other ) const -> Parser { return operator|( other ); }
+
+ auto getHelpColumns() const -> std::vector {
+ std::vector cols;
+ for (auto const &o : m_options) {
+ auto childCols = o.getHelpColumns();
+ cols.insert( cols.end(), childCols.begin(), childCols.end() );
+ }
+ return cols;
+ }
+
+ void writeToStream( std::ostream &os ) const {
+ if (!m_exeName.name().empty()) {
+ os << "usage:\n" << " " << m_exeName.name() << " ";
+ bool required = true, first = true;
+ for( auto const &arg : m_args ) {
+ if (first)
+ first = false;
+ else
+ os << " ";
+ if( arg.isOptional() && required ) {
+ os << "[";
+ required = false;
+ }
+ os << "<" << arg.hint() << ">";
+ if( arg.cardinality() == 0 )
+ os << " ... ";
+ }
+ if( !required )
+ os << "]";
+ if( !m_options.empty() )
+ os << " options";
+ os << "\n\nwhere options are:" << std::endl;
+ }
+
+ auto rows = getHelpColumns();
+ size_t consoleWidth = CLARA_CONFIG_CONSOLE_WIDTH;
+ size_t optWidth = 0;
+ for( auto const &cols : rows )
+ optWidth = (std::max)(optWidth, cols.left.size() + 2);
+
+ optWidth = (std::min)(optWidth, consoleWidth/2);
+
+ for( auto const &cols : rows ) {
+ auto row =
+ TextFlow::Column( cols.left ).width( optWidth ).indent( 2 ) +
+ TextFlow::Spacer(4) +
+ TextFlow::Column( cols.right ).width( consoleWidth - 7 - optWidth );
+ os << row << std::endl;
+ }
+ }
+
+ friend auto operator<<( std::ostream &os, Parser const &parser ) -> std::ostream& {
+ parser.writeToStream( os );
+ return os;
+ }
+
+ auto validate() const -> Result override {
+ for( auto const &opt : m_options ) {
+ auto result = opt.validate();
+ if( !result )
+ return result;
+ }
+ for( auto const &arg : m_args ) {
+ auto result = arg.validate();
+ if( !result )
+ return result;
+ }
+ return Result::ok();
+ }
+
+ using ParserBase::parse;
+
+ auto parse( std::string const& exeName, TokenStream const &tokens ) const -> InternalParseResult override {
+
+ struct ParserInfo {
+ ParserBase const* parser = nullptr;
+ size_t count = 0;
+ };
+ const size_t totalParsers = m_options.size() + m_args.size();
+ assert( totalParsers < 512 );
+ // ParserInfo parseInfos[totalParsers]; // <-- this is what we really want to do
+ ParserInfo parseInfos[512];
+
+ {
+ size_t i = 0;
+ for (auto const &opt : m_options) parseInfos[i++].parser = &opt;
+ for (auto const &arg : m_args) parseInfos[i++].parser = &arg;
+ }
+
+ m_exeName.set( exeName );
+
+ auto result = InternalParseResult::ok( ParseState( ParseResultType::NoMatch, tokens ) );
+ while( result.value().remainingTokens() ) {
+ bool tokenParsed = false;
+
+ for( size_t i = 0; i < totalParsers; ++i ) {
+ auto& parseInfo = parseInfos[i];
+ if( parseInfo.parser->cardinality() == 0 || parseInfo.count < parseInfo.parser->cardinality() ) {
+ result = parseInfo.parser->parse(exeName, result.value().remainingTokens());
+ if (!result)
+ return result;
+ if (result.value().type() != ParseResultType::NoMatch) {
+ tokenParsed = true;
+ ++parseInfo.count;
+ break;
+ }
+ }
+ }
+
+ if( result.value().type() == ParseResultType::ShortCircuitAll )
+ return result;
+ if( !tokenParsed )
+ return InternalParseResult::runtimeError( "Unrecognised token: " + result.value().remainingTokens()->token );
+ }
+ // !TBD Check missing required options
+ return result;
+ }
+ };
+
+ template
+ template
+ auto ComposableParserImpl::operator|( T const &other ) const -> Parser {
+ return Parser() | static_cast( *this ) | other;
+ }
+} // namespace detail
+
+
+// A Combined parser
+using detail::Parser;
+
+// A parser for options
+using detail::Opt;
+
+// A parser for arguments
+using detail::Arg;
+
+// Wrapper for argc, argv from main()
+using detail::Args;
+
+// Specifies the name of the executable
+using detail::ExeName;
+
+// Convenience wrapper for option parser that specifies the help option
+using detail::Help;
+
+// enum of result types from a parse
+using detail::ParseResultType;
+
+// Result type for parser operation
+using detail::ParserResult;
+
+
+} // namespace clara
+
+#endif // CLARA_HPP_INCLUDED
diff --git a/src/catchup/CatchupConfiguration.cpp b/src/catchup/CatchupConfiguration.cpp
index b9df3f858e..af04c36d48 100644
--- a/src/catchup/CatchupConfiguration.cpp
+++ b/src/catchup/CatchupConfiguration.cpp
@@ -3,7 +3,9 @@
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
#include "catchup/CatchupConfiguration.h"
+
#include
+#include
namespace stellar
{
@@ -21,4 +23,42 @@ CatchupConfiguration::resolve(uint32_t remoteCheckpoint) const
: toLedger();
return CatchupConfiguration{resolvedToLedger, count()};
}
+
+uint32_t
+parseLedger(std::string const& str)
+{
+ if (str == "current")
+ {
+ return CatchupConfiguration::CURRENT;
+ }
+
+ auto pos = std::size_t{0};
+ auto result = std::stoul(str, &pos);
+ if (pos < str.length() || result < 2)
+ {
+ throw std::runtime_error(
+ fmt::format("{} is not a valid ledger number", str));
+ }
+
+ return result;
+}
+
+uint32_t
+parseLedgerCount(std::string const& str)
+{
+ if (str == "max")
+ {
+ return std::numeric_limits::max();
+ }
+
+ auto pos = std::size_t{0};
+ auto result = std::stoul(str, &pos);
+ if (pos < str.length())
+ {
+ throw std::runtime_error(
+ fmt::format("{} is not a valid ledger count", str));
+ }
+
+ return result;
+}
}
diff --git a/src/catchup/CatchupConfiguration.h b/src/catchup/CatchupConfiguration.h
index 772a9cdafa..a9cb290d21 100644
--- a/src/catchup/CatchupConfiguration.h
+++ b/src/catchup/CatchupConfiguration.h
@@ -5,6 +5,7 @@
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
#include
+#include
namespace stellar
{
@@ -25,8 +26,7 @@ namespace stellar
// Value of destination ledger can be also set to CatchupConfiguration::CURRENT
// which means that CatchupWork will get latest checkpoint from history archive
// and catchup to that instead of destination ledger. This is usefull when
-// doing offline commandline catchups with --catchup-complete, --catchup-at,
-// --catchup-to and --catchup-recent arguments to stellar-core.
+// doing offline commandline catchups with stellar-core catchup command.
class CatchupConfiguration
{
public:
@@ -56,4 +56,7 @@ class CatchupConfiguration
uint32_t mToLedger;
uint32_t mCount;
};
+
+uint32_t parseLedger(std::string const& str);
+uint32_t parseLedgerCount(std::string const& str);
}
diff --git a/src/database/Database.h b/src/database/Database.h
index d2a5eaea21..e6cfcaab5b 100644
--- a/src/database/Database.h
+++ b/src/database/Database.h
@@ -163,7 +163,7 @@ class Database : NonMovableOrCopyable
bool canUsePool() const;
// Drop and recreate all tables in the database target. This is called
- // by the --newdb command-line flag on stellar-core.
+ // by the new-db command on stellar-core.
void initialize();
// Save `vers` as schema version.
diff --git a/src/history/InferredQuorumUtils.cpp b/src/history/InferredQuorumUtils.cpp
new file mode 100644
index 0000000000..27ef85998e
--- /dev/null
+++ b/src/history/InferredQuorumUtils.cpp
@@ -0,0 +1,72 @@
+// Copyright 2018 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+
+#include "history/HistoryManager.h"
+#include "main/Application.h"
+#include "main/Config.h"
+#include "scp/QuorumSetUtils.h"
+#include "util/Logging.h"
+#include "util/XDROperators.h"
+#include "xdr/Stellar-SCP.h"
+#include "xdr/Stellar-types.h"
+
+#include
+#include
+
+namespace stellar
+{
+
+void
+checkQuorumIntersection(Config cfg)
+{
+ VirtualClock clock;
+ cfg.setNoListen();
+ Application::pointer app = Application::create(clock, cfg, false);
+ InferredQuorum iq = app->getHistoryManager().inferQuorum();
+ iq.checkQuorumIntersection(cfg);
+}
+
+void
+inferQuorumAndWrite(Config cfg)
+{
+ InferredQuorum iq;
+ {
+ VirtualClock clock;
+ cfg.setNoListen();
+ Application::pointer app = Application::create(clock, cfg, false);
+ iq = app->getHistoryManager().inferQuorum();
+ }
+ LOG(INFO) << "Inferred quorum";
+ std::cout << iq.toString(cfg) << std::endl;
+}
+
+void
+writeQuorumGraph(Config cfg, std::string const& outputFile)
+{
+ InferredQuorum iq;
+ {
+ VirtualClock clock;
+ cfg.setNoListen();
+ Application::pointer app = Application::create(clock, cfg, false);
+ iq = app->getHistoryManager().inferQuorum();
+ }
+ std::string filename = outputFile.empty() ? "-" : outputFile;
+ if (filename == "-")
+ {
+ std::stringstream out;
+ iq.writeQuorumGraph(cfg, out);
+ LOG(INFO) << "*";
+ LOG(INFO) << "* Quorum graph: " << out.str();
+ LOG(INFO) << "*";
+ }
+ else
+ {
+ std::ofstream out(filename);
+ iq.writeQuorumGraph(cfg, out);
+ LOG(INFO) << "*";
+ LOG(INFO) << "* Wrote quorum graph to " << filename;
+ LOG(INFO) << "*";
+ }
+}
+}
diff --git a/src/history/InferredQuorumUtils.h b/src/history/InferredQuorumUtils.h
new file mode 100644
index 0000000000..c6a90d29db
--- /dev/null
+++ b/src/history/InferredQuorumUtils.h
@@ -0,0 +1,17 @@
+#pragma once
+
+// Copyright 2018 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+
+#include
+
+namespace stellar
+{
+
+class Config;
+
+void checkQuorumIntersection(Config cfg);
+void inferQuorumAndWrite(Config cfg);
+void writeQuorumGraph(Config cfg, std::string const& outputFile);
+}
diff --git a/src/main/ApplicationUtils.cpp b/src/main/ApplicationUtils.cpp
new file mode 100644
index 0000000000..d6c86e8c6f
--- /dev/null
+++ b/src/main/ApplicationUtils.cpp
@@ -0,0 +1,428 @@
+// Copyright 2018 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+
+#include "main/ApplicationUtils.h"
+#include "bucket/Bucket.h"
+#include "catchup/CatchupConfiguration.h"
+#include "history/HistoryArchive.h"
+#include "history/HistoryArchiveManager.h"
+#include "historywork/GetHistoryArchiveStateWork.h"
+#include "ledger/LedgerManager.h"
+#include "main/ExternalQueue.h"
+#include "main/Maintainer.h"
+#include "main/PersistentState.h"
+#include "main/StellarCoreVersion.h"
+#include "util/Logging.h"
+#include "work/WorkManager.h"
+
+#include
+#include
+
+namespace stellar
+{
+
+namespace
+{
+bool
+checkInitialized(Application::pointer app)
+{
+ try
+ {
+ // check to see if the state table exists
+ app->getPersistentState().getState(PersistentState::kDatabaseSchema);
+ }
+ catch (...)
+ {
+ LOG(INFO) << "* ";
+ LOG(INFO) << "* The database has not yet been initialized. Try --newdb";
+ LOG(INFO) << "* ";
+ return false;
+ }
+ return true;
+}
+}
+
+int
+runWithConfig(Config cfg)
+{
+ if (cfg.MANUAL_CLOSE)
+ {
+ // in manual close mode, we set FORCE_SCP
+ // so that the node starts fully in sync
+ // (this is to avoid to force scp all the time when testing)
+ cfg.FORCE_SCP = true;
+ }
+
+ LOG(INFO) << "Starting stellar-core " << STELLAR_CORE_VERSION;
+ VirtualClock clock(VirtualClock::REAL_TIME);
+ Application::pointer app;
+ try
+ {
+ app = Application::create(clock, cfg, false);
+
+ if (!checkInitialized(app))
+ {
+ return 0;
+ }
+ else
+ {
+ if (!app->getHistoryArchiveManager().checkSensibleConfig())
+ {
+ return 1;
+ }
+ if (cfg.ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING)
+ {
+ LOG(WARNING) << "Artificial acceleration of time enabled "
+ << "(for testing only)";
+ }
+
+ app->applyCfgCommands();
+
+ app->start();
+ }
+ }
+ catch (std::exception& e)
+ {
+ LOG(FATAL) << "Got an exception: " << e.what();
+ return 1;
+ }
+ auto& io = clock.getIOService();
+ asio::io_service::work mainWork(io);
+ while (!io.stopped())
+ {
+ clock.crank();
+ }
+ return 0;
+}
+
+void
+httpCommand(std::string const& command, unsigned short port)
+{
+ std::string ret;
+ std::ostringstream path;
+
+ path << "/";
+ bool gotCommand = false;
+
+ std::locale loc("C");
+
+ for (auto const& c : command)
+ {
+ if (gotCommand)
+ {
+ if (std::isalnum(c, loc))
+ {
+ path << c;
+ }
+ else
+ {
+ path << '%' << std::hex << std::setw(2) << std::setfill('0')
+ << (unsigned int)c;
+ }
+ }
+ else
+ {
+ path << c;
+ if (c == '?')
+ {
+ gotCommand = true;
+ }
+ }
+ }
+
+ int code = http_request("127.0.0.1", path.str(), port, ret);
+ if (code == 200)
+ {
+ LOG(INFO) << ret;
+ }
+ else
+ {
+ LOG(INFO) << "http failed(" << code << ") port: " << port
+ << " command: " << command;
+ }
+}
+
+void
+loadXdr(Config cfg, std::string const& bucketFile)
+{
+ VirtualClock clock;
+ cfg.setNoListen();
+ Application::pointer app = Application::create(clock, cfg, false);
+ if (checkInitialized(app))
+ {
+ uint256 zero;
+ Bucket bucket(bucketFile, zero);
+ bucket.apply(*app);
+ }
+ else
+ {
+ LOG(INFO) << "Database is not initialized";
+ }
+}
+
+void
+setForceSCPFlag(Config cfg, bool set)
+{
+ VirtualClock clock;
+ cfg.setNoListen();
+ Application::pointer app = Application::create(clock, cfg, false);
+
+ if (checkInitialized(app))
+ {
+ app->getPersistentState().setState(
+ PersistentState::kForceSCPOnNextLaunch, (set ? "true" : "false"));
+ if (set)
+ {
+ LOG(INFO) << "* ";
+ LOG(INFO) << "* The `force scp` flag has been set in the db.";
+ LOG(INFO) << "* ";
+ LOG(INFO)
+ << "* The next launch will start scp from the account balances";
+ LOG(INFO) << "* as they stand in the db now, without waiting to "
+ "hear from";
+ LOG(INFO) << "* the network.";
+ LOG(INFO) << "* ";
+ }
+ else
+ {
+ LOG(INFO) << "* ";
+ LOG(INFO) << "* The `force scp` flag has been cleared.";
+ LOG(INFO) << "* The next launch will start normally.";
+ LOG(INFO) << "* ";
+ }
+ }
+}
+
+void
+initializeDatabase(Config cfg)
+{
+ VirtualClock clock;
+ cfg.setNoListen();
+ Application::pointer app = Application::create(clock, cfg);
+
+ LOG(INFO) << "*";
+ LOG(INFO) << "* The next launch will catchup from the network afresh.";
+ LOG(INFO) << "*";
+}
+
+void
+showOfflineInfo(Config cfg)
+{
+ // needs real time to display proper stats
+ VirtualClock clock(VirtualClock::REAL_TIME);
+ cfg.setNoListen();
+ Application::pointer app = Application::create(clock, cfg, false);
+ if (checkInitialized(app))
+ {
+ app->reportInfo();
+ }
+ else
+ {
+ LOG(INFO) << "Database is not initialized";
+ }
+}
+
+int
+reportLastHistoryCheckpoint(Config cfg, std::string const& outputFile)
+{
+ VirtualClock clock(VirtualClock::REAL_TIME);
+ cfg.setNoListen();
+ Application::pointer app = Application::create(clock, cfg, false);
+
+ if (!checkInitialized(app))
+ {
+ return 1;
+ }
+
+ auto state = HistoryArchiveState{};
+ auto& wm = app->getWorkManager();
+ auto getHistoryArchiveStateWork =
+ wm.executeWork(
+ "get-history-archive-state-work", state);
+
+ auto ok = getHistoryArchiveStateWork->getState() == Work::WORK_SUCCESS;
+ if (ok)
+ {
+ std::string filename = outputFile.empty() ? "-" : outputFile;
+
+ if (filename == "-")
+ {
+ LOG(INFO) << "*";
+ LOG(INFO) << "* Last history checkpoint " << state.toString();
+ LOG(INFO) << "*";
+ }
+ else
+ {
+ state.save(filename);
+ LOG(INFO) << "*";
+ LOG(INFO) << "* Wrote last history checkpoint " << filename;
+ LOG(INFO) << "*";
+ }
+ }
+ else
+ {
+ LOG(INFO) << "*";
+ LOG(INFO) << "* Fetching last history checkpoint failed.";
+ LOG(INFO) << "*";
+ }
+
+ app->gracefulStop();
+ while (clock.crank(true))
+ ;
+
+ return ok ? 0 : 1;
+}
+
+void
+genSeed()
+{
+ auto key = SecretKey::random();
+ std::cout << "Secret seed: " << key.getStrKeySeed().value << std::endl;
+ std::cout << "Public: " << key.getStrKeyPublic() << std::endl;
+}
+
+int
+initializeHistories(Config cfg, std::vector const& newHistories)
+{
+ VirtualClock clock;
+ cfg.setNoListen();
+ Application::pointer app = Application::create(clock, cfg, false);
+
+ for (auto const& arch : newHistories)
+ {
+ if (!app->getHistoryArchiveManager().initializeHistoryArchive(arch))
+ return 1;
+ }
+ return 0;
+}
+
+void
+writeCatchupInfo(Json::Value const& catchupInfo, std::string const& outputFile)
+{
+ std::string filename = outputFile.empty() ? "-" : outputFile;
+ auto content = catchupInfo.toStyledString();
+
+ if (filename == "-")
+ {
+ LOG(INFO) << "*";
+ LOG(INFO) << "* Catchup info: " << content;
+ LOG(INFO) << "*";
+ }
+ else
+ {
+ std::ofstream out{};
+ out.open(filename);
+ out.write(content.c_str(), content.size());
+ out.close();
+
+ LOG(INFO) << "*";
+ LOG(INFO) << "* Wrote catchup info to " << filename;
+ LOG(INFO) << "*";
+ }
+}
+
+int
+catchup(Application::pointer app, CatchupConfiguration cc,
+ Json::Value& catchupInfo)
+{
+ if (!checkInitialized(app))
+ {
+ return 1;
+ }
+
+ // set known cursors before starting maintenance job
+ ExternalQueue ps(*app);
+ ps.setInitialCursors(app->getConfig().KNOWN_CURSORS);
+ app->getMaintainer().start();
+
+ auto done = false;
+ app->getLedgerManager().loadLastKnownLedger(
+ [&done](asio::error_code const& ec) {
+ if (ec)
+ {
+ throw std::runtime_error(
+ "Unable to restore last-known ledger state");
+ }
+
+ done = true;
+ });
+ auto& clock = app->getClock();
+ while (!done && clock.crank(true))
+ ;
+
+ try
+ {
+ app->getLedgerManager().startCatchup(cc, true);
+ }
+ catch (std::invalid_argument const&)
+ {
+ LOG(INFO) << "*";
+ LOG(INFO) << "* Target ledger " << cc.toLedger()
+ << " is not newer than last closed ledger"
+ << " - nothing to do";
+ LOG(INFO) << "* If you really want to catchup to " << cc.toLedger()
+ << " run stellar-core new-db";
+ LOG(INFO) << "*";
+ return 2;
+ }
+
+ auto& io = clock.getIOService();
+ auto synced = false;
+ asio::io_service::work mainWork(io);
+ done = false;
+ while (!done && clock.crank(true))
+ {
+ switch (app->getLedgerManager().getState())
+ {
+ case LedgerManager::LM_BOOTING_STATE:
+ {
+ done = true;
+ break;
+ }
+ case LedgerManager::LM_SYNCED_STATE:
+ {
+ break;
+ }
+ case LedgerManager::LM_CATCHING_UP_STATE:
+ {
+ switch (app->getLedgerManager().getCatchupState())
+ {
+ case LedgerManager::CatchupState::WAITING_FOR_CLOSING_LEDGER:
+ {
+ done = true;
+ synced = true;
+ break;
+ }
+ case LedgerManager::CatchupState::NONE:
+ {
+ done = true;
+ break;
+ }
+ default:
+ {
+ break;
+ }
+ }
+ break;
+ }
+ case LedgerManager::LM_NUM_STATE:
+ abort();
+ }
+ }
+
+ LOG(INFO) << "*";
+ if (synced)
+ {
+ LOG(INFO) << "* Catchup finished.";
+ }
+ else
+ {
+ LOG(INFO) << "* Catchup failed.";
+ }
+ LOG(INFO) << "*";
+
+ catchupInfo = app->getJsonInfo();
+ return synced ? 0 : 3;
+}
+}
diff --git a/src/main/ApplicationUtils.h b/src/main/ApplicationUtils.h
new file mode 100644
index 0000000000..5077b6b504
--- /dev/null
+++ b/src/main/ApplicationUtils.h
@@ -0,0 +1,28 @@
+#pragma once
+
+// Copyright 2018 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+
+#include "main/Application.h"
+
+namespace stellar
+{
+
+class CatchupConfiguration;
+
+int runWithConfig(Config cfg);
+void setForceSCPFlag(Config cfg, bool set);
+void initializeDatabase(Config cfg);
+void httpCommand(std::string const& command, unsigned short port);
+void loadXdr(Config cfg, std::string const& bucketFile);
+void showOfflineInfo(Config cfg);
+int reportLastHistoryCheckpoint(Config cfg, std::string const& outputFile);
+void genSeed();
+int initializeHistories(Config cfg,
+ std::vector const& newHistories);
+void writeCatchupInfo(Json::Value const& catchupInfo,
+ std::string const& outputFile);
+int catchup(Application::pointer app, CatchupConfiguration cc,
+ Json::Value& catchupInfo);
+}
diff --git a/src/main/CommandLine.cpp b/src/main/CommandLine.cpp
new file mode 100644
index 0000000000..d34d7b5cac
--- /dev/null
+++ b/src/main/CommandLine.cpp
@@ -0,0 +1,754 @@
+// Copyright 2018 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+
+#include "main/CommandLine.h"
+#include "catchup/CatchupConfiguration.h"
+#include "history/InferredQuorumUtils.h"
+#include "main/Application.h"
+#include "main/ApplicationUtils.h"
+#include "main/Config.h"
+#include "main/StellarCoreVersion.h"
+#include "main/dumpxdr.h"
+#include "main/fuzz.h"
+#include "scp/QuorumSetUtils.h"
+#include "test/test.h"
+#include "util/Logging.h"
+#include "util/optional.h"
+
+#include
+#include
+#include
+
+namespace stellar
+{
+
+void
+writeWithTextFlow(std::ostream& os, std::string const& text)
+{
+ size_t consoleWidth = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH;
+ os << clara::TextFlow::Column(text).width(consoleWidth) << "\n\n";
+}
+
+namespace
+{
+
+class CommandLine
+{
+ public:
+ struct ConfigOption
+ {
+ el::Level mLogLevel{el::Level::Info};
+ std::vector mMetrics;
+ std::string mConfigFile;
+
+ Config getConfig(bool logToFile = true) const;
+ };
+
+ class Command
+ {
+ public:
+ using RunFunc = std::function;
+
+ Command(std::string const& name, std::string const& description,
+ RunFunc const& runFunc);
+ int run(CommandLineArgs const& args) const;
+ std::string name() const;
+ std::string description() const;
+
+ private:
+ std::string mName;
+ std::string mDescription;
+ RunFunc mRunFunc;
+ };
+
+ explicit CommandLine(std::vector const& commands);
+
+ optional selectCommand(int argc, char* const* argv);
+ int executeCommand(Command const& command, int argc, char* const* argv);
+ void writeToStream(std::string const& exeName, std::ostream& os) const;
+
+ private:
+ std::vector mCommands;
+};
+
+class ParserWithValidation
+{
+ public:
+ ParserWithValidation(clara::Parser parser,
+ std::function isValid = [] {
+ return std::string{};
+ })
+ {
+ mParser = parser;
+ mIsValid = isValid;
+ }
+
+ ParserWithValidation(clara::Arg arg, std::function isValid =
+ [] { return std::string{}; })
+ {
+ mParser = clara::Parser{} | arg;
+ mIsValid = isValid;
+ }
+
+ ParserWithValidation(clara::Opt opt, std::function isValid =
+ [] { return std::string{}; })
+ {
+ mParser = clara::Parser{} | opt;
+ mIsValid = isValid;
+ }
+
+ const clara::Parser&
+ parser() const
+ {
+ return mParser;
+ }
+
+ std::string
+ validate() const
+ {
+ return mIsValid();
+ }
+
+ private:
+ clara::Parser mParser;
+ std::function mIsValid;
+};
+
+template
+std::function
+required(T& value, std::string const& name)
+{
+ return [&value, name] {
+ if (value.empty())
+ {
+ return name + " argument is required";
+ }
+ else
+ {
+ return std::string{};
+ };
+ };
+}
+
+template
+ParserWithValidation
+requiredArgParser(T& value, std::string const& name)
+{
+ return {clara::Arg(value, name).required(), required(value, name)};
+}
+
+ParserWithValidation
+fileNameParser(std::string& string)
+{
+ return requiredArgParser(string, "FILE-NAME");
+}
+
+clara::Opt
+outputFileParser(std::string& string)
+{
+ return clara::Opt{string, "FILE-NAME"}["--output-file"]("output file");
+}
+
+clara::Opt
+logLevelParser(el::Level& value)
+{
+ return clara::Opt{
+ [&](std::string const& arg) { value = Logging::getLLfromString(arg); },
+ "LEVEL"}["--ll"]("set the log level");
+}
+
+clara::Opt
+metricsParser(std::vector& value)
+{
+ return clara::Opt{value, "METRIC-NAME"}["--metric"](
+ "report metric METRIC-NAME on exit");
+}
+
+clara::Opt
+base64Parser(bool& base64)
+{
+ return clara::Opt{base64}["--base64"]("use base64");
+}
+
+clara::Parser
+configurationParser(CommandLine::ConfigOption& configOption)
+{
+ return logLevelParser(configOption.mLogLevel) |
+ metricsParser(configOption.mMetrics) |
+ clara::Opt{configOption.mConfigFile, "FILE-NAME"}["--conf"](
+ "specify a config file ('-' for STDIN, "
+ "default 'stellar-core.cfg')");
+}
+
+int
+runWithHelp(CommandLineArgs const& args,
+ std::vector parsers, std::function f)
+{
+ auto isHelp = false;
+ auto parser = clara::Parser{} | clara::Help(isHelp);
+ for (auto const& p : parsers)
+ parser |= p.parser();
+ auto errorMessage =
+ parser
+ .parse(args.mCommandName,
+ clara::detail::TokenStream{std::begin(args.mArgs),
+ std::end(args.mArgs)})
+ .errorMessage();
+ if (errorMessage.empty())
+ {
+ for (auto const& p : parsers)
+ {
+ errorMessage = p.validate();
+ if (!errorMessage.empty())
+ {
+ break;
+ }
+ }
+ }
+
+ if (!errorMessage.empty())
+ {
+ writeWithTextFlow(std::cerr, errorMessage);
+ writeWithTextFlow(std::cerr, args.mCommandDescription);
+ parser.writeToStream(std::cerr);
+ return 1;
+ }
+
+ if (isHelp)
+ {
+ writeWithTextFlow(std::cout, args.mCommandDescription);
+ parser.writeToStream(std::cout);
+ return 0;
+ }
+
+ return f();
+}
+
+CatchupConfiguration
+parseCatchup(std::string const& catchup)
+{
+ auto static errorMessage =
+ "catchup value should be passed as , "
+ "where destination ledger is any valid number or 'current' and ledger "
+ "count is any valid number or 'max'";
+
+ auto separatorIndex = catchup.find("/");
+ if (separatorIndex == std::string::npos)
+ {
+ throw std::runtime_error(errorMessage);
+ }
+
+ try
+ {
+ return {parseLedger(catchup.substr(0, separatorIndex)),
+ parseLedgerCount(catchup.substr(separatorIndex + 1))};
+ }
+ catch (std::exception&)
+ {
+ throw std::runtime_error(errorMessage);
+ }
+}
+
+CommandLine::Command::Command(std::string const& name,
+ std::string const& description,
+ RunFunc const& runFunc)
+ : mName{name}, mDescription{description}, mRunFunc{runFunc}
+{
+}
+
+int
+CommandLine::Command::run(CommandLineArgs const& args) const
+{
+ return mRunFunc(args);
+}
+
+std::string
+CommandLine::Command::name() const
+{
+ return mName;
+}
+
+std::string
+CommandLine::Command::description() const
+{
+ return mDescription;
+}
+
+Config
+CommandLine::ConfigOption::getConfig(bool logToFile) const
+{
+ Config config;
+ auto configFile =
+ mConfigFile.empty() ? std::string{"stellar-core.cfg"} : mConfigFile;
+
+ LOG(INFO) << "Config from " << configFile;
+
+ // yes you really have to do this 3 times
+ Logging::setLogLevel(mLogLevel, nullptr);
+ config.load(configFile);
+
+ Logging::setFmt(KeyUtils::toShortString(config.NODE_SEED.getPublicKey()));
+ Logging::setLogLevel(mLogLevel, nullptr);
+
+ if (logToFile)
+ {
+ if (config.LOG_FILE_PATH.size())
+ Logging::setLoggingToFile(config.LOG_FILE_PATH);
+ Logging::setLogLevel(mLogLevel, nullptr);
+ }
+
+ config.REPORT_METRICS = mMetrics;
+ return config;
+}
+
+CommandLine::CommandLine(std::vector const& commands)
+ : mCommands{commands}
+{
+ mCommands.push_back(Command{"help", "display list of available commands",
+ [this](CommandLineArgs const& args) {
+ writeToStream(args.mExeName, std::cout);
+ return 0;
+ }});
+
+ std::sort(
+ std::begin(mCommands), std::end(mCommands),
+ [](Command const& x, Command const& y) { return x.name() < y.name(); });
+}
+
+optional
+CommandLine::selectCommand(int argc, char* const* argv)
+{
+ if (argc < 2)
+ {
+ return nullopt();
+ }
+
+ auto command = std::find_if(
+ std::begin(mCommands), std::end(mCommands),
+ [&](Command const& command) { return command.name() == argv[1]; });
+ if (command == std::end(mCommands))
+ {
+ return nullopt();
+ }
+
+ return make_optional(*command);
+}
+
+int
+CommandLine::executeCommand(Command const& command, int argc, char* const* argv)
+{
+ auto args = CommandLineArgs{argv[0],
+ fmt::format("{0} {1}", argv[0], command.name()),
+ command.description(),
+ {argv + 2, argv + argc}};
+ return command.run(args);
+}
+
+void
+CommandLine::writeToStream(std::string const& exeName, std::ostream& os) const
+{
+ os << "usage:\n"
+ << " " << exeName << " "
+ << "COMMAND";
+ os << "\n\nwhere COMMAND is one of following:" << std::endl;
+
+ size_t consoleWidth = CLARA_TEXTFLOW_CONFIG_CONSOLE_WIDTH;
+ size_t commandWidth = 0;
+ for (auto const& command : mCommands)
+ commandWidth = std::max(commandWidth, command.name().size() + 2);
+
+ commandWidth = std::min(commandWidth, consoleWidth / 2);
+
+ for (auto const& command : mCommands)
+ {
+ auto row = clara::TextFlow::Column(command.name())
+ .width(commandWidth)
+ .indent(2) +
+ clara::TextFlow::Spacer(4) +
+ clara::TextFlow::Column(command.description())
+ .width(consoleWidth - 7 - commandWidth);
+ os << row << std::endl;
+ }
+}
+}
+
+int
+runCatchup(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+ std::string catchupString;
+ std::string outputFile;
+
+ auto validateCatchupString = [&] {
+ try
+ {
+ parseCatchup(catchupString);
+ return std::string{};
+ }
+ catch (std::runtime_error& e)
+ {
+ return std::string{e.what()};
+ }
+ };
+ auto catchupStringParser = ParserWithValidation{
+ clara::Arg(catchupString, "DESTINATION-LEDGER/LEDGER-COUNT").required(),
+ validateCatchupString};
+
+ return runWithHelp(args,
+ {configurationParser(configOption), catchupStringParser,
+ outputFileParser(outputFile)},
+ [&] {
+ auto config = configOption.getConfig();
+ config.setNoListen();
+
+ VirtualClock clock(VirtualClock::REAL_TIME);
+ auto app = Application::create(clock, config, false);
+ Json::Value catchupInfo;
+ auto result = catchup(
+ app, parseCatchup(catchupString), catchupInfo);
+ if (!catchupInfo.isNull())
+ writeCatchupInfo(catchupInfo, outputFile);
+
+ return result;
+ });
+}
+
+int
+runCheckQuorum(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+
+ return runWithHelp(args, {configurationParser(configOption)}, [&] {
+ checkQuorumIntersection(configOption.getConfig());
+ return 0;
+ });
+}
+
+int
+runConvertId(CommandLineArgs const& args)
+{
+ std::string id;
+
+ return runWithHelp(args, {requiredArgParser(id, "ID")}, [&] {
+ StrKeyUtils::logKey(std::cout, id);
+ return 0;
+ });
+}
+
+int
+runDumpXDR(CommandLineArgs const& args)
+{
+ std::string xdr;
+
+ return runWithHelp(args, {fileNameParser(xdr)}, [&] {
+ dumpXdrStream(xdr);
+ return 0;
+ });
+}
+
+int
+runForceSCP(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+ auto reset = false;
+
+ auto resetOption = clara::Opt{reset}["--reset"](
+ "reset force SCP flag, so next time stellar-core "
+ "is run it will wait to hear from the network "
+ "rather than starting with the local ledger");
+
+ return runWithHelp(args, {configurationParser(configOption), resetOption},
+ [&] {
+ setForceSCPFlag(configOption.getConfig(), !reset);
+ return 0;
+ });
+}
+
+int
+runFuzz(CommandLineArgs const& args)
+{
+ el::Level logLevel{el::Level::Info};
+ std::vector metrics;
+ std::string fileName;
+
+ return runWithHelp(args,
+ {logLevelParser(logLevel), metricsParser(metrics),
+ fileNameParser(fileName)},
+ [&] {
+ fuzz(fileName, logLevel, metrics);
+ return 0;
+ });
+}
+
+int
+runGenFuzz(CommandLineArgs const& args)
+{
+ std::string fileName;
+
+ return runWithHelp(args, {fileNameParser(fileName)}, [&] {
+ genfuzz(fileName);
+ return 0;
+ });
+}
+
+int
+runGenSeed(CommandLineArgs const& args)
+{
+ return runWithHelp(args, {}, [&] {
+ genSeed();
+ return 0;
+ });
+}
+
+int
+runHttpCommand(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+ std::string command;
+
+ return runWithHelp(args,
+ {configurationParser(configOption),
+ requiredArgParser(command, "COMMAND")},
+ [&] {
+ httpCommand(command,
+ configOption.getConfig(false).HTTP_PORT);
+ return 0;
+ });
+}
+
+int
+runInferQuorum(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+
+ return runWithHelp(args, {configurationParser(configOption)}, [&] {
+ inferQuorumAndWrite(configOption.getConfig());
+ return 0;
+ });
+}
+
+int
+runLoadXDR(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+ std::string xdr;
+
+ return runWithHelp(
+ args, {configurationParser(configOption), fileNameParser(xdr)}, [&] {
+ loadXdr(configOption.getConfig(), xdr);
+ return 0;
+ });
+}
+
+int
+runNewDB(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+
+ return runWithHelp(args, {configurationParser(configOption)}, [&] {
+ initializeDatabase(configOption.getConfig());
+ return 0;
+ });
+}
+
+int
+runNewHist(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+ std::vector newHistories;
+
+ return runWithHelp(args,
+ {configurationParser(configOption),
+ requiredArgParser(newHistories, "HISTORY-LABEL")},
+ [&] {
+ return initializeHistories(configOption.getConfig(),
+ newHistories);
+ });
+}
+
+int
+runOfflineInfo(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+
+ return runWithHelp(args, {configurationParser(configOption)}, [&] {
+ showOfflineInfo(configOption.getConfig());
+ return 0;
+ });
+}
+
+int
+runPrintXdr(CommandLineArgs const& args)
+{
+ std::string xdr;
+ std::string fileType{"auto"};
+ auto base64 = false;
+
+ auto fileTypeOpt = clara::Opt(fileType, "FILE-TYPE")["--filetype"](
+ "[auto|ledgerheader|meta|result|resultpair|tx|txfee]");
+
+ return runWithHelp(
+ args, {fileNameParser(xdr), fileTypeOpt, base64Parser(base64)}, [&] {
+ printXdr(xdr, fileType, base64);
+ return 0;
+ });
+}
+
+int
+runReportLastHistoryCheckpoint(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+ std::string outputFile;
+
+ return runWithHelp(
+ args, {configurationParser(configOption), outputFileParser(outputFile)},
+ [&] {
+ reportLastHistoryCheckpoint(configOption.getConfig(), outputFile);
+ return 0;
+ });
+}
+
+int
+run(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+
+ return runWithHelp(args, {configurationParser(configOption)}, [&] {
+ Config cfg;
+ try
+ {
+ cfg = configOption.getConfig();
+ }
+ catch (std::exception& e)
+ {
+ LOG(FATAL) << "Got an exception: " << e.what();
+ return 1;
+ }
+ // run outside of catch block so that we properly capture crashes
+ return runWithConfig(cfg);
+ });
+}
+
+int
+runSecToPub(CommandLineArgs const& args)
+{
+ return runWithHelp(args, {}, [&] {
+ priv2pub();
+ return 0;
+ });
+}
+
+int
+runSignTransaction(CommandLineArgs const& args)
+{
+ std::string transaction;
+ std::string netId;
+ bool base64;
+
+ auto netIdOption = clara::Opt(netId, "NETWORK-PASSPHRASE")["--netid"](
+ "network ID used for signing")
+ .required();
+ auto netIdParser = ParserWithValidation{
+ netIdOption, required(netId, "NETWORK-PASSPHRASE")};
+
+ return runWithHelp(
+ args, {fileNameParser(transaction), netIdParser, base64Parser(base64)},
+ [&] {
+ signtxn(transaction, netId, base64);
+ return 0;
+ });
+}
+
+int
+runVersion(CommandLineArgs const&)
+{
+ std::cout << STELLAR_CORE_VERSION << std::endl;
+ return 0;
+}
+
+int
+runWriteQuorum(CommandLineArgs const& args)
+{
+ CommandLine::ConfigOption configOption;
+ std::string outputFile;
+
+ return runWithHelp(
+ args, {configurationParser(configOption), outputFileParser(outputFile)},
+ [&] {
+ writeQuorumGraph(configOption.getConfig(), outputFile);
+ return 0;
+ });
+}
+
+optional
+handleCommandLine(int argc, char* const* argv)
+{
+ auto commandLine = CommandLine{
+ {{"catchup",
+ "execute catchup from history archives without connecting to "
+ "network",
+ runCatchup},
+ {"check-quorum", "check quorum intersection from history",
+ runCheckQuorum},
+ {"convert-id", "displays ID in all known forms", runConvertId},
+ {"dump-xdr", "dump an XDR file, for debugging", runDumpXDR},
+ {"force-scp",
+ "next time stellar-core is run, SCP will start with "
+ "the local ledger rather than waiting to hear from the "
+ "network",
+ runForceSCP},
+ {"fuzz", "run a single fuzz input and exit", runFuzz},
+ {"gen-fuzz", "generate a random fuzzer input file", runGenFuzz},
+ {"gen-seed", "generate and print a random node seed", runGenSeed},
+ {"http-command", "send a command to local stellar-core",
+ runHttpCommand},
+ {"infer-quorum", "print a quorum set inferred from history",
+ runInferQuorum},
+ {"load-xdr", "load an XDR bucket file, for testing", runLoadXDR},
+ {"new-db", "creates or restores the DB to the genesis ledger",
+ runNewDB},
+ {"new-hist", "initialize history archives", runNewHist},
+ {"offline-info", "return information for an offline instance",
+ runOfflineInfo},
+ {"print-xdr", "pretty-print one XDR envelope, then quit", runPrintXdr},
+ {"report-last-history-checkpoint",
+ "report information about last checkpoint available in "
+ "history archives",
+ runReportLastHistoryCheckpoint},
+ {"run", "run stellar-core node", run},
+ {"sec-to-pub", "print the public key corresponding to a secret key",
+ runSecToPub},
+ {"sign-transaction",
+ "add signature to transaction envelope, then quit",
+ runSignTransaction},
+ {"test", "execute test suite", runTest},
+ {"write-quorum", "print a quorum set graph from history",
+ runWriteQuorum},
+ {"version", "print version information", runVersion}}};
+
+ auto command = commandLine.selectCommand(argc, argv);
+ if (!command)
+ {
+ return nullopt();
+ }
+
+ if (command->name() == "run")
+ {
+ // run outside of catch block so that we properly capture crashes
+ return make_optional(
+ commandLine.executeCommand(*command, argc, argv));
+ }
+
+ try
+ {
+ return make_optional(
+ commandLine.executeCommand(*command, argc, argv));
+ }
+ catch (std::exception& e)
+ {
+ LOG(FATAL) << "Got an exception: " << e.what();
+ return make_optional(1);
+ }
+}
+}
diff --git a/src/main/CommandLine.h b/src/main/CommandLine.h
new file mode 100644
index 0000000000..b9777d44cd
--- /dev/null
+++ b/src/main/CommandLine.h
@@ -0,0 +1,27 @@
+#pragma once
+
+// Copyright 2018 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+
+#include "util/Logging.h"
+#include "util/optional.h"
+
+#include
+#include
+
+namespace stellar
+{
+
+struct CommandLineArgs
+{
+ std::string mExeName;
+ std::string mCommandName;
+ std::string mCommandDescription;
+ std::vector mArgs;
+};
+
+optional handleCommandLine(int argc, char* const* argv);
+
+void writeWithTextFlow(std::ostream& os, std::string const& text);
+}
diff --git a/src/main/Config.cpp b/src/main/Config.cpp
index 74822cbe29..675fbc4c34 100644
--- a/src/main/Config.cpp
+++ b/src/main/Config.cpp
@@ -242,6 +242,14 @@ Config::loadQset(std::shared_ptr group, SCPQuorumSet& qset,
void
Config::load(std::string const& filename)
{
+ if (filename != "-" && !fs::exists(filename))
+ {
+ std::string s;
+ s = "No config file ";
+ s += filename + " found";
+ throw std::invalid_argument(s);
+ }
+
LOG(DEBUG) << "Loading config from: " << filename;
try
{
@@ -835,4 +843,13 @@ Config::getExpectedLedgerCloseTime() const
}
return Herder::EXP_LEDGER_TIMESPAN_SECONDS;
}
+
+void
+Config::setNoListen()
+{
+ // prevent opening up a port for other peers
+ RUN_STANDALONE = true;
+ HTTP_PORT = 0;
+ MANUAL_CLOSE = true;
+}
}
diff --git a/src/main/Config.h b/src/main/Config.h
index 7aab6989e0..ecebfe9de4 100644
--- a/src/main/Config.h
+++ b/src/main/Config.h
@@ -224,5 +224,7 @@ class Config : public std::enable_shared_from_this
bool resolveNodeID(std::string const& s, PublicKey& retKey) const;
std::chrono::seconds getExpectedLedgerCloseTime() const;
+
+ void setNoListen();
};
}
diff --git a/src/main/DeprecatedCommandLine.cpp b/src/main/DeprecatedCommandLine.cpp
new file mode 100644
index 0000000000..25c873c893
--- /dev/null
+++ b/src/main/DeprecatedCommandLine.cpp
@@ -0,0 +1,449 @@
+// Copyright 2018 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+
+#include "main/DeprecatedCommandLine.h"
+#include "bucket/Bucket.h"
+#include "history/HistoryArchiveManager.h"
+#include "history/InferredQuorumUtils.h"
+#include "historywork/GetHistoryArchiveStateWork.h"
+#include "ledger/LedgerManager.h"
+#include "main/Application.h"
+#include "main/ApplicationUtils.h"
+#include "main/Config.h"
+#include "main/ExternalQueue.h"
+#include "main/Maintainer.h"
+#include "main/PersistentState.h"
+#include "main/StellarCoreVersion.h"
+#include "main/dumpxdr.h"
+#include "main/fuzz.h"
+#include "test/test.h"
+#include "util/Fs.h"
+#include "util/Logging.h"
+#include "util/optional.h"
+#include "work/WorkManager.h"
+
+#include
+#include
+#include
+
+namespace stellar
+{
+
+namespace
+{
+
+enum opttag
+{
+ OPT_CATCHUP_AT,
+ OPT_CATCHUP_COMPLETE,
+ OPT_CATCHUP_RECENT,
+ OPT_CATCHUP_TO,
+ OPT_CMD,
+ OPT_CONF,
+ OPT_CONVERTID,
+ OPT_CHECKQUORUM,
+ OPT_BASE64,
+ OPT_DUMPXDR,
+ OPT_LOADXDR,
+ OPT_FORCESCP,
+ OPT_FUZZ,
+ OPT_GENFUZZ,
+ OPT_GENSEED,
+ OPT_GRAPHQUORUM,
+ OPT_HELP,
+ OPT_INFERQUORUM,
+ OPT_OFFLINEINFO,
+ OPT_OUTPUT_FILE,
+ OPT_REPORT_LAST_HISTORY_CHECKPOINT,
+ OPT_LOGLEVEL,
+ OPT_METRIC,
+ OPT_NEWDB,
+ OPT_NEWHIST,
+ OPT_PRINTXDR,
+ OPT_SEC2PUB,
+ OPT_SIGNTXN,
+ OPT_NETID,
+ OPT_TEST,
+ OPT_FILETYPE,
+ OPT_VERSION
+};
+
+const struct option stellar_core_options[] = {
+ {"catchup-at", required_argument, nullptr, OPT_CATCHUP_AT},
+ {"catchup-complete", no_argument, nullptr, OPT_CATCHUP_COMPLETE},
+ {"catchup-recent", required_argument, nullptr, OPT_CATCHUP_RECENT},
+ {"catchup-to", required_argument, nullptr, OPT_CATCHUP_TO},
+ {"c", required_argument, nullptr, OPT_CMD},
+ {"conf", required_argument, nullptr, OPT_CONF},
+ {"convertid", required_argument, nullptr, OPT_CONVERTID},
+ {"checkquorum", optional_argument, nullptr, OPT_CHECKQUORUM},
+ {"base64", no_argument, nullptr, OPT_BASE64},
+ {"dumpxdr", required_argument, nullptr, OPT_DUMPXDR},
+ {"printxdr", required_argument, nullptr, OPT_PRINTXDR},
+ {"signtxn", required_argument, nullptr, OPT_SIGNTXN},
+ {"netid", required_argument, nullptr, OPT_NETID},
+ {"loadxdr", required_argument, nullptr, OPT_LOADXDR},
+ {"forcescp", optional_argument, nullptr, OPT_FORCESCP},
+ {"fuzz", required_argument, nullptr, OPT_FUZZ},
+ {"genfuzz", required_argument, nullptr, OPT_GENFUZZ},
+ {"genseed", no_argument, nullptr, OPT_GENSEED},
+ {"graphquorum", optional_argument, nullptr, OPT_GRAPHQUORUM},
+ {"help", no_argument, nullptr, OPT_HELP},
+ {"inferquorum", optional_argument, nullptr, OPT_INFERQUORUM},
+ {"offlineinfo", no_argument, nullptr, OPT_OFFLINEINFO},
+ {"output-file", required_argument, nullptr, OPT_OUTPUT_FILE},
+ {"report-last-history-checkpoint", no_argument, nullptr,
+ OPT_REPORT_LAST_HISTORY_CHECKPOINT},
+ {"sec2pub", no_argument, nullptr, OPT_SEC2PUB},
+ {"ll", required_argument, nullptr, OPT_LOGLEVEL},
+ {"metric", required_argument, nullptr, OPT_METRIC},
+ {"newdb", no_argument, nullptr, OPT_NEWDB},
+ {"newhist", required_argument, nullptr, OPT_NEWHIST},
+ {"test", no_argument, nullptr, OPT_TEST},
+ {"version", no_argument, nullptr, OPT_VERSION},
+ {nullptr, 0, nullptr, 0}};
+
+void
+usage(int err = 1)
+{
+ std::ostream& os = err ? std::cerr : std::cout;
+ os << "usage: stellar-core [OPTIONS]\n"
+ "where OPTIONS can be any of:\n"
+ " --base64 Use base64 for --printtxn and --signtxn\n"
+ " --catchup-at SEQ Do a catchup at ledger SEQ, then quit\n"
+ " Use current as SEQ to catchup to "
+ "'current' history checkpoint\n"
+ " --catchup-complete Do a complete catchup, then quit\n"
+ " --catchup-recent NUM Do a recent catchup for NUM ledgers, "
+ "then quit\n"
+ " --catchup-to SEQ Do a catchup to ledger SEQ, then quit\n"
+ " Use current as SEQ to catchup to "
+ "'current' history checkpoint\n"
+ " --c Send a command to local stellar-core. "
+ "try "
+ "'--c help' for more information\n"
+ " --conf FILE Specify a config file ('-' for STDIN, "
+ "default 'stellar-core.cfg')\n"
+ " --convertid ID Displays ID in all known forms\n"
+ " --dumpxdr FILE Dump an XDR file, for debugging\n"
+ " --loadxdr FILE Load an XDR bucket file, for testing\n"
+ " --forcescp Next time stellar-core is run, SCP will "
+ "start "
+ "with the local ledger rather than waiting to hear from the "
+ "network.\n"
+ " --fuzz FILE Run a single fuzz input and exit\n"
+ " --genfuzz FILE Generate a random fuzzer input file\n"
+ " --genseed Generate and print a random node seed\n"
+ " --help Display this string\n"
+ " --inferquorum Print a quorum set inferred from "
+ "history\n"
+ " --checkquorum Check quorum intersection from history\n"
+ " --graphquorum Print a quorum set graph from history\n"
+ " --output-file Output file for --graphquorum and "
+ "--report-last-history-checkpoint commands\n"
+ " --offlineinfo Return information for an offline "
+ "instance\n"
+ " --ll LEVEL Set the log level. (redundant with --c "
+ "ll "
+ "but "
+ "you need this form for the tests.)\n"
+ " LEVEL can be: trace, debug, info, error, "
+ "fatal\n"
+ " --metric METRIC Report metric METRIC on exit\n"
+ " --newdb Creates or restores the DB to the "
+ "genesis "
+ "ledger\n"
+ " --newhist ARCH Initialize the named history archive "
+ "ARCH\n"
+ " --printxdr FILE Pretty print XDR content from FILE, "
+ "then quit\n"
+ " --filetype "
+ "[auto|ledgerheader|meta|result|resultpair|tx|txfee] toggle for type "
+ "used for printxdr\n"
+ " --report-last-history-checkpoint\n"
+ " Report information about last checkpoint "
+ "available in history archives\n"
+ " --signtxn FILE Add signature to transaction envelope,"
+ " then quit\n"
+ " (Key is read from stdin or terminal, as"
+ " appropriate.)\n"
+ " --sec2pub Print the public key corresponding to a "
+ "secret key\n"
+ " --netid STRING Specify network ID for subsequent "
+ "signtxn\n"
+ " (Default is STELLAR_NETWORK_ID "
+ "environment variable)\n"
+ " --test Run self-tests\n"
+ " --version Print version information\n";
+ exit(err);
+}
+
+int
+catchupAt(Application::pointer app, uint32_t at, Json::Value& catchupInfo)
+{
+ return catchup(app, CatchupConfiguration{at, 0}, catchupInfo);
+}
+
+int
+catchupComplete(Application::pointer app, Json::Value& catchupInfo)
+{
+ return catchup(app,
+ CatchupConfiguration{CatchupConfiguration::CURRENT,
+ std::numeric_limits::max()},
+ catchupInfo);
+}
+
+int
+catchupRecent(Application::pointer app, uint32_t count,
+ Json::Value& catchupInfo)
+{
+ return catchup(app,
+ CatchupConfiguration{CatchupConfiguration::CURRENT, count},
+ catchupInfo);
+}
+
+int
+catchupTo(Application::pointer app, uint32_t to, Json::Value& catchupInfo)
+{
+ return catchup(
+ app, CatchupConfiguration{to, std::numeric_limits::max()},
+ catchupInfo);
+}
+}
+
+int
+handleDeprecatedCommandLine(int argc, char* const* argv)
+{
+ std::cout << "Using DEPRECATED command-line syntax.\n";
+ std::cout << "Please refer to documentation for new syntax.\n\n";
+
+ std::string cfgFile("stellar-core.cfg");
+ std::string command;
+ el::Level logLevel = el::Level::Info;
+ std::vector rest;
+
+ optional forceSCP = nullptr;
+ bool base64 = false;
+ bool doCatchupAt = false;
+ uint32_t catchupAtTarget = 0;
+ bool doCatchupComplete = false;
+ bool doCatchupRecent = false;
+ uint32_t catchupRecentCount = 0;
+ bool doCatchupTo = false;
+ uint32_t catchupToTarget = 0;
+ bool inferQuorum = false;
+ bool checkQuorum = false;
+ bool graphQuorum = false;
+ bool newDB = false;
+ bool getOfflineInfo = false;
+ auto doReportLastHistoryCheckpoint = false;
+ std::string outputFile;
+ std::string loadXdrBucket;
+ std::vector newHistories;
+ std::vector metrics;
+ std::string filetype = "auto";
+ std::string netId;
+
+ int opt;
+ while ((opt = getopt_long_only(argc, argv, "c:", stellar_core_options,
+ nullptr)) != -1)
+ {
+ switch (opt)
+ {
+ case OPT_BASE64:
+ base64 = true;
+ break;
+ case OPT_CATCHUP_AT:
+ doCatchupAt = true;
+ catchupAtTarget = parseLedger(optarg);
+ break;
+ case OPT_CATCHUP_COMPLETE:
+ doCatchupComplete = true;
+ break;
+ case OPT_CATCHUP_RECENT:
+ doCatchupRecent = true;
+ catchupRecentCount = parseLedgerCount(optarg);
+ break;
+ case OPT_CATCHUP_TO:
+ doCatchupTo = true;
+ catchupToTarget = parseLedger(optarg);
+ break;
+ case 'c':
+ case OPT_CMD:
+ command = optarg;
+ rest.insert(rest.begin(), argv + optind, argv + argc);
+ optind = argc;
+ break;
+ case OPT_CONF:
+ cfgFile = std::string(optarg);
+ break;
+ case OPT_CONVERTID:
+ StrKeyUtils::logKey(std::cout, std::string(optarg));
+ return 0;
+ case OPT_DUMPXDR:
+ dumpXdrStream(std::string(optarg));
+ return 0;
+ case OPT_PRINTXDR:
+ printXdr(std::string(optarg), filetype, base64);
+ return 0;
+ case OPT_FILETYPE:
+ filetype = std::string(optarg);
+ break;
+ case OPT_SIGNTXN:
+ signtxn(std::string(optarg), netId, base64);
+ return 0;
+ case OPT_SEC2PUB:
+ priv2pub();
+ return 0;
+ case OPT_NETID:
+ netId = optarg;
+ return 0;
+ case OPT_LOADXDR:
+ loadXdrBucket = std::string(optarg);
+ break;
+ case OPT_FORCESCP:
+ forceSCP = make_optional(optarg == nullptr ||
+ std::string(optarg) == "true");
+ break;
+ case OPT_FUZZ:
+ fuzz(std::string(optarg), logLevel, metrics);
+ return 0;
+ case OPT_GENFUZZ:
+ genfuzz(std::string(optarg));
+ return 0;
+ case OPT_GENSEED:
+ {
+ genSeed();
+ return 0;
+ }
+ case OPT_INFERQUORUM:
+ inferQuorum = true;
+ break;
+ case OPT_CHECKQUORUM:
+ checkQuorum = true;
+ break;
+ case OPT_GRAPHQUORUM:
+ graphQuorum = true;
+ break;
+ case OPT_OFFLINEINFO:
+ getOfflineInfo = true;
+ break;
+ case OPT_OUTPUT_FILE:
+ outputFile = optarg;
+ break;
+ case OPT_LOGLEVEL:
+ logLevel = Logging::getLLfromString(std::string(optarg));
+ break;
+ case OPT_METRIC:
+ metrics.push_back(std::string(optarg));
+ break;
+ case OPT_NEWDB:
+ newDB = true;
+ break;
+ case OPT_NEWHIST:
+ newHistories.push_back(std::string(optarg));
+ break;
+ case OPT_REPORT_LAST_HISTORY_CHECKPOINT:
+ doReportLastHistoryCheckpoint = true;
+ break;
+ case OPT_TEST:
+ {
+ rest.push_back(*argv);
+ rest.insert(++rest.begin(), argv + optind, argv + argc);
+ return test(static_cast(rest.size()), &rest[0], logLevel,
+ metrics);
+ }
+ case OPT_VERSION:
+ std::cout << STELLAR_CORE_VERSION << std::endl;
+ return 0;
+ case OPT_HELP:
+ default:
+ usage(0);
+ return 0;
+ }
+ }
+
+ Config cfg;
+ try
+ {
+ // yes you really have to do this 3 times
+ Logging::setLogLevel(logLevel, nullptr);
+ cfg.load(cfgFile);
+
+ Logging::setFmt(KeyUtils::toShortString(cfg.NODE_SEED.getPublicKey()));
+ Logging::setLogLevel(logLevel, nullptr);
+
+ if (command.size())
+ {
+ httpCommand(command, cfg.HTTP_PORT);
+ return 0;
+ }
+
+ // don't log to file if just sending a command
+ if (cfg.LOG_FILE_PATH.size())
+ Logging::setLoggingToFile(cfg.LOG_FILE_PATH);
+ Logging::setLogLevel(logLevel, nullptr);
+
+ cfg.REPORT_METRICS = metrics;
+
+ if (forceSCP || newDB || getOfflineInfo || !loadXdrBucket.empty() ||
+ inferQuorum || graphQuorum || checkQuorum || doCatchupAt ||
+ doCatchupComplete || doCatchupRecent || doCatchupTo ||
+ doReportLastHistoryCheckpoint)
+ {
+ auto result = 0;
+ if (newDB)
+ initializeDatabase(cfg);
+ if ((result == 0) && (doCatchupAt || doCatchupComplete ||
+ doCatchupRecent || doCatchupTo))
+ {
+ Json::Value catchupInfo;
+ VirtualClock clock(VirtualClock::REAL_TIME);
+ cfg.setNoListen();
+ auto app = Application::create(clock, cfg, false);
+ if (doCatchupAt)
+ result = catchupAt(app, catchupAtTarget, catchupInfo);
+ if ((result == 0) && doCatchupComplete)
+ result = catchupComplete(app, catchupInfo);
+ if ((result == 0) && doCatchupRecent)
+ result =
+ catchupRecent(app, catchupRecentCount, catchupInfo);
+ if ((result == 0) && doCatchupTo)
+ result = catchupTo(app, catchupToTarget, catchupInfo);
+ app->gracefulStop();
+ while (app->getClock().crank(true))
+ ;
+ if (!catchupInfo.isNull())
+ writeCatchupInfo(catchupInfo, outputFile);
+ }
+ if ((result == 0) && forceSCP)
+ setForceSCPFlag(cfg, *forceSCP);
+ if ((result == 0) && getOfflineInfo)
+ showOfflineInfo(cfg);
+ if ((result == 0) && doReportLastHistoryCheckpoint)
+ result = reportLastHistoryCheckpoint(cfg, outputFile);
+ if ((result == 0) && !loadXdrBucket.empty())
+ loadXdr(cfg, loadXdrBucket);
+ if ((result == 0) && inferQuorum)
+ inferQuorumAndWrite(cfg);
+ if ((result == 0) && checkQuorum)
+ checkQuorumIntersection(cfg);
+ if ((result == 0) && graphQuorum)
+ writeQuorumGraph(cfg, outputFile);
+ return result;
+ }
+ else if (!newHistories.empty())
+ {
+ return initializeHistories(cfg, newHistories);
+ }
+ }
+ catch (std::exception& e)
+ {
+ LOG(FATAL) << "Got an exception: " << e.what();
+ return 1;
+ }
+ // run outside of catch block so that we properly capture crashes
+ return runWithConfig(cfg);
+}
+}
diff --git a/src/main/DeprecatedCommandLine.h b/src/main/DeprecatedCommandLine.h
new file mode 100644
index 0000000000..4bdff32587
--- /dev/null
+++ b/src/main/DeprecatedCommandLine.h
@@ -0,0 +1,11 @@
+#pragma once
+
+// Copyright 2018 Stellar Development Foundation and contributors. Licensed
+// under the Apache License, Version 2.0. See the COPYING file at the root
+// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+
+namespace stellar
+{
+
+int handleDeprecatedCommandLine(int argc, char* const* argv);
+}
diff --git a/src/main/dumpxdr.cpp b/src/main/dumpxdr.cpp
index e23e6f675d..a0362c3673 100644
--- a/src/main/dumpxdr.cpp
+++ b/src/main/dumpxdr.cpp
@@ -33,8 +33,6 @@ using namespace std::placeholders;
namespace stellar
{
-const char* signtxn_network_id;
-
std::string
xdr_printer(const PublicKey& pk)
{
@@ -274,15 +272,15 @@ readSecret(const std::string& prompt, bool force_tty)
}
void
-signtxn(std::string const& filename, bool base64)
+signtxn(std::string const& filename, std::string netId, bool base64)
{
using namespace std;
try
{
- if (!signtxn_network_id)
- signtxn_network_id = getenv("STELLAR_NETWORK_ID");
- if (!signtxn_network_id)
+ if (netId.empty())
+ netId = getenv("STELLAR_NETWORK_ID");
+ if (netId.empty())
throw std::runtime_error("missing --netid argument or "
"STELLAR_NETWORK_ID environment variable");
@@ -301,7 +299,7 @@ signtxn(std::string const& filename, bool base64)
SecretKey sk(SecretKey::fromStrKeySeed(
readSecret("Secret key seed: ", txn_stdin)));
TransactionSignaturePayload payload;
- payload.networkId = sha256(std::string(signtxn_network_id));
+ payload.networkId = sha256(netId);
payload.taggedTransaction.type(ENVELOPE_TYPE_TX);
payload.taggedTransaction.tx() = txenv.tx;
txenv.signatures.emplace_back(
diff --git a/src/main/dumpxdr.h b/src/main/dumpxdr.h
index bc756efc8a..67215a0701 100644
--- a/src/main/dumpxdr.h
+++ b/src/main/dumpxdr.h
@@ -9,10 +9,9 @@
namespace stellar
{
-extern const char* signtxn_network_id;
void dumpXdrStream(std::string const& filename);
void printXdr(std::string const& filename, std::string const& filetype,
bool base64);
-void signtxn(std::string const& filename, bool base64);
+void signtxn(std::string const& filename, std::string netId, bool base64);
void priv2pub();
}
diff --git a/src/main/main.cpp b/src/main/main.cpp
index 57aefe0a9a..a3a6962b89 100644
--- a/src/main/main.cpp
+++ b/src/main/main.cpp
@@ -1,685 +1,15 @@
// Copyright 2014 Stellar Development Foundation and contributors. Licensed
// under the Apache License, Version 2.0. See the COPYING file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
-#include "util/asio.h"
-#include "bucket/Bucket.h"
-#include "bucket/BucketManager.h"
-#include "catchup/CatchupConfiguration.h"
-#include "catchup/CatchupManager.h"
-#include "catchup/CatchupWork.h"
-#include "catchup/VerifyLedgerChainWork.h"
-#include "crypto/Hex.h"
-#include "crypto/KeyUtils.h"
-#include "crypto/SecretKey.h"
-#include "database/Database.h"
-#include "history/HistoryArchiveManager.h"
-#include "history/HistoryManager.h"
-#include "historywork/GetHistoryArchiveStateWork.h"
-#include "ledger/LedgerManager.h"
-#include "lib/http/HttpClient.h"
-#include "lib/util/getopt.h"
-#include "main/Application.h"
-#include "main/Config.h"
-#include "main/ExternalQueue.h"
-#include "main/Maintainer.h"
-#include "main/PersistentState.h"
-#include "main/StellarCoreVersion.h"
-#include "main/dumpxdr.h"
-#include "main/fuzz.h"
-#include "test/test.h"
-#include "util/Fs.h"
-#include "util/Logging.h"
-#include "util/Timer.h"
-#include "util/optional.h"
-#include "work/WorkManager.h"
-#include
-#include
-#include
-#include
-
-INITIALIZE_EASYLOGGINGPP
-
-namespace stellar
-{
-
-using namespace std;
-
-enum opttag
-{
- OPT_CATCHUP_AT,
- OPT_CATCHUP_COMPLETE,
- OPT_CATCHUP_RECENT,
- OPT_CATCHUP_TO,
- OPT_CMD,
- OPT_CONF,
- OPT_CONVERTID,
- OPT_CHECKQUORUM,
- OPT_BASE64,
- OPT_DUMPXDR,
- OPT_LOADXDR,
- OPT_FORCESCP,
- OPT_FUZZ,
- OPT_GENFUZZ,
- OPT_GENSEED,
- OPT_GRAPHQUORUM,
- OPT_HELP,
- OPT_INFERQUORUM,
- OPT_OFFLINEINFO,
- OPT_OUTPUT_FILE,
- OPT_REPORT_LAST_HISTORY_CHECKPOINT,
- OPT_LOGLEVEL,
- OPT_METRIC,
- OPT_NEWDB,
- OPT_NEWHIST,
- OPT_PRINTXDR,
- OPT_SEC2PUB,
- OPT_SIGNTXN,
- OPT_NETID,
- OPT_TEST,
- OPT_FILETYPE,
- OPT_VERSION
-};
-
-static const struct option stellar_core_options[] = {
- {"catchup-at", required_argument, nullptr, OPT_CATCHUP_AT},
- {"catchup-complete", no_argument, nullptr, OPT_CATCHUP_COMPLETE},
- {"catchup-recent", required_argument, nullptr, OPT_CATCHUP_RECENT},
- {"catchup-to", required_argument, nullptr, OPT_CATCHUP_TO},
- {"c", required_argument, nullptr, OPT_CMD},
- {"conf", required_argument, nullptr, OPT_CONF},
- {"convertid", required_argument, nullptr, OPT_CONVERTID},
- {"checkquorum", optional_argument, nullptr, OPT_CHECKQUORUM},
- {"base64", no_argument, nullptr, OPT_BASE64},
- {"dumpxdr", required_argument, nullptr, OPT_DUMPXDR},
- {"printxdr", required_argument, nullptr, OPT_PRINTXDR},
- {"filetype", required_argument, nullptr, OPT_FILETYPE},
- {"signtxn", required_argument, nullptr, OPT_SIGNTXN},
- {"netid", required_argument, nullptr, OPT_NETID},
- {"loadxdr", required_argument, nullptr, OPT_LOADXDR},
- {"forcescp", optional_argument, nullptr, OPT_FORCESCP},
- {"fuzz", required_argument, nullptr, OPT_FUZZ},
- {"genfuzz", required_argument, nullptr, OPT_GENFUZZ},
- {"genseed", no_argument, nullptr, OPT_GENSEED},
- {"graphquorum", optional_argument, nullptr, OPT_GRAPHQUORUM},
- {"help", no_argument, nullptr, OPT_HELP},
- {"inferquorum", optional_argument, nullptr, OPT_INFERQUORUM},
- {"offlineinfo", no_argument, nullptr, OPT_OFFLINEINFO},
- {"output-file", required_argument, nullptr, OPT_OUTPUT_FILE},
- {"report-last-history-checkpoint", no_argument, nullptr,
- OPT_REPORT_LAST_HISTORY_CHECKPOINT},
- {"sec2pub", no_argument, nullptr, OPT_SEC2PUB},
- {"ll", required_argument, nullptr, OPT_LOGLEVEL},
- {"metric", required_argument, nullptr, OPT_METRIC},
- {"newdb", no_argument, nullptr, OPT_NEWDB},
- {"newhist", required_argument, nullptr, OPT_NEWHIST},
- {"test", no_argument, nullptr, OPT_TEST},
- {"version", no_argument, nullptr, OPT_VERSION},
- {nullptr, 0, nullptr, 0}};
-
-static void
-usage(int err = 1)
-{
- std::ostream& os = err ? std::cerr : std::cout;
- os << "usage: stellar-core [OPTIONS]\n"
- "where OPTIONS can be any of:\n"
- " --base64 Use base64 for --printtxn and --signtxn\n"
- " --catchup-at SEQ Do a catchup at ledger SEQ, then quit\n"
- " Use current as SEQ to catchup to "
- "'current' history checkpoint\n"
- " --catchup-complete Do a complete catchup, then quit\n"
- " --catchup-recent NUM Do a recent catchup for NUM ledgers, "
- "then quit\n"
- " --catchup-to SEQ Do a catchup to ledger SEQ, then quit\n"
- " Use current as SEQ to catchup to "
- "'current' history checkpoint\n"
- " --c Send a command to local stellar-core. "
- "try '--c help' for more information\n"
- " --conf FILE Specify a config file ('-' for STDIN, "
- "default 'stellar-core.cfg')\n"
- " --convertid ID Displays ID in all known forms\n"
- " --dumpxdr FILE Dump an XDR file, for debugging\n"
- " --loadxdr FILE Load an XDR bucket file, for testing\n"
- " --forcescp Next time stellar-core is run, SCP will "
- "start with the local ledger rather than waiting to hear from the "
- "network.\n"
- " --fuzz FILE Run a single fuzz input and exit\n"
- " --genfuzz FILE Generate a random fuzzer input file\n"
- " --genseed Generate and print a random node seed\n"
- " --help Display this string\n"
- " --inferquorum Print a quorum set inferred from "
- "history\n"
- " --checkquorum Check quorum intersection from history\n"
- " --graphquorum Print a quorum set graph from history\n"
- " --output-file Output file for --graphquorum and "
- "--report-last-history-checkpoint commands\n"
- " --offlineinfo Return information for an offline "
- "instance\n"
- " --ll LEVEL Set the log level. (redundant with --c "
- "ll but you need this form for the tests.)\n"
- " LEVEL can be: trace, debug, info, error, "
- "fatal\n"
- " --metric METRIC Report metric METRIC on exit\n"
- " --newdb Creates or restores the DB to the "
- "genesis ledger\n"
- " --newhist ARCH Initialize the named history archive "
- "ARCH\n"
- " --printxdr FILE Pretty print XDR content from FILE, "
- "then quit\n"
- " --filetype "
- "[auto|ledgerheader|meta|result|resultpair|tx|txfee] toggle for type "
- "used for printxdr\n"
- " --report-last-history-checkpoint\n"
- " Report information about last checkpoint "
- "available in history archives\n"
- " --signtxn FILE Add signature to transaction envelope,"
- " then quit\n"
- " (Key is read from stdin or terminal, as"
- " appropriate.)\n"
- " --sec2pub Print the public key corresponding to a "
- "secret key\n"
- " --netid STRING Specify network ID for subsequent "
- "signtxn\n"
- " (Default is STELLAR_NETWORK_ID "
- "environment variable)\n"
- " --test Run self-tests\n"
- " --version Print version information\n";
- exit(err);
-}
-
-static void
-setNoListen(Config& cfg)
-{
- // prevent opening up a port for other peers
- cfg.RUN_STANDALONE = true;
- cfg.HTTP_PORT = 0;
- cfg.MANUAL_CLOSE = true;
-}
-
-static void
-sendCommand(std::string const& command, const std::vector& rest,
- unsigned short port)
-{
- std::string ret;
- std::ostringstream path;
-
- path << "/";
- bool gotCommand = false;
-
- std::locale loc("C");
-
- for (auto const& c : command)
- {
- if (gotCommand)
- {
- if (std::isalnum(c, loc))
- {
- path << c;
- }
- else
- {
- path << '%' << std::hex << std::setw(2) << std::setfill('0')
- << (unsigned int)c;
- }
- }
- else
- {
- path << c;
- if (c == '?')
- {
- gotCommand = true;
- }
- }
- }
-
- int code = http_request("127.0.0.1", path.str(), port, ret);
- if (code == 200)
- {
- LOG(INFO) << ret;
- }
- else
- {
- LOG(INFO) << "http failed(" << code << ") port: " << port
- << " command: " << command;
- }
-}
-
-static bool
-checkInitialized(Application::pointer app)
-{
- try
- {
- // check to see if the state table exists
- app->getPersistentState().getState(PersistentState::kDatabaseSchema);
- }
- catch (...)
- {
- LOG(INFO) << "* ";
- LOG(INFO) << "* The database has not yet been initialized. Try --newdb";
- LOG(INFO) << "* ";
- return false;
- }
- return true;
-}
-
-static int
-catchup(Application::pointer app, uint32_t to, uint32_t count,
- Json::Value& catchupInfo)
-{
- if (!checkInitialized(app))
- {
- return 1;
- }
-
- auto done = false;
- app->getLedgerManager().loadLastKnownLedger(
- [&done](asio::error_code const& ec) {
- if (ec)
- {
- throw std::runtime_error(
- "Unable to restore last-known ledger state");
- }
-
- done = true;
- });
- auto& clock = app->getClock();
- while (!done && clock.crank(true))
- ;
-
- try
- {
- app->getLedgerManager().startCatchup({to, count}, true);
- }
- catch (std::invalid_argument const&)
- {
- LOG(INFO) << "*";
- LOG(INFO) << "* Target ledger " << to
- << " is not newer than last closed ledger"
- << " - nothing to do";
- LOG(INFO) << "* If you really want to catchup to " << to
- << " run stellar-core with --newdb parameter.";
- LOG(INFO) << "*";
- return 2;
- }
-
- auto& io = clock.getIOService();
- auto synced = false;
- asio::io_service::work mainWork(io);
- done = false;
- while (!done && clock.crank(true))
- {
- switch (app->getLedgerManager().getState())
- {
- case LedgerManager::LM_BOOTING_STATE:
- {
- done = true;
- break;
- }
- case LedgerManager::LM_SYNCED_STATE:
- {
- break;
- }
- case LedgerManager::LM_CATCHING_UP_STATE:
- {
- switch (app->getLedgerManager().getCatchupState())
- {
- case LedgerManager::CatchupState::WAITING_FOR_CLOSING_LEDGER:
- {
- done = true;
- synced = true;
- break;
- }
- case LedgerManager::CatchupState::NONE:
- {
- done = true;
- break;
- }
- default:
- {
- break;
- }
- }
- break;
- }
- case LedgerManager::LM_NUM_STATE:
- abort();
- }
- }
-
- LOG(INFO) << "*";
- if (synced)
- {
- LOG(INFO) << "* Catchup finished.";
- }
- else
- {
- LOG(INFO) << "* Catchup failed.";
- }
- LOG(INFO) << "*";
-
- catchupInfo = app->getJsonInfo();
- return synced ? 0 : 3;
-}
-
-static int
-catchupAt(Application::pointer app, uint32_t at, Json::Value& catchupInfo)
-{
- return catchup(app, at, 0, catchupInfo);
-}
-
-static int
-catchupComplete(Application::pointer app, Json::Value& catchupInfo)
-{
- return catchup(app, CatchupConfiguration::CURRENT,
- std::numeric_limits::max(), catchupInfo);
-}
-
-static int
-catchupRecent(Application::pointer app, uint32_t count,
- Json::Value& catchupInfo)
-{
- return catchup(app, CatchupConfiguration::CURRENT, count, catchupInfo);
-}
-
-static int
-catchupTo(Application::pointer app, uint32_t to, Json::Value& catchupInfo)
-{
- return catchup(app, to, std::numeric_limits::max(), catchupInfo);
-}
-
-static void
-writeCatchupInfo(Json::Value const& catchupInfo, std::string const& outputFile)
-{
- std::string filename = outputFile.empty() ? "-" : outputFile;
- auto content = catchupInfo.toStyledString();
-
- if (filename == "-")
- {
- LOG(INFO) << "*";
- LOG(INFO) << "* Catchup info: " << content;
- LOG(INFO) << "*";
- }
- else
- {
- std::ofstream out{};
- out.open(filename);
- out.write(content.c_str(), content.size());
- out.close();
-
- LOG(INFO) << "*";
- LOG(INFO) << "* Wrote catchup info to " << filename;
- LOG(INFO) << "*";
- }
-}
-
-static int
-reportLastHistoryCheckpoint(Config const& cfg, std::string const& outputFile)
-{
- VirtualClock clock(VirtualClock::REAL_TIME);
- Application::pointer app = Application::create(clock, cfg, false);
-
- if (!checkInitialized(app))
- {
- return 1;
- }
-
- auto state = HistoryArchiveState{};
- auto& wm = app->getWorkManager();
- auto getHistoryArchiveStateWork =
- wm.executeWork(
- "get-history-archive-state-work", state);
-
- auto ok = getHistoryArchiveStateWork->getState() == Work::WORK_SUCCESS;
- if (ok)
- {
- std::string filename = outputFile.empty() ? "-" : outputFile;
-
- if (filename == "-")
- {
- LOG(INFO) << "*";
- LOG(INFO) << "* Last history checkpoint " << state.toString();
- LOG(INFO) << "*";
- }
- else
- {
- state.save(filename);
- LOG(INFO) << "*";
- LOG(INFO) << "* Wrote last history checkpoint " << filename;
- LOG(INFO) << "*";
- }
- }
- else
- {
- LOG(INFO) << "*";
- LOG(INFO) << "* Fetching last history checkpoint failed.";
- LOG(INFO) << "*";
- }
- app->gracefulStop();
- while (clock.crank(true))
- ;
-
- return ok ? 0 : 1;
-}
-
-static uint32_t
-parseLedger(std::string const& str)
-{
- if (str == "current")
- {
- return CatchupConfiguration::CURRENT;
- }
-
- auto pos = std::size_t{0};
- auto result = std::stoul(str, &pos);
- if (pos < str.length() || result < 2)
- {
- throw std::runtime_error(
- fmt::format("{} is not a valid ledger number", str));
- }
-
- return result;
-}
-
-static uint32_t
-parseLedgerCount(std::string const& str)
-{
- auto pos = std::size_t{0};
- auto result = std::stoul(str, &pos);
- if (pos < str.length())
- {
- throw std::runtime_error(
- fmt::format("{} is not a valid ledger count", str));
- }
-
- return result;
-}
-
-static void
-setForceSCPFlag(Config const& cfg, bool isOn)
-{
- VirtualClock clock;
- Application::pointer app = Application::create(clock, cfg, false);
-
- if (checkInitialized(app))
- {
- app->getPersistentState().setState(
- PersistentState::kForceSCPOnNextLaunch, (isOn ? "true" : "false"));
- if (isOn)
- {
- LOG(INFO) << "* ";
- LOG(INFO) << "* The `force scp` flag has been set in the db.";
- LOG(INFO) << "* ";
- LOG(INFO)
- << "* The next launch will start scp from the account balances";
- LOG(INFO) << "* as they stand in the db now, without waiting to "
- "hear from";
- LOG(INFO) << "* the network.";
- LOG(INFO) << "* ";
- }
- else
- {
- LOG(INFO) << "* ";
- LOG(INFO) << "* The `force scp` flag has been cleared.";
- LOG(INFO) << "* The next launch will start normally.";
- LOG(INFO) << "* ";
- }
- }
-}
-
-static void
-showOfflineInfo(Config const& cfg)
-{
- // needs real time to display proper stats
- VirtualClock clock(VirtualClock::REAL_TIME);
- Application::pointer app = Application::create(clock, cfg, false);
- if (checkInitialized(app))
- {
- app->reportInfo();
- }
- else
- {
- LOG(INFO) << "Database is not initialized";
- }
-}
-
-static void
-loadXdr(Config const& cfg, std::string const& bucketFile)
-{
- VirtualClock clock;
- Application::pointer app = Application::create(clock, cfg, false);
- if (checkInitialized(app))
- {
- uint256 zero;
- Bucket bucket(bucketFile, zero);
- bucket.apply(*app);
- }
- else
- {
- LOG(INFO) << "Database is not initialized";
- }
-}
-
-static void
-inferQuorumAndWrite(Config const& cfg)
-{
- InferredQuorum iq;
- {
- VirtualClock clock;
- Application::pointer app = Application::create(clock, cfg, false);
- iq = app->getHistoryManager().inferQuorum();
- }
- LOG(INFO) << "Inferred quorum";
- std::cout << iq.toString(cfg) << std::endl;
-}
-
-static void
-checkQuorumIntersection(Config const& cfg)
-{
- VirtualClock clock;
- Application::pointer app = Application::create(clock, cfg, false);
- InferredQuorum iq = app->getHistoryManager().inferQuorum();
- iq.checkQuorumIntersection(cfg);
-}
-
-static void
-writeQuorumGraph(Config const& cfg, std::string const& outputFile)
-{
- InferredQuorum iq;
- {
- VirtualClock clock;
- Application::pointer app = Application::create(clock, cfg, false);
- iq = app->getHistoryManager().inferQuorum();
- }
- std::string filename = outputFile.empty() ? "-" : outputFile;
- if (filename == "-")
- {
- std::stringstream out;
- iq.writeQuorumGraph(cfg, out);
- LOG(INFO) << "*";
- LOG(INFO) << "* Quorum graph: " << out.str();
- LOG(INFO) << "*";
- }
- else
- {
- std::ofstream out(filename);
- iq.writeQuorumGraph(cfg, out);
- LOG(INFO) << "*";
- LOG(INFO) << "* Wrote quorum graph to " << filename;
- LOG(INFO) << "*";
- }
-}
-
-static void
-initializeDatabase(Config& cfg)
-{
- VirtualClock clock;
- Application::pointer app = Application::create(clock, cfg);
-
- LOG(INFO) << "*";
- LOG(INFO) << "* The next launch will catchup from the network afresh.";
- LOG(INFO) << "*";
-}
-
-static int
-initializeHistories(Config& cfg, vector newHistories)
-{
- VirtualClock clock;
- Application::pointer app = Application::create(clock, cfg, false);
-
- for (auto const& arch : newHistories)
- {
- if (!app->getHistoryArchiveManager().initializeHistoryArchive(arch))
- return 1;
- }
- return 0;
-}
-
-static int
-startApp(string cfgFile, Config& cfg)
-{
- LOG(INFO) << "Starting stellar-core " << STELLAR_CORE_VERSION;
- LOG(INFO) << "Config from " << cfgFile;
- VirtualClock clock(VirtualClock::REAL_TIME);
- Application::pointer app;
- try
- {
- app = Application::create(clock, cfg, false);
-
- if (!checkInitialized(app))
- {
- return 0;
- }
- else
- {
- if (!app->getHistoryArchiveManager().checkSensibleConfig())
- {
- return 1;
- }
- if (cfg.ARTIFICIALLY_ACCELERATE_TIME_FOR_TESTING)
- {
- LOG(WARNING) << "Artificial acceleration of time enabled "
- << "(for testing only)";
- }
+#include "main/CommandLine.h"
+#include "main/DeprecatedCommandLine.h"
+#include "util/Logging.h"
- app->start();
+#include
+#include
- app->applyCfgCommands();
- }
- }
- catch (std::exception& e)
- {
- LOG(FATAL) << "Got an exception: " << e.what();
- return 1;
- }
- auto& io = clock.getIOService();
- asio::io_service::work mainWork(io);
- while (!io.stopped())
- {
- clock.crank();
- }
- return 0;
-}
-}
+INITIALIZE_EASYLOGGINGPP
int
main(int argc, char* const* argv)
@@ -694,255 +24,11 @@ main(int argc, char* const* argv)
}
xdr::marshaling_stack_limit = 1000;
- std::string cfgFile("stellar-core.cfg");
- std::string command;
- el::Level logLevel = el::Level::Info;
- std::vector rest;
-
- optional forceSCP = nullptr;
- bool base64 = false;
- bool doCatchupAt = false;
- uint32_t catchupAtTarget = 0;
- bool doCatchupComplete = false;
- bool doCatchupRecent = false;
- uint32_t catchupRecentCount = 0;
- bool doCatchupTo = false;
- uint32_t catchupToTarget = 0;
- bool inferQuorum = false;
- bool checkQuorum = false;
- bool graphQuorum = false;
- bool newDB = false;
- bool getOfflineInfo = false;
- auto doReportLastHistoryCheckpoint = false;
- std::string outputFile;
- std::string loadXdrBucket;
- std::vector newHistories;
- std::vector metrics;
- string filetype = "auto";
-
- int opt;
- while ((opt = getopt_long_only(argc, argv, "c:", stellar_core_options,
- nullptr)) != -1)
+ auto result = handleCommandLine(argc, argv);
+ if (result)
{
- switch (opt)
- {
- case OPT_BASE64:
- base64 = true;
- break;
- case OPT_CATCHUP_AT:
- doCatchupAt = true;
- catchupAtTarget = parseLedger(optarg);
- break;
- case OPT_CATCHUP_COMPLETE:
- doCatchupComplete = true;
- break;
- case OPT_CATCHUP_RECENT:
- doCatchupRecent = true;
- catchupRecentCount = parseLedgerCount(optarg);
- break;
- case OPT_CATCHUP_TO:
- doCatchupTo = true;
- catchupToTarget = parseLedger(optarg);
- break;
- case 'c':
- case OPT_CMD:
- command = optarg;
- rest.insert(rest.begin(), argv + optind, argv + argc);
- optind = argc;
- break;
- case OPT_CONF:
- cfgFile = std::string(optarg);
- break;
- case OPT_CONVERTID:
- StrKeyUtils::logKey(std::cout, std::string(optarg));
- return 0;
- case OPT_DUMPXDR:
- dumpXdrStream(std::string(optarg));
- return 0;
- case OPT_PRINTXDR:
- printXdr(std::string(optarg), filetype, base64);
- return 0;
- case OPT_FILETYPE:
- filetype = std::string(optarg);
- break;
- case OPT_SIGNTXN:
- signtxn(std::string(optarg), base64);
- return 0;
- case OPT_SEC2PUB:
- priv2pub();
- return 0;
- case OPT_NETID:
- signtxn_network_id = optarg;
- return 0;
- case OPT_LOADXDR:
- loadXdrBucket = std::string(optarg);
- break;
- case OPT_FORCESCP:
- forceSCP = make_optional(optarg == nullptr ||
- string(optarg) == "true");
- break;
- case OPT_FUZZ:
- fuzz(std::string(optarg), logLevel, metrics);
- return 0;
- case OPT_GENFUZZ:
- genfuzz(std::string(optarg));
- return 0;
- case OPT_GENSEED:
- {
- SecretKey key = SecretKey::random();
- std::cout << "Secret seed: " << key.getStrKeySeed().value
- << std::endl;
- std::cout << "Public: " << key.getStrKeyPublic() << std::endl;
- return 0;
- }
- case OPT_INFERQUORUM:
- inferQuorum = true;
- break;
- case OPT_CHECKQUORUM:
- checkQuorum = true;
- break;
- case OPT_GRAPHQUORUM:
- graphQuorum = true;
- break;
- case OPT_OFFLINEINFO:
- getOfflineInfo = true;
- break;
- case OPT_OUTPUT_FILE:
- outputFile = optarg;
- break;
- case OPT_LOGLEVEL:
- logLevel = Logging::getLLfromString(std::string(optarg));
- break;
- case OPT_METRIC:
- metrics.push_back(std::string(optarg));
- break;
- case OPT_NEWDB:
- newDB = true;
- break;
- case OPT_NEWHIST:
- newHistories.push_back(std::string(optarg));
- break;
- case OPT_REPORT_LAST_HISTORY_CHECKPOINT:
- doReportLastHistoryCheckpoint = true;
- break;
- case OPT_TEST:
- {
- rest.push_back(*argv);
- rest.insert(++rest.begin(), argv + optind, argv + argc);
- return test(static_cast(rest.size()), &rest[0], logLevel,
- metrics);
- }
- case OPT_VERSION:
- std::cout << STELLAR_CORE_VERSION << std::endl;
- return 0;
- case OPT_HELP:
- default:
- usage(0);
- return 0;
- }
+ return *result;
}
- Config cfg;
- try
- {
- // yes you really have to do this 3 times
- Logging::setLogLevel(logLevel, nullptr);
- if (cfgFile == "-" || fs::exists(cfgFile))
- {
- cfg.load(cfgFile);
- }
- else
- {
- std::string s;
- s = "No config file ";
- s += cfgFile + " found";
- throw std::invalid_argument(s);
- }
- Logging::setFmt(KeyUtils::toShortString(cfg.NODE_SEED.getPublicKey()));
- Logging::setLogLevel(logLevel, nullptr);
-
- if (command.size())
- {
- sendCommand(command, rest, cfg.HTTP_PORT);
- return 0;
- }
-
- // don't log to file if just sending a command
- if (cfg.LOG_FILE_PATH.size())
- Logging::setLoggingToFile(cfg.LOG_FILE_PATH);
- Logging::setLogLevel(logLevel, nullptr);
-
- cfg.REPORT_METRICS = metrics;
-
- if (forceSCP || newDB || getOfflineInfo || !loadXdrBucket.empty() ||
- inferQuorum || graphQuorum || checkQuorum || doCatchupAt ||
- doCatchupComplete || doCatchupRecent || doCatchupTo ||
- doReportLastHistoryCheckpoint)
- {
- auto result = 0;
- setNoListen(cfg);
- if (newDB)
- initializeDatabase(cfg);
- if ((result == 0) && (doCatchupAt || doCatchupComplete ||
- doCatchupRecent || doCatchupTo))
- {
- Json::Value catchupInfo;
- VirtualClock clock(VirtualClock::REAL_TIME);
- auto app = Application::create(clock, cfg, false);
- // set known cursors before starting maintenance job
- ExternalQueue ps(*app);
- ps.setInitialCursors(cfg.KNOWN_CURSORS);
- app->getMaintainer().start();
- if (doCatchupAt)
- result = catchupAt(app, catchupAtTarget, catchupInfo);
- if ((result == 0) && doCatchupComplete)
- result = catchupComplete(app, catchupInfo);
- if ((result == 0) && doCatchupRecent)
- result =
- catchupRecent(app, catchupRecentCount, catchupInfo);
- if ((result == 0) && doCatchupTo)
- result = catchupTo(app, catchupToTarget, catchupInfo);
- app->gracefulStop();
- while (app->getClock().crank(true))
- ;
- if (!catchupInfo.isNull())
- writeCatchupInfo(catchupInfo, outputFile);
- }
- if ((result == 0) && forceSCP)
- setForceSCPFlag(cfg, *forceSCP);
- if ((result == 0) && getOfflineInfo)
- showOfflineInfo(cfg);
- if ((result == 0) && doReportLastHistoryCheckpoint)
- result = reportLastHistoryCheckpoint(cfg, outputFile);
- if ((result == 0) && !loadXdrBucket.empty())
- loadXdr(cfg, loadXdrBucket);
- if ((result == 0) && inferQuorum)
- inferQuorumAndWrite(cfg);
- if ((result == 0) && checkQuorum)
- checkQuorumIntersection(cfg);
- if ((result == 0) && graphQuorum)
- writeQuorumGraph(cfg, outputFile);
- return result;
- }
- else if (!newHistories.empty())
- {
- setNoListen(cfg);
- return initializeHistories(cfg, newHistories);
- }
-
- if (cfg.MANUAL_CLOSE)
- {
- // in manual close mode, we set FORCE_SCP
- // so that the node starts fully in sync
- // (this is to avoid to force scp all the time when testing)
- cfg.FORCE_SCP = true;
- }
- }
- catch (std::exception& e)
- {
- LOG(FATAL) << "Got an exception: " << e.what();
- return 1;
- }
- // run outside of catch block so that we properly capture crashes
- return startApp(cfgFile, cfg);
+ return handleDeprecatedCommandLine(argc, argv);
}
diff --git a/src/test/test.cpp b/src/test/test.cpp
index d5b472877b..f8d2894bef 100644
--- a/src/test/test.cpp
+++ b/src/test/test.cpp
@@ -191,6 +191,76 @@ test(int argc, char* const* argv, el::Level ll,
return r;
}
+int
+runTest(CommandLineArgs const& args)
+{
+ el::Level logLevel{el::Level::Info};
+
+ Catch::Session session{};
+
+ auto parser = session.cli();
+ parser |= Catch::clara::Opt(
+ [&](std::string const& arg) {
+ logLevel = Logging::getLLfromString(arg);
+ },
+ "LEVEL")["--ll"]("set the log level");
+ parser |= Catch::clara::Opt(gTestMetrics, "METRIC-NAME")["--metric"](
+ "report metric METRIC-NAME on exit");
+ parser |= Catch::clara::Opt(gTestAllVersions)["--all-versions"](
+ "test all versions");
+ parser |= Catch::clara::Opt(gVersionsToTest, "version")["--version"](
+ "test specific version(s)");
+ parser |= Catch::clara::Opt(gBaseInstance, "offset")["--base-instance"](
+ "instance number offset so multiple instances of "
+ "stellar-core can run tests concurrently");
+ session.cli(parser);
+
+ auto result = session.cli().parse(
+ args.mCommandName, Catch::clara::detail::TokenStream{
+ std::begin(args.mArgs), std::end(args.mArgs)});
+ if (!result)
+ {
+ writeWithTextFlow(std::cerr, result.errorMessage());
+ writeWithTextFlow(std::cerr, args.mCommandDescription);
+ session.cli().writeToStream(std::cerr);
+ return 1;
+ }
+
+ if (session.configData().showHelp)
+ {
+ writeWithTextFlow(std::cout, args.mCommandDescription);
+ session.cli().writeToStream(std::cout);
+ return 0;
+ }
+
+ if (session.configData().libIdentify)
+ {
+ session.libIdentify();
+ return 0;
+ }
+
+ // Note: Have to setLogLevel twice here to ensure --list-test-names-only is
+ // not mixed with stellar-core logging.
+ Logging::setFmt("");
+ Logging::setLogLevel(logLevel, nullptr);
+ Config const& cfg = getTestConfig();
+ Logging::setLoggingToFile(cfg.LOG_FILE_PATH);
+ Logging::setLogLevel(logLevel, nullptr);
+
+ LOG(INFO) << "Testing stellar-core " << STELLAR_CORE_VERSION;
+ LOG(INFO) << "Logging to " << cfg.LOG_FILE_PATH;
+
+ if (gVersionsToTest.empty())
+ {
+ gVersionsToTest.emplace_back(Config::CURRENT_LEDGER_PROTOCOL_VERSION);
+ }
+
+ auto r = session.run();
+ gTestRoots.clear();
+ gTestCfg->clear();
+ return r;
+}
+
void
for_versions_to(uint32 to, Application& app, std::function const& f)
{
diff --git a/src/test/test.h b/src/test/test.h
index 1c58bd26dd..fb6ffd17d6 100644
--- a/src/test/test.h
+++ b/src/test/test.h
@@ -4,6 +4,7 @@
// under the Apache License, Version 2.0. See the COPYING file at the root
// of this distribution or at http://www.apache.org/licenses/LICENSE-2.0
+#include "main/CommandLine.h"
#include "main/Config.h"
#include "test/TestPrinter.h"
#include "util/Logging.h"
@@ -13,11 +14,14 @@ namespace stellar
class Application;
class Config;
+struct CommandLineArgs;
Config const& getTestConfig(int instanceNumber = 0,
Config::TestDbMode mode = Config::TESTDB_DEFAULT);
+
int test(int argc, char* const* argv, el::Level logLevel,
std::vector const& metrics);
+int runTest(CommandLineArgs const& args);
extern bool force_sqlite;