Moonbeam共识和确定性¶
概览¶
虽然Moonbeam致力于兼容以太坊Web3 API和EVM,但开发者仍需了解Moonbeam在共识和确定性方面与以太坊之间的主要差异。
简而言之,共识是不同主体就共享状态达成一致的一种方式。当创建区块时,网络的节点需要决定哪个区块将会代表下一个有效状态。而确定性则负责定义该有效状态何时无法被替代或是逆转。
以太坊最开始是使用基于工作量证明((PoW)的共识协议提供概率确定性。然而,从2022开始,以太坊不再使用PoW,而是改用权益证明(PoS)提供最终确定性。与其相反的是,Moonbeam使用基于委托权益证明(DPoS)的混合共识协议提供最终确定性。DPoS是Polkadot的提名权益证明 (NPoS)概念的演变,它允许委托人选择他们想要支持的候选收集人以及支持的比例,从而将更多权力交给Token持有者。
本教程将概述关于共识和确定性的一些主要差异,以及首次使用Moonbeam时需要了解的事项。
以太坊共识和确定性¶
如上所述,以太坊目前使用PoS共识协议,其中验证人将ETH质押在网络中并负责生产区块和检查新区块的有效性。区块生产的时间是固定的,分为12秒slots和32 slot epochs。每个slot随机选择一个验证人生产区块并传播至网络。每个slot都有一个随机选择的验证人委员会,负责确定区块的有效性。网络中的权益越大,验证人被选择生成或验证区块的机会就越大。
在以太坊的PoS共识协议中,最终确定性是通过“checkpoint”区块来实现的。验证者人就特定checkpoint区块的区块状态达成一致,这些checkpoint区块始终是一个epoch中的第一个区块,如果三分之二的验证人同意,则该区块被最终确定。区块确定性可以恢复,但是由于有强大的经济激励措施,因此验证人不会试图串通恢复区块。您可以在Vitalik的On Settlement Finality博客文章的Casper部分获取关于确定性的更多信息 。
Moonbeam共识和确定性¶
在Polkadot运行的机制中具有收集人和验证人,收集人负责通过收集用户端的交易记录并为中继链验证人生产状态交易证明来维持平行链(本示例中为Moonbeam)的运作。而收集人集(产生区块的节点)是根据其网络上获得的质押量来选择的。
在确定性方面,Polkadot和Kusama依赖GRANDPA运作。GRANDPA为任何指定交易(区块)提供确定性的功能。换句话说,当区块/交易被标志为结束后,除非通过链上治理和分叉,将无法被恢复。Moonbeam遵循这样的最终确定性。
PoS和DPoS的主要差异¶
在共识方面,Moonbeam主要基于委托权益证明(DPoS)模式,而以太坊遵循权益证明(PoS)模式,两者略有不同。尽管这两种机制都依赖于使用权益来验证和创建新区块,但仍存在一些关键差异。
通过以太坊上的PoS,验证人被选择根据自己在网络中的权益来生成和验证区块。只要验证人存入了验证人保证金,就可以选择他们来生成和验证区块。 然而,如前所述,网络中的质押量越大,选择验证者来生成和验证区块的机会就越大。
另一方面,通过Moonbeam上的DPoS,收集人有资格根据自身质押量加上网络中的委托人的质押量来生成区块。任何Token持有者都可以选择将其Token委托给候选收集人。质押量排名靠前的候选收集人(包括社区代表)将加入活跃收集人集。活跃收集人集中的候选收集人数量受治理的约束。一旦进入活跃收集人集,收集人将被随机选择以使用Nimbus共识框架生成区块。请注意,一旦收集人进入活跃收集人集,其总质押量不会影响他们被选择生产区块的机会。
就确定性而言,由于其使用的checkpoint确定性系统,以太坊上的区块可能比Moonbeam上的区块需要更长的时间才能完成。在以太坊中,验证人确定checkpoint区块的最终性,checkpoint区块始终是一个epoch中的第一个区块。由于一个epoch有32个slot,每个slot为12秒,因此一个区块的最终确定至少需要384秒,即6.4分钟。
Moonbeam不使用checkpoint区块,而是依赖Polkadot的GRANDPA确定性小工具,其中确定性过程与区块生产并行完成。此外,最终确定过程结合了区块链的结构,允许中继链验证人对他们认为有效的最高区块进行投票。在这种情况下,投票将适用于最终确定的所有区块,从而加快最终确定过程。当一个区块被包含在中继链后,一个区块就可以在Moonbeam上的一个区块内最终确定。
检查交易确定性策略¶
尽管确定性的小工具有所不同,但您可以使用相同的、相对简单的策略来检查以太坊和Moonbeam上的交易确定性:
- 您可以询问网络获取最新最终确定区块的哈希
- 使用哈希检索区块号
- 将其与交易的区块号进行对比。如果交易被包含在之前的区块中,则该交易已完成
- 为保证检查的安全性,按编号检索区块并验证给定的交易哈希是否在区块中
以下部分将概述如何使用Ethereum JSON-RPC(自定义Web3请求)和Substrate (Polkadot) JSON-RPC检查交易确定性。
使用Moonbeam RPC端点检查交易确定性¶
Moonbeam添加了对两个自定义RPC端点moon_isBlockFinalized
和moon_isTxFinalized
的支持,可用于检查链上事件是否已完成。
您可以前往Moonbeam自定义API页面的确定性RPC端点部分获取更多信息。
使用以太坊库检查交易确定性¶
下方代码片段遵循上一部分中概述的策略来检查交易确定性。其使用默认块参数的finalized
选项来获取最新最终确定的区块。
要在Moonbeam或Moonriver网络上测试本指南中的示例,您可以从受支持的网络端点提供商之一获取您自己的端点和API密钥。
注意事项
下方教程中提供的代码片段不适用于生产环境。请确保针对每个用例进行调整。
import { ethers } from 'ethers';
// Define the transaction hash to check finality
const txHash = 'INSERT_TX_HASH';
// Define the RPC of the provider for Moonbeam
// This can be adapted for Moonriver or Moonbase Alpha
const providerRPC = {
moonbeam: {
name: 'moonbeam',
rpc: 'INSERT_RPC_API_ENDPOINT',
chainId: 1284,
}
};
// Define the Web3 provider
const web3Provider = new ethers.JsonRpcProvider(providerRPC.moonbeam.rpc, {
chainId: providerRPC.moonbeam.chainId,
name: providerRPC.moonbeam.name,
});
const main = async () => {
// Get the last finalized block
const finalizedBlockHeader = await web3Provider.getBlock('finalized');
const finalizedBlockNumber = finalizedBlockHeader.number;
// Get the transaction receipt of the given transaction hash
const txReceipt = await web3Provider.getTransactionReceipt(txHash);
// If block number of receipt is not null, compare it against finalized head
if (txReceipt) {
const txBlockNumber = txReceipt.blockNumber;
// As a safety check, get given block to check if transaction is included
const txBlock = await web3Provider.getBlock(txBlockNumber);
console.log(`Current finalized block number is ${finalizedBlockNumber}`);
console.log(
`Your transaction in block ${txBlockNumber} is finalized? ${
finalizedBlockNumber >= txBlockNumber
}`
);
console.log(
`Your transaction is indeed in block ${txBlockNumber}? ${txBlock.transactions.includes(
txHash
)}`
);
} else {
console.log(
'Your transaction has not been included in the canonical chain'
);
}
};
main();
import { Web3 } from 'web3';
// Define the transaction hash to check finality
const txHash = 'INSERT_TX_HASH';
// Define the Web3 provider for Moonbeam
// This can be adapted for Moonriver or Moonbase Alpha
const web3Provider = new Web3('INSERT_RPC_API_ENDPOINT');
const main = async () => {
// Get the last finalized block
const finalizedBlockHeader = await web3Provider.eth.getBlock('finalized');
const finalizedBlockNumber = finalizedBlockHeader.number;
// Get the transaction receipt of the given transaction hash
const txReceipt = await web3Provider.eth.getTransactionReceipt(txHash);
// If block number of receipt is not null, compare it against finalized head
if (txReceipt) {
const txBlockNumber = txReceipt.blockNumber;
// As a safety check, get given block to check if transaction is included
const txBlock = await web3Provider.eth.getBlock(txBlockNumber);
console.log(`Current finalized block number is ${finalizedBlockNumber}`);
console.log(
`Your transaction in block ${txBlockNumber} is finalized? ${
finalizedBlockNumber >= txBlockNumber
}`
);
console.log(
`Your transaction is indeed in block ${txBlockNumber}? ${txBlock.transactions.includes(
txHash
)}`
);
} else {
console.log(
'Your transaction has not been included in the canonical chain'
);
}
};
main();
from web3 import Web3
# Define the transaction hash to check finality
tx_hash = "INSERT_TX_HASH"
# Define the Web3 provider for Moonbeam
# This can be adapted for Moonriver or Moonbase Alpha
web3_provider = Web3(Web3.HTTPProvider("INSERT_RPC_API_ENDPOINT"))
if __name__ == "__main__":
# Get the latest finalized block
finalized_block_header = web3_provider.eth.get_block("finalized")
finalized_block_number = finalized_block_header.number
# Get the transaction receipt of the given transaction hash
tx_receipt = web3_provider.eth.get_transaction_receipt(tx_hash)
# If block number of receipt is not null, compare it against finalized head
if tx_receipt is not None:
tx_block_number = tx_receipt.blockNumber
# As a safety check, get given block to check if transaction is included
tx_block = web3_provider.eth.get_block(tx_block_number)
is_in_block = False
for tx in tx_block.transactions:
if tx_hash == web3_provider.to_hex(tx):
is_in_block = True
print(f"Current finalized block number is { str(finalized_block_number) }")
print(
f"Your transaction in block { str(tx_block_number) } is finalized? { str(finalized_block_number >= tx_block_number) }"
)
print(
f"Your transaction is indeed in block { str(tx_block_number) }? { is_in_block }"
)
else:
print("Your transaction has not been included in the canonical chain")
您可以修改这些脚本以使用moon_isBlockFinalized
和moon_isTxFinalized
。为此,您可以使用Web3.js和Ethers.js的send
方法对Substrate JSON-RPC进行自定义调用。自定义RPC请求也可以使用 Web3.py和make_request
方法。您需要将方法名称和参数传递给自定义请求,您可以在Moonbeam自定义API页面上找到该请求。
import { ethers } from 'ethers';
// Define the block hash to check finality
const blockHash = 'INSERT_BLOCK_HASH';
// Define the RPC of the provider for Moonbeam
// This can be adapted for Moonriver or Moonbase Alpha
const providerRPC = {
moonbeam: {
name: 'moonbeam',
rpc: 'INSERT_RPC_API_ENDPOINT',
chainId: 1284,
},
};
// Define the Web3 provider
const web3Provider = new ethers.JsonRpcProvider(providerRPC.moonbeam.rpc, {
chainId: providerRPC.moonbeam.chainId,
name: providerRPC.moonbeam.name,
});
// Define the function for the custom web3 request
const customWeb3Request = async (web3Provider, method, params) => {
try {
return await web3Provider.send(method, params);
} catch (error) {
throw new Error(error.body);
}
};
const main = async () => {
// Check if the block has been finalized
const isFinalized = await customWeb3Request(
web3Provider,
'moon_isBlockFinalized',
[blockHash]
);
console.log(`Block is finalized? ${isFinalized}`);
};
main();
import { Web3 } from 'web3';
// Define the block hash to check finality
const blockHash = 'INSERT_BLOCK_HASH';
// Define the Web3 provider for Moonbeam
// This can be adapted for Moonriver or Moonbase Alpha
const web3Provider = new Web3('INSERT_RPC_API_ENDPOINT');
// Define the function for the custom Web3 request
const customWeb3Request = async (web3Provider, method, params) => {
try {
return await requestPromise(web3Provider, method, params);
} catch (error) {
throw new Error(error);
}
};
// In Web3.js you need to return a promise
const requestPromise = async (web3Provider, method, params) => {
return new Promise((resolve, reject) => {
web3Provider.send(
{
jsonrpc: '2.0',
id: 1,
method,
params,
},
(error, result) => {
if (error) {
reject(error.message);
} else {
if (result.error) {
reject(result.error.message);
}
resolve(result);
}
}
);
});
};
const main = async () => {
// Check if the block has been finalized
const isFinalized = await customWeb3Request(
web3Provider.currentProvider,
'moon_isBlockFinalized',
[blockHash]
);
console.log(JSON.stringify(isFinalized));
console.log(`Block is finalized? ${isFinalized.result}`);
};
main();
from web3 import Web3
# Define the block hash to check finality
block_hash = 'INSERT_BLOCK_HASH'
# Set the RPC_address for Moonbeam
# This can also be adapted for Moonriver or Moonbase Alpha
RPC_address = 'INSERT_RPC_API_ENDPOINT'
# Define the Web3 provider
web3_provider = Web3(Web3.HTTPProvider(RPC_address))
# Asynchronous JSON-RPC API request
def custom_web3_request(method, params):
response = web3_provider.provider.make_request(method, params)
return response
if __name__ == "__main__":
# Check if the block has been finalized
is_finalized = custom_web3_request(
'moon_isBlockFinalized', [block_hash])
print(
f'Block is finalized? { is_finalized["result"] }')
使用Substrate库检查交易确定性¶
Polkadot.js API package和Python Substrate Interface package为开发者提供一种使用JavaScript和Python与Substrate链交互的方式。
给定交易哈希(tx_hash
),以下代码片段将获取当前最终确定的区块并将其与您提供的交易区块号进行比较。该代码依赖于来自Substrate JSON-RPC的三个RPC请求:
chain_getFinalizedHead
- 第一个请求将获取最后的最终确定区块的区块哈希chain_getHeader
- 第二个请求将获取给定区块哈希的区块头eth_getTransactionReceipt
- 第三个请求将检索给定区块哈希的交易收据
您可以在Polkadot.js官方文档网站获取关于Polkadot.js和Substrate JSON RPC的更多信息,并在PySubstrate官方文档网站获取关于Python Substrate接口的更多信息。
import { ApiPromise, WsProvider } from '@polkadot/api';
import { types } from 'moonbeam-types-bundle';
// Define the transaction hash to check finality
const txHash = 'INSERT_TX_HASH';
// Define the provider for Moonbeam
// This can be adapted for Moonriver or Moonbase Alpha
const wsProvider = new WsProvider('INSERT_WSS_API_ENDPOINT');
const main = async () => {
// Create the provider using Moonbeam types
const polkadotApi = await ApiPromise.create({
provider: wsProvider,
typesBundle: types,
});
await polkadotApi.isReady;
// Get the latest finalized block of the Substrate chain
const finalizedHeadHash = (
await polkadotApi.rpc.chain.getFinalizedHead()
).toJSON();
// Get finalized block header to retrieve number
const finalizedBlockHeader = (
await polkadotApi.rpc.chain.getHeader(finalizedHeadHash)
).toJSON();
// Get the transaction receipt of the given tx hash
const txReceipt = (
await polkadotApi.rpc.eth.getTransactionReceipt(txHash)
).toJSON();
// You can not verify if the tx is in the block because polkadotApi.rpc.eth.getBlockByNumber
// does not return the list of tx hashes
// If block number of receipt is not null, compare it against finalized head
if (txReceipt) {
console.log(
`Current finalized block number is ${finalizedBlockHeader.number}`
);
console.log(
`Your transaction in block ${txReceipt.blockNumber} is finalized? ${
finalizedBlockHeader.number >= txReceipt.blockNumber
}`
);
} else {
console.log(
'Your transaction has not been included in the canonical chain'
);
}
polkadotApi.disconnect();
};
main();
from substrateinterface import SubstrateInterface
# Define the Ethereum transaction hash to check finality
tx_hash = "INSERT_TX_HASH"
# Point API provider to Moonbeam
# This can be adapted for Moonriver or Moonbase Alpha
moonbeam_API_provider = SubstrateInterface(
url="INSERT_WSS_API_ENDPOINT",
)
if __name__ == "__main__":
# Get the latest finalized block header of the chain
finalized_block_header = moonbeam_API_provider.get_block_header(finalized_only=True)
# Get the finalized block number from the block header
finalized_block_number = finalized_block_header["header"]["number"]
# Get the transaction receipt of the given transaction hash through a
# custom RPC request
tx_receipt = moonbeam_API_provider.rpc_request(
"eth_getTransactionReceipt", [tx_hash]
)
# Check if tx_receipt is null
if tx_receipt is None:
print("The transaction hash cannot be found in the canonical chain.")
else:
# Get the block number of the transaction
tx_block_number = int(tx_receipt["result"]["blockNumber"], 16)
# Get the transaction block through a custom RPC request
tx_block = moonbeam_API_provider.rpc_request(
"eth_getBlockByNumber", [tx_block_number, False]
)
print(f"Current finalized block number is { str(finalized_block_number) }")
print(
f"Your transaction in block { str(tx_block_number) } is finalized? { str(finalized_block_number >= tx_block_number) }"
)
print(
f'Your transaction is indeed in block { str(tx_block_number) }? { str(tx_hash in tx_block["result"]["transactions"]) }'
)
| Created: October 1, 2021