HI

Rholang

为你打开Rholang世界的大门

V1.0

中文版

Rholang 简介

 

Rholang是一门基于Rho calculus (Rho 演算) 的并发语言,专为分布式系统设计。

 

Rholang程序是完全异步的,由消息传输机制驱动执行。

 

Rholang不只是一门智能合约语言,可用于其它计算环境中。

 

Rholang 源于 Rho Calculus

 

传统的计算机语言基于图灵机和自动机的概念,适于描述单个自动机的行为。

 

而Pi Calculus(Pi演算)则为开放而自由的网络而生,描述分布式的网络环境。

 

Rho calculus是在Pi Calculus的基础上,增加了Name(名字)和Process(过程)的转换方法,称之为Reflection(反射性)。

 

Rho calculus

P,Q,R ::=0                                      //nil or stopped process
  | for ( ptrn1 <- x1; ... ; ptrnN <- xN ).P    //input guards
  | x!( Q )                                     //output
  | \*x\                                        //dereferenced or unquoted name
  | P|Q                                         //parallel composition
x,ptrn ::= @P                                   //name or quoted process 

目录

Rholang环境准备

方法1:在本地运行Rnode节点 (推荐)

1.本地运行Rnode节点(以Mac为例),下载最新版的Rnode客户端,下载地址:https://developer.rchain.coop/。(注意请下载文件名为 .TGZ的客户端,本教程中使用的均为该客户端)

2.打开“终端”,通过命令进入解压缩后的文件夹,找到bin文件夹并进入。

3.在bin文件夹下通过命令:./rnode run -s -n 来运行一个单独的本地节点(Stand Alone)。

4.将写好的智能合约存入文件夹,通过命令:./rnode eval /路径/xxx.rho运行智能合约。

Cryptofex可以在Windows、Mac和Linux上本地运行,下载地址:https://cryptofex.io/

方法2:Cryptofex IDE

方法3:使用Rholang在线Interpreter 

在线Interpreter不需要配置任何环境就可以直接在网页端写出智能合约并运行,网页链接:https://rchain.cloud

Send & Receive

01

Send

1. a!(P)指Send一次,a!!(P) 指持续Send

 

2. Send 可以理解为:一个Process被发送/持续发送到目标Name中,换个说法是:目标Name被放入/持续放入了一个Process

 

3. 一个最常见的Send:stdout!(P),作用是:打印到屏幕

 

4. stdout(`rho:io:stdout`)是Rholang的Power Box,相当于标准库

new HelloWorld, stdout(`rho:io:stdout`) in {
    HelloWorld!("Hello, world!")|
    for (@text <- HelloWorld) {
        stdout!(text)
    }
}

//运行结果:屏幕上出现“Hello,world!”

Send

Receive

1. for (a <- b) {Q} 指Receive一次,即:目标Name里的Process被取走后,触发process Q

 

2. for (a <= b) {Q} 指持续Receive,即:只要目标Name里有Process,就被取走,取走后触发Process Q

 

3. contract b(a) = {P}是for (a <= b) {P}的语法糖

//用for接收消息
new pizzaShop, stdout(`rho:io:stdout`) in {
    pizzaShop!("2 medium pies")|
    pizzaShop!("5 medium pies")|
    for (order <- pizzaShop) {
        stdout!("Order Received.")
    }
}

//用for持续接收消息
new pizzaShop, stdout(`rho:io:stdout`) in {
    for (order <= pizzaShop) {
        stdout!("Pizza Order Received")
    }|
    pizzaShop!("one hot chocolate")|
    pizzaShop!("two large latte's please")
}

//用contract持续接收消息
new coffeeShop, stdout(`rho:io:stdout`) in {
    contract coffeeShop(order) = {
        stdout!("Coffee Order Received")
    }|
    coffeeShop!("one hot chocolate")|
    coffeeShop!("two large cappuccinos please")
}

#5:即使pizzaShop被放入了两个Process,for也只能取走一个

 

#12:只要 pizzaShop被放入了Process,就会被取走

 

#21:只要coffeeShop 被放入了Process,contract coffeeShop就被触发,Process就会被取走

Receive

如何更深入地理解Send 和 Receive?(难点)

1. a!(Q)

Process Q 向 Name a的传递

 

2. for (@c <- a ) {P}

a里面的Process被传递到了@c这个Name里     

a里的Process被消耗,同时c被赋值(bind)为a里的Process,可以直接使用,@c 成为一个有作用域的Name   

new a, stdout(`rho:io:stdout`) in {
    a!(1)|
    for (@b <- a) {
        for (@c <- @b) {
            stdout!([c, "index1"])
        }|
        stdout!([b,"index2"])
    }
}

3. a!(Q)|for (@c < -a) {P}

包含同一个Name的一次Send 和 一次Receive 组成了一个Comm Event

//运行结果是:[1, "index2"]
//@b里没有Process,所以@c取不到值

for (p1Pushups <- player1; p2Pushups <- player2) {
    stdout!("The winner is...")
}

很多时候,要得到两个或更多数据源的数据才能够执行一个计算。比如,两个运动员赛跑,要得到他们两个的成绩之后,才能判断输赢。

 

在Rholang里,这种情况要使用join运算符,也就是#1中两个receive中间的

Join Operator

new aliceLaunch, bobLaunch, stdout(`rho:io:stdout`) in {
    for (x <- aliceLaunch) {
        for (y <- bobLaunch) {
            stdout!("Launching the rocket")
        }
    }|
    aliceLaunch!("launch")|
    bobLaunch!("launch")
}

错误实现   

一旦Alice发现问题,想要取消发送指令,她无法做到终止发射。

例子1: Rocket Launch

一架火箭准备被发射,程序设定,只有它在收到Alice 和 Bob 两个飞行员发出的发射指令后,才能被触发发射

Join Operator

new aliceLaunch, bobLaunch, stdout(`rho:io:stdout`) in {
    for (x <- aliceLaunch; y <- bobLaunch) {
        stdout!("Launching the rocket")
    }|
    aliceLaunch!("launch")|
    bobLaunch!("launch")
}

正确实现   

#2中,x、y都Receive到指令后才能触发#3,在那之前,aliceLaunch 和 bobLaunch里的消息均不会被取走。

例子1: Rocket Launch

Join Operator

两名玩家独立发送消息,谁的消息先被收到,谁就输了

例子2:Patience Game

错误实现   

new p1, p2, stdout(`rho:io:stdout`) in {
    p1!("Send any message")|
    p2!("Hope I win")|
    for (m2 <- p2) {
        for (m1 <- p1) {
            stdout!("Player one wins!")
        }
    }|
    for (m1 <- p1) {
        for (m2 <- p2) {
            stdout!("Player two wins!")
        }
    }
}

#4和#9并行,此时,p1、p2里的消息都已经被取走,#5和#10没有一个能被触发。

Join Operator

例子2:Patience Game

正确实现   

new p1, p2, a, stdout(`rho:io:stdout`) in {
    p1!("Send any message")|
    p2!("Hope I win")|
    a!(1)|
    for (m2 <- p2; _ <-a) {
        for (m1 <- p1) {
            stdout!("Player one wins!")
        }
    }|
    for (m1 <- p1; _ <-a) {
        for (m2 <- p2) {
            stdout!("Player two wins!")
        }
    }
}

a的引入起到了“开关”的作用,配合Join运算符的使用,实现程序目的

Join Operator

例子3: Dining Philosophers and Deadlock

什么是Deadlock?

 

多个进程或线程相互等待对方的资源,在等到对方资源之前,也不会释放自己的资源,这样造成循环等待的现象,若无外力作用,他们都无法推进下去,这样就产生了deadlock。

 

Dining Philosophers 就是非常经典的Deadlock问题。

 

在下面的案例中,把Dining Philosophers简单化:两名哲学家,分别坐在桌子的东边和西边,共享一副刀叉,而同时拥有刀叉的一方方能就餐。

 

Join Operator

new philosopher1, philosopher2, north, south, knife, spoon in {
    north!(*knife)|
    south!(*spoon)|
    for (@knf <- north) {
        for (@spn <- south) {
            philosopher1!("Complete!")|
            north!(knf)|
            south!(spn)
        }
    }|
    for (@spn <- south) { 
        for (@knf <- north) {
            philosopher2!("Complete!")|
            north!(knf)|
            south!(spn)
        } 
    }
}

错误实现   

例子3: Dining Philosophers and Deadlock

Join Operator

new philosopher1, philosopher2, north, south, knife, spoon in {
    north!(*knife)|
    south!(*spoon)|
    for (@knf <- north; @spn <- south) {
        philosopher1!("Complete!")|
        north!(knf)|
        south!(spn)
    }|
    for (@spn <- south; @knf <- north) {
        philosopher2!("Complete!")|
        north!(knf)|
        south!(spn)
    }
}

正确实现   

例子3: Dining Philosophers and Deadlock

Join Operator

并行 和 串行

02

什么是并行

 

在Rholang中,所有的Process都是并行的,Process之间用|来连接。

new a in {
    a!(1)|
    a!(2)|
    for(@x<-a){Nil}|
    if(P){Q}
    else{R}|
    contract a(b)={P}
}

并行的特点

new a, stdout (`rho:io:stdout`) in{
    a!(1)|
    a!(2)|
    a!(3)|
    a!(4)|
    a!(5)|
    for(@x<=a){
        stdout!(x)
    }
}

 

并行有两个特点:

1.所有Process都会执行。

2.并行执行没有先后顺序,是随机的。

 

#2 #3 #4 #5 #6 #7是并行的,所以输出顺序是随机的。

例子1: 随机输出

new a,b,stdout (`rho:io:stdout`) in{
    a!(1)|
    for(@x<-a){
        b!(x+1)|
        stdout!(x)
    }|
    for(@y<-b){
        stdout!(y)
    }
}

Rholang中如何做到顺序执行

通过参数的传递来做到顺序执行。

例子2: 顺序输出

 

  • a里有Process, #3 才会执行
  • b里有Process, #6才会执行。

Process & Name

03

Process和Name

 

Process是一段Rholang代码,比如实现某种功能的完整代码,或是一段很短的代码片段。

举几个例子:

  • Send:stdout!(" Sup Rholang? ")
  • 最小的Process,表示什么都不做:Nil
  • 接到消息后什么都不做:for(msg <- phone){ Nil }
  • Ground Terms:“Hello World”、“Hi, Rholang”、1、 ......

 

All computation done by processes communicating over names

所以在Rholang中除了Process就是Name,且两者可以相互转换。

Name are quoted processes,Name又被称为Channel,用来传输和存储Process。

在Rholang中,有两种形式的name:

 

一种是使用new和for语法定义的Name,如

  • new a in { for ( b <- a) { Nil } } 中的a和b都是新定义的Name。

 

另一种是被引用的Process,用@标记,如

  • 引用了Ground Term的name:@“Hello World”
  • 最小的name:@Nil
  • 引用了具体的process的name:@P、@{a! (P)}、@{for (b <- a) {P}}

Process和Name

new alice, bob, stdout(`rho:io:stdout`) in {
    alice!("How to program: Change stuff and see what happens.")|
    for (message <- alice) {
        bob!(*message)
    }|
    for (message <- bob) {
        stdout!(*message)
    }
}

例子1:Telephone Game

Alice向Bob传递消息,Bob收到后将它打印出来。

alice、bob、message是Name;

“How to program: Change...... ”、*message以及整段代码是Process。

Process和Name

In rholang we send processes and receive names.

在 a! ( P ) 中,a是Name,P是Process;

在for(b <- a) { P }中,a和b是Name,P是Process。

例子中,哪些Name,哪些是Process?

 

用@符号来标记Process,就转换成了Name,如@P ;

用*符号标记来Name,就转换成了Process,如*x ;

Process通过Name传递。

Process与Name的转换

Process与Name的转换

process name
Nil @Nil
*importantData importantData
"BobsPhone" ackChannel
*@"BobsPhone" @*ackChannel
**name @@Nil
new a, b, stdout(`rho:io:stdout`) in {
    a!(*b)|
    b!(1)|
    for (@c <- a) {
        stdout!(c)
    }
}

//结果: Unforgeable(0x0ef84f0816679bafc4365da3e28d2ea9f674823f0dc7709dd3c74d2b8ac0a038)

a,b是name,c是process

 

#2 当使用Send语句时,b需要转化为process,被*标记

*b并不是对应b中存放的process,而是将b由name转换成process

 

#4 在for语法中,箭头左侧是name,c用@标记

Process与Name的转换

for (@n <- m) {stdout!(n)}

Process与Name的转换

通常,在for语句中,出现在箭头左侧的Name,如果更多的以Process的形式被传递和存储,则使用被引用的Process,如左侧例子中的@n。

 

同样的,在contract语句中,括号里面的Name,如果更多的以Process的形式被传递和存储,则使用被引用的Process,如右侧例子中的@a。

 

但当Name用来作为确认程序执行结束或返回结果用时(如ack、return),不需要进行Process与Name的转换,如右侧例子中的ack。

contract example(@a, ack) = {
    stdout!(a)|
    ack!(Nil)
}
new get, initial, stdout(`rho:io:stdout`) in {
    initial!(0)|
    contract get(@"new", @arg, ack) = {
        for (@old <- initial) {   
            initial!(arg)|
            ack!(old)  
        }
    }|
    new ack in {
        get!("new", 1, *ack) |
        for (_ <- ack) {
            stdout!("The process had finished.")
        } 
    }   
}

注意程序中get、initial、“new”、@arg、 ack、 @old的用法

Process与Name的转换

如果Process是等价的,那被引用后形成的Name就是等价的

 

1. @{P|Q}、@{Q|P}、@{P|Q|Nil}等价

是并行,没有先后顺序,所以P|Q、Q|P、P|Q|Nil 是等价的Process

 

2.@{ for (x <- chan) {P} } 与 @{ for (z <- chan) {P} } 等价

x、z是for 语句中的Bind Variable,所以for (x <- chan) {P}、for (z <- chan) {P}是等价的Process

Name等价

3. @{8+2} 与 @{5+5}等价

Name等价

new stdout(`rho:io:stdout`) in {
    for (a <- @{8+2}) {
        stdout!(*a)
    }|
    @{5+5}!("Hello")
}
Evaluating:
new x0 in {
  @{(5 + 5)}!("Hello") |
  for( @{x1} <- @{(8 + 2)} ) {
    x0!(x1)
  }
}
"Hello"

在名字被使用之前,先进行运算,运算结果(也是Process)等价,则被引用的形成的Name等价。运算包括:

 

算术运算:+, -, *, /

关系运算:>, >=, <, <=, ==, !=

逻辑运算符:and, or, not

匹配运算符:P match Q

Name 的类型

04

new pizzaShop, stdout(`rho:io:stdout`) in {
    contract pizzaShop(order) = {
        stdout!("Order Received.")
    }
}|
pizzaShop!("Extra bacon please")|
pizzaShop!("Hawaiian Pizza to go")

#1 pizzaShop只在new后面的{...}内起作用,在外部无法对pizzaShop进行操作

约束变量:指在某Process中定义,只在该Process之内起作用的Name。

约束变量和自由变量

Bind Variable & Free Variable

new a {P}
for(a<-b){P}
contract b(a)={P}

对于P来说,a均为约束变量,只在P内起作用

错误实现

#6 #7 pizzaShop在这里未被定义,会报错

提交订单

new pizzaShop, stdout(`rho:io:stdout`) in {
    contract pizzaShop(order) = {
        stdout!("Order Received.")
    }|
    pizzaShop!("Extra bacon please")|
    pizzaShop!("Hawaiian Pizza to go")
}

// 运行结果
// "Order Received."
// "Order Received."

正确实现

提交订单

约束变量和自由变量

Bind Variable & Free Variable

自由变量:指在某Process之外被定义,那么对该Process而言,这个Name是自由变量。

new b in {Q|for (a <- b) {P}}
new b in {Q|contract b(a) = {P}}

对于P来说,b均为自由变量。

在包含P的更大作用域内被定义,不仅在过程P内起作用,在Q内也起作用。

约束变量和自由变量

Bind Variable & Free Variable

所有Name在被定义时,被约束在其后的整个Process,但对于其内部的Process是自由变量

new a,stdout(`rho:io:stdout`) in {
    a!(Nil)|
    a!(Nil)|
    for (x <- a) {P}|
    contract a(y) = {Q}
}

a对于整个Process而言是约束变量,对于P、Q而言是自由变量

约束变量和自由变量

Bind Variable & Free Variable

for (x <- y){Nil}

约束

自由

都不是

 

for (y <- x){Nil}

 约束

 自由

都不是

 

new x in { x!(true) }

约束

自由

都不是

contract x(y) = { Nil }

约束

自由

都不是

 

contract y(x) = { Nil }

约束

自由

都不是

 

 

在以下Process中,x是什么变量

   

   

   

   

   

约束变量和自由变量

Bind Variable & Free Variable

  1. Unforgeable Name 是Object Capability Security Model  (OCAP) 里的一个核心概念

  2. 出于安全的考虑,在一个Process内部定义的Name不应该被外部访问到,除非有意通过消息机制传递出去。

Unforgeable Name

Unforgeable Name的定义

用new定义的Name是Unforgeable Name,由一串独一无二的字符表示。可以理解为身份证系统中,同名的人身份ID互不相同。

new a,stdout(`rho:io:stdout`) in{
    stdout!(*a)|
    new a in{
        stdout!(*a)
    }
}|
new a,stdout(`rho:io:stdout`) in{
    stdout!(*a)
}

