5.4.5 共识步骤

365 阅读7分钟

5.4.5 共识步骤

Reach 的共识步骤存在于共识转移语句的延拓部分。它代表着应用中共识网络合约执行的动作。

5.4.5.1 语句

任何对计算有效的语句都对共识步骤有效。但也允许一些额外的语句。

5.4.5.1.1 commit

commit();

commit 语句格式为:commit();。commit 将语句延拓部分作为 DApp 计算的下一提交。换句话说,它结束当前的共识步骤,并允许更多的本地步骤

5.4.5.1.2 only 和 each

共识步骤中允许 only 和 each ,当后端观察到共识步骤完成时会执行它们(即:在相关的 commit 语句之后)。

5.4.5.1.3 Participant.set 和 .set

Participant.set(PART, ADDR); PART.set(ADDR);

在执行之后,给定的参与者被固化到给定的地址。尝试 .set 一个参与者类无效的。如果此参与者后端正在运行,而其地址与给定的地址不匹配,则它会中止。这只可能在共识步骤中发生。

研讨:中继帐户是一个很好的介绍性项目,它演示了如何使用 Reach 的这一特性。

5.4.5.1.4 while

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

while 语句存在于共识步骤中,其格式为: var LHS = INIT_EXPR; invariant(INVARIANT_EXPR); while( COND_EXPR ) BLOCK

其中:

  • LHS 是标识符定义中的有效左侧,表达式 INIT_EXPR 位于右侧;
  • INVARIANT_EXPR 是一个表达式,称之为循环常量。该常量在每次执行 BLOCK 之前和之后都必须为 true;
  • 若 COND_EXPR 为 true 则执行,否则循环终止,并控制转移到 while 语句延拓部分;
  • 由 LHS 绑定的标识符在 INVARIANT_EXPR 、 COND_EXPR 、 BLOCK 和 while 语句尾部被绑定。

阅读 Reach 指南中有关寻找循环常量的内容。

5.4.5.1.5 continue

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

continue 语句存在于 while 语句中,其格式为: LHS = UPDATE_EXPR; continue;

其中,由 LHS 绑定的标识符是由最近的封闭 while 语句绑定的变量的子集,UPDATE_EXPR 是可由 LHS 绑定的表达式

continue 语句终止语句,因此必须有一个空

continue 语句可以不带标识符更新地书写,相当于写成: [] = []; continue;

continue 语句必须受共识转移支配,这意味着 while 语句的主体在调用 continue; 之前必须始终 commit(); 。在将来版本的 Reach 中将会取消此限制,届时 Reach 将执行终止检查。

5.4.5.1.6 parallelReduce

const [ keepGoing, as, bs ] =  parallelReduce([ true, 0, 0 ])  .invariant(balance() == 2 * wager)  .while(keepGoing)  .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, () => {    showOutcome(TIMEOUT)();    race(Alice, Bob).publish();    return [ false, as, bs ]; });

parallelReduce 语句格式为: const LHS =  parallelReduce(INIT_EXPR)  .invariant(INVARIANT_EXPR)  .while(COND_EXPR)  .case(PART_EXPR,    PUBLISH_EXPR,    PAY_EXPR,    CONSENSUS_EXPR)  .timeout(DELAY_EXPR, () =>    TIMEOUT_BLOCK); 其中:

  • LHS 和 NIT_EXPR 就像 while 循环的初始化组件;
  • .invariant 和 .while 组件是类似于 while 循环的常量和条件;
  • 而 .case 和 .timeout 组件类似于 fork 语句中相应的组件。

当 PART_EXPRs 的计算结果都是唯一参与者时, .case 组件可以重复很多次,就像 fork 语句中一样。

.timeRemaining

parallelReduce 中处理绝对期限时,TIMEOUT_BLOCK 中有一种通用模式,即让参与者竞争发布并返回累加器。对于以下情况可以使用简写 .timeRemainingconst [ timeRemaining, keepGoing ] = makeDeadline(deadline); const [ x, y, z ] =  parallelReduce([ 1, 2, 3 ])    .while(keepGoing())    ...    .timeRemaining(timeRemaining())

可以扩展为: .timeout(timeRemaining(), () => {  race(...Participants).publish();  return [ x, y, z ]; })

.throwTimeout .throwTimeout 是一种简写,当发生超时时它将作为异常抛出累加器。因此,使用此分支的 parallelReduce 必须被包含在 try 语句中。例如: try {  const [ x, y, z ] =    parallelReduce([ 1, 2, 3 ])    ...    .throwTimeout(deadline) } catch (e) { ... }

其中 throwTimeout 可以扩展为: .timeout(deadline, () => {  throw [ x, y, z ]; })

