5.4.3 步骤
Reach 的步骤存在于 Reach.App 主体部分,或是 commit 语句的延拓部分。它表示应用程序中每个参与者所执行的操作。
5.4.3.1 语句
任何对计算有效的语句都对步骤有效。但也允许一些额外语句。
5.4.3.1.1 only 和 each
Alice.only(() => { const pretzel = interact.random(); });
本地步骤格式为 PART.only(() => BLOCK) ,其中 PART 是一个参与者标识符,BLOCK 是一个代码块。在 BLOCK 内部,PART 与参与者的地址绑定。 任何在本地步骤的代码块中定义的绑定关系,都可以作为新的本地状态在语句的尾部使用。例如:
Alice.only(() => { const x = 3; }); Alice.only(() => { const y = x + 1; });
是一个有效程序,其中 Alice 的本地状态包含私有值 x(与3绑定)和 y(与4绑定)。然而,此类绑定不属于共识状态,是纯粹的本地状态。例如:
Alice.only(() => { const x = 3; }); Bob.only(() => { const y = x + 1; });
是一个无效程序。因为 Bob 并不知道 x 是什么。 — each([Alice, Bob], () => { const pretzel = interact.random(); });
each 本地步骤语句格式为 each(PART_TUPLE() => BLOCK) ,其中 PART_TUPLE 是参与者元组,BLOCK 是一个代码块。each 语句是一系列可以用 only 语句来写的本地步骤的缩略形式。
5.4.3.1.2 支付金额
支付金额可以是下述情形之一:
代币金额可以是下述情形之一:
例如,以下均是支付金额:
0 5 [ 5 ] [ 5, [ 2, gil ] ] [ [ 2, gil ], 5 ] [ 5, [ 2, gil ], [ 8, zorkmids ] ]
对于同一个支付金额而言,重复声明一个代币数量是无效的。例如,以下均是无效支付金额:
[ 1, 2 ] [ [2, gil], [1, gil] ]
5.4.3.1.3 publish,pay,when,和 timeout
Alice.publish(wagerAmount) .pay(wagerAmount) .timeout(DELAY, () => { Bob.publish(); commit(); return false; }); Alice.publish(wagerAmount) .pay(wagerAmount) .timeout(DELAY, () => closeTo(Bob, false)); Alice.publish(wagerAmount) .pay(wagerAmount) .timeout(false);
共识转移语句格式为: PART_EXPR.publish(ID_0,...,ID_n).pay(PAY_EXPR)..when(WHEN_EXPR).timeout(DELAY_EXPR,()=>TIMEOUT_BLOCK) 其中:
- PART_EXPR 是确定参与者的表达式或是竞争表达式。
- ID_0 到 ID_n 是用于 PART 的公有本地状态的标识符。
- PAY_EXPR 是用于确定支付金额的公有表达式。
- WHEN_EXPR 是一个计算出布尔值的公有表达式,用于判断是否执行共识转移。
- DELAY_EXPR 是一个公有表达式,它只依赖于共识状态,并计算出一个由自然数表示的时间增量。
- TIMEOUT_BLOCK 是一个超时代码块,它将在 DELAY_EXPR 计算出的单位时间过后执行,这一单位时间从上一个共识步骤结束开始计算,PART执行当次共识转移的时间不算在内。
共识转移语句的延拓部分是一个共识步骤,以一个 commit 语句结束。超时代码块的延拓部分与存在超时的函数的延拓部分相同。
阅读关于非参与者的导览章节,理解何时使用、以及如何最有效地使用超时
若在本次共识转移中,发布和网络代币转移两者任一未发生,则可以省略 publish 组件异或 pay 组件。若条件假定为 true,那么 when 组件总是可以省略。publish 或是 pay 必须首先生成,其后的组件可以任意顺序生成。例如,以下均有效:
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);
当 when 组件并不是恒为 true 时,timeout 组件必须存在。这确保了你的客户端能够最终完成程序的执行。若共识转移确定会在一个非类参与者与一个可能会尝试(即不恒为 false )进行转移的参与者类之间竞争产生,那么 timeout 可以通过 .timeout(false) 的形式显式省略。
.throwTimeout 可以用来代替 .timeout。它接受一个 DELAY_EXPR 和一个 EXPR ,发生超时就会抛出。如果未给定 EXPR ,则会抛出 null 值。若有共识转移使用 .throwTimeout,它必须被包含在 try 语句中。
若一个共识转移明确指定单个参与者,而该参与者既没有在应用中被固化,又不是一个参与者类,那么该语句会做这件事。也就是说,这之后 PART 可以通过一个地址来调用。
若一个共识转移明确指定单个参与者类,那么该类的所有成员都会尝试执行转移,但是只有一个会成功。
共识转移将所有参与者标识符 ID_0 到 ID_n 与共识转移中包含的值进行绑定。若存在一个参与者之前绑定了标识符,但未被包含在 PART_EXPR 中,则该程序无效。换句话说,以下程序无效:
Alice.only(() => { const x = 1; }); Bob.only(() => { const x = 2; }); Claire.only(() => { const x = 3; }); race(Alice, Bob).publish(x); commit();
因为 Claire 没被包含在 race 内。然而,若我们把 Claire 的 x 重命名为 y,那就是有效的。虽然 Alice 和 Bob 都绑定了 x,但他俩都参与了 race,这使得程序有效。在这个程序的尾部,x 被绑定到了 1 或 2。
5.4.3.1.4 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(); });
fork 语句格式为: fork() .case(PART_EXPR, PUBLISH_EXPR, PAY_EXPR, CONSENSUS_EXPR) .timeout(DELAY_EXPR, () => TIMEOUT_BLOCK); 其中:
- PART_EXPR 是确定参与者的表达式;
- PUBLISH_EXPR 是一个语法箭头表达式,它在指定参与者的本地步骤中被计算,且该表达式必须得出一个对象。该对象包含 msg 和 when 两个字段,msg 字段可能是任何类型,而 when 字段必须是一个布尔值。
- PAY_EXPR 是一个表达式,该表达式通过 msg 值参数化的函数确定值,返回一个支付金额;
- CONSENSUS_EXPR 是一个语法箭头表达式,该表达式通过在共识步骤中计算的 msg 值参数化;
- 以及,timeout 和 throwTimeout 参数与共识转移中的同名参数相同。
若 PUBLISH_EXPR 返回的对象中缺少 msg 字段,则 msg 字段被视为 null。
若 PUBLISH_EXPR 返回的对象中缺少 when 字段,则 when 字段被视为 true。
若缺少 PAY_EXPR,则被视为 () => 0。
.case 组件可以重复多次。
同一个参与者可以被多个 case 指定。在这种情况下,case 的顺序至关重要。也就是说,只有当前一个 case 的 when 字段为 false 时,才会对后续 case 进行处理。
如果由 PART_EXPR 指定的参与者未被固化(从 Participant.set 的意义上来说),在它赢得竞争时被固化,前提它不是一个参与者类。 — fork 语句是通用的 race 和 switch 模版的缩写,您也可以自己编写。
其思想是,case 组件中的每个参与者对他们想要 publish 的值进行独立的本地步骤计算,然后所有参与者 race 争取 publish。赢得 race 的人不仅决定值(以及支付金额),而且还决定了共识步骤代码运行什么来消耗值。
// 首先我们那定义一个Data实例,这样每个参与者都可以发布一个不同的值 const ForkData = Data({Alice: UInt, Bob: Null}); // 然后我们为每个参与者绑定值 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()); }); // 参与者竞争 race(Alice, Bob) .publish(fork_msg) .when(fork_when) // 支付金额由发布的一方决定 .pay(fork_msg.match( { Alice: (v => v), Bob: (() => wager) } )) // 超时部分总是一样的 .timeout(deadline, () => { race(Alice, Bob).publish(); transfer(wager).to(this); commit(); exit(); });
// 确保正确的参与者发布正确类型的值 require(fork_msg.match( { // Alice 之前已经发布过 Alice: (v => this == Alice), // 但是 Bob 没有 Bob: (() => true) } ));
// 然后我们选择合适的主体来运行 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(); } }
这种模版编写起来不仅繁琐而且容易出错,因此 fork 语句为 Reach 程序员简化了它。当一个参与者指定了多个实例时,参与者的 msg 字段将被一个额外的变量包装,用以表示选择了什么实例。
5.4.3.1.5 wait
wait(AMOUNT);
写作 wait(AMOUNT); 的 wait 语句用以延迟计算,直到时间增量值流逝。该语句只存在于一个步骤中。
5.4.3.1.6 exit
exit();
写作 exit(); 的 exit 语句停止计算。这是终止语句,因此必须有一个空尾。该语句只存在于一个步骤中。
5.4.3.2 表达式
任何对计算有效的语句都对步骤有效。但也允许一些额外表达式。
5.4.3.2.1 race
race(Alice, Bob).publish(bet);
写作 race(PARTICIPANT_0,...,PARTICIPANT_n); 的表达式构造了一个参与者,该参与者可用于共识转移语句中,例如 publish 或 pay。该表达式中的各参与者相互竞争,获胜者执行共识转移。
Reach 提供了一个缩略写法 Anybody,用以表示所有参与者之间的竞争。
5.4.3.2.2 unknowable
unknowable( Notter, Knower(var_0, ..., var_N), [msg] )
这是一种知识断言,表示参与者 Notter 不知道变量 var_0 到 var_N 的结果,但参与者 Knower 知道这些值。它接受一个可选的字节参数,该参数会被包含在所有报告的冲突中。
5.4.3.2.3 closeTo
closeTo( Who, after )
让参与者 Who 制作发布,然后将 balance() 转移给 Who,并在步骤中执行 after 函数后结束 DApp。after 参数是可选项。