Testnet Beta 在上个月发布了,按照官方的说法,他是主网之前的最后一个测试网,和正式的主网在功能上已经很相似了。对 Leo 来说这次有了一些重大的更新,Leo 开发者需要对 Leo 程序进行一些更改才能在新的 Testnet Beta 上进行开发。这篇文章将重点介绍这些重大变更,确保能正常在新的 Testnet Beta 上开发应用。
重要提示:
截止今天(2024.06.21) Leo 的最新版本是 v1.12.0。v2.0.0 还没有发布,现在我们只能从 Leo 仓库的 testnet-beta 分支通过源码构建。
Leo v1.12.0 将是最后一个兼容 Testnet3 的版本。
Leo v2.0.0(即将发布)将是第一个兼容 Testnet Beta 的版本。
以下这些是 Leo v2.0.0 中不再支持的功能。如果你正在使用下面提到的这些内容,请务必更新你的代码!
API Endpoints
Finalize
条件语句中的赋值操作
Input 文件
Programs 命名
程序限制
Features
新概念
如果你遇到上面未涵盖的重大变更或者有其它不清楚的问题,请在这里提交问题,Leo 团队将联系并帮助你正确迁移应用程序到 Testnet Beta 中。
对于上述每个重大变更,我们都提供了相应的程序更新指南。有些更新可能只是单行代码的修复,而其他更新则可能涉及更复杂的概念。
如果你使用 API 端点,可能需要将 URL 更新为新的 Testnet Beta 端点。
在 Testnet Beta 中官方提供的端点: http://api.explorer.aleo.org/v1/testnet。 如果你使用自定义端点,只需将 URL 中的 testnet3 更新为 testnet。
链上代码的 finalize 编程模型已被新的 async/await 模型取代。详细信息请参见此部分。
这两个模型的主要区别在于,链上代码现在在 async function 中定义,而不是 finalize 块。如果程序没有声明链上代码(没有 finalize 块),则在 async/await 模型下无需修改即可工作。
以下是将 finalize 风格的 Leo 程序转换为 async/await 模型的两个示例:
旧模型:
program foo.aleo {
transition bar(...) -> credit {
...
return c then finalize(...);
}
finalize bar(...) {
...
}
}
新模型:
program foo.aleo {
async transition bar(...) -> (credit, Future) {
...
return (c, finalize_bar(...));
}
async function finalize_bar(...) {
...
}
}
snarkVM 的更新引入了 branch 指令,允许用户有条件地执行代码。例如用户可以在满足条件时从映射中删除条目。然而,这对 Leo 中的某些代码结构施加了一些基本限制。详细信息请参见此部分。
具体来说,Leo v2.0.0 不允许程序重新赋值给在现有作用域之外声明的变量,仅在链上部分代码中。
例如,以下代码将导致编译错误:
let x: u8 = 1u8;
if (condition) {
x = x + y;
} else {
x = x + z;
}
data.set(0u8, x);
用户有多种选项来绕过这一限制。一种选项是显式展开条件路径。例如:
let x: u8 = 1u8;
if (condition) {
x = x + y;
data.set(0u8, x);
} else {
x = x + z;
data.set(0u8, x);
}
另一种选项是使用三元运算符正确地构造一个与原始代码等效的代码块。
let x: u8 = 1u8;
let x_1: u8 = x + y;
let x_2: u8 = x + z;
x = condition ? x_1 : x_2;
data.set(0u8, x);
Leo CLI 过去通过具有特殊语法的文件接受输入。这已被弃用,转而使用 snarkOS、snarkVM 和 leo 中的标准 CLI 格式。
例如,如果你有一个输入文件:
// 文件名:hello.in
// hello/src/main.leo 的程序输入
[main]
public a: u32 = 1u32;
b: u32 = 2u32;
你可以通过 leo run main 运行 main 方法。Leo CLI 会找到正确的输入文件并使用输入文件中的 1u32 和 2u32 作为main 方法的参数。
而在新版本中,你需要指定具体的参数 leo run main 1u32 2u32 运行 main 方法。或者还可以通过指定参数 --file,并提供一个包含 main 方法所需参数的文件。例如你可以传递一个名为 main.in 的文件,其内容为:
以前我们通常在文件扩展名、导入、程序和外部调用中使用 .leo。例如 import foo.leo、program foo.leo 和 foo.aleo/bar(...)。
更新后的规则是:
所有包含 Leo 程序的文件必须以
.leo文件扩展名结尾。所有导入必须使用
.aleo前缀定义。所有程序必须使用
.aleo前缀声明。所有外部调用必须使用
.aleo前缀进行。
例如,如果你原本有以下文件:
// 文件名:main.leo
import foo.leo;
program baz.leo {
transition quip {
return foo.leo/bar();
}
}
更新后的程序应为:
// 文件名:main.leo
import foo.aleo;
program baz.aleo {
transition quip {
return foo.aleo/bar();
}
}
对于 Testnet Beta,snarkVM 对 Aleo 程序施加了以下限制:
程序的最大大小为 100 KB(按字符数计算)。
映射的最大数量为 31。
导入的最大数量为 64。
导入深度的最大数量为 64。
调用深度的最大数量为 31。
函数的最大数量为 31。
结构体的最大数量为 310。
记录的最大数量为 310。
闭包的最大数量为 62。
**如果你编译后的 Leo 程序超过这些限制,请考虑将程序模块化或重新设计。**这些限制只能通过 Aleo 网络基金会定义的治理过程正式协议升级来变更。
一些需要注意的协议级别限制:
**最大交易大小为 128 KB。**如果你的程序超过此限制,可能是因为需要大输入或生成大输出,请考虑优化 Leo 代码中的数据类型。
**你的交易在链上执行时消耗的最大 micro-credits 数量为
100_000_000。**如果你的程序超过此限制,请考虑优化 Leo 代码中的链上组件。
与上述限制一样,这些限制只能通过治理过程变更。
以下是 Leo v2.0.0 中可用的一些新功能!
Leo 现在允许用户读取导入程序中定义的所有映射。例如:
let val: u32 = Mapping::get(token.aleo/account, 0u32);
let val_2: u32 = Mapping::get_or_use(token.aleo/account, 0u32, 0u32);
用户现在可以直接从命令行部署和执行 Leo 程序,无需先编译。例如:
leo deploy --recursive
leo execute --program credits.aleo --broadcast transfer_public_to_private <ADDRESS> 500u64
现在可以使用 Leo CLI 工具访问已部署程序、映射值、区块、交易、节点对等、验证器委员会和内存池的详细信息。
例如,查询程序映射值:
leo query program credits.aleo --mapping-value account aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px
成功返回的数据示例:
"10331249u64"
实现无缝且直观的依赖管理。用户可以导入已部署的网络程序或本地项目中的程序作为依赖项。
// Pull credits.aleo as a dependency
leo add -n credits
// Add a local dependency named foo.aleo whose leo project path is ../foo
leo add -l ../foo foo
// Attach dependencies inside the Leo file
import credits.aleo
import foo.aleo
本节介绍了 Leo 最新更新中的一些新概念。
finalize 模型设计用于将程序的第一部分在链下执行,第二部分在链上执行,但它有无法定义代码执行顺序的问题。例如,如果函数 A 首先调用 B,然后调用 C,那么执行顺序可能是 B、C、A。
为了解决这个问题,引入了 async/await 模型。这个模型借鉴了其他编程语言中的异步编程概念,但也有一些 Aleo 区块链特有的限制。熟悉异步编程的用户会觉得这个模型很熟悉。
在高层次上,链上代码是异步的,不返回值,而是返回一个 Future。Future 可以简单地一个接一个地执行,也可以通过复杂的控制流执行。
async/await 模型的规则包括:
异步函数只能从 async transition 调用。
异步函数不能返回值。
异步转换不能在条件块内调用。
异步函数调用返回一个 Future,即稍后要执行的代码。
异步转换调用返回一个可选值,并且必须返回一个 Future。
生成的 Future 不能返回。
Future 可以传递给异步函数调用,并且必须被 await。
所有 Future 要么被异步函数调用消耗,要么作为输出返回。
考虑以下 Leo transition.
transition weird_sub(a: u8, b: u8) -> u8 {
if (a >= b) {
return a.sub_wrapped(b);
} else {
return b.sub_wrapped(a);
}
}
这段代码被编译为以下 Aleo 指令:
function weird_sub:
input r0 as u8.private;
input r1 as u8.private;
gte r0 r1 into r2;
sub.w r0 r1 into r3;
sub.w r1 r0 into r4;
ternary r2 r3 r4 into r5;
output r5 as u8.private;
注意,条件语句的两个分支都会执行,然后使用三元指令选择正确的输出。这种编译方法之所以可行,是因为转换中的操作纯粹是函数式的。
然而,链上命令并非都是函数式的;例如 get、get.or_use、contains、remove 和 set 等命令的语义都依赖于程序的状态。因此,链上代码是使用 branch 和 position 命令编译的,这些命令允许程序定义跳过的代码序列。但是,由于跳过的指令中的目标寄存器未初始化,不能在后续指令中访问它们。换句话说,根据所采取的分支,某些寄存器是无效的,尝试访问它们将导致执行错误。唯一产生这种访问尝试的 Leo 代码模式是尝试从条件语句分配到父作用域的代码,因此这些代码是被禁止的。
这个限制可以通过未来对 snarkVM 的改进来缓解,但我们暂时将此讨论搁置。
Aleo 官方链接:
