Validator Sets - Wiki OpenEthereum Documentation

A number of Engines available in OpenEthereum achieve consensus by referring to a list of “validators” (referred to as authorities if they are linked to physical entities). Validators are a group of accounts which are allowed to participate in the consensus, they validate the transactions and blocks to later sign messages about them. The validator set can be specified in a number of different ways.

Immutable list

A simple list of addresses specified at genesis.

"validators": {
    "list": [
        "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e",
        "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1"
    ]
}

Contracts

Available since 1.7.

The list can be also a part of the blockchain state by being stored in an Ethereum contract.

Example contracts can be found here with the contract used on Kovan Network.

It is best to include the contract in the genesis placing the bytecode as a “constructor” in the “accounts” field like so:

"0x0000000000000000000000000000000000000005": { "balance": "1", "constructor" : "0x..." }

If the constructor takes arguments they must be encoded and appended to the contract bytecode (using e.g. ethabi). Also if the contract initializes any address with msg.sender (for example as a contract owner) you must take into account that when defining the contract in genesis, msg.sender will be set to the system address (SYSTEM_ADDRESS: 2^160 - 2).

Non-reporting contract

A simple validator contract has to have the following interface:

[{"constant":false,"inputs":[],"name":"finalizeChange","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"_validators","type":"address[]"}],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"InitiateChange","type":"event"}]

which corresponds to this Solidity contract definition:

contract ValidatorSet {
    /// Issue this log event to signal a desired change in validator set.
    /// This will not lead to a change in active validator set until
    /// finalizeChange is called.
    ///
    /// Only the last log event of any block can take effect.
    /// If a signal is issued while another is being finalized it may never
    /// take effect.
    ///
    /// _parent_hash here should be the parent block hash, or the
    /// signal will not be recognized.
    event InitiateChange(bytes32 indexed _parent_hash, address[] _new_set);

    /// Get current validator set (last enacted or initial if no changes ever made)
    function getValidators() constant returns (address[] _validators);

    /// Called when an initiated change reaches finality and is activated.
    /// Only valid when msg.sender == SUPER_USER (EIP96, 2**160 - 2)
    ///
    /// Also called when the contract is first enabled for consensus. In this case,
    /// the "change" finalized is the activation of the initial set.
    function finalizeChange();
}

The function getValidators should always return the active set or the initial set of validators if the contract hasn’t been activated yet. The “active” validator set is the set of the most recently finalized signal (finalizeChange event).

Switching the set should be done by issuing an InitiateChange event with the parent block hash and the new set, storing the pending set, and then waiting for the call to finalizeChange (by the SYSTEM_ADDRESS: 2^160 - 2) before setting the active set to the pending set. This mechanism is used to ensure that the previous validator set “signs off” on the changes before activation, leading to full security in situations like warp and light sync, where state transitions aren’t checked.

Other than these restrictions, the switching rules are fully determined by the contract implementing that method. The spec should contain the contract address:

"validators": {
    "safeContract": "0x0000000000000000000000000000000000000005"
}

Reporting contract

Sometimes one might want to automatically take action when one of the validators behaves badly. The definition of bad behaviour depends on a consensus engine and there are two types of bad behaviour:

Benign misbehaviour in Aura may be simply not receiving a block from a designated primary, while malicious misbehaviour would be releasing two different blocks for the same step.

This type of contract can listen to misbehaviour reports from the consensus engine and decide what are the consequences for the validators.

The correct interface is:

[{"constant":false,"inputs":[],"name":"finalizeChange","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"getValidators","outputs":[{"name":"_validators","type":"address[]"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"},{"name":"proof","type":"bytes"}],"name":"reportMalicious","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"validator","type":"address"},{"name":"blockNumber","type":"uint256"}],"name":"reportBenign","outputs":[],"payable":false,"type":"function"},{"anonymous":false,"inputs":[{"indexed":true,"name":"_parent_hash","type":"bytes32"},{"indexed":false,"name":"_new_set","type":"address[]"}],"name":"InitiateChange","type":"event"}]

which corresponds to this Solidity contract definition:

contract ReportingValidatorSet {
    // all same as ValidatorSet
    event InitiateChange(bytes32 indexed _parent_hash, address[] _new_set);

    function getValidators() constant returns (address[] _validators);
    function finalizeChange();

    // Reporting functions: operate on current validator set.
    // malicious behavior requires proof, which will vary by engine.

    function reportBenign(address validator, uint256 blockNumber);
    function reportMalicious(address validator, uint256 blockNumber, bytes proof);
}

InitiateChange, getValidators and finalizeChange should function exactly as in a non-reporting contract. There are two new functions, reportBenign and reportMalicious. Each should come with the address of a validator being reported and the block number at which misbehavior occurred. reportMalicious also requires a proof of malice, which is an arbitrary byte-string which different engines will set to different values. Validators will call these when they detect misbehavior.

These should function on only the current active validator set.

It is specified as:

"validators": {
    "contract": "0x0000000000000000000000000000000000000005"
}

Multi set

Available only in 1.6 and above. This validator set can specify any combination of other validator sets. Switching is done based on the number of the current block. It can be useful for conducting chain forks. First set has to start at block 0.

"validators": {
    "multi": {
        "0": {
            "list": [
                "0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1",
                "0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
            ]
        },
        "100": {
            "contract": "0x0000000000000000000000000000000000000005"
        }
    }
}

Note that transitions to a contract will not take effect immediately. Rather, they will take effect when the transition block is finalized by the previous set of validators. Again, this is to provide full security for warp and light sync.

Transitions to a fixed list will take effect immediately because regardless of whether an attacker gives a light client a transition block with an invalid state, the subsequent validator set will always be the same.

Limitations

Currently events emitted by the validator set contract when the engine calls finalizeChange are not observable. This is a limitation of how “system” transactions are implemented at the moment.