On this page: Statements commit
commit only and each View Objects Participant.set and .set while
while continue
continue parallel  Reduce
parallel  Reduce
pay  Spec .time  Remaining
time  Remaining .throw  Timeout
throw  Timeout parallel  Reduce intuition Expressions this transfer
transfer require
require check  Commitment
check  Commitment Token minting
destroy Remote objects
with  Bill Mappings:   creation and modification
Map Sets:   creation and modification
5.4.6 Consensus Steps

A Reach consensus step occurs in the continuation of a consensus transfer statement. It represents the actions taken by the consensus network contract of an application. Statements

Any statements valid for a computation are valid for a consensus step. However, some additional statements are allowed. commit


A commit statement, written commit();, commits to statement’s continuation as the next step of the DApp computation. In other words, it ends the current consensus step and allows more local steps. only and each

only and each are allowed in consensus steps and are executed by backends once they observe the completion of the consensus step (i.e., after the associated commit statement.) View Objects

Views are defined in application initialization in Reach. They are accessed by frontends by using the Reach standard library of the frontend language, such as JavaScript. This section is about defining the value of a view in your Reach program.


If VIEW is a view object, then its fields are the elements of the associated view. Each of these fields are bound to an object with a set method that accepts the function or value to be bound to that view at the current step, and all steps dominated by the current step (unless otherwise overridden). If this function is not provided with an argument, then the corresponding view is unset.

For example, consider the following program:

 1    'reach 0.1';
 3    const Tlast = Maybe(Address);
 4    const Ti = Maybe(UInt);
 5    const T = Tuple(Tlast, Ti);
 7    export const main =
 8     Reach.App({},
 9      [ Participant('Alice', { checkView: Fun([T], Null) }),
10        Participant('Bob', {}),
11        View('Main', { last: Address, i: UInt }),
12      ],
13      (A, B, vMain) => {
14        A.publish(); commit();
15        const checkView = (x) =>
16          A.only(() => interact.checkView(x));
18        // The contract doesn't exist yet, so no view
19        checkView([Tlast.None(), Ti.None()]);
21        A.publish();
22        vMain.i.set(1);
23        vMain.last.set(A);
24        // These views are now visible
25        checkView([Tlast.Some(A), Ti.Some(1)]);
26        commit();
28        // Block race of Alice and Bob for Alice to observe the state
29        A.publish();
30        commit();
32        B.publish();
33        vMain.i.set(2);
34        vMain.last.set(B);
35        if ( A != B ) {
36          // The views above are visible
37          checkView([Tlast.Some(B), Ti.Some(2)]);
38          commit();
39        } else {
40          // Or, we overwrite them
41          vMain.i.set(3);
42          vMain.last.set();
43          checkView([Tlast.None(), Ti.Some(3)]);
44          commit();
45        }
47        A.publish();
48        // The contract doesn't exist anymore, so no view
49        checkView([Tlast.None(), Ti.None()]);
50        commit();
52        exit();
53      });

In this program, the Reach backend calls the frontend interact function, checkView with the expected value of the views at each point in the program. The frontend compares that value with what is returned by

[ await ctc.getViews().Main.last(),
  await ctc.getViews().Main.i() ]

When a view is bound to a function, it may inspect any values in its scope, including linear state. Participant.set and .set

Participant.set(PART, ADDR);

After execution, the given participant is fixed to the given address. It is invalid to attempt to .set a participant class. If a backend is running for this participant and its address does not match the given address, then it will abort. This may only occur within a consensus step.

Workshop: Relay Account is a good introductory project that demonstrates how to use this feature of Reach. while

var [ heap1, heap2 ] = [ 21, 21 ];
{ const sum = () => heap1 + heap2; }
invariant(balance() == 2 * wagerAmount);
while ( sum() > 0 ) {
  [ heap1, heap2 ] = [ heap1 - 1, heap2 ];
  continue; } 

A while statement may occur within a consensus step and is written:

DEFINE_BLOCK; // optional

where LHS is a valid left-hand side of an identifier definition where the expression INIT_EXPR is the right-hand side, and DEFINE_BLOCK is an optional block that may define bindings that use the LHS values which are bound inside the rest of the while and its tail, and INVARIANT_EXPR is an expression, called the loop invariant, that must be true before and after every execution of the block BLOCK, and if COND_EXPR is true, then the block executes, and if not, then the loop terminates and control transfers to the continuation of the while statement. The identifiers bound by LHS are bound within DEFINE_BLOCK, INVARIANT_EXPR, COND_EXPR, BLOCK, and the tail of the while statement.

