5.4 Programs
5.4.1 Validity
5.4.2 Modules
5.4.3 Steps
5.4.4 Local Steps
5.4.5 Consensus Steps
5.4.6 Computations
5.4.3 Steps
On this page:
5.4.3.1 Statements
5.4.3.1.1 only and each
only
each
5.4.3.1.2 publish, pay, when, and timeout
publish
pay
timeout
5.4.3.1.3 fork
fork
5.4.3.1.4 wait
wait
5.4.3.1.5 exit
exit
5.4.3.2 Expressions
5.4.3.2.1 race
race
5.4.3.2.2 unknowable
unknowable
5.4.3.2.3 close  To
close  To
5.4.3 Steps

A Reach step occurs in the body of Reach.App or in the continuation of a commit statement. It represents the actions taken by each of the participants in an application.

5.4.3.1 Statements

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

5.4.3.1.1 only and each

Alice.only(() => {
  const pretzel = interact.random(); }); 

A local step statement is written PART.only(() => BLOCK), where PART is a participant identifier and BLOCK is a block. Within BLOCK, PART is bound to the address of the participant. Any bindings defined within the block of a local step are available in the statement’s tail as new local state. For example,

Alice.only(() => {
  const x = 3; });
Alice.only(() => {
  const y = x + 1; }); 

is a valid program where Alice’s local state includes the private values x (bound to 3) and y (bound to 4). However, such bindings are not consensus state, so they are purely local state. For example,

Alice.only(() => {
  const x = 3; });
Bob.only(() => {
  const y = x + 1; }); 

is an invalid program, because Bob does not know x.

each([Alice, Bob], () => {
  const pretzel = interact.random(); }); 

An each local step statement can be written as each(PART_TUPLE () => BLOCK), where PART_TUPLE is a tuple of participants and BLOCK is a block. It is an abbreviation of many local step statements that could have been written with only.

5.4.3.1.2 publish, pay, when, and timeout

Alice.publish(wagerAmount)
     .pay(wagerAmount)
     .timeout(DELAY, () => {
       Bob.publish();
       commit();
       return false; }); 
Alice.publish(wagerAmount)
     .pay(wagerAmount)
     .timeout(DELAY, () => closeTo(Bob, false)); 

A consensus transfer is written PART_EXPR.publish(ID_0, ..., ID_n).pay(PAY_EXPR)..when(WHEN_EXPR).timeout(DELAY_EXPR, () => TIMEOUT_BLOCK), where PART_EXPR is an expression that evaluates to a participant or race expression, ID_0 through ID_n are identifiers for PART’s public local state, PAY_EXPR is a public expression evaluating to an amount of network tokens, WHEN_EXPR is a public expression evaluating to a boolean and determines if the consensus transfer takes place, DELAY_EXPR is a public expression that depends on only consensus state and evaluates to a time delta represented by a natural number, TIMEOUT_BLOCK is a timeout block, which will be executed after DELAY_EXPR units of time have passed from the end of the last consensus step without PART executing this consensus transfer. The continuation of a consensus transfer statement is a consensus step, which is finalized with a commit statement. The continuation of a timeout block is the same as the continuation of the function the timeout occurs within.

See the guide section on non-participation to understand when to use timeouts and how to use them most effectively.

The publish component exclusive-or the pay component may be omitted, if either there is no publication or no transfer of network tokens to accompany this consensus transfer. The when component may always be omitted, in which case it is assumed to be true. The timeout component may omitted if when is statically true. publish or pay must occur first, after which components may occur in any order. For example, the following are all valid:

Alice.publish(coinFlip);

Alice.pay(penaltyAmount);

Alice.pay(penaltyAmount).publish(coinFlip);

Alice.publish(coinFlip)
     .timeout(DELAY, () => closeTo(Bob, () => exit()));

Alice.pay(penaltyAmount)
     .timeout(DELAY, () => {
       Bob.publish();
       commit();
       exit(); });

Alice.publish(bid).when(wantsToBid);

If a consensus transfer specifies a single participant, which has not yet been fixed in the application and is not a participant class, then this statement does so; therefore, after it the PART may be used as an address.

A consensus transfer binds the identifiers ID_0 through ID_n for all participants to the values included in the consensus transfer. If an existing participant, not included in PART_EXPR, has previously bound one of these identifiers, then the program is not valid. In other words, the following program is not valid:

Alice.only(() => {
 const x = 1; });
Bob.only(() => {
 const x = 2; });
Claire.only(() => {
 const x = 3; });
race(Alice, Bob).publish(x);
commit();

because Claire is not included in the race. However, if we were to rename Claire’s x into y, then it would be valid, because although Alice and Bob both bind x, they participate in the race, so it is allowed. In the tail of this program, x is bound to either 1 or 2.

5.4.3.1.3 fork

fork()
.case(Alice, (() => ({
  msg: 19,
  when: declassify(interact.keepGoing()) })),
  ((v) => v),
  (v) => {
    require(v == 19);
    transfer(wager + 19).to(this);
    commit();
    exit();
  })
