Universa consensus explained


Overview

Universa consensus is a two level decision process to approve some smart contract state, e.g. creation and/or state change, or removal. The two level procedure ensures that:

  1. The parties involved have checked the contract and found it OK;
  2. The network has checked the parties agreement, the contract rules and permissions and found it OK.

After the second stage the contract is either “stamped” and approved by the network consensus (explained below), or rejected.

Level 1: local consensus

This is the state (or a process to reach the state) when all parties related to the new contract state approve it by digitally signing its new state.

Universa parties are (by default) some completely anonymous holders of the private keys, whose public keys (or addresses) are mentioned in the contract. In the simplest case it’s somebody who creates a new contract, or current contract owner. The contracts could be really complex and require many parties to confirm some sort of changes, in this cases all related parties must sign the new state with their private keys. When all required signatures are added to the contract, it is considered as the local consensus.

Level 2: network consensus

Once the local consensus is found, the contract is being sent to the Universa network for the approval (note that only the declarative part and the state of the contract is transmitted to the network. It means that all other data, like enclosed documents, attachments and scripts, are trimmed out before sending the contract for approval). Every node of the network receives the copy and performs a thorough check of its logic, restrictions, permissions, signatures and the states of any referred contracts. Then each node publishes its local decision (either positive or negative), and inform the network about it.

In a case if 10% of nodes + 1 node made a negative decision, the negative consensus is considered found, the voting process is stopped, and all nodes record the rejection of the contract state in the ledger.

Otherwise, as soon as 90% of the nodes made a positive decision, the positive consensus is considered found, and all the nodes record the approval of the new contract state in the ledger.

Each node has its own copy of the ledger, and stamps the time when new contract state was presented for approval.

Consensus procedure

Local consensus