Read about finding loop invariants in the Reach guide. continue

[ heap1, heap2 ] = [ heap1 - 1, heap2 ];

A continue statement may occur within a while statement’s block and is written:


where the identifiers bound by LHS are a subset of the variables bound by the nearest enclosing while statement and UPDATE_EXPR is an expression which may be bound by LHS.

A continue statement is a terminator statement, so it must have an empty tail.

A continue statement may be written without the preceding identifier update, which is equivalent to writing

[] = [];

A continue statement must be dominated by a consensus transfer, which means that the body of a while statement must always commit(); before calling continue;. This restriction may be lifted in future versions of Reach, which will perform termination checking.

As a special case, a continue statement may occur in a step, if the UPDATE_EXPR transitions to a consensus step. In other words, this is a valid program:

const f = () => {
 return 1;

var x = 0;
invariant(balance() == 0);
while ( x == 0 ) {
 x = f();
} parallelReduce

const [ keepGoing, as, bs ] =
  parallelReduce([ true, 0, 0 ])
  .invariant(balance() == 2 * wager)
  .case(Alice, (() => ({
    when: declassify(interact.keepGoing()) })),
    (_) => {
      each([Alice, Bob], () => {
        interact.roundWinnerWas(true); });
      return [ true, 1 + as, bs ]; })
  .case(Bob, (() => ({
    when: declassify(interact.keepGoing()) })),
    (_) => {
      each([Alice, Bob], () => {
        interact.roundWinnerWas(false); });
      return [ true, as, 1 + bs ]; })
  .timeout(deadline, () => {
    race(Alice, Bob).publish();
    return [ false, as, bs ]; });

If you’re unsure of what kind of consensus transfer to use, you may want to read the explanation of the differences in the Guide.

A parallel reduce statement is written:

const LHS =
  .define(() => DEFINE_BLOCK)
  .timeout(DELAY_EXPR, () =>

The LHS and INIT_EXPR are like the initialization component of a while loop; and, the .invariant and .while components are like the invariant and condition of a while loop; the DEFINE_BLOCK is like the DEFINE_BLOCK of a while loop; while the .case, .timeout, and .paySpec components are like the corresponding components of a fork statement.

The .case component may be repeated many times, provided the PART_EXPRs each evaluate to a unique participant, just like in a fork statement.

The .define component may define bindings that reference the LHS values. These bindings are accessible from every component of the parallelReduce statement, except for the INIT_EXPR. .timeRemaining

When dealing with absolute deadlines in parallelReduce, there is a common pattern in the TIMEOUT_BLOCK to have participants race to publish and return the accumulator. There is a shorthand, .timeRemaining, available for this situation:

const [ timeRemaining, keepGoing ] = makeDeadline(deadline);
const [ x, y, z ] =
  parallelReduce([ 1, 2, 3 ])

which will expand to:

.timeout(timeRemaining(), () => {
  return [ x, y, z ]; }) .throwTimeout

.throwTimeout is a shorthand that will throw the accumulator as an exception when a timeout occurs. Therefore, a parallelReduce that uses this branch must be inside of a try statement. For example,

try {
  const [ x, y, z ] =
    parallelReduce([ 1, 2, 3 ])
} catch (e) { ... } 

will expand throwTimeout to:

.timeout(deadline, () => {
  throw [ x, y, z ]; }) parallelReduce intuition

A parallel reduce statement is essentially an abbreviation of pattern of a while loop combined with a fork statement that you could write yourself. This is an extremely common pattern in decentralized applications.

The idea is that there are some values (the LHS) which after intialization will be repeatedly updated uniquely by each of the racing participants until the condition does not hold.

while(COND_EXPR) {
    (m) => {
      continue; })
  .timeout(DELAY_EXPR, () =>
} Expressions

Any expressions valid for a computation are valid for a consensus step. However, some additional expressions are allowed. this

Inside of a consensus step, this refers to the address of the participant that performed the consensus transfer. This is useful when the consensus transfer was initiated by a race expression. transfer

transfer(2, gil).to(Alice); 

A transfer expression, written transfer(AMOUNT_EXPR).to(ADDR_EXPR), where AMOUNT_EXPR is an expression that evaluates to an unsigned integer, and ADDR_EXPR evaluates to an address, performs a transfer of network tokens from the contract to the named participant. AMOUNT_EXPR must evaluate to less than or equal to the balance of network tokens in the contract account.

A transfer expression may also be written transfer(AMOUNT_EXPR, TOKEN_EXPR).to(ADDR_EXPR), where TOKEN_EXPR is a Token, which transfers non-network tokens of the specified type.

A transfer expression may only occur within a consensus step. require

require( claim, [msg] ) 

A requirement where claim evaluates to true with honest participants. This may only appear in a consensus step. It accepts an optional bytes argument, which is included in any reported violation. checkCommitment

checkCommitment( commitment, salt, x ) 

Makes a requirement that commitment is the digest of salt and x. This is used in a consensus step after makeCommitment was used in a local step. Token minting

require(supply >= 2 * amt);
const tok = new Token({name, symbol, url, metadata, supply});
transfer(amt, tok).to(who);
assert(tok.supply() == supply - amt);
assert(tok.destroyed() == false);

Consensus Network Connectors discusses how Reach supports token minting on specific consensus networks.

We refer to creation of a new non-network token as token minting. It is written with the expression new Token(PARAMS), where PARAMS is an object with the following keys:
  • name: A value of type Bytes(32); defaults to empty.

  • symbol: A value of type Bytes(8); defaults to empty.

  • url: A value of type Bytes(96); defaults to empty.

  • metadata: A value of type Bytes(32); defaults to empty. This value is intended to be a digest of a larger metadata document.

  • supply: A value of type UInt; defaults to UInt.max.

This returns a Token value and deposits a supply amount of the new non-network tokens into the contract account associated with the DApp. These tokens must be destroyed by the end of the DApp.

Reach assumes that network tokens and non-network tokens behavior identically, but often they do not; this article discusses the causes and consequences of this.

Token.burn(tok, amt), or tok.burn(amt), where tok is a Token value and amt is a UInt value, may be used to burn tokens in the contract account, meaning that they are utterly destroyed and can never be recovered.

Token.destroy(tok), or tok.destroy(), where tok is a Token value, may be used to destroy the token so that it may never be used again by any users on the consensus network. This must be called before the application exits.

Token.destroyed(tok), or tok.destroyed(), where tok is a Token value, returns whether destroy has been called on tok yet.

Token.supply(tok), or tok.supply(), where tok is a Token value, may be used to query the current supply of tokens, i.e. the number of tokens which have not been burnt. Remote objects

const randomOracle =
  remote( randomOracleCtcInfo, {
    getRandom: Fun([], UInt),
const randomVal = randomOracle.getRandom.pay(randomFee)();

Consensus Network Connectors discusses how Reach supports remote objects on specific consensus networks.

A remote object represents a foreign contract in a Reach application. During a consensus step, a Reach computation may consensually communicate with such an object via a prescribed interface.

A remote object is constructed by calling the remote function with a Contract and an interface—an object where each key is bound to a function type. For example:

const randomOracle =
  remote( randomOracleCtcInfo, {
    getRandom: Fun([], UInt),
const token =
  remote( tokenCtcInfo, {
    balanceOf: Fun([Address], UInt),
    transferTo: Fun([UInt, Address], Null),

Once constructed, the fields of a remote object represent those remote contract interactions, referred to as remote functions. For example, randomOracle.getRandom, token.balanceOf, and token.transferTo are remote functions in the example.

A remote function may be invoked by calling it with the appropriate arguments, whereupon it returns the specified output. In addition, a remote function may be augmented with one of the following operations: Mappings: creation and modification

const bidsM = new Map(UInt);
bidsM[this] = 17;
delete bidsM[this];

A new mapping of linear state may be constructed in a consensus step by writing new Map(TYPE_EXPR), where TYPE_EXPR is some type.

This returns a value which may be used to dereference particular mappings via map[ADDR_EXPR], where ADDR_EXPR is an address. Such dereferences return a value of type Maybe(TYPE_EXPR), because the mapping may not contain a value for ADDR_EXPR.

A mapping may be modified by writing map[ADDR_EXPR] = VALUE_EXPR to install VALUE_EXPR (of type TYPE_EXPR) at ADDR_EXPR, or by writing delete map[ADDR_EXPR] to remove the mapping entry. Such modifications may only occur in a consensus step. Sets: creation and modification

const bidders = new Set();
bidders.member(Alice); // false

A Set is another container for linear state. It is simply a type alias of Map(Null); it is only useful for tracking Addresses. Because a Set is internally a Map, it may only be constructed in a consensus step.

A Set may be modified by writing s.insert(ADDRESS) to install ADDRESS in the set, s, or s.remove(ADDRESS) to remove the ADDRESS from the set. Such modifications may only occur in a consensus step.

s.member(ADDRESS) will return a Bool representing whether the address is in the set.