# gvite 开发常见问题

# 部署测试节点

本地运行测试节点

# rpc 接口常见错误

常见 rpc 错误汇总

# 发交易 SOP

发交易步骤:

  1. 确定交易内容,包括 previousHash、blockType、accountAddress、toAddress、sendBlockHash、amount、tokenId、data 等
  2. 计算交易所需配额、账户可用配额和全网拥堵程度,如果当前可用配额充足,直接构造完整的账户块,发交易即可
  3. 如果当前可用配额不足,判断全网是否拥堵,如果全网拥堵,可以等待一段时间再发这笔交易;如果不拥堵,尝试计算 PoW 再发交易

相关 RPC 接口:计算交易配额、账户可用配额、全网拥堵程度计算 PoW发交易

注意:同一个账户的多笔交易之间必须严格有序,按照 hash 和 previousHash 的依赖关系形成一条链,previousHash 相同的两个不同的块最终只有一个块会被快照,另一个块会被丢弃。因此批量收发交易时,同一个账户最好用一个线程来收发交易,用 previousHash 字段来强制指定交易发送顺序,这样账户链不容易分叉,前一笔交易被回滚时也比较容易发现。

ledger_sendRawTransaction 接口常见错误和处理方法:

  • lack of difficulty field,nonce 字段非空,difficulty 字段为空; check pow nonce failed,difficulty 字段和 nonce 字段不匹配; tokenId doesn’t exist,tokenId 不存在; verify hash failed,哈希计算错误; verify signature failed,签名计算错误; receive's AccountAddress doesn't match the send'ToAddress,响应交易的 sendBlockHash 对应的请求交易不是当前账户的在途交易; Inconsistent execution results in vm, err:xxx,xxx 字段和虚拟机的执行结果不一致,通常是创建合约请求交易的 toAddress、调用内置合约请求交易的 data; abi: method not found,调用内置合约时合约方法不存在,data 字段不对; invalid method param,调用内置合约时参数错误,通常是 amount、tokenId 和 data 中的参数不对; contract not exists,请求交易的 toAddress 是合约地址,合约不存在

账户块字段错误,修改对应的字段内容后重试即可

  • The node time is inaccurate, quite different from the time of latest snapshot block.

节点上的最新快照块时间太旧,检查节点同步状态

  • calc PoW twice referring to one snapshot block

在同一个快照块期间计算两次 PoW,等一个快照块后重试即可

  • generator_vm panic error

vm 执行过程中发生了 panic,通常是由于执行期间节点发生了回滚,重试即可

  • block is already received successfully

响应交易的 sendBlockHash 对应的请求交易已经被接收,不能重复接收同一个块

  • fail to find receive's send in verifyProducerLegality;failed to find the recvBlock's fromBlock

响应交易的 fromBlockHash 对应的块不存在,通常是由于分叉或回滚,确认请求交易是否被回滚,如果未回滚,重试即可

  • verify prevBlock failed, incorrect use of prevHash or fork happened

账户链上最新的块和当前交易的 previousHash、height 对不上,通常是由于账户链分叉,可以尝试用账户链上最新块的 hash 和 height 重新生成这笔交易

  • insufficient balance for transfer

余额不足,请确认账户余额和转账金额

  • out of quota

配额不足。如果当前拥堵,等不拥堵时重试;如果当前有抵押,并且抵押获得的 utpe 足够覆盖这笔交易,等待配额恢复后重试或者尝试计算 PoW 即可;如果当前没有抵押或者抵押获得的 utpe 不能覆盖这笔交易,先抵押或者计算 PoW

# 如何计算需要抵押多少 VITE

一个账户的当前可用配额取决于当前抵押的 VITE 金额和这个账户在过去 74 个快照块期间的配额使用情况。判断需要抵押多少 VITE 时,参考配额使用规则计算这个账户在 75 秒内平均每秒需要使用多少配额

两种方法根据所需配额计算需要抵押多少 VITE:

例如一个账户需要在 75 秒内发送两笔不带备注的转账交易,那么这个账户每秒使用的配额为 21000*2/75=560,那么这个账户至少需要抵押 267 VITE

# 如何自动接收在途交易

vitejs 等 sdk 提供了自动接收在途交易的接口,推荐使用这些工具来实现自动接收在途交易。

对于更灵活的场景,可以通过轮询在途交易列表或者订阅在途交易事件的方式监听账户在途交易,然后构造响应交易的方式来发送交易

# 为何订阅不到消息

按以下步骤排查问题:

  • 确认节点已经同步到最新高度
  • 确认 gvite 节点的 node_config.json 的 PublicModules 中配置了 "subscribe" ,并且配置了 "SubscribeEnabled":true
  • 确认订阅成功,并且在订阅成功后,相应的事件发生了。例如订阅了指定账户地址的交易事件,可以用查询用户账户块列表接口查询用户账户链上是否有新交易。A 账户给 B 账户发交易,想确认交易是否发送成功,应该订阅 A 账户的新交易或者订阅 B 账户的在途交易。

