Injecting Batches and On-Chain Block Validation Rules¶
Injecting transactions into blocks by the validator is required to support a variety of use cases such as:
- Setting information in state for use by transaction processors that cannot reasonably be provided with every transaction. For example, setting the block number or timestamp.
- Automatically submitting transactions in response to a particular state. For example, submitting bond quote matching transactions.
- Testing automation. For example, programming the compute time of test transactions to increase after a certain number of batches.
Imposing a set of rules on the relationships between transactions, or inter-transaction validation, is also required for some use cases. Two examples of inter-transaction validation would be:
- Only N of transaction type X may be included in a block.
- Transaction type X may only occur at position Y in a block.
The BatchInjector class supports injecting transactions into blocks:
interface BatchInjector: // Called when a new block is created and before any batches are added. A list of // batches to insert at the beginning of the block must be returned. A StateView // is provided for inspecting state as of the previous block. block_start(string previous_block_id) -> list<Batch> // Called before inserting the incoming batch into the pending queue for the // given block. A list of batches to insert before this batch must be returned. before_batch(string previous_block_id, Batch batch) -> list<Batch> // Called after inserting the incoming batch into the pending queue for the // given block. A list of batches to insert after this batch must be returned. after_batch(string previous_block_id, Batch batch) -> list<Batch> // Called just before finalizing and completing a Block. An ordered list of batches // that will be committed in the block is passed in. A list of batches to insert at // the end of the block must be returned. block_end(string previous_block_id, list<Batch> batches) -> list<Batch>
The Journal will call each of the methods at the appropriate times for all included BatchInjectors, thus injecting batches into the new block.
The set of BatchInjectors to load is configured with an on-chain setting; this is similar to configuring the consensus module loaded by the validator. The sawtooth.validator.batch_injectors setting key stores a comma-separated list of batch injectors to load. This list is parsed by the validator at the beginning of block publishing for each block and the appropriate injectors are loaded.
This setting is controlled using the existing Settings Transaction Family. Take care when when updating this setting, because an incorrect value may cause transaction families to behave incorrectly.
On-Chain Validation Rules¶
An on-chain setting holds a set of validation rules that are enforced for each block. On-chain validation rules will be stored as a string in the setting key sawtooth.validator.block_validation_rules. Rules will be enforced by the block validator.
A simple syntax will be used for specifying validations rules as defined by the following.
- A validation rule consists of a name followed by a colon and a comma-separated
list of arguments:
- Separate multiple rules with semicolons:
- Spaces, tabs, and newlines are ignored.
The following rules are defined:
- Only N transaction of transaction type X may be included in a block. The first
argument must be an integer. The second argument is the name of a transaction
family. For example, the string
NofX:2,intkeymeans allow only two IntegerKey transactions per block.
- A transaction of type X must be in the block at position Y. The first argument
is interpreted as the name of a transaction family. The second argument must
be interpretable as an integer and defines the index of the transaction in
the block that must be checked. Negative numbers can be used and count
backwards from the last transaction in the block. The first transaction in the
block has index 0. The last transaction in the block has index -1. If abs(Y)
is larger than the number of transactions per block, then there would not be
a transaction of type X at Y and the block would be invalid. For example, the
XatY:intkey,0means the first transaction in the block must be an IntegerKey transaction.
- A transaction must be signed by the same key as the block. This rule takes a list of transaction indices in the block and enforces the rule on each. This rule is useful in combination with the other rules to ensure a client is not submitting transactions that should only be injected by the winning validator.
The BlockInfoInjector inserts a BlockInfo transaction at the beginning of every block. The transaction updates state with information about the block that was just committed as well as a timestamp. For more information, see the BlockInfo Transaction Family
The following validation rules are added to the set of on-chain validation rules in order to prevent bad actors from injecting incorrect but valid BlockInfo transactions. The rules require that only one BlockInfo transaction is included per block, that the transaction is at the beginning of the block, and that the transaction is signed by the same key that signed the block.