The new contract or its new state could be prepared manually, by using contract viewer and editor in one of our clients (Web Client, Windows/macOS/Linux desktop applications, Command Line Interface (CLI) tools like (uniclient) or by some external or custom software. To implement the software we are providing such APIs as Java API, Javascript/Webpage API and integration mechanisms like UMI; (for more information check out the Software Developer Central in KB).

The smart contract itself could change its state or require to create new contracts too with its attached scripts. So, we don’t need to specify of fix the way the involved parties work on the new contract or state; but when they want to come to consensus, all of them must sign the new contract or state. What signatures are required for any contract change is specified by the contract itself, so the state of the contract could always be checked for consistency against its own rules and limitations.

When all required parties (could be as few as just the current owner) have signed the correct state of the contract, it means that the local consensus is found, and the contract/state could be passed to the Universa for verification and approval.

For the sake of simplicity we name the new contract or its new state a revision. The new contract has revision of 1, then it increments on every approved revision.

Network consensus

Each node checks and votes for the new revisions in the same way. Below is a simplified description, as the actual checks are numerous and complex.

Checking

Note: each revision except the first one will revoke at least its previous revision during the approval process. Usually, new revision revokes its old state by approving new one, like spending a coin, passing ownership, etc.

The node may receive a new revision in two ways: either from a client, or from some other node that has just checked it (see description below). In the latter case the node downloads the contracts from the other node first.

First, the node checks the new revision internal state and required signatures, and verifies that all the contracts referenced in the revision exist and are approved (not revoked); in any other case the check is considered failed. Note that if the new revision is already marked as rejected in the ledger, it is discarded without any further processing. In the same way, other errors that do not depend on the ledger state (like bad contract format, missing required signatures, corrupt definition) could lead to the discarding the state without any voting.

Then the node checks the old revision(s) which this revision is going to revoke. Each such revision should be present in the local ledger, be approved, and not locked for removal. If the record-to-be-revoked does not exist or is locked by some other revision, the check is considered failed. If it is ok, such records are marked in the ledger as locked by the new revision.

Then, the node checking procedure reserves the entries in the ledger for all new documents this revision creates, e.g. for the new revision itself and its siblings if any. Each sibling contract is checked the same way as the new revision. If the ledger already have any active (approved) or reserved entry requested by some other revision, the check fails.

If all the checks are OK, the node consider the new revision state as PENDING_POSITIVE, otherwise PENDING_NEGATIVE. It means that the node has a local decision and waits for the positive or negative consensus of the whole network.

Voting

As soon as the node has checked the revision, it broadcasts a message (UDP in v2 protocol) to all other nodes, containing its decision. This message states that:

  1. The sending node can provide the revision to others.
  2. The sending node has checked the revision and made a local decision, and votes pro or contra it.
  3. The sending node asks other nodes to vote for this revision.

Then the node waits for replies. As each other node follows the same procedure steps, soon it will receive the messages from the other nodes. If the message from some node is not received in the expected time, it resends it to the silent nodes.

On receiving such a message, the node counts pro and contra votes from other nodes and checks the counters.

If the number of negative votes reach the negative consensus threshold (10% of nodes + 1 vote), it rejects the revision, and consider the consensus established and negative.

When the number of positive votes counter reaches positive consensus level (90% of nodes), it accepts the revision, considering the consensus positive.

Reject procedure

The node marks the revision in question as rejected and sets its expiration in future using KEEP_REJECTED_DURATION parameter. During this time the record will be kept in the ledger and prevent its further registration attempts. All the new just-reserved entries are deleted, and all locked existing entries are unlocked.

Accept procedure

The node performs a transaction (in database world terms) to mark the new revision as APPROVED, and the entries locked for removal as REVOKED. If there are siblings to create, the node marks them as APPROVED too.

The revoked entries are set to expire in future after KEEP_REVOKED_DURATION. Until then the record will be kept in the ledger preventing reinserting revoked revisions. Note that each revision has creation time in the contract state, signed and immutable, so it is not possible to re-approve the revoked state that is discarded on expiration, as the system will not allow approval of the states with creation time past more than in half of the KEEP_REVOKED_DURATION, which typical value is 40 days.

A word on double spending

Note: there is an newer article, Double spending protection, covering this aspect in more details.

Double spending (in Universa terms) is an attempt to approve 2+ different new states (revisions) of some contracts. For example, a coin/token contract that attacker tries to pay with passing its ownership to 2+ other parties at the same time. To do it, the attacker creates several revisions and submits them for approval to different nodes simultaneously.

Let’s examine the case with 2 different revisions (named A and B) of the current contract (named C). and 2 nodes that receive it simultaneously. As the contracts are valid, both nodes lock the previous state C for removal by A or by B respectively, then broadcast their own votes over the network.

Each node in the network receiving A or B for approval will try to lock the C state for removal by A or B respectively. If it is already locked by one of them, it could not be re-locked, so each node will definitely goes positive on only one of them. E.g. some nodes will vote for A, some for B, but no one will vote for both.

Then the race starts: both A and B are collecting votes. Depending on the network random lags and varying speed, there could be 3 voting process results:

  1. A is processed faster than B and collects 90% of votes, and will be approved. It means, that these 90% vote against the B (when B contract arrives to them), and it will be rejected as soon as collected 10% + 1 vote against.
  2. B is processed faster than A and collects 90% positive votes, so B is approved and A rejected.
  3. A and B are processed at the roughly close speed, so (about) the half of votes will be for A and another half for B. Then both of them will soon reach negative consensus of 10% + 1, as (10% + 1) + (10% +1) = 20% + 2, so the negative consensus for both cases could be reached even before a half of all nodes have voted. Both A and B are rejected, the C state remains unchanged.

All of these are valid results: either, the old contract C is unchanged, or only one of its new revision, A or B, is approved.

The case with more conflicting states leads to the same results: the race leads to either

  • some new state reaches the consensus and is approved, rejecting all concurrents and revoking old state, or
  • all new states are declined by reaching negative consensus, the old state is not changed.

Rewriting the history

In Universa it is technically impossible to execute this sort of attack, as (1) the history is not kept by the network and (2) the historical data can not affect approving new revisions in any way. As explained above, only current set of active revisions is checked and counts. For example, if we have a coin that has changed owners 10 times, the system will check only the current revision, and only the current owner signature is needed to pass it further.

Still, if some party collects and keeps the whole chain of that coin, it could be approved by the Universa as the most recent state is approved by the network, and this state approves the previous state, containing a signature of it, and again up to the root contract that has no parent. In other word, if some party collects this history up to the currently approved revision, the whole chain can be approved and used as proof against modifications.

The other type of the historical attack is re-emission of revoked origin contract. For example, an attacker creates (emits) a new contract, passes it to somebody else, then emits the copy (tries to register it as a new contract again).

Depending on the revocation time, there could be 2 cases:

  1. Universa still keeps in the ledger the information of its REVOKED state. Then the network will reject the emission attempt as the corresponding entry in the ledger exists and is revoked. This is a definitely malicious attack scenario, that could lead to cancelling the subscription or membership.
  2. Universa has already discarded the revoked item entry from the ledger. In this case, the network will reject the emission attempt because the creation date in the contract is too far in the past. This is a suspicious activity.

Thus, the attacker can’t update the creation contract as it will break the signature (it will be a new, different contract with a different ID, calculated from its contents), no the re-emitted revoked state.