In general, the winner of this pushup competition can't be determined until both participants are finished.
有时候仅当从两个以上不同的数据源获取数据后,才会开始计算。例如,在你得知了你的彩票号码和中奖号码之前,你无法知道你是否赢得大奖。在你知道购买物品价格和购买总额之前,你无法进行购买。在你知道每个参赛者做了多少个俯卧撑前,你无法知道谁赢得俯卧撑比赛。
rholang提供了Join操作,来应对这种情况。使用;符号来执行一次Join操作。
for (p1Pushups <- @"player1"; p2Pushups <- @"player2") { @"stdout"!("The winner is...") }
火箭发射
一家太空探索公司想要确保,仅当两个航空工程师,Alice和Bob,都下达了发射命令后,他们的火箭才会发射。例如,Bob将通过发送BobLaunch!("launch")来下达命令。当两位工程师都下达了命令,那么火箭便可以发射。
练习
思考一下,使用我们刚提到的Join操作符,应该怎么写这个代码呢?
错误的方式
下面的例子中,其中一人先收到发射指令,并尝试处理火箭发射问题,然后再轮到另一个人。
launchBad.rho
问题在于,当Alice批准发射,而Bob还没有,Alice应该能够更改她的指令,但在此例中她不行。设想一下,如果她突然发觉火箭有一个问题,或者收到了一些不好的消息,想要停止发射。
No use in grabbing just one set of mail. Might as well wait until the second set
当使用Join时,她依然可以更改她的决定,因为for只会在双方的消息都进入通道并准备好后,才会开始取出双方的消息。
发射的解决方案
launch.rho
下列哪一段代码是Alice所需,用以撤销发射命令的?
@"AliceCancel"!("cancelZ")
@"AliceLaunch"!("cancel")
for (x <- @"AliceLaunch"){Nil}
Join的概念起初是在哲学家进餐问题中被提出,并且在这篇简短的rholang教程中(更详细的解释)[developer.rchain.coop/tutorial/#d…"]。
在for (x <- y; a <- b){ Nil }中, 应该优先向哪一个通道发送消息?
y
b
无所谓
同时被发送
在for (x <- y; a <- b){ Nil }中, 哪一条消息被优先取出?
x
a
无所谓
会被同时取出
练习
有一个比赛,两名选手将各自在各自的通道发送消息。谁第一个发送了消息,谁就输掉比 赛,第二个发送消息的人获胜。你的任务是写一段代码告诉我们谁赢了。参赛选手应按如下方式发送消息。
P1!("Send any message") P2!("Hope I win")
在这场需要靠耐心获胜竞赛这一例子中,我们不使用求并运算,因为我们在意哪个选手先行动。希望你没有陷入我的陷阱中;)
patienceSolution.rho
正如注释所说,你应该使用REPL模式运行上面的代码,然后用两种不同的顺序来发送的消息确保两个选手都获胜一次。另一个方案如下所示,让一个玩家去通知另一个玩家何时执行。我们将在下一节继续研究这种方法。
P1First.rho
在上面我们写的代码中,为什么可能出现没有人赢得这场耐心比赛?
因为两名选手可以同时发送消息
选手们在错误的通道发送消息
第一个块接收P2,而第二个块接收P1,所以代码并不能保证游戏完成
课程6 -- 不可伪造的Names和Acks
使通道"私有"
A competing pizza shop steals orders because the channel isn't secure.
到目前为止,每一个我们发送信息的通道都是公共的"name",如@"pizzaShop"。 任何一个人都可以往这个通道发送信息(可能对于某些商用行为是好的),但是任何一个人也可以从这个通道中获取信息(这对于一些商业就很糟糕了)。想象一下如果竞争者可以从披萨店中获取他们的披萨订单让披萨店无法获取他们的订单,那肯定十分糟糕。
披萨店的竞争者需要什么样的代码来窃取披萨点的订单?
contract evilPizzaShop(interceptedMessage) = {Nil}
@"evilPizzaShop"!("pizzaShop")
@"pizzaShop"!("intercept")
for (interceptedMessage <- @"pizzaShop"){...}
绑定和自由的Names
上面我们学习到如何通过for和contract获取信息。这两种方式都构造出"绑定的"“names”。举个下面例子,order就是在咖啡店代码里一个绑定的"name"。
bound1.rho
当我们使用contract语法的时候也是一样的。
bound2.rho
如果一个"name"存在在一个特定的"process"中并且不能被"process"外部访问,我们就认为一个"name"是绑定的。所以"name" order是绑定在咖啡代码中。另一方面,在上面的例子中,任何一个能从别的地方访问的"name"都是"自由的"“name”。在上面的例子中,@"coffeeShop" 是一个自由的"name"。
指出下面每段代码中 x 是绑定的还是自由的。
for (x <- y){Nil}
绑定的
自由的
都不是
for (y <- x){Nil}
绑定的
自由的
都不是
new x in { x!(true) }
绑定的
自由的
都不是
contract x(y) = { Nil }
绑定的
自由的
都不是
contract y(x) = { Nil }
绑定的
自由的
都不是
for (y <- @"x"){Nil}
绑定的
自由的
都不是
new操作符
for 和 contract都是在连续计算中绑定"name"的完美方法。但是如果我们想要创建一个绑定的"name"用于发送? 举个例子,我们的披萨店不想让自己的订单被人截取。我们通过new操作符解决这个问题。
newPizzaShop.rho
首先要注意到 pizzaShop 是一个"name"即使它不是以 @开始。
那是因为new操作符直接把它创造为一个"name"而不是一个引号括起的"process"。无论你如何使用new创造一个"name", 它总是一个绑定的"name"。
然后,注意这种方法不仅可以阻止其它披萨店获取订单,还阻止新的客户下订单。我们将会在bundles教程中解决这个问题。
当你在new 限制范围外尝试下订单会发生什么事情。
订单正常发送
订单正常发送但是需要更长时间
出现关于顶层自由变量的错误
代码可以运行,但是没有订单成功被接受不了
我们学习到所有的"name"可以通过用@标记转化为"process"。所以 pizzaShop这个"name"通过@转化后是一个什么样的"process"? 尝试将那个"process"打印到stdout 看看。
@标记的"pizzaShop"
并没有任何标记
"一些不可以伪造的16进制代码"
私有 vs 不可伪造
Although the messages can no longer be stolen, they can still be eavesdropped on. You've been warned.
new 是一个限制操作符因为它把自己创建的绑定的"names"限制在它的花扩话中或者说"词法范围"内. 在rholang的世界里,这些新建的"names"就只能在确定的范围内可见,但是记住,程序员可以从外部世界中查找到这些"names"。当你在区块链环境工作中尤其要注意。
所以说,虽然竞争的披萨店不再可能窃取 本来给我们店的披萨订单,但是他们仍然可以在区块链浏览器中知道我们这些订单的信息。有些情况下,一些程序员会把new 创建的"names"称为 "私有的", 但是一个更恰当的词应该是 "不可伪造的(unforgeable)", 这就能解释前面的问题了。
我们前面用到了 new 来阻止元组空间被污染. 为什么使用不可伪造的"names"可以让我们避免每个合约跑之前都清理一次元组空间?
因为 new 创建自由的"names"
因为 new 创建出不可伪造的"names",它们不能被外部代码使用
因为 new 自动清理元组空间
确认通道
We acknowledge communications all the time in conversations
不可伪造"names"一个通用的用法就是 "确认通道", 简称为"ack" 通道. 披萨店可以仅仅让顾客知道订单已经被下达,而不是通过打印到屏幕让每一个人都知道来确认订单。
为了能实现这个方法,披萨点需要知道如何联系客户。所以客户需要提供一个确认通道来回调。通常这样的通道被称为ack.
pizzaAck.rho
为什么前面例子的确认信息并没有显示在屏幕上?
代码中有错误
订单没有正确被接收
确认信息没有发送到stdout
练习
之前的例子会导致元组空间中的@"Alice" 和 @"Bob"通道被污染.修改它,让Alice 和 Bob 各自有自己的不可伪造的"name".
给发送的"names"权限
我们刚刚看到顾客如何给出一个ack通道来获取订单确定信息. 其实我们可以做得更好. 在我们之前的代码,任何一个人都可以在ack通道中联系客户. 那意味着任何一个人都可以发送一个伪造的ack通道给客户让客户认为订单已经下发成功,但是实际上并没有。所以Alice 和 Bob 真的需要严格保管他们的不可伪造的"names". 因为给别人那个"name"就意味着别人可以联系你。
privateAck.rho
解决方案是创建一个新的不可伪造的"name",然后发送它到披萨店以至于只有他们可以回复你。即使披萨店是在new alice的外面, 它仍然可以在那个通道上发送信息因为Alice给了通道名字。这是一个很好的方法来委派权限。
在这个例子中,我们相信披萨店只会在ack通道中 发送 ,但是要注意它也又可能是在通道中接收信息,如果它想要的话。我们将在下一节bundles中学习如何只给出一部分的权限出来。
Bob也想要订一份披萨,给出一个不可伪造的ack通道。我们应该在哪里创建他自己的不可伪造的通道?
在他自己的那行,alice代码后面
在Alice同一行
在程序代码的第一行
stdoutAck 和 stderrAck
现在既然你知道了ack通道, 那么你应该要知道其它两种打印到屏幕的方法.它们是叫做stdoutAck 和 stderrAck的通道. 他们就像第一课说的stdout一样工作,但是他们需要一个ack通道。
stdoutAck.rho
顺便说一句,你注意到每次启动一个新的元组空间都有一堆东西么?这些东西其中4个东西是内置的用于接受屏幕打印的通道。另外一些是用于加密的。我们将在以后讨论到。
练习
stdout!("1")|stdout!("2")|stdout!("3")
注意这段程序不会按照一定的顺序打印出数字。他们是同时发生的。想象我们现在真的要按照顺序打印几行。修改代码,使用ack通道来保证数字按顺序打印出来。
练习
预测这个程序怎么运行(它会输出什么,它在元组空间怎么简化计算。)然后运行它来检测你的预测。
new myChan in { myChan!("Hi There") } | for (msg <- myChan) {stdout!(*msg)}
如果你对上面的程序预测失败,修改程序,让程序按照你的想法运行。
提问
在 for(x <- y){Nil}中哪个name是绑定的
x
y
Nil
在 new x in {Nil}哪个"name"是绑定的
x
y
Nil
如果 pizzzaShop 是一个"name", 那么 @pizzaShop是什么?
一个name
一个process
无效的语法
为什么pizzaShopAck 代码发送 "bob" 作为一个ack通道而不是@"bob"?
没有原因; 就是一种风格。
因为 @"bob" 是一个name, 但是我们必须发送processed。
那是给ack通道用的特别语法。
``