Reach 主题专栏 | while & parallelReduce

Reach 主题专栏系列文章围绕单主题解说并举例,本片文章主题为While和parallelReduce。

本文由 Reach X 1Circle Winter Camp 的 问题不大队伍编写

While

语法

var LHS = INIT_EXPR;
DEFINE_BLOCK; //optional
invariant(INVARIANT_EXPR);
while( COND_EXPR ) BLOCK

while是处于共识步骤中语句,

第一行用于定义一个变量,

第二行DEFINE_BLOCK可以定义使用上一行定义的变量的值的绑定的绑定。

invariant(INVARIANT_EXPR)中的INVARIANT_EXPR表示循环中的不变表达式,每次在执行BLOCK之前和之后都必须为trueCOND_EXPR为循环条件,如果为true,则执行后面的BLOCK。

范例

index.rsh

// 改编自教程2.7-Play and Play Again
// 这个方案是Alice和Bob都随机出一个数,比大小,类似于石头剪刀布
'reach 0.1';

const [ isOutcome, A_WINS, DRAW, B_WINS ] = makeEnum(3);
// 修改胜者条件及返回值
const winner = (numberAlice,numberBob) => (numberAlice>numberBob?0:numberAlice<numberBob?2:1);

forall(UInt, numberAlice =>
  forall(UInt, numberBob =>
    assert(isOutcome(winner(numberAlice, numberBob)))));

forall(UInt, (number) =>
  assert(winner(number, number) == DRAW));

const Player = {
  ...hasRandom,
  getNumber: Fun([], UInt),
  seeOutcome: Fun([UInt], Null),
  informTimeout: Fun([], Null),
};

export const main = Reach.App(() => {
  const Alice = Participant('Alice', {
    ...Player,
    wager: UInt,
    deadline: UInt,
  });
  const Bob   = Participant('Bob', {
    ...Player,
    acceptWager: Fun([UInt], Null),
  });
  init();

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

  Alice.only(() => {
    const wager = declassify(interact.wager);
    const deadline = declassify(interact.deadline);
  });
  Alice.publish(wager, deadline)
    .pay(wager);
  commit();

  Bob.only(() => {
    interact.acceptWager(wager);
  });
  Bob.pay(wager)
    .timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout));  // 发起共识转移
    // while是一个共识步骤 前仅有一个变量var,如有多个值使用[] ,一个不变量 invariant
    var outcome = DRAW;  // 定义一个标识符
    invariant( balance() == 2 * wager && isOutcome(outcome) ); // 不变量,运行时检查是否满足
    while ( outcome == DRAW ) {
      commit();

      Alice.only(() => {
        const _numberAlice = interact.getNumber();
        const [_commitAlice, _saltAlice] = makeCommitment(interact, _numberAlice);
        const commitAlice = declassify(_commitAlice);
      });
      Alice.publish(commitAlice)
        .timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout));
      commit();

      unknowable(Bob, Alice(_numberAlice, _saltAlice));
      Bob.only(() => {
        const numberBob = declassify(interact.getNumber());
      });
      Bob.publish(numberBob)
        .timeout(relativeTime(deadline), () => closeTo(Alice, informTimeout));
      commit();

      Alice.only(() => {
        const saltAlice = declassify(_saltAlice);
        const numberAlice = declassify(_numberAlice);
      });
      Alice.publish(saltAlice, numberAlice)
        .timeout(relativeTime(deadline), () => closeTo(Bob, informTimeout));
      checkCommitment(commitAlice, saltAlice, numberAlice);

      outcome = winner(numberAlice, numberBob);
      continue;
    }

    assert(outcome == A_WINS || outcome == B_WINS);
    transfer(2 * wager).to(outcome == A_WINS ? Alice : Bob);
    commit();

  each([Alice, Bob], () => {
    interact.seeOutcome(outcome);
  });
});

index.mjs

import { loadStdlib } from '@reach-sh/stdlib';
import * as backend from './build/index.main.mjs';
const stdlib = loadStdlib(process.env);

   (async () => {
      const startingBalance = stdlib.parseCurrency(100);
      const accAlice = await stdlib.newTestAccount(startingBalance);
      const accBob = await stdlib.newTestAccount(startingBalance);

      const fmt = (x) => stdlib.formatCurrency(x, 4);
      const getBalance = async (who) => fmt(await stdlib.balanceOf(who));
      const beforeAlice = await getBalance(accAlice);
      const beforeBob = await getBalance(accBob);

      const ctcAlice = accAlice.deploy(backend);
      const ctcBob = accBob.attach(backend, ctcAlice.getInfo());

      const OUTCOME = ['Alice wins', 'Draw', 'Bob wins'];

      const Player = (Who) => ({
            ...stdlib.hasRandom,
            getNumber: async () => {
            const number = Math.floor(Math.random() * 3);
            console.log(`${Who} played ${number}`);

                 return number;
                },
                seeOutcome: (outcome) => {
                console.log(`${Who} saw outcome ${OUTCOME[outcome]}`);
               },

             });

             await Promise.all([
                    ctcAlice.p.Alice({
                     ...Player('Alice'),
                      wager: stdlib.parseCurrency(5),
                      deadline: 10,
                    }),
                    ctcBob.p.Bob({
                      ...Player('Bob'),
                      acceptWager: (amt) => {
                        console.log(`Bob accepts the wager of ${fmt(amt)}.`);
                      },
                    }),
                  ]);

    const afterAlice = await getBalance(accAlice);  
    const afterBob = await getBalance(accBob);  

    console.log(`Alice went from ${beforeAlice} to ${afterAlice}.`);  
    console.log(`Bob went from ${beforeBob} to ${afterBob}.`);  

    })(); // <-- Don't forget these!

运行结果 pic1.jpg 第一轮双方都出的数字0打成平手,继续while循环,直到不为平局跳出循环

parallelReduce

语法

const LHS =
    parallelReduce(INIT_EXPR)
    .define(() => DEFINE_BLOCK)
    .invariant(INVARIANT_EXPR)
    .while(COND_EXPR)
    .paySpec(TOKENS_EXPR)
    .case(PART_EXPR,
    PUBLISH_EXPR,
    PAY_EXPR,
    CONSENSUS_EXPR)
    .api(API_EXPR,
    ASSUME_EXPR,
    PAY_EXPR,
    CONSENSUS_EXPR)
    .timeout(DELAY_EXPR, () =>
    TIMEOUT_BLOCK);

parallelReduce同样是处于共识步骤中,LHS表示初始化的变量,INIT_EXPR是初始化变量的值,DEFINE_BLOCK也是类似于while中可供选择,

.invariant(INVARIANT_EXPR)表示循环不变量,每次执行前后都必须为true

.while(COND_EXPR)表示循环条件,如果为true开启循环。.case.api.timeout.paySpec组件就像fork语句的相应组件。

.case可以重复多次

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

.timeRemainingparallelReduce中处理截止时间时,TIMEOUT_BLOCK中有一种常见的模式,让各个participant进行racepublish后返回一个累加器

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

.throwTimeout是一种简写方法,当发生超时时,它将把累加器作为异常抛出。因此,使用此分支的parallelReduce必须位于 try 语句内部。

范例

index.rsh

'reach 0.1';
'use strict';
// 截止日期将用UInt表示,因为它是一个相对时间增量,表示块编号的变化。
// 将ticketPrice用UInt 32表示
// 买票的决定将由函数Fun ([UInt], Bool) 
// 我们的参与者交互界面,加上一些方便的日志功能,目前看起来是这样的:
const Common = {
  // 显示获胜者地址
  showOutcome: Fun([Address], Null),    
};
const Deployer = {
  ...Common,
  getParams: Fun([], Object({          
    deadline: UInt,
    itemPrice: UInt,
  })),
};
const Consumer = {
  ...Common,
  shouldBuyItem: Fun([UInt], Bool),
  showPurchase: Fun([Address], Null),
};