// 运行结果
// Unforgeable(0x729d3a68aaf4b4b4985b168f95626d2842cc4e3a972e265bc2538fbf1c83db58)
// Unforgeable(0x46404982fa2da2ba74ac7b6816eca1af46a8ab5880bfdf73cacf4f794262a775)
// Unforgeable(0x5ab9526e8bcc709e85e914ba96ad09a7e90e92258b9ca087f4ca754eb0341df4)

注:每次运行,Unforgeable Name都不一样

Unforgeable Name

用new定义的Name

用for和contract定义的Name,不是Unforgeable Name,可以理解为昵称或代号。

new a,stdout(`rho:io:stdout`) in{
    stdout!(*a)|
    a!(100)|
    for(x<-a){
        stdout!(*x)
    }
}

// 运行结果
// Unforgeable(0x6f2df38e2d7fa3cc4f7f40cbede8834a069ac6d52a4b8f9825fd5ed5fa043ce6)
// 100

Unforgeable Name

用for和contract定义的Name

for 定义的Name

Unforgeable Name

Unforgeable Name 的传递

  • Name可以反射为Process (a是Name,那么*a是Process)
  • 通过Unforgeable Name 的传递,可以实现Process的传递。传递Unforgeable Name就相当于把该Name相关的Process都共享给了另一个Name。
new a, b in {
    b!(*a)|
    for (x <- b) {
        x!(P)
    }|
    for (y <- a) {Q}
}
new a in {
    a!(P)|
    for (x <- a) {Q}
}

=

#3 先被触发,x被赋值为a的Unforgeable Name

#4 *x与*a等价,因此{x!(P)}与{a!(P)}等价,从而#6 被触发

new alice, bob, stdout(`rho:io:stdout`) in {
    alice!(*bob)|
    bob!("Bob,You are the best. From Charlie")|
    stdout!(["un1 ", *bob])|
    for (bobCh <- alice) {
        for (@letter <- bobCh){
            stdout!(letter)
        }|
        stdout!(["un2",*bobCh])
    }
}
  • alice, bob, bobCh, @letter中哪些Name有Unforgeable Name?
  • 以上两个智能合约输出结果一样吗?

Unforgeable Name

Unforgeable Name 的传递

new alice, bob, stdout(`rho:io:stdout`) in {
    alice!(*bob)|
    stdout!(["un1",*bob])|
    for (@letter <- bob){
        stdout!(letter)
    }|
    for (bobCh <- alice) {
        bobCh!("Bob,You are the best. From Charlie")|
        stdout!(["un2",*bobCh])
    }
}

Charlie给Bob写了一封信,Alice帮Bob读信

Charlie写了一封信,Alice帮他寄给Bob

#2 Bob的Unforgeable Name发送给了Alice,从而Alice可以对Bob读和写

  1. 每一个智能合约部署到区块链后,对所有人都是可见的。从区块链上读到的合约内容中,用new定义的Name以一串字符(Unforgeable Name)表示。
  2. Unforgeable Name的生成不可预测,互不相同。
  3. It is impossible to guess the name of a process, and thereby interact with that process. 链上智能合约中包含很多可见的Unforgeable Name,仅通过Unforgeable Name不可调用和触发该Name。(必须通过该Unforgeable Name在区块链上的Uri才能调用和触发它,每一个Unforgeable Name 的Uri在区块链上不可查,也不可预测。详见智能合约与区块链的交互。)

Unforgeable Name

Unforgeable Name在区块链上的应用

new oldHen, round, counter, stdout(`rho:io:stdout`) in {
    contract oldHen ( ack ) = {
        round!(1)|
        for (@round_ <= round) {
            counter!(1)|
            if (round_ < 4) {
                for (@counter_ <= counter){
                    if(counter_ < 4 ){
                        counter!( counter_ + 1 )|
                        stdout!(["counter",counter_ ])                    
                    }    
                    else {
                        round!( round_ + 1)|
                        stdout!(["round", round_ ])                
                    }
                }
            }
        }
    }|
    oldHen!(1)
}

计数器

每轮都是一个从1到3的计数器,一共3轮

错误实现

More Cases

#4 "<="相当于有多个Process (#4-#18,称为P1)在等待round里的值。

#7 "<="相当于有多个Process (#7-#16,称为P2)在等待counter里的值。

一旦某P1被触发,counter就被放入1。由于counter的作用域是所有P2,因此其他P1里的P2就有可能被触发,从而counter的结果变成乱序的死循环。

……

P1

……

P2

P2

P2

P1

……

P2

P2

P2

P1

……

P2

P2

P2

counter!(1)

counter!(1)

counter!(1)

More Cases

计数器

每轮都是一个从1到3的计数器,一共3轮

错误实现示意图

可能触发任意等待中的P2

new oldHen, round, stdout(`rho:io:stdout`) in {
    contract oldHen ( ack ) = {
        round!(1)|
        for (@round_ <= round) {
            if (round_ < 4) {
                new counter in{
                    counter!(1)|
                    for (@counter_ <= counter){
                        if(counter_ < 4 ){
                            counter!( counter_ + 1 )|
                            stdout!(["counter",counter_])                    
                        }    
                        else {
                            round!( round_ + 1)|
                            stdout!(["round", round_])                
                        }
                    }
                } 
            }
        }
    }|
    oldHen!(1)
}

