Reach 主题专栏 | while & parallelReduce

• Ivan
• 更新于 2022-01-26 23:51
• 阅读 2210

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

While

语法

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

`while`是处于共识步骤中语句，

`invariant(INVARIANT_EXPR)`中的`INVARIANT_EXPR`表示循环中的不变表达式，每次在执行BLOCK之前和之后都必须为`true``COND_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&lt;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,
});
const Bob   = Participant('Bob', {
...Player,
acceptWager: Fun([UInt], Null),
});
init();

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

Alice.only(() => {
const wager = declassify(interact.wager);
});
.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)
commit();

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

Alice.only(() => {
const saltAlice = declassify(_saltAlice);
const numberAlice = declassify(_numberAlice);
});
Alice.publish(saltAlice, numberAlice)
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';

(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),
}),
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}.`);

})(); // &lt;-- Don't forget these!``````

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 ]; })``````

`.timeRemaining``parallelReduce`中处理截止时间时，`TIMEOUT_BLOCK`中有一种常见的模式，让各个participant进行`race``publish`后返回一个累加器

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

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

范例

index.rsh

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

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());
});
// 部署人设置商品价格和截止日期
// parallelReduce部分,允许消费者购买直到超时
const [ keepGoing, winner, itemsSold ] =
parallelReduce([ true, deployer, 0 ])
.invariant(balance() == itemsSold * itemPrice)  // 不变量
.while(keepGoing) //循环条件
//.case组件,就像fork语句中的对应组件根据不同情况执行不同代码
.case(
consumer,
() => ({
}),
(_) => itemPrice, // 门票价格
(_) => {
const myconsumer = this;
consumer.only(() => interact.showPurchase(myconsumer));
return [ true, myconsumer, itemsSold + 1 ];
}
)
//.throwTimeout是一种简写，当发生超时时将把累加器作为异常抛出
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 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),
};
// 等待后端完成
await Promise.all([
backend.deployer(ctcDeployer, {
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() &lt; 0.5,
console.log(`Consumer \${i} got a item.`);
}
}
});
})
));

})();``````