Little utility to execute interoperability tests between SRI and other Sv2 complaint software.
- Stop any
bitcoindregtest processes running (themessage-generatorstarts it for you). - In
test.jsonspecifybitcoind,bitcoin-cli, anddatadirpaths:
...
"setup_commands": [
{
"command": "PATH TO bitcoind",
"args": ["--regtest", "--datadir=PATH TO bitcoin datadir"],
...
{
"command": "PATH TO bitcoin-cli",
"args": [
"--regtest",
"--datadir=PATH TO bitcoin datadir",
...
% cargo run test.json- If the test is in the
/test/message-geneatordirectory, you have to lauch it from the MG directory using relative path. For example,
cargo run ../../test/message-generator/test/pool-sri-test-1-standard.json
The message generator executes a test with the following steps:
- Setup Commands: Executes shell commands or bash scripts to be run on start up, e.g. a
bitcoinda node. - Role Connection: Setups up one or two TCP connections, both plain and noise are supported, e.g. a connection to an Upstream role.
- Execution Commands: Executes shell commands or bash scripts to be run after a connection has been opened between two roles, e.g. a mocked Pool to a test Proxy.
- Actions: Executes the actions using the previously opened connection. If a SV2 test is executing, each action
sends zero or more
Sv2Framesand checks if the received frame satisfies the conditions (defined in the action). If a SV1 test is executing, each action sends one or more JSON-RPC Requests and checks if the received Response satisfies the conditions. More than one condition can be defined if more than one message is expected to be received. - Cleanup Commands: Executes shell commands or bash scripts to be run on test completion, e.g. remove a
bitcoinddatadir.
The true tests are located in test/message-generator/test. Some files have the structure of a
test but in fact they are not.
For example, the file in
test/message-generator/mock are mocks of application, namely they are tests whose purpose is
to pretend to be an SV2 role. Currently only the TemplateProvider and the JobDeclarator are the
only roles mocked. These mocks are usually used in the true tests. For example, in the test
test/message-generator/test/pool-sri-test-standard-1.json
the pool has a mocked environment, i.e. the JD and TP mocks.
The files in test/message-generator/messages also are not true tests, but the are intended to be
modules. For example, the common_messages.json is the module that contains the frame builders of
the common messages for the true tests and the mocks.
True tests are made to be run and produce positive outcome. Mocks and common messages are meant to work as a part of a true test and they are not supposed to be run as standalone.
Tests are written in json and must follow the below format:
version is a string that indicates if the test is a SV1 test or a SV2 test. It can be either "1" or "2".
doc is an array of strings that explain what the test do. This field is optional.
{
"doc": [
"This test do:",
"1) launch bitcoind in regtest and wait for TP initialization",
"2) create a bunch of block using bictoin-cli",
"..."
]
}common_messages is an array of messages (defined below) belonging to the common (sub)protocol,
where the common subprotocol is composed by: SetupConnection and SetupConnectionSuccees and SetupConnectionError.
This field is optional.
mining_messages is an array of messages (defined below) belonging to the mining (sub)protocol. This field is optional.
job_declaration_messages is an array of messages (defined below) that belongs to the job declaration (sub)protocol. This field is optional.
template_distribution_messages is an array of messages (defined below) that belongs to the template distribution (sub)protocol. This field is optional.
sv1_messages is an array of messages that belongs to the SV1 protocol. If version is "2" this field should be empty.
Definition of messages mentioned above
A message is an object with two field message and id.
The message field contains the actual message. The id field contains a unique id
used to refer to the message in other part of the test.
For SV2 messages, the message field is an object composed by:
- type: message type name as defined in the Sv2 spec is a string eg
SetupConnection - all the other field of that specific message as defined in the Sv2 spec with the only exception
of the field
protocolthat is a string and not a number eg instead of0we haveMiningProtocol
{
"common_messages": [
{
"message": {
"type": "SetupConnection",
"protocol": "MiningProtocol",
"min_version": 2,
"max_version": 2,
"flags": 0,
"endpoint_host": "",
"endpoint_port": 0,
"vendor": "",
"hardware_version": "",
"firmware": "",
"device_id": ""
},
"id": "setup_connection"
}
]
}For SV1 messages, the message field is a JSON-RPC StandardRequest, composed by the fields id, method and params.
{
"sv1_messages": [
{
"message": {
"id": 1,
"method": "mining.subscribe",
"params": ["cpuminer"]
},
"id": "mining.subscribe"
},
{
"message": {
"id": 2,
"method": "mining.authorize",
"params": ["username", "password"]
},
"id": "mining.authorize"
}
]
}Objects in frame_builders are used by the message generator to construct Sv2 frames in order to send the message
to the tested software. Objects in frame_builders can be either automatic (where the sv2 frame header is
constructed by the SRI libs and is supposed to be correct) or manual (if we want to test a software
against an incorrect frame).
frame_builders is an array of objects. Every object in frame_builders must contain message_id, that is a
string with the id of the previously defined message. In the example below, the message id refers to
the item setup_connection of common_messages. Every object in frames must have the
field type, a string that can be either automatic or manual with meaning of the paragraph
above.
If type == manual the object must contain 3 additional fields:
message_type: a string the must start with0xfollowed by an hex encoded integer not bigger than0xffextension_type: a string composed by 16 elements, each element must be either0or1, the elements can be separated by_that is not counted as element so we can have as many separator as we want eg:0000_0000_0000_0000Separator are there only to human readability and are removed when the test is parsed.channel_msg: abool
{
"frame_builders": [
{
"type": "automatic",
"message_id": "setup_connection"
},
{
"type": "manual",
"message_id": "close_channel",
"message_type": "0x18",
"extension_type": "0000_0000_0000_0000",
"channel_msg": true
}
]
}If the frame relative to common messages is defined is a different file (for example, some
common_messages frames are defined in /test/message-geneator/messages/common_messages.json), to
use it you have to use the syntax <address::id>. For example, in the test
/test/message/generator/test, the following message
{
"type": "automatic",
"message_id": "test/message-generator/messages/common_messages.json::setup_connection_success_template_distribution"
}
calls the id setup_connection_success_template_distribution that appears in the file
test/message-generator/messages/common_messages.json. In the main file, the id of this
message will be the abbreviated with setup_connection_success_template_distribution.
actions is an array of objects. If the test version is "2", each object is composed by:
role: can be eitherclientorserver. This because the message generator can act at the same time as a client and as a server for example when we are mocking a proxy.messages_ids: an array of strings, that are ids of messages previously defined.results: is an array of objects, used by the message generator to test if certain property of the received frames are true or not. Accepts values:- "type": String - match option - match_message_type, match_message_field, match_message_len, or match_extension_type
- "value": Array - varries depending on "type"
{
"actions": [
{
"message_ids": ["open_standard_mining_channel"],
"role": "client",
"results": [
{
"type": "match_message_field",
"value": [
"MiningProtocol",
"OpenStandardMiningChannelSuccess",
[
"request_id",
{"U32": 89}
]
]
}
]
}
]
}If the test version is "1", each object is composed by:
messages_ids: an array of strings, that are ids of sv1_messages previously defined.results: is an array of objects, used by the message generator to test if certain property of the received Responses are true or not. Accepts values:- "type": String - match option - match_message_id, match_message_field
- "value": Array - varries depending on "type"
An array of commands (defined below) that are executed before that the message generator open one ore more connection with the tested software.
An array of commands (defined below) that are executed after that the message generator open one ore more connection with the tested software.
An array of commands (defined below) that are executed after that all the actions have been executed. TODO this commands should be executed not only if all the action pass but also if something fail
Definition of commands mentioned above
A command is an object with the following fields:
command: the bash command that we want to executeargs: the command's argscondition: can be eitherWithConditionsorNonein the first case the command executor will wait for some condition before return in the latter it just launch the command and return
WithConditions is an object composed by the following fields:
conditions: an array of object that must be true or false on order for the executor to returntimer_secs: number of seconds after then the command is considered stuck and the test failwarn_no_panic:boolif true the test do not fail with panic if timer secs terminate but it just exit (passing) and emit a warning
The objects contained in conditions are structured in the following way:
output_string: a string that we need to check in the StdOut or StdErroutput_location: say if the string is expected in StdOut or StdErrcondition: aboolif true and we haveoutput_stringinoutput_locationthe executor return and keep going, if false the executor fail.
In the below example we launch bitcoind we wait for "sv2 thread start" in StdOut and we fail if
anything is written in StdErr
{
"setup_commands": [
{
"command": "./test/bitcoind",
"args": ["--regtest", "--datadir=./test/bitcoin_data/"],
"conditions": {
"WithConditions": {
"conditions": [
{
"output_string": "sv2 thread start",
"output_location": "StdOut",
"condition": true
},
{
"output_string": "",
"output_location": "StdErr",
"condition": false
}
],
"timer_secs": 10,
"warn_no_panic": false
}
}
}
]
}role is a string and can be on of the three: client server proxy
- If we have client we expect to have a
downstreamfield - If we have server we expect to have an
upstreamfield - If we have proxy we expect to have both
downstream is an object the contain the info need to open a connection as a downstream is composed by:
ip: a string with the upstream ip addressport: a string with the port addresspub_key: optional if present we open a noise connection if no we open a plain connection, is a string with the server public key
upstream is an object the contain the info need to start listening:
ip: a string with the ip where we will accept connectionport: a string with the port addresspub_key: optional if present accept noise connection if no plain connectionsecret_key: optional if present accept noise connection if no plain connection
Information on installation and use of llvm-cov found here: https://crates.io/crates/cargo-llvm-cov/0.1.13 More information on underlying dependancies here: https://doc.rust-lang.org/rustc/instrument-coverage.html
Example of llvm-cov code coverage run with pool setup_command llvm-cov requires a minimum of rustc 1.60.0 and LLVM 13.0.0
{
"command": "cargo",
"args": [
"llvm-cov",
"--no-report",
"run",
"-p",
"pool",
"--",
"-c",
"./roles/v2/pool/pool-config.toml"
],
"conditions": {...}
},"--no-report" caches the results in the background until the end of the test. It also allows you collect coverage data from other processes in parallel by adding "llv-cov --no-report", and all report data is reflected in the final report.
To generate the report, execute the llvm-cov report command in the cleanup_commands. This example adds an output file path(from project root), the output file name and type, and paths to ignore in the report.
"cleanup_commands": [
{
"command": "cargo",
"args": [
"llvm-cov",
"--ignore-filename-regex",
"utils/|experimental/|protocols/",
"--output-path",
"target/tmp/pool_translator_cov.txt",
"report"
],
"conditions": "None"
}
]