.case(Bob, (() => ({
  when: declassify(interact.keepGoing()) })),
  (() => wager),
  () => {
    commit();

    Alice.only(() => interact.showOpponent(Bob));

    race(Alice, Bob).publish();
    transfer(2 * wager).to(this);
    commit();
    exit();
  })
.timeout(deadline, () => {
  race(Alice, Bob).publish();
  transfer(wager).to(this);
  commit();
  exit(); });

A fork statement is written:

fork()
.case(PART_EXPR,
  PUBLISH_EXPR,
  PAY_EXPR,
  CONSENSUS_EXPR)
.timeout(DELAY_EXPR, () =>
  TIMEOUT_BLOCK);

where: PART_EXPR is an expression that evaluates to a participant; PUBLISH_EXPR is a syntactic arrow expression that is evaluated in a local step for the specified participant and must evaluate to an object that may contain a msg field, which may be of any time, and a when field, which must be a boolean; PAY_EXPR is an expression that evaluates to a function parameterized over the msg value and returns an integer; CONSENSUS_EXPR is a syntactic arrow expression parameterized over the msg value which is evaluated in a consensus step; and, the timeout parameter are as in an consensus transfer.

If the msg field is absent from the object returned from PUBLISH_EXPR, then it is treated as if it were null.

If the when field is absent from the object returned from PUBLISH_EXPR, then it is treated as if it were true.

If the PAY_EXPR is absent, then it is treated as if it were () => 0.

The .case component may be repeated many times, provided the PART_EXPRs each evaluate to a unique participant.

If the participant specified by PART_EXPR is not already fixed (in the sense of Participant.set), then if it wins the race, it is fixed, provided it is not a participant class.

A fork statement is an abbreviation of a common race and switch pattern you could write yourself.

The idea is that each of the participants in the case components do an independent local step evaluation of a value they would like to publish and then all race to publish it. The one that "wins" the race then determines not only the value (& pay amount), but also what consensus step code runs to consume the value.

The sample fork statement linked to the fork keyword is roughly equivalent to:

// We first define a Data instance so that each participant can publish a
// different kind of value
const ForkData = Data({Alice: UInt, Bob: Null});
// Then we bind these values for each participant
Alice.only(() => {
 const fork_msg = ForkData.Alice(19);
 const fork_when = declassify(interact.keepGoing()); });
Bob.only(() => {
 const fork_msg = ForkData.Bob(null);
 const fork_when = declassify(interact.keepGoing()); });
// They race
race(Alice, Bob)
 .publish(fork_msg)
 .when(fork_when)
 // The pay ammount depends on who is publishing
 .pay(fork_msg.match( {
   Alice: (v => v),
   Bob: (() => wager) } ))
 // The timeout is always the same
 .timeout(deadline, () => {
   race(Alice, Bob).publish();
   transfer(wager).to(this);
   commit();
   exit(); });

 // We ensure that the correct participant published the correct kind of value
 require(fork_msg.match( {
   // Alice had previously published
   Alice: (v => this == Alice),
   // But Bob had not.
   Bob: (() => true) } ));

 // Then we select the appropriate body to run
 switch (fork_msg) {
   case Alice: {
     assert (this == Alice);
     require(v == 19);
     transfer(wager + 19).to(this);
     commit();
     exit(); }
   case Bob: {
     Bob.set(this);
     commit();

     Alice.only(() => interact.showOpponent(Bob));

     race(Alice, Bob).publish();
     transfer(2 * wager).to(this);
     commit();
     exit(); }
 }

This pattern is tedious to write and error-prone, so the fork statement abbreviates it for Reach programmers.

5.4.3.1.4 wait

wait(AMOUNT); 

A wait statement, written wait(AMOUNT);, delays the computation until AMOUNT time delta units have passed. It may only occur in a step.

5.4.3.1.5 exit

exit(); 

An exit statement, written exit();, halts the computation. It is a terminator statement, so it must have an empty tail. It may only occur in a step.

5.4.3.2 Expressions

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

5.4.3.2.1 race

race(Alice, Bob).publish(bet); 

A race expression, written race(PARTICIPANT_0, ..., PARTICIPANT_n);, constructs a participant that may be used in a consensus transfer statement, such as publish or pay, where the various participants race to be the first one to perform the consensus transfer.

See the guide section on races to understand the benefits and dangers of using race.

5.4.3.2.2 unknowable

unknowable( Notter, Knower(expr_0, ..., expr_N), [msg] ) 

A knowledge assertion that the participant Notter does not know the results of the evaluations of expressions expr_0 through expr_N, but that the participant Knower does know those values. It accepts an optional bytes argument, which is included in any reported violation.

5.4.3.2.3 closeTo

closeTo( Who, after ) 

Has participant Who make a publication, then transfer the balance() to Who and end the DApp after executing the function after in a step.