export const main = Reach.App(
  { },
  [
    Participant('deployer', Deployer),
    ParticipantClass('consumer', Consumer),
  ],
  (deployer, consumer) => {
    const showOutcome = (who) =>
      each([deployer, consumer], () => {  //向所有人显示结果的助手
        interact.showOutcome(who); });
        deployer.only(() => {
      const { itemPrice, deadline } =
        declassify(interact.getParams());
    });
    // 部署人设置商品价格和截止日期
    deployer.publish(itemPrice, deadline);
    // parallelReduce部分,允许消费者购买直到超时
    const [ keepGoing, winner, itemsSold ] =
      parallelReduce([ true, deployer, 0 ])
        .invariant(balance() == itemsSold * itemPrice)  // 不变量
        .while(keepGoing) //循环条件
        //.case组件,就像fork语句中的对应组件根据不同情况执行不同代码
        .case(
          consumer,
          () => ({
            when: declassify(interact.shouldBuyItem(itemPrice)),// 设置解密时间
          }),
          (_) => itemPrice, // 门票价格
          (_) => {
            const myconsumer = this;
            consumer.only(() => interact.showPurchase(myconsumer));
            return [ true, myconsumer, itemsSold + 1 ];
          }
        )
        //.throwTimeout是一种简写,当发生超时时将把累加器作为异常抛出
        .timeout(relativeTime(deadline), () => {
          Anybody.publish();
          return [ false, winner, itemsSold ]; });
          // 最后买的人获胜并获得余额
    transfer(balance()).to(winner);
    commit();
    showOutcome(winner);
  });

index.mjs

import {loadStdlib} from '@reach-sh/stdlib';
import * as backend from './build/index.main.mjs';

const numOfConsumers = 10;  // 设置消费者数量为10
(async () => {
  const stdlib = await loadStdlib();
  const startingBalance = stdlib.parseCurrency(100);
//为资助者和任意数量的买家创建测试账户。买家购买门票的决定将仅仅依赖于生成一个随机布尔值
  const accDeployer = await stdlib.newTestAccount(startingBalance);
  const accConsumerArray = await Promise.all(
    Array.from({ length: numOfConsumers }, () =>
      stdlib.newTestAccount(startingBalance)
    )
  );

  const ctcDeployer = accDeployer.contract(backend);
  const ctcInfo   = ctcDeployer.getInfo();
// 部署者参数
  const deployerParams = {
    itemPrice: stdlib.parseCurrency(5),
    deadline: 5,
  };
// 等待后端完成
  await Promise.all([
    backend.deployer(ctcDeployer, {
      showOutcome: (addr) => console.log(`Deployer saw ${stdlib.formatAddress(addr)} won.`),
      getParams: () => deployerParams,
    }),
  ].concat(
    accConsumerArray.map((accConsumer, i) => {
      const ctcConsumer = accConsumer.contract(backend, ctcInfo);
      return backend.consumer(ctcConsumer, {
        showOutcome: (outcome) => {
          console.log(`Consumer ${i} saw they ${stdlib.addressEq(outcome, accConsumer) ? 'won' : 'lost'}.`);
        },
        shouldBuyItem : () => Math.random() < 0.5,
        showPurchase: (addr) => {
          if (stdlib.addressEq(addr, accConsumer)) {
            console.log(`Consumer ${i} got a item.`);
          }
        }
      });
    })
  ));

})();

运行结果 pic2.jpg

deadline到之前最后一位要买票的消费者是3号,故3号消费者won

本文参与2022世界杯预选赛赛程直播社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Ivan
Ivan

Reach Developer Evangelist

14 篇文章, 248 学分