parallelReduce 语句本质上是while循环与你自己编写的 fork 语句相结合的一种缩写模式。这在去中心化应用程序中极为常见。 该理念是:有一些值( LHS )初始化后,会反复由每个竞争参赛者来进行唯一更新,直到条件不成立。 var LHS = INIT_EXPR; invariant(INVARIANT_EXPR) while(COND_EXPR) {  fork()  .case(PART_EXPR,    PUBLISH_EXPR,    PAY_EXPR,    () => {      LHS = CONSENSUS_EXPR;      continue; })  .timeout(DELAY_EXPR, () =>    TIMEOUT_BLOCK); }

5.4.5.2 表达式

任何对计算有效的语句都对共识步骤有效。但也允许一些额外表达式。

5.4.5.2.1 this

共识步骤中,this 指执行共识转移的参与者的地址。当共识转移是由 race 表达式发起时,这种方式会很有用。

5.4.5.2.2 transfer

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

transfer 表达式的格式为: transfer(AMOUNT_EXPR).to(PART) 其中:

transfer 表达式也可以写成: transfer(AMOUNT_EXPR,TOKEN_EXPR).to(PART) 其中 TOKEN_EXPR 是 Token 类型,用于指定转移哪一种非网络代币

transfer 表达式只能存在于共识步骤中。

5.4.5.2.4 checkCommitment

checkCommitment( commitment, salt, x )

制作一个要求,要求 commitment 是 salt 和 x 的摘要。该表达式用于 makeCommitment 已经在本地步骤中被使用过后的共识步骤中。

5.4.5.2.5 Remote 对象

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

远程对象(即 Remote 对象)是 Reach 应用程序中外来合约的表现形式。在共识步骤期间,Reach 计算可以通过规定的接口一致地与该对象通信。

远程对象是通过调用带有地址和接口的 remote 函数来构造的,该接口是一个对象,其中每个键都绑定一个函数类型。例如:

const randomOracle =  remote( randomOracleAddr, {    getRandom: Fun([], UInt),  }); const token =  remote( tokenAddr, {    balanceOf: Fun([Address], UInt),    transferTo: Fun([UInt, Addres], Null),  });

一旦构建,远程对象的字段就代表了那些远程合约交互,称之为远程函数。例如示例中的 randomOracle.getRandom 、token.balanceOf 和 token.transferTo 就是远程函数

远程函数可以通过使用合适的参数,然后返回指定输出的方式来调用。此外,远程函数可以通过以下操作之一进行扩展: REMOTE_FUN.pay(AMT)—返回一个远程函数,当该函数被调用时,从调用方接收 AMT 个网络代币

REMOTE_FUN.bill(AMT)—返回一个远程函数,当该函数返回时,向调用方提供 AMT 个网络代币

REMOTE_FUN.withBill()—返回一个远程函数,该函数返回时,向调用方提供一定数量的网络代币。确切的数量会以元组打包原始结果的方式返回,其中第一个元素是数量,第二个元素是原始结果。例如: const [ returned, randomValue ] =  randomOracle.getRandom.pay(stipend).withBill()(); 这算是一种与随机预言机通信的方法,该预言机获得实际成本的保守近似值(即实际应支付的值),并返回未被使用部分。这种返回未使用部分的情形无法通过 REMOTE_FUN.bill 获得。

远程对象目前还不支持非网络代币的交互。

5.4.5.2.6 映射:创建与修改

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

在共识步骤中可以通过 new Map(TYPE_EXPR) 的方式创建新的线形状态映射,其中 TYPE_EXPR 是某种类型。

创建映射的表达式会返回一个值,该值可通过 map[ADDR_EXPR] 对特定映射进行间接引用,其中 ADDR_EXPR 是一个地址。这样间接引用返回的值类型可能为 (TYPE_EXPR) ,因为映射可能不包含 ADDR_EXPR 的值。

可以通过 map[ADDR_EXPR]=VALUE_EXPR 来对 ADDR_EXPR 设定 VALUE_EXPR(类型为 TYPE_EXPR ),或通过 delete map[ADDR_EXPR] 来删除已添加的映射。这类修改只可存在于共识步骤中。

5.4.5.2.7 集合:创建与修改

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

Set (即集合)是线形状态的另一种容器。它仅仅是 Map(Null) 的别名;并且它仅在追踪地址时有用。由于 Set 本质是 Map,因此它也只能存在于共识步骤中。  

Set 可以通过 s.insert(ADDRESS) 在集合 s 内部设置 ADDRESS 的方式进行修改。或者通过 s.remove(ADDRESS) 从集合中移除 ADDRESS 。这类修改只能存在于共识步骤中。

s.member(ADDRESS) 会返回一个 Bool 值,代表该地址是否存在于集合中。