Reach 主题专栏系列文章围绕单主题解说并举例,本片文章主题为While和parallelReduce。
本文由 Reach X 1Circle Winter Camp 的 问题不大队伍编写
var LHS = INIT_EXPR;
DEFINE_BLOCK; //optional
invariant(INVARIANT_EXPR);
while( COND_EXPR ) BLOCK
while
是处于共识步骤中语句,
第一行用于定义一个变量,
第二行DEFINE_BLOCK
可以定义使用上一行定义的变量的值的绑定的绑定。
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<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!
运行结果 第一轮双方都出的数字0打成平手,继续while循环,直到不为平局跳出循环
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 ])
...
.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.`);
}
}
});
})
));
})();
运行结果
deadline到之前最后一位要买票的消费者是3号,故3号消费者won
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!