5.4.3 步骤

320 阅读8分钟

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) 其中:

共识转移语句延拓部分是一个共识步骤,以一个 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 值参数化;
  • 以及,timeoutthrowTimeout 参数与共识转移中的同名参数相同。

若 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 的人不仅决定值(以及支付金额),而且还决定了共识步骤代码运行什么来消耗值。

有关 fork 关键词的简单 fork 语句大致如下:

// 首先我们那定义一个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);  的表达式构造了一个参与者,该参与者可用于共识转移语句中,例如 publishpay。该表达式中的各参与者相互竞争,获胜者执行共识转移

Reach 提供了一个缩略写法 Anybody,用以表示所有参与者之间的竞争。

阅读关于竞争的导览章节,理解使用 race 的益处与风险。

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 参数是可选项。