每一个P1(#4 -20)里定义的counter都不同,作用域为该P1里的Q(#6-18)。

 

每一个P1里有多个P2(#8-17)在等待counter里的值。

 

一个P1里的counter只能触发该P1里的P2,不能触发其他P1里的P2

计数器

每轮都是一个从1到3的计数器,一共3轮

正确实现

More Cases

计数器

每轮都是一个从1到3的计数器,一共3轮

More Cases

……

P1

P1

P1

……

P2

P2

P2

counter1!(1)

……

P2

P2

P2

counter2!(1)

……

P2

P2

P2

counter3!(1)

Q

Q

Q

正确实现示意图

在这个例子中,因为"<="的特殊性,由变量类型导致的错误非常隐蔽,这就提醒我们在定义name时应该注意其作用范围。

这个计数器有没有更好的实现方法?

More Cases

用 Join Opertator的方法

计数器

每轮都是一个从1到3的计数器,一共3轮

Contract Factory

05

关于Contract Factory

1. Contract Factory是一组定义好的合约

 

2. 在面向对象编程的语言里,经常使用封装(数据+方法)的技术,来模拟现实世界的业务逻辑,

在Rholang里,此项功能是用Contract Factory来实现的

 

3. 通常,Contract Factory有两种写法,示例如下:

// Separation of Powers
// line4、line5被触发时,method1 与 method2 会被分别传入unforgeable name
contract factory(method1, method2) = {
    contract method1(ack) = { ... }
    |
    contract method2(ack) = { ... }
}

// Control Panel
// 只有factory拥有unforgeable name
contract factory(cPanel) = {
    contract @[cPanel, "method1"](ack) = { ... }
    |
    contract @[cPanel, "method2"](ack) = { ... }
}

例子1:计数器

从0开始、可被重置的计数器

new currentCount, increase, reset, stdout(`rho:io:stdout`) in {

    currentCount!(0)|

    contract increase(ack) = {
        for (old <- currentCount) {
            currentCount!(*old + 1)|
            ack!(*old)
        }
    }|

    contract reset(ack) = {
        for (old <- currentCount) {
            currentCount!(0)|
            ack!(*old)
        }
    }|

    new ack in {
        increase!(*ack)|
        for (_ <- ack) {
            increase!(*ack)|
            for (_ <- ack) {
                increase!(*ack)|
                for (_ <- ack) {
                    increase!(*ack)|
                    for (_ <- ack; count <- currentCount) {
                        stdout!(*count)
                    }
                } 
            }
        }
    }
}

#3:设置初始值

 

#5-10:触发后,计数器+1

 

#12-17:触发后,计数器重置

 

#19-26:对合约的调用

用简单的Contract方法实现   

关于Contract Factory

new counterFactory, stdout(`rho:io:stdout`) in {

    contract counterFactory(increase, reset) = {
        new currentCount in {
            currentCount!(0)|

            contract increase(ack) = {
                for (old <- currentCount) {
                    currentCount!(*old + 1)|
                    ack!(*old)|
                    stdout!(*old)
                }
            }|

            contract reset(ack) = {
                for(old <- currentCount){
                currentCount!(0)|
                ack!(*old)
                }
            }
        }
    }|

    new ack, AliceIncrease, AliceReset in {
        counterFactory!(*AliceIncrease,*AliceReset)|
        AliceIncrease!(*ack)|
        // AliceIncrease!(*ack)|
        // AliceIncrease!(*ack)|
        AliceIncrease!(*ack)
    }|

    new ack, BobIncrease, BobReset in {
        counterFactory!(*BobIncrease,*BobReset)|
        BobIncrease!(*ack)|
        // BobIncrease!(*ack)|
        // BobIncrease!(*ack)|
        BobIncrease!(*ack)
    }
}

#3:counterfactory包含的两个功能:增加、重置

 

#7-13:触发后,计数器+1

 

#15-22:触发后,计数器重置

 

#25-27:Alice对counterfactory的调用,必须调用过#3后,才能调用#7/#15

 

#32-38:Bob对counterfactory的调用,必须调用过#3后,才能调用#7/#15

用Contract Factory的方法实现   

例子1:计数器

关于Contract Factory

两种实现方法有什么区别?

通过使用Contract Factory,把计数器增加和重置的功能定义好、封装,不同的合约调用者(Alice与Bob)可以各自独立地调用任意功能。

例子1:计数器

关于Contract Factory

传入通道和传出通道

传入通道和传出通道的本质其实是通过Unforgeable Name的传递来完成对相应合约的触发。

new factory in {

    contract factory(method1, method2) = {
        contract method1(ack) = {
            Nil
        }|
        contract method2(ack) = {
            Nil
        }
    }|

    new a, b in {
        factory!(*a, *b)|
        a!(1)|
        b!("Hello")
    }
}

传入通道中Unforgeable Name的传递

#3:method1和method2被bound在contract factory里面,没有Unforgeable Name

 

#12/13:method1被赋值为a的Unforgeable Name,method2被赋值为b的Unforgeable Name

 

#14/15:把某个Process发送给a,就相当于把

它发送给method1,于是contract method1 被触发,contact method2的触发同理。

传入通道和传出通道

new factory in {
    contract factory(x, y) = {
        new method1, method2 in {
            x!(*method1)|
            y!(*method2)|
            contract method1(ack) = {
            Nil
            }|
            contract method2(ack) = {
            Nil
            }
        }        
    }|
    new a, b in {
        factory!(*a, *b)|
        for (ack1 <- a) {
            ack1!(1)
        }|
        for (ack2 <- b) {
            ack2!("Hello")
        }
    }
}

#2/14/15:x被赋值为a的Unforgeable Name,y被赋值为b的Unforgeable Name(传入通道)

 

#4/5:将method1、method2的Unforgeable Name 发送给x、y

 

#16-20:从a和b中取出method1和method2的Unforgeable Name,然后把某个Process发送给这两个Unforgeable Name,即可触发相应的contract

传出通道中Unforgeable Name的传递

传入通道和传出通道

在上一段代码中,method1的Unforgeable Name 是怎么传递的?

unforgeable name

x

a

ack1

传入通道和传出通道

机场控制台把现有的天气和跑道信息存在一个Channel里,然后设置一个控制器,它能够在必要的时候对原始信息进行修改,而飞行员可以查询到相关信息。 

例子2:机场控制台

传入通道和传出通道

传入通道

new stationFactory, stdout(`rho:io:stdout`) in {
    contract stationFactory(initialMessage, getInfo, setInfo) = {
        new currentMessage in {
            currentMessage!(*initialMessage)|
            contract setInfo(newMessage) = {
                for (msg <- currentMessage) {
                    currentMessage!(*newMessage)
                }
            }|
            contract getInfo(return) = {
                for (msg <- currentMessage) {
                    return!(*msg)|
                    currentMessage!(*msg)
                }
            }
        }
    }|
    new airportInfo, set in {
        stationFactory!("Weather is nice", *airportInfo, *set)|
        set!("Strong crosswinds, be advised")|
        airportInfo!(*stdout)
    }   
}

例子2:机场控制台

传入通道和传出通道

设置一个银行账户,可存、可取、可查询。需要注意的是,账户必须保证安全,除了账户所有人之外,其他人不能知道里面的余额,更不能有权限把钱拿出来。

例子3:银行账户

传入通道和传出通道

new openAccount, stdout(`rho:io:stdout`) in {
    // This contract registers a new account and creates its methods.
    contract openAccount(initialDeposit, deposit, withdraw, check) = {
        new balanceCh in {
            balanceCh!(*initialDeposit)|
            // Deposit Contract
            contract deposit(amount, ack1) = {
                for (old <- balanceCh) {
                    balanceCh!(*old + *amount)|
                    ack1!(Nil)
                }
            }|
            // Withdraw Contract
            contract withdraw(amount, ack2) = {
                for (old <- balanceCh) {
                    balanceCh!(*old - *amount)|
                    ack2!(Nil)
                }
            }|
            // Check contract
            contract check(ack3) = {
                for (balance <- balanceCh) {
                    ack3!(*balance)
                }
            }
        }
    }|
    // Customer Sarah creates an uses an account
    new sarahDeposit, sarahWithdraw, sarahCheck, sarahack in {
        openAccount!(10, *sarahDeposit, *sarahWithdraw, *sarahCheck)|
        sarahWithdraw!(3, *sarahack)|
        for (_ <- sarahack) {
            sarahCheck!(*stdout)
        }
    }
}

#3/#29-30:不同的人在调用主智能合约时,都可以通过新定义属于自己的传入通道来实现账户的安全性

 

#14/#31:触发withdraw合约,实现账户更新

 

#10/#17/#32ack是一个确认channel,作用是:保证在调用check合约时,查询到的是最新的balance。

例子3:银行账户

传入通道和传出通道

 stephanie!(*sarahDeposit)

如果 Sarah想要授权给她的朋友Stephanie向她自己的账户存款的权限(不开放取款和查询余额的功能),她应该执行什么代码?

把Sarah的存款通道传出给Stephanie

例子3:银行账户

传入通道和传出通道

补充知识点——关于ack

 

1. ack,acknowledgement 的缩写,是Rholang中常用的、用于确认的Channel

 

2. 当ack 里面被放入Process时,用来证明:一次相对应的Send 和 Receive(即一次COMM event )已经完成。

 

3. ack 也可以当做普通的Name来使用

传入通道和传出通道

new pizzaShop, stdout(`rho:io:stdout`) in {
    contract pizzaShop(order, ack) = {
        ack!("Order Received.")
    }|
    new alice in {
        pizzaShop!("One medium veggie pizza", *alice)|
        for (@info<-alice) {
            stdout!(info)
        }
    }
}

例子4:披萨店订单确认

Alice订购披萨,披萨店在收到订单后向Alice专属的确认Channel内返回“订单已收到”的信息

#2/#6:定义Alice专属的确认Channel,把它的Unforgeable Name 和订单一起传入 contract

pizzashop

 

#3:订单收到,向确认Channel放入消息

确认Channel——ack

传入通道和传出通道

确认channel——ack

用作确认channel时,ack 还有一个功能:在Rholang这个以并行为基本逻辑的语言中,实现顺序执行。

传入通道和传出通道

确认channel——ack

例子5:有顺序的发送

new chan, ack, stdoutAck(`rho:io:stdoutAck`) in {
    chan!(0)|
    for (_ <- ack) {
        chan!(1)
    }|
    for (@num <= chan) {
        stdoutAck!(num, *ack)
    }
}

#3-#4:只有把值从ack里取出来,才能触发向chan发送1

 

#7:stdoutAck的功能是把num收到的值输出到屏幕上,同时在ack里面放入Nil,相当于:stdout!(num) | ack!(Nil)

传入通道和传出通道

在第二章里,通过Join Operator实现了两个飞行员共同控制火箭的发射。现在,为该情景添加如下功能:

两个人都有权利撤回发射,将命令从“发射”更改到“取消发射”。

例子6:Rocket Launch 升级版

传入通道和传出通道

new rocketFactory, stdout(`rho:io:stdout`) in {

    contract rocketFactory(aliceLaunch, bobLaunch, ack0) = {
        new aliceReadyCh, bobReadyCh, a, b in {
            aliceReadyCh!(false)|
            bobReadyCh!(false)|

            contract aliceLaunch(ack1) = {
                new aliceAbort in {
                    for (_ <- aliceReadyCh) {
                        aliceReadyCh!(true)|
                        a!(true)
                    }|
                    contract aliceAbort(ack2) = {
                        for (_ <- aliceReadyCh) {
                            aliceReadyCh!(false)|
                            ack2!(Nil)|
                            stdout!("Abort")
                        }
                    }|
                    ack1!(*aliceAbort)
                }
            }|

            contract bobLaunch(ack1) = {
                new bobAbort in {
                    for (_ <- bobReadyCh) {
                        bobReadyCh!(true)|
                        b!(true)
                    }|
                    contract bobAbort(ack2) = {
                        for (_ <- bobReadyCh) {
                            bobReadyCh!(false)|
                            ack2!(Nil)|
                            stdout!("Abort")
                        }
                    }|
                    ack1!(*bobAbort)
                }
            }|

            for (a_<- a; b_ <-b) {
                match (*a_,*b_) {
                    (true,true) => stdout!("Launch!")
                }
            }| 
            ack0!(Nil)
        }
    }|
    //发射
    new aliceLaunch, bobLaunch, ack in {
        rocketFactory!(*aliceLaunch, *bobLaunch, *ack)|
        for (_ <- ack) {
            aliceLaunch!(*ack)|
            bobLaunch!(*ack)
        }
    }|
    //取消发射
    new aliceLaunch, bobLaunch, ack in {
        rocketFactory!(*aliceLaunch, *bobLaunch, *ack)|
        for (_ <- ack) {
            aliceLaunch!(*ack)|
            for (x <-ack) {
                x!(Nil)
            }
        }
    }
}

传出通道的使用:#8第一次被触发后,aliceAbort的Unforgeable Name被传出到了ack1中,所以,下次再触发#8时,相当于向aliceAbort里发送Process,触发contract aliceAbort ,实现“取消发射”

 

引入a的目的:保证#42-44做逻辑判断时,不会略过aliceLaunch合约而直接匹配到#5中aliceReadych里面最原始的false

例子6:Rocket Launch 升级版

传入通道和传出通道

More Cases

例子7:取消/停止触发

new MakeRevokableForwarder in {
    contract MakeRevokableForwarder(target, ret) = {
        new port, kill, forwardFlag in {
            ret!(*port, *kill)|
            forwardFlag!(true)|
            contract port(msg) = {
                for (@status <- forwardFlag) {
                    forwardFlag!(status)|
                    match status { true => target!(*msg) }
                }
            }|
            for (_ <- kill; _ <- forwardFlag) {
                forwardFlag!(false)
            }
        }
    }|
    new target, ret in {
        MakeRevokableForwarder!(*target,*ret)|
        for (port, kill<- ret) {
            port!(111)
        // |kill!(000)
        }
    }
}

传入通道的使用:#17中target和ret的Unforgeable Name 被传入#2

 

传出通道的使用:#3中port和kill的Unforgeable Name通过ret被传出,所以#20中向port发送Process即可触发#6的contract

 

触发port的结果:被放入port的msg最后被放入到了target;触发kill的结果,forwardFlag里面被放入false,游戏结束。

例子8:存衣处

new MakeCoatCheck in {
    contract MakeCoatCheck(ret) = {
        new port, table in {
            ret!(*port)|
            for (@"new", @arg, ack <= port) {  // 传出通道的使用:port的Unforgeable Name被传出到了ret,#28中又把它取出,之后向cc发送Process即触发相应的合约(#5/11/17)
                new ticket in {   // 定义一个ticket,代表存衣凭证。它的Unforgeable Name被传出到ack中
                    ack!(*ticket)|
                    @{*ticket | *table}!(arg)
                }
            }|
            for (@"get", @arg, ack <= port) {   // 查看功能。@"get"用于匹配,@arg代表凭证,ack是返回通道
                for (@value <- @{arg | *table}) {
                    @{arg | *table}!(value)|
                    ack!(value)
                }
            }|
            for (@"set", @arg1, @arg2, ack <= port) {   // 更换存衣功能。@"set"用于匹配,arg1代表凭证,arg2代表新的衣服,ack是返回通道
                for (_ <- @{arg1 | *table}) {
                    @{arg1 | *table}!(arg2)|
                    ack!(true)
                }
            }
        }
    }|
    // Usage
    new ret, get, set in {
        MakeCoatCheck!(*ret)|  // 传入通道的使用:ret的Unforgeable Name被传入#2,两个ret等价
        for (cc <- ret) {
            // Creates new cell with initial value 0
            cc!("new", 0, *ret)|
            for (ticket <- ret) {
                contract get(return) = { 
                    cc!("get", *ticket, *return) 
                }|
                contract set(@value, return) = { 
                    cc!("set", *ticket, value, *return) 
                }|        
                get!(*ret)|
                for (@r <- ret) {
                    //r is equal to 0
                    set!(1, *ret)|
                    for (_ <- ret) {
                        get!(*ret)|
                        for (@r <- ret) {
                            //r is equal to 1
                            Nil
                        }
                    }
                }
            }
        }
    }
}

More Cases

数据结构

06

String

 

1. String是一种组织字符的数据结构,如"hello"

2. String之间用++ 连接

new string, stdout(`rho:io:stdout`) in{
    contract string (@word) = {
        stdout!("Hello" ++ word)
    }|
    string!("World")
}

Tuple

 

1. Tuple是有顺序的元素集。

2. Tuple! (3, 4, 5),Tuple里面有三个元素;Tuple! ((3, 4, 5)) ,Tuple里面只有一个元素。

new tCh, stdout (`rho:io:stdout`) in {
    tCh!!((3, 4, 5))|
    for (@t <- tCh){
        stdout!(["Test nth. Expected: 5. Got ", t.nth(2)])
    }|
    for(@t <-  tCh){
        stdout!([“toByteArray test.Got:”,t.toByteArray()])
  }
}

(Tuple name).nth(位置)取出tuple中的元素。

例子1: 对Tuple的调用

Tuple的基础知识

new a,stdout (`rho:io:stdout`) in{
    contract a (x,y,z) ={
        stdout!("HelloWorld")
    }|
    a!(1,2,3)|
    a!(1)
}

在调用时必须向a send由三个元素组成的Tuple。

例子1: contract定义的name是Tuple

Tuple在contract语句中的应用

Tuple

 

建立在Tuple结构上的name,称为compound name。

new alice, bob, key1, key2, stdout(`rho:io:stdout`) in {

    alice!(*key1)|
    bob!(*key2)|

    contract @(*key1, *key2)(_) = {
        stdout!("Congratulations, Alice and Bob, you've cooperated.")
    }|
    for(@a<-alice;@b<-bob){
        @(a,b)!(1)
    }
}

当alice和bob同时用自己的key调用contract的时候,contract才能被执行。

Tuple在contract语句中的应用

例子2:联合

Tuple

P Output
stdout!(t.nth(2)) Nil
stdout!(t.toByteArray()) 2a19aa01160a042a0210020a042a0210040a000a062a041a024869

Tuple调用方法一览

Tuple ! ((1,2,Nil,"Hi"))|for(@t<-Tuple){P}

Tuple

List

 

List是一个有顺序的数组。

new lCh, stdout(`rho:io:stdout`) in {

    lCh!!([3, 4, 5])|

    for (@l <- lCh){
        stdout!({l.nth(2)})
    }|
    for (@l <- lCh){
        stdout!(["Test slice. Expected: [4, 5]. Got: ", l.slice(1, 3)])
    }|

    for (@l <- lCh){
        stdout!({l.length()})
    }
}

例子:对List的调用

List的基础知识

P Output
stdout!({l.nth(2)}) Nil
stdout!({l.length()}) 4
stdout!({l.slice(1,3)}) [2,Nil]
stdout!({l.toByteArray()}) 2a19a201160a042a0210020a042a0210040a000a062a041a024869

List调用方法一览

List ! ([1,2,Nil,"Hi"])|for(@l<-List){P}

List

Set

Set是一个无序的process的合集,其中每一个元素都是不可重复的。

new sCh, stdout(`rho:io:stdout`) in {
    sCh!!(Set(3, 4, 5))|
    // 在Set里面加一个process
    for (@s <- sCh){
        stdout!(["Test add. Expected Set(3, 4, 5, 6), Got: ", s.add(6)])
    }|
    // 删除Set里面的一个process
    for (@s <- sCh){
        stdout!(["Test delete. Expected: Set(3, 4). Got: ", s.delete(5)])
    }|
    for (@s <- sCh){
        stdout!(["Test contains. Expected: true. Got:", s.contains(5)])|
        stdout!(["Test contains. Expected: false. Got: ", s.contains(6)])
    }|
    for (@s <- sCh){
        stdout!(["Test union. Expected: Set(1, 2, 3, 4, 5). Got: ", s.union(Set(1, 2))])
    }|
    for (@s <- sCh){
        stdout!(["Test diff. Expected: Set(5) Got: ", s.diff(Set(3, 4))])
    }|
    for (@s <- sCh){
        stdout!(["Test toByteArray. Got: ", s.toByteArray()])
    }      
}

例子:对Set的调用

Set的基础知识

P Output
stdout!({s.union(Set(1,4))}) Set(1,2,4,Nil,"Hi")
stdout!({s.delete(2)}) Set(1,Nil,"Hi')
stdout!({s.contains(5)}) false
stdout!({s.size()}) 4
stdout!({s.toByteArray()}) 2a19b201160a000a042a0210020a042a0210040a062a041a024869

Set调用方法一览

Sets! (Set(1,2,Nil,"Hi"))|for(@s<-Sets){P}

Set

Map

Map的每一个元素都由key和value组成,每个key都不能重复。表现形式为{"a":1,"b":2}。

例子:对Map的调用

new mCh, print(`rho:io:stdout`) in {
    mCh!!({"a": 3, "b": 4, "c": 5})|
    // .union 用来将两个map联合起来
    for (@m <- mCh){
        print!(["Test union. Expected: {a: 3, b: 4, c: 5, d: 6}. Got: ", m.union({"d": 6})])
    }|
    // .diff用来调出map中除了.diff()里面的东西。
    for (@m <- mCh){
        print!(["Test diff. Expected: {b: 4, c: 5}. Got: ", m.diff({"a": 3})])
    }|
    for (@m <- mCh){
        print!(["Test toByteArray. Got: ", m.toByteArray()])
    }|
    for (@m <- mCh){
        print!(["Test delete. Expected: {a: 3, b: 4}. Got: ", m.delete("c")])
    }|
    for (@m <- mCh){
        print!(["Test contains. Expected: true. Got: ", m.contains("c")])|
        print!(["Test contains. Expected: false. Got: ", m.contains("d")])
    }|
    for (@m <- mCh){
        print!(["Test get. Expected: 4. Got: ", m.get("b")])
    }|
    for (@m <- mCh){ 
        //getOrElse的第二个参数是Default Value
        print!(["getOrElseSuccessful. Expected: 4. Got: ", m.getOrElse("b", "?")])|
        print!(["getOrElseFailed. Expected: ?. Got: ", m.getOrElse("d", "?")])
    }|
    for (@m <- mCh){
        print!(["Test set. Expected {a: 3, b: 2, c: 5}. Got: ", m.set("b", 2)])
    }|
    for (@m <- mCh){
        print!(["Test keys. Expected Set(a, b, c)). Got: ", m.keys()])
    }|
    for (@m <- mCh){
        print!(["Test size. Expected 3. Got: ", m.size()])
  }
}

Map的基础知识

P Output
​stdout!({m.union{{"c":3}}}) ​{"a":1,"b":2,"c":3}
stdout!({m.delete{"b"}}) {"a":1}
stdout({m.contains{"c"}}) false
stdout!({m.get{"b"}}) 2
stdout!({m.getOrElse{"d","fail"}}) fail
stdout!({m.set{"b",4}}) {"a":1,"b":4}
stdout!({m.keys()}) Set("a","b")
stdout!({m.size()}) 2
stdout!({m.toByteArray()}) 2a21ba011e0a0d0a052a031a016112042a0210020a0d0a052a031a016212042a021004

Map调用方法一览

Map ! ({"a":1,"b":2})|for(@m<-Map){P}

Map

调用方法 调用目的 Tuple List Map Set
nth 选取Tuple或list中顺序的某一位
toByteArray 将数据结构以16进制的方式输出
union 将两个数据结构合在一起
diff 调出数据结构中除diff()括号内以外的数据
add 在Set中添加一个process
delete 删除key
contains 检验map中是否包含某个key
get 得到key的value
getOrElse 如果map中没有这个key则输出Default Value
set 更改key的value
keys 调出map中所有key
size map中key的数量
length 输出list中的元素位数
slice 截取list

数据结构调用方法一览表

匹配

07

写在前面

在Rholang官方教程中,匹配都叫做"Pattern Matching"。

为了便于理解,在本章节中将"Pattern Matching"分为结构样式匹配和逻辑命题匹配。

结构样式匹配

Rholang里的结构样式

数据类型
Int : -1,12,345
Strings : "Hello World"
Lists : [1,2,"Hi"]
Booleans : true, false
Tuples : (1,2,3)
Sets : Set(a, 5, b)
Maps : {"a":1, "b":10, "c":100}
_
语句结构
send : a!(b)
receive : for(x<-a){P}
Par: P|Q
Nil

结构样式匹配

用match进行结构样式匹配

for (@x <- <channel>) {
    match x{
        pattern1 =>{P}
        pattern2 =>{Q}
    }
}
  1. P和X均为Process。
  2. X是match新定义的Process时,@X 被赋值为P。
  3. 按顺序从上到下依次匹配,匹配成功则停止。
  4. _可以匹配一切。通常我们它放在最后,用来表示以上都没有匹配。
match P{
  X =>{...}
  _ =>{...}
}

当向channel Send Process 符合pattern1时,执行P;符合pattern2时,执行Q。

new patternMatcher, stdout(`rho:io:stdout`) in {
    for (x <= patternMatcher) {
        match *x {
            Nil     => stdout!("Got the stopped process")
            "hello" => stdout!("Got the string hello")
            [x, y]  => stdout!("Got a two-element list")
            Int     => stdout!("Got an integer")
            _       => stdout!("Got something else")
        }
    }|
    patternMatcher!({for(@0 <- @0){0}})|
    patternMatcher!({@"world"!("hello")})|
    patternMatcher!({Nil|"hello"})|
    patternMatcher!({0|"hello"})|
    patternMatcher!(Nil)
}

match数据类型匹配

# 13   {Νil|"hello"}={"hello"}

结构样式匹配

用match进行结构样式匹配

new patternMatcher, stdout(`rho:io:stdout`) in {
    for (x <= patternMatcher) {
        match *x {
            Nil               => stdout!("Got the stopped process")
            {_!(_)}           => stdout!("Got a send")
            {for(@0 <- _){_}} => stdout!("Got a receive on @0")
            _                 => stdout!("Got something else")
        }
    }|
    patternMatcher!({for(@0 <- @0){0}}) |
    patternMatcher!({@"world"!("hello")}) |
    patternMatcher!({0|"hello"}) |
    patternMatcher!(Nil)
}

结构样式匹配

用match进行结构样式匹配

match语句结构匹配

结构样式匹配

用for进行结构样式匹配

new chan, stdout(`rho:io:stdout`) in {
    chan!(1,2,3) |
    for (@x, @y, @z <= chan) {
        stdout!(["three", x, y, z])
    } |

    chan!(7,8) |
    chan!([9, 10], 11) |
    for (@a, @b <= chan) {
        stdout!(["two", a, b])
    } |

    chan!((4,5,6)) |
    chan!(12 | 13) |
    for (@a <= chan) {
        stdout!(["one", a])
    } 
}

for里的数据类型匹配

  • 元素个数和数据类型必须同时匹配才能匹配成功
  • {P|Q}既是两个并行的Process,也是一个Process
for (name pattern <- channel) { process }

当向channel send的Process符合name pattern时,上述语句才会被触发

new chan,stdout(`rho:io:stdout`)in{
    for (_ <- chan) {stdout!("Hello")}|
    chan!(1|2)|

    for (x <- chan) {stdout!("World")}|
    chan!(1|2|3)|

    for (@{y|x} <- chan) {stdout!("Hi")}|
    chan!(333)|

    for (@{y|_} <- chan) {stdout!("Hi~")}|
    chan!(22|44|66)|

    for (@x,@y <- chan) { stdout!(x)}|
    chan!(5,10)|

    for (@x,_ <- chan) { stdout!(x)}|
    chan!(5,10)|

    for (@{x!(Q)} <- @"chan") { stdout!(Q)}|
    @"chan"!({@"a"!(999)})|

    for (@{for (@P <- z) { R }} <- @"chan"){stdout!(R)}|
    @"chan"!(for (x <- @"a") {666})

}

for语句结构匹配

结构样式匹配

用for进行结构样式匹配

# 2 # 5  匹配1个Process

# 8 # 11 匹配1个或1个以上并行的Process

# 14 #17 匹配2个Process

# 20 # 23 先进行语句结构匹配,然后赋值

new chan,stdout(`rho:io:stdout`)in{

    for (@{y|Nil} <- chan) {stdout!(y)}|
    chan!(Nil|333)|

    for (@{for (@5 <- x) {Nil}} <- chan) { stdout!("Hi")}|
    chan!(for (@5 <- chan) {Nil})|

    for(@{x!("Hello")} <- chan){ stdout!("World")}|
    chan!({@"a"!("Hello")})

}

name pattern含有String,Int或Nil,触发语句需要与其样式完全一致

for语句结构及数据类型匹配

结构样式匹配

用for进行结构样式匹配

new chan,stdout(`rho:io:stdout`) in{

    contract @(*chan,"method1")(@ack)={stdout!(["method1",ack])}|
    @(*chan,"method1")!(1)|

    contract @[*chan,"method2"](@ack)={stdout!(["method2",ack])}|
    @[*chan,"method2"]!(2)|

    contract chan(@"method3",@ack)={stdout!(["method3",ack])}|
    chan!("method3",3)|

    contract @(*chan,"method4")(@"method4",@ack)={stdout!(["method4",ack])}|
    @(*chan,"method4")!("method4",4)

}

结构样式匹配

用contract进行结构样式匹配

contract里的结构样式匹配

  • # 3 channel pattern是一个Tuple,Tuple第一个元素为chan的Unforgeable name,第二个元素为"method1"
  • # 6 channel pattern是一个List,List第一个元素为chan的Unforgeable name,第二个元素为"method2"
  • # 9 name pattern含有两个变量,第一个是@"method3" 
contract channel pattern (name pattern) ={ Process }

当channel pattern 和name pattern的都匹配上时,上述语句被触发

new log(`rho:io:stdout`), missile in {
    
    contract @(*missile, "launch")(_) = {
        log!("launching...")
    }|

    contract @(*missile, "inspect")(_) = {
        log!("inspecting...")
    }|

    contract @"getInspectionChannel"(return) = {
        return!((*missile, "inspect"))
    }
    
}
//请补充调用合约,分别实现launch功能,和inspect功能
//|{...}
  • #3 #7 两个contract的channel pattern都是Tuple,Tuple第一个元素为missile的Unforgeable name,第二个元素分别为"launch"和"inspect"
  • #11-#13 @"getInspectionChennel"是一区块链上的公共name,在#1-#13之外,任何人都能对它进行操作

拿到@(*missile,"inspect")的Unforgeable name的人能触发contract @(*missile,"launch")吗?

如何做到?

结构样式匹配

用contract进行结构样式匹配

火箭发射和监控

new log(`rho:io:stdout`), missile in {
    contract @(*missile, "launch")(_) = {
        log!("launching...")
    }|

    contract @(*missile, "inspect")(_) = {
        log!("inspecting...")
    }|

    contract @"getInspectionChannel"(return) = {
        return!((*missile, "inspect"))
    }
}
|
new ack in {
    @"getInspectionChannel"!(*ack)|
    for(@(missile, "inspect") <- ack){
        @(missile, "launch")!(Nil)
    }
}

#17 通过for匹配,拿到missile的Unforgeable name,进而触发contract @(*missile,"launch")

结构样式匹配

用contract进行结构样式匹配

窃取火箭发射权

new getInspectionChannel, log(`rho:io:stdout`), missile in {

  contract @(*missile, "launch")(_) = {
    log!("launching...")
  }
  |
  contract @(*missile, "inspect")(_) = {
    log!("inspecting...")
  }
  |
  contract getInspectionChannel(return) = {
    return!(bundle+{(*missile, "inspect")})
  }
}

#12 bundle+后的(*missile,“inspect”),只能写不能读。

(可以向@(*missile,“inspect”) send process,不能从@(*missile,“inspect”) receive process。详见第九章第一节)

结构样式匹配

用contract进行结构样式匹配

更安全的火箭发射

逻辑命题 判断
a>b a大于b时该命题为真
a < b a小于b时该命题为真
a == b  a等于b时该命题为真
a <= b a小于等于b时该命题为真
a != b a不等于b时该命题为真
a and b, a/\b 当a和b都为真时,该命题为真
a or b, a\/b   当a或b为真时,该命题为真
not a 当a为假时,该命题为真

以上a,b均为Process

逻辑命题匹配

Rholang里的逻辑命题

new stdout(`rho:io:stdout`) in {
    for (@balance <= @"signTest") {
        if (balance > 0) {
            stdout!("Account in good standing.")
        } 
        else {
            stdout!("Account overdrawn.")
        }
    }
}
|
@"signTest"!(5)

筛选大于0的balance

逻辑命题匹配

if/else逻辑命题匹配

if (cond) {P}
else {Q}

Process cond为真命题时执行P,为假命题时执行Q

match cond {
  true => P
  false => Q
}
  1. Process cond与booleans进行匹配。cond为真命题执行P,cond为假命题执行Q
  2. cond里只能包含关系运算符,不能包含逻辑连接符

逻辑命题匹配

match逻辑命题匹配

match x {
  cond => P
  _ => Q
}

方法一

方法二

  1. x与cond命题等价时,执行P;否则执行Q

match是if/else的语法糖

new agech,stdout(`rho:io:stdout`) in{
    agech!(10)|
    agech!(18)|
    agech!(20)|
    for(@age<=agech){
        match {age>=18} {
            true =>{stdout!("permission")}
            _    =>{stdout!("rejection")}
        }
    }
}

逻辑命题匹配

match逻辑命题匹配

筛选出年龄大于等于18的人

试试用方法二改写上述代码

参考More Cases

new log(`rho:io:stdout`), binderChecker in {
    contract binderChecker(@data, return) = {
        match data {
            "nombre" \/ "name" => return!("name-like")
            _ => return!("not name-like")
        }
    }|
    binderChecker!("name", *log)|
    binderChecker!("nombre", *log)
}

逻辑命题匹配

match逻辑命题匹配

匹配多种字符串

逻辑命题匹配

for逻辑命题匹配

new StudentGradeLevel, stdout(`rho:io:stdout`) in {
    for(@{{@"grade"!(x)}/\{@y!(10)}} <- StudentGradeLevel){stdout!("a")}|
    StudentGradeLevel!(@"grade"!(10))
}

用for模拟数据库处理

for (name pattern <- channel) { Process }
  1. 当name pattern是一个逻辑命题时,触发语句必须向channel Send一个真命题,上述Process才会被触发。
  2. 目前name pattern里的逻辑连接符只能使用and(/\)
  3. name pattern不能包含关系运算符。
new print(`rho:io:stdout`), register in {

    for (@{{@"name"!(_) | _} /\ {@"age"!(_) | _}} <= register){
        print!("Both name and age were in the data")
    }|
    register!(@"name"!(Nil))|
    register!(@"age"!(Nil))|
    register!(@"name"!(Nil) | @"age"!(Nil))|
    register!(@"name"!(Nil) | @"age"!(Nil) | @"height"!(175))
    
}

筛选出数据库中@"name" 和@"age"数据

  • {@"name"!(_) | _}第一个"_"表示向@"name" send 任意process均可与之匹配上,第二个"_"表示任意process与@"name"!(_) 并行,均能与之匹配上
  • {@"age"!(_) | _}同上

逻辑命题匹配

for逻辑命题匹配

逻辑命题匹配

contract逻辑命题匹配

new StudentGradeLevel, x, stdout(`rho:io:stdout`) in{
    contract StudentGradeLevel (@{{@"grade"!(x)}/\{@y!(10)}}) ={stdout!("a")}|
    StudentGradeLevel!(@"grade"!(10))
}

用contract进行数据库处理

contract channel  (name pattern) ={ Process }
  1. 当name pattern是一个逻辑命题时,触发语句必须向channel send一个真命题,上述Process才会被触发
  2. 目前name pattern里的逻辑命题只能使用and(/\)

​以下样式只能用来做匹配,不能作为name使用。

  • _  =>  匹配所有Process
  • Int =>  匹配所有整数,包括整数和负数
  • Bool =>  匹配true 和 false
  • String =>  匹配字符串
  • Uri=>  匹配智能合约的ID
  • ByteArray=>  匹配所有类型的数据结构

如果要输出1,怎么办?

More Cases

如何进行匹配并赋值

匹配整形

new ack, stdout(`rho:io:stdout`) in {
    for (@Int <-ack){
        stdout!(Int)
    }|
    ack!(1)
}
//结果为Nil
new ack, stdout(`rho:io:stdout`) in {
    for (@{x /\ Int} <- ack) {
        stdout!(x)
    }|
    ack!(1)
}
//结果是1
  • x/\Int与Int等价,x被赋值为与Int进行匹配的Process
  • 首先1与Int匹配,然后1被赋值给x

More Cases

如何进行匹配并赋值

匹配整形并输出

new age, stdout(`rho:io:stdout`) in {
    for(@{x+5}<- age ){ 
        stdout!(x)
    }|
    age!({10+5})
} 

#2 x为自由变量

#5 10+5比{ }优先级高,age 里的Process为15,与{x+5}无法匹配

More Cases

无法匹配的样式

for里的算术运算

当name pattern 里包含自由变量时,该语句无法被触发

new age, stdout(`rho:io:stdout`) in {
    for(@{x>5}<- age ){ 
        stdout!(x)
    }|
    age!({10>5})
} 

#2 x为自由变量

#5 10>5比{ }优先级高,age 里的Process为true,与{x>5}无法匹配

for里的逻辑运算

More Cases

无法匹配的样式

new age, stdout(`rho:io:stdout`) in {
    for(@x<- age ){ 
        match x{
            y>5 => stdout!("123")
        }
    }|
    age!(10)
} 

#4 y是自由变量,导致匹配失败

match里的逻辑运算

More Cases

无法匹配的样式

new age, stdout(`rho:io:stdout`) in {
    for (@x <- age) { 
        match x/\10 {
            true => stdout!(x)
        }
    }|
    age!(10)
}

#3 报错:不能使用/\

PS: 也不能使用\/

match里的逻辑运算

More Cases

无法匹配的样式

new helloNameAge, getOlder, stdout(`rho:io:stdout`) in {
    contract helloNameAge(@{@"name"!(name) | @"age"!(age) | _}) = {
        stdout!(["Hello, ", name, " aged ", age])
    } |
    contract getOlder(@{rest /\ {@"name"!(_) | _} | @"age"!(age) }, ret) = {
        ret!(@"age"!(age + 1) | rest)
    } |
    getOlder!(@"name"!("Joe") | @"age"!(39), *helloNameAge)
}

例子1: 将数据记录里的年龄+1

  • 本案例包含结构样式匹配,逻辑命题匹配
  • # 5 是contract的name pattern 匹配,内部包含样式匹配和逻辑命题匹配
  • # 2  是contract的name pattern匹配

More Cases

contract里的结构样式&逻辑命题匹配

例子2: 筛选出age>35岁的人的身份数据

new people, stdout(`rho:io:stdout`) in {
    people!(@"name"!("Joe") | @"age"!(20) | @"eyes"!("blue") | @"seq"!(0)) |
    people!(@"name"!("Julie") | @"age"!(30) | @"eyes"!("brown") | @"seq"!(0)) |
    people!(@"name"!("Jane") | @"age"!(40) | @"eyes"!("green") | @"seq"!(0)) |
    people!(@"name"!("Jack") | @"age"!(50) | @"eyes"!("grey") | @"seq"!(0))|
    for (@{@"seq"!(0) | {row /\ {@"name"!(name) | @"age"!(age) | _}}} <= people) {
        if (age > 35) {
            stdout!([name, age])
        }|
        people!(row | @"seq"!(1))
    }
}

More Cases

for里的结构样式&逻辑命题匹配

  • #10 信息被放回数据库,并且@“seq”里的值变为1,代表该条身份数据已被读取,且不能再次与#6匹配,避免重复筛选。
  • 本案例包含样式匹配,逻辑命题匹配及赋值

迭代

08

迭代指智能合约迭代的过程,即反复地运用同一合约计算,前一次迭代得到的结果作为下一次迭代的输入。

迭代

new myIncrease, stdout(`rho:io:stdout`) in { 
    new current in {
        current!(0)|
        contract myIncrease(ack) = {
            for (@old <- current) {
                current!(old + 1)|
                ack!(old)
            }
        }
    }|
    new ack in {
        myIncrease!(*ack)|
        contract ack(@a) = {
            if (a <= 5) {
                myIncrease!(*ack)|
                stdout!(a)
            }
        } 
    }
}

例子1:计数器

设置一个可以计数到5,并带有重置功能的计数器

#4-9 是实现计数功能的智能合约。利用迭代,使#6产生的结果在合约再次被触发后,作为输入条件被#5执行。

#12-18 是触发合约,同样利用迭代,不断触发myIncrease执行,直到不再满足条件后程序停止。

More Cases

new sum, add, stdout(`rho:io:stdout`) in {
    contract sum(@arr, ret) = {
        match arr {
            [h, ...t] => {
                new ack in {
                    sum!(t, *ack)|
                    for (@arg <- ack) {
                        add!(h, arg, *ret)
                    }    
                }
            }            
            _ => ret!(0)
        } 
    }|
    contract add(@a, @arg, ret) = {
        ret!(a + arg)
    }|
    sum!([4,5,6], *stdout) 
}

例子2:计算数组[4,5,6]中各数字的加和

#4 [h, ...t]是一个数组结构,h表示数组中第一位元素,t表示除h之外的其他元素。[h, ...t]用来将数组拆散。

 

#5-6 将拆出的 t 再次放入sum合约中迭代。注意,每次迭代产生的ack都是新的,用来保证之后的计算按顺序进行。

 

#7-8 触发#15 add合约,等待计算。

 

#12 数组为空后,ret中放入0。经过传递,最终0被放入arg中。

 

#16 ret被放入新的结果,作为输入,再次触发add合约...直到完成计算。

More Cases

Bundle

09

Bundle的作用是设置合约调用者的读写权限,共有四种语法:

 
Syntax Can Read Can Write
bundle- {proc} YES NO
bundle+ {proc} NO YES
bundle0 {proc} NO NO
bundle {proc} YES YES

Bundle

如何理解bundle

Bundle对合约调用者的限制,其原理类似于非对称密码学中公私钥的签名和加密。

非对称式密码学是密码学的一种算法,它需要两个密钥:

  • 公钥(公开给他人)
  • 私钥(不公开,只能由自己秘密保管)

用其中一个加密,用且只能用另一个解密。

虽然两个密钥在数学上相关,但如果知道了其中一个,并不能凭此计算出另外一个。

 

用法:

  • 用公钥对文件加密,只能由自己秘密保管的私钥进行解密,其他人无法读取,故称用公钥加密。
  • 用私钥对文件加密,由于私钥只自己持有,其他人无法更改经私钥加密的文件,效果等同于在纸本文件上亲笔签署的效果,所以该过程称为签名。

 

 

只写(bundle+)相当于用公钥加密

如何理解Bundle

只读(bundle-)就像公钥加密方法中的签名

如何理解bundle

区块链上的内容是全部可见的,bundle只能解决合约调用时读写权限问题,并不能解决隐私问题。

所以,在使用区块链时需要注意对敏感信息的处理,避免隐私泄露。

如何理解bundle

例子1:AliceFanMail

Eve想要偷取Alice的邮件,如何防止Eve作恶成功?

Bundle的用法

new alice, stdout(`rho:io:stdout`) in {
    new aliceFanMail in {
        alice!!(bundle+ {*aliceFanMail})|
        for (mail <= aliceFanMail) {
            stdout!("Alice received a fanmail")
        }
    }|
    for (aliceFanMail <- alice) {
        aliceFanMail!("Dear Alice, you're #TheBest")
    }|
    for (aliceFanMail <- alice) {
        for (@stolenMail <= aliceFanMail) {
            stdout!(["Eve stole a message: ", stolenMail])
        }
    }
}

#3 aliceFanMail被Bundle后传出

#8-10 粉丝接到aliceFanMail后给Alice发信

#11-15 Eve想要偷取Alice的信件,但没成功

示例2:Jackpot

玩家A扔出一个带有数字的球,其余玩家抢球,抢到球的玩家获得相应分数。如何防止除A以外的人扔球?

new gameCh, stdout(`rho:io:stdout`) in {
    new throw in {
        gameCh!!(bundle- {*throw})|
        throw!(4) |
        throw!(2) |
        throw!(6)
        }|
    for (throw <- gameCh) {
        for (points <= throw) {
            stdout!(["Bill caught it. Points: ", *points])
        }
    }|
    for (throw <- gameCh) {
        throw!(100)
    }
}

Bundle的用法

#2-7 throw被bundle后开始扔球

#8-12 玩家接球,并报出得分

#13-15 玩家想要作弊,扔出一个分数为100的球,但不成功

示例3:高阶版Jackpot

玩家扔出1-10共10个数字的球,由另外3个玩家抢球。抢到球的玩家将本轮得分与之前的得分相加并打印出来。

new gameCh, ack, stdout(`rho:io:stdout`) in {
    new ret in {
        contract gameCh(return) = {
            return!(bundle- {*ret})
        } |
        new throw in {
            throw!(1)|
            contract throw(@num) = {
                if (num <= 10){
                    throw!(num + 1)|
                    ret!(num) 
                }
            }
        }
    }|
    new initial in {
        new return in {
            gameCh!(*return)|
            initial!(0)|
            for (@throw <- return) {
                for (@points <= @throw; @ini <= initial) {
                    initial!(points + ini)|
                    stdout!(["P1 get the ball, the sum number is ", points + ini])          
                }           
            }
        } |
        new return in {
            gameCh!(*return)|
            initial!(0)|
            for (@throw <- return) {
                for (@points <= @throw; @ini <= initial) {
                    initial!(points + ini)|
                    stdout!(["P2 get the ball, the sum number is ", points + ini])          
                }           
            }
        } |
        new return in {
            gameCh!(*return)|
            initial!(0)|
            for (@throw <- return) {
                for (@points <= @throw; @ini <= initial) {
                    initial!(points + ini)|
                    stdout!(["P3 get the ball, the sum number is ", points + ini])      
                }           
            }
        } 
    }
}

#3-4 将ret bundle后传出去。

#6-14 通过迭代的方法实现扔球合约,将结果通过ret传出。

#16-26、#27-36、#37-46 是三个玩家接球并打印结果的合约。

More cases

About

感谢 Mike Stay 和 Joshy Orndorff的英文Rholang教程

 

本教程编辑团队来自 歪脑袋矿池(WeNode.io):

 

Rholang-China 招募RChain及Rholang的爱好者加入生态建设   https://rholang-china.org/

 

加RChain官方微信群

请加BlockChainForever001

拉你入群

 Dimworm

愁虫

Ziqi

Lango

Iris

今夕何年

 Nancy

柳菇凉