# 如何区分合约地址和普通账户地址

可以根据地址字符串生成规则直接区分一个地址是合约账户还是普通账户,见 VEP 16: 账户地址规范

区块链浏览器中的账户详情页会展示一个地址是合约账户还是普通账户。

# 智能合约如何获取随机数

Solidity++ 中提供了两个语法来获取随机数

// 获取随机数种子,一次合约调用中多次调用方法,每次返回的值相同
uint64 random = random64();
// 获取随机数,一次合约调用中多次调用方法,每次返回的值不同
uint64 random = nextrandom();

随机数取值逻辑见 VEP 12: VITE 中随机数的实现

使用随机数的智能合约,在创建合约时需要指定 randomDegree 参数,参数值表示发给这个合约账户的请求交易被多少个包含随机数的快照块确认之后出响应交易,取值范围 0-75,取值越大越安全,合约出响应交易的速度也越慢。

# 如何反解析智能合约调用参数和事件

编译合约代码时指定 --abi 参数可以获得合约的 ABI 定义,包括合约构造函数、方法、事件(也叫 event、vmlog)等。可以根据合约 ABI 定义反解析请求交易的 data 字段,获取合约调用参数;根据 ABI 定义反解析 vmlog,获取事件参数。

在 Vite 链上部署智能合约后可以在区块链浏览器的合约账户详情页上传合约代码,上传成功后即可在账户详情页查看合约的源代码和 ABI 等信息。

内置合约的 ABI 定义见调用内置合约

Vite 的 ABI 是在以太坊的 ABI (opens new window) 基础上扩展的,vitejs 等 sdk 提供了 abi 解析和反解析的工具。

# 如何查询合约状态

合约执行过程中查询合约状态的两种方法:

  • 通过 event 打印中间变量的值,soliditypp vscode 插件提供了反解析 event 的功能,也可以用 vitejs 的 abi 反解析工具反解析 event。
  • 在 node_config.json 文件中配置 "VMDebug":true ,DataDir/maindata/vmlog/ 目录下的 interpreter.log 会打印合约代码执行过程日志,包含每一条合约指令的名称,执行后 stackmemorystorage 状态等。

离线查询合约状态的两种方法:

# 执行智能合约时消耗的配额如何计算

智能合约在在接收一笔交易时花费的配额等于接收交易本身的配额加上执行合约代码消耗的配额

对于执行合约代码消耗的配额,由于 Vite 的虚拟机是在 EVM 的基础上扩展的,Vite 执行合约时收取配额的逻辑类似 EVM 中执行合约时收取 gas 的逻辑,建议先阅读 EVM 的相关文档,了解一下 EVM 的执行原理。

下面以一个简单的示例来说明一笔合约响应交易的配额如何收取:

调用一个智能合约方法时,如果方法是 non-payable 的,即不可转账的,那么合约在执行这个方法时会先检查这笔调用交易的转账金额是否为 0,检查转账金额的二进制代码大致如下:

341561001057600080FD

我们把这段代码翻译成更容易理解的 OPCODE 来说明执行逻辑和配额计算逻辑。

34 CALLVALUE 读取请求交易的转账金额,放到堆栈顶部,消耗配额1
15 ISZERO 判断堆栈顶部的转账金额是否为0,如果为0在堆栈顶部放1,否则放0,消耗配额1
610010 PUSH2 0x0010 在堆栈顶部放入0x0010,消耗配额1
57 JUMPI 如果堆栈顶部的第二个元素不为0,则跳转到0x0010处继续执行代码,否则不跳转,消耗配额4
6000 PUSH1 0x00 在堆栈顶部放入0x00,消耗配额1
80 DUP1 复制堆栈顶部的元素放入堆栈,消耗配额1
FD REVERT 回滚(回滚指令会从栈顶读取两个元素,作为返回值),消耗配额0

如果执行这段代码时,转入金额为 0,那么这笔响应交易最终会回滚,共消耗 21000+1+1+1+4+1+1+0=21009 配额。

可以在本地部署测试环境,在 node_config.json 文件中配置 "VMDebug":true ,观察 DataDir/maindata/vmlog/ 目录下的 interpreter.log 日志文件,日志中包含合约执行过程中每一条合约指令的名称、剩余配额等。

# 为什么查不到智能合约的 vmlog

gvite 默认不保存 vmlog(即 event),智能合约依赖的全节点如果需要使用 vmlog,注意修改 node_config.json,配置以下任意一个参数:

  • "VmLogAll":true 表示保存所有合约的 vmlog
  • "VmLogWhiteList":["vite_d789431f1d820506c83fd539a0ae9863d6961382f67341a8b5","vite_000000000000000000000000000000000000000595292d996d"] 表示只保存指定合约的 vmlog

# 如何在测试钱包中调试

可以用类似 eruda (opens new window) 的日志工具,查看控制台日志