README
¶
Constructor
The constructor package is used for coordinating the construction
and broadcast of transactions on any blockchain that implements the
Rosetta API. It was designed to power automated Construction API
testing in the rosetta-cli (check:construction)
but could be useful for anyone building a Rosetta API wallet.
Framework
When first learning about a new topic, it is often useful to understand the
hierarchy of concerns. In the constructor, this "hierarchy" is as follows:
Workflows -> Jobs
Scenarios
Actions
Workflows contain collections of Scenarios to execute. Scenarios are
executed atomically in database transactions (rolled back if execution fails)
and culminate in an optional broadcast. This means that a single Workflow
could contain multiple broadcasts (which can be useful for orchestrating
staking-related transactions that affect a single account).
To perform a Workflow, we create a Job. This Job has a unique identifier
and stores state for all Scenarios in the Workflow. State is shared across
an entire Job so Actions in a Scenario can access the output of Actions
in other Scenarios. The syntax for accessing this shared state can be found
here.
Actions are discrete operations that can be performed in the context of a
Scenario. A full list of all Actions that can be performed can be found
here.
If you have suggestions for more actions, please
open an issue in rosetta-sdk-go!
Broadcast Invocation
If you'd like to broadcast a transaction at the end of a Scenario,
you must populate the following fields:
<scenario>.network<scenario>.operations<scenario>.confirmation_depth(allows for stake-related transactions to complete before marking as a success)
Optionally, you can populate the following field:
<scenario>.preprocess_metadata
Once a transaction is confirmed on-chain (after the provided
<scenario>.confirmation_depth, it is stored by the tester at
<scenario>.transaction for access by other Scenarios in the same Job.
Dry Runs
In UTXO-based blockchains, it may be necessary to amend the operations stored
in <scenario>.operations based on the suggested_fee returned in
/construction/metadata. The constructor supports running a "dry run" of
a transaction broadcast if you set the follow field:
<scenario>.dry_run = true
The suggested fee will then be stored as <scenario>.suggested_fee for use by
other Scenarios in the same Job. You can find an example of this in the
Ethereum configuration.
If this field is not populated or set to false, the transaction
will be constructed, signed, and broadcast.
Using with rosetta-cli
If you use the constructor for automated Construction API testing (without prefunded
accounts), you MUST implement 2 required Workflows:
create_accountrequest_funds
If you don't implement these 2 Workflows, processing could stall.
Please note that create_account can contain a transaction broadcast if
on-chain origination is required for new accounts on your blockchain.
If you plan to run the constructor in CI, you may wish to
provide prefunded accounts
when running the tester (otherwise you would need to manually fund generated
accounts).
Optionally, you can also provide a return_funds workflow that will be invoked
when exiting check:construction. This can be useful in CI when you want to return
all funds to a single accout or faucet (instead of black-holing them in all the addresses
created during testing).
Writing Workflows
It is possible to write Workflows from scratch using JSON, however, it is
highly recommended to use the Rosetta Constructor DSL. You can
see an example of how these two approaches compare below:
Without DSL
[
{
"name": "request_funds",
"concurrency": 1,
"scenarios": [
{
"name": "find_account",
"actions": [
{
"input": "{\"symbol\":\"tBTC\", \"decimals\":8}",
"type": "set_variable",
"output_path": "currency"
},
{
"input": "{\"minimum_balance\":{\"value\": \"0\", \"currency\": {{currency}}}, \"create_limit\":1}",
"type": "find_balance",
"output_path": "random_account"
}
]
},
{
"name": "request",
"actions": [
{
"type": "load_env",
"output_path": "min_balance",
"input": "MIN_BALANCE"
},
{
"type": "math",
"ouput_path": "adjusted_min",
"input": "{\"operation\":\"addition\", \"left_value\": {{min_balance}}, \"right_value\": \"600\"}"
},
{
"input": "{\"account_identifier\": {{random_account.account_identifier}}, \"minimum_balance\":{\"value\": {{adjusted_min}}, \"currency\": {{currency}}}}",
"type": "find_balance",
"output_path": "loaded_account"
}
]
}
]
},
{
"name": "create_account",
"concurrency": 1,
"scenarios": [
{
"name": "create_account",
"actions": [
{
"input": "{\"network\":\"Testnet3\", \"blockchain\":\"Bitcoin\"}",
"type": "set_variable",
"output_path": "network"
},
{
"input": "{\"curve_type\": \"secp256k1\"}",
"type": "generate_key",
"output_path": "key"
},
{
"input": "{\"network_identifier\": {{network}}, \"public_key\": {{key.public_key}}}",
"type": "derive",
"output_path": "account"
},
{
"input": "{\"account_identifier\": {{account.account_identifier}}, \"keypair\": {{key}}}",
"type": "save_account"
}
]
}
]
}
]
With DSL
request_funds(1){
find_account{
currency = {
"symbol":"tBTC",
"decimals":8
};
random_account = find_balance({
"minimum_balance":{
"value": "0",
"currency": {{currency}}
},
"create_limit":1
});
},
request{
min_balance = load_env("MIN_BALANCE");
adjusted_min = {{min_balance}} + 600;
loaded_account = find_balance({
"account_identifier": {{random_account.account_identifier}},
"minimum_balance":{
"value": {{adjusted_min}},
"currency": {{currency}}
}
});
}
}
create_account(1){
create_account{
network = {"network":"Testnet3", "blockchain":"Bitcoin"};
key = generate_key({"curve_type":"secp256k1"});
account = derive({
"network_identifier": {{network}},
"public_key": {{key.public_key}}
});
save_account({
"account_identifier": {{account.account_identifier}},
"keypair": {{key}}
});
}
}
Future Work
- Create a
walletpackage that usesWorkflowsas core logic- Requests made to the
walletcould be injected into theWorkflowstate before starting execution to enable user-provided parameters
- Requests made to the
- Support the creation of modular functions that can be reused across
Workflows- The functions would likely maintain their own state and just require
some collection of inputs to be defined when they start execution and allow
for mapping results back to the
Workflowstate when finished.
- The functions would likely maintain their own state and just require
some collection of inputs to be defined when they start execution and allow
for mapping results back to the
- Develop a testing suite that allows
Workflowwriters to test their scripts- Often times, the development cycle for working with
Workflowsis to write and then test on a live network. It should be possible to mockActionsand ensure a set ofOperationsare created.
- Often times, the development cycle for working with