Skip to content
This repository has been archived by the owner on Jan 11, 2024. It is now read-only.

Command Family #6

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
304 changes: 304 additions & 0 deletions text/0000-command-family.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,304 @@
* Command Family
* Start Date: YYYY-MM-DD
* RFC PR: (leave this empty)
* Transact Issue: (leave this empty)

# Summary
[summary]: #summary

The Command Family is a transaction family that can exercise all of the
operations provided on the TransactionContext in addition to other actions, such
as sleeping or marking the transaction as invalid. This family will be used for
creating a wide variety of tests, both for correctness and performance of the
Transact library.

# Motivation
[motivation]: #motivation

This family will be used for creating a wide variety of tests, both for
correctness and performance.

# Guide-level explanation
[guide-level-explanation]: #guide-level-explanation

The Command family is a Transact transaction family that provides the following
operations, based on operations defined in its transaction payloads. These
operations, or commands, cover the following actions:

* Read state entries
* Write state entries
* Add an event to the receipt
* Add arbitrary receipt data
* "Return" InvalidTransaction
* "Return" InternalError
* Sleep for a specified duration

The above operations provide test coverage of all the possible behaviours of a
transaction handler.

Additionally, a Command Family workload tool will be provided to generate a
stream of command family transactions. The transactions themselves will be
described by a yaml file declaration. The tool will accept arguments to adjust
the rates, domain of arguments and random seed of a given workload run.

# Reference-level explanation
[reference-level-explanation]: #reference-level-explanation

## Transaction Payload

The transaction payload for the command family will be as follows:

```protobuf
// A transaction payload for the Command Family
message CommandPayload {
// The list of commands to execute
repeated Command commands = 1;
}

// A key-value pair where the value is a bytes field.
message BytesEntry {
// The key, for instance a state address or a string value, depending on
// the target of the entry.
string key = 1;
// The byte value to be stored
bytes value = 2;
}

// The list of state keys to be retrieved via a GET_STATE command.
message GetState {
// a non-empty list of valid state keys
repeated string state_keys = 1;
}

// The list of state entries to be set via a SET_STATE command.
message SetState {
// a non-empty list of BytesEntry instances, where the key field is a
// valid state key.
repeated BytesEntry state_writes = 1;
}

// The list of state keys to be deleted via a DELETE_STATE command.
message DeleteState {
// a non-empty list of valid state keys
repeated string state_keys = 1;
}

// Describes an event to add to the receipt via an ADD_EVENT command.
message AddEvent {
// The event type
string type = 1;
// The attribute entries
repeated BytesEntry attributes = 2;
// Arbitrary opaque event data
bytes data = 3;
}

// The opaque byte data to add to the receipt via an ADD_RECEIPT_DATA
// command.
message AddReceiptData {
// a non-empty byte array
bytes receipt_data = 1;
}

// The duration and method of sleep to apply during a SLEEP command.
message Sleep {
enum SleepType {
// This will wait, releasing cpu utilization to other processes
WAIT = 0;
// This will consume a cpu core for the specified time
BUSY_WAIT = 1;
}

// The duration to sleep, in milliseconds
u32 duration_millis = 1;
SleepType sleep_type = 2;
}

// The details to return on an RETURN_INVALID command.
message ReturnInvalid {
// An optional error message
string error_message = 1;
}

// The details to return on an RETURN_INTERNAL_ERROR command.
message ReturnInternalError {
// An optional error message
string error_message = 1;
}

// A command to be executed
message Command {
enum CommandType {
UNSET_COMMAND_TYPE = 0;
GET_STATE = 1;
SET_STATE = 2;
DELETE_STATE = 3;
ADD_EVENT = 4;
ADD_RECEIPT_DATA = 5;
SLEEP = 6;
RETURN_INVALID = 7;
RETURN_INTERNAL_ERROR = 8;
}

// The type of the command, which indicates which of the following
// fields will be set.
CommandType type = 1;

GetState get_state = 2;
SetState set_state = 3;
DeleteState delete_state = 4;
AddEvent add_event = 5;
AddReceiptData add_receipt_data = 6;
Sleep sleep = 7;
ReturnInvalid return_invalid = 8;
ReturnInternalError return_internal_error = 9;
}
```

## Command Execution

A Command Family transaction consists of an ordered list of Commands. The
commands are executed in order and only if the preceding command was successful.
Commands which return an explicit result (invalid or internal error) will
short-circuit the remaining commands in the list.

Each command performs a single operation, from the perspective of
TransactionContext, or from the result returned. The sleep command is currently
an exception, in that the transaction handler will pause for the specified
duration before proceeding to the next command, if any.

### Get State

The `GET_STATE` command will use the `state_keys` field of the `GetState`
message to request a read of the addresses specified. This will make use of the
`TransactionContext`'s `get_state_entries` method. In other words, it will only
make a single call to the context.

Each entry in the `state_keys` field must be a valid state key (for example, a
valid address in a Sawtooth merkle trie).

Note, no validation will occur if the requested state value is actually

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this only to ensure get_state_entries() is working as expected without any panics?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing values do not imply an invalid transaction, though. That's a business logic decision.

returned, nor what the entry should or shouldn't contain.

### Set State

The `SET_STATE` command will use the `state_writes` field of the `SetState`
message to request a write of the bytes entries specified. This will make use
of the `TransactionContext`'s `set_state_entries` method, where the keys and
values are specified by `BytesEntry` messages. In other words, it will only make
a single call to the context.

The key in the `BytesEntry` must be a valid state key (for example, a valid
address in a Sawtooth merkle trie).

### Delete State

The `DELETE_STATE` command will use the `state_keys` field of the `DeleteState`
message to request a read of the addresses specified. This will make use of the
`TransactionContext`'s `delete_state_entries` method. In other words, it will
only make a single call to the context.

Each entry in the `state_keys` field must be a valid state key (for example, a

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about valid but non-existing key case, so that negative scenario is tested against the Transact read? Or is that scenario left to "force invalid transaction" case?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

valid, but non-existing keys is not a invalid transaction.

valid address in a Sawtooth merkle trie).

### Add Event

The `ADD_EVENT` command will use the event field of the `AddEvent` message to
add an event to the transaction receipt. This will make use of the
`TransactionContext`'s `add_event` method, using the contents of the `Event`
message contained in the event field to set the matching parameters.

The Event type field must not be empty. The remaining fields, attributes and
data, may be empty.

### Add Receipt Data

The `ADD_RECEIPT_DATA` command will use the `receipt_data` field of the
`AddReceiptData` message to add this data to the transaction receipt. This will
make use of the `TransactionContext`'s `add_receipt_data` method.

The `receipt_data` field must not be empty.

### Sleep

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I failed to get understanding of why this feature is required or how is this used. Could you please add an example case?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The value here is for testing timing aspects of the overall system - namely parallel scheduling.


The `SLEEP` command will use the `duration_millis` field of the `Sleep` message
in order to delay the execution of the transaction. This field is a millisecond
value. The `SleepType` indicates whether the transaction handler should
busy-wait (i.e. consume CPU resources) or standard wait.

The provided sleep duration must not be zero.

### Return Invalid Transaction

The `RETURN_INVALID` command will force the return of an `InvalidTransaction`
result from the transaction handler. It will use the `error_message` field in
the ReturnInvalid message to provide arbitrary feedback. Any remaining commands
will be ignored.

The `error_message` field may be empty.

### Return Internal Error

The `RETURN_INTERNAL_ERROR` command will force the return of an `InternalError`
result from the transaction handler. It will use the `error_message` field in
the `ReturnInternalError` message to provide arbitrary feedback. Any remaining
commands will be ignored.

The `error_message` field may be empty.

## Transaction Header

The transaction header is a standard header in that it will include the family
name and the family version as normal.

family_name: "command"
family_version: "0.1"

In a Sawtooth context, there is an additional field for namespaces. For the
command family, the namespace should be "any" (i.e. `*`). The command family
should be allowed to read and write any value from/to state.

Alternatively, the namespace should be set to the first 6 char of the SHA-512
hash of the family name, "command". Address values used in `GET`-, `SET`- or
`DELETE_STATE` commands would have to be required to be under that namespace.

## Workload Generator

The workload generator for the command family will generate a stream of batches,
consisting of one or more command transactions. These transactions generated by
the stream will be described declaratively using a yaml file.

The declarations should describe the commands to be used, with ranges of values
that may be generated.

# Drawbacks
[drawbacks]: #drawbacks

None.

# Rationale and alternatives
[alternatives]: #alternatives

This family is designed to improve the overall testing of the Transact library.
It provides coverage of the operations available to a transaction handler and
therefore improves overall testing of the Transact library.

Alternatively, individual transaction families could be created for specific
test circumstances, using the transaction order as a way to implement operation
order. This approach has the side-effect of operations potentially occurring in
an unpredictable order, depending on the scheduler implementation. Likewise, it
would not allow for testing multiple operations before failing the transaction.

# Prior art
[prior-art]: #prior-art

The Command family builds on existing Sawtooth example and test-related
transaction families, such as IntKey and Smallbank. Both of these exercise
subsets of the operations on the TransactionContext, but not in a broad sense.
Likewise, the workload generation for these families is mainly focused on batch
throughput, and not focused tests on the effects of certain operations.

# Unresolved questions
[unresolved]: #unresolved-questions

None.