Skip to content

共识和确定性

Moonbeam v Ethereum - Consensus and Finality Banner

概览

虽然Moonbeam致力于兼容以太坊Web3 API和EVM,但开发者仍需了解Moonbeam在共识和确定性方面与以太坊之间的主要差异。

简而言之,共识是不同主体就共享状态达成一致的一种方式。当创建区块时,网络的节点需要决定哪个区块将会代表下一个有效状态。而确定性则负责定义该有效状态何时无法被替代或是逆转。

截止本文撰写时,以太坊使用的是基于工作量证明(PoW)的共识协议提供概率确定性。与其相反的是,Moonbeam使用基于提名权益证明(NPoS)的混合共识协议提供确定性。

本教程将概述关于共识和确定性的一些主要差异,以及首次使用Moonbeam时需要了解的事项。

以太坊共识和确定性

如同先前所述,以太坊目前使用的是PoW共识协议以及最长链规则,确定性的部分则是由概率决定的。

概率确定性代表一个区块(及其所有交易)不被恢复的概率会随着搭建在其之上的区块数量增加而增加。因此,确认的区块数越多,交易就越安全,发生这种被篡改及重组的可能性也就越低。如Vitalik所撰写的关于确定性的博客中所建议的:”您可以等待13次确认以获得攻击者100万分之一攻击成功的概率。“

Moonbeam共识和确定性

在波卡运行的机制中具有收集人和验证人,收集人负责通过收集用户端的交易记录并为中继链验证人生产状态交易证明来维持平行链(本示例中为Moonbeam)的运作。而收集人集(产生区块的节点)是根据其网络上获得的质押数量来选择的。

在确定性方面,波卡和Kusama依赖GRANDPA运作。GRANDPA为任何指定交易(区块)提供确定性的功能。换句话说,当区块/交易被标志为结束后,除非通过链上治理和分叉,将无法被恢复。Moonbeam遵循这样的最终确定性。

主要差异

在共识方面,Moonbeam主要基于委托权益证明(NPoS)模式,而以太坊遵循工作量证明(PoW)模式,两者大相径庭。因此,PoW概念如difficultyuncleshashrate等,在Moonbeam上没有任何意义。

对于与以太坊PoW相关并返回值的API,默认值将会被返回。现有遵循PoW机制的以太坊合约(如矿池合约)将会无法在Moonbeam上运作。

然而,Moonbeam的最终确定性可用于提供比以太坊目前更好的用户体验。检查交易确定性的策略相当简单:

  1. 您查询网络最新终结区块的哈希

  2. 您使用哈希截取区块编号

  3. 您使用区块编号对比您的交易,如果您的交易被包含在先前区块当中代表它已经被确认

  4. 进行安全检查,按编号检索区块,并验证给定的交易哈希被包含在该区块当中

以下部分将会列出您该如何使用以太坊JSON-RPC(自定义Web3请求)和Substrate(波卡)JSON-RPC检查交易终结的进度。

使用Moonbeam RPC端点查询交易确定性

Moonbeam添加了对moon_isBlockFinalizedmoon_isTxFinalized自定义RPC端点的支持,可用于查询链上事件是否已最终确定。

您可以在Moonbeam自定义API页面 中查阅详细API信息。

使用以太坊库查询交易确定性

您可以在Web3.jsEthers.js中使用send方法连接至Substrate JSON-RPC。同样也可以在Web3.py中使用make_request方法执行自定义RPC请求,您可以使用Web3.js的例子作为基准。

此代码依赖来自Substrate JSON-RPC的两个自定义RPC请求:chain_getFinalizedHeadchain_getHeader。第一个请求将会获得最新确认区块的区块哈希,第二个请求将获得已知区块哈希的区块标题。 eth_getBlockByNumbereth_getTransactionReceipt的调用也是如此,以检查给定的交易哈希是否包含在区块中。

要在Moonbeam或Moonriver网络上测试本指南中的示例,您可以从受支持的网络端点提供商之一获取您自己的端点和API密钥。

注意事项

以下所提供的代码片段并不适用于每个生产环境,请确保您已根据实际案例进行修改或调整。

import Web3 from 'web3';

// Define the transaction hash to check finality
const txHash = 'tx_hash';

// Define the Web3 provider for Moonbeam
// This can also be adapted for Moonriver or Moonbase Alpha
const web3 = new Web3('RPC-API-ENDPOINT-HERE'); // Insert your RPC URL here

// 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 () => {
  // Get the latest finalized block of the Substrate chain
  // Uses Polkadot JSON-RPC
  const finalizedHeadHash = await customWeb3Request(
    web3.currentProvider,
    'chain_getFinalizedHead',
    []
  );

  // Get finalized block header to retrieve number
  // Uses Polkadot JSON-RPC
  const finalizedBlockHeader = await customWeb3Request(web3.currentProvider, 'chain_getHeader', [
    finalizedHeadHash.result,
  ]);
  const finalizedBlockNumber = parseInt(finalizedBlockHeader.result.number, 16);

  // Get the transaction receipt of the given tx hash
  // Uses Ethereum JSON-RPC
  const txReceipt = await customWeb3Request(web3.currentProvider, 'eth_getTransactionReceipt', [
    txHash,
  ]);

  // If block number of receipt is not null, compare it against finalized head
  if (txReceipt) {
    // Convert to Number
    const txBlockNumber = parseInt(txReceipt.result.blockNumber, 16);

    // As a safety check, get given block to check if transaction is included
    // Uses Ethereum JSON-RPC
    const txBlock = await customWeb3Request(web3.currentProvider, 'eth_getBlockByNumber', [
      txBlockNumber,
      false,
    ]);

    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.result.transactions.includes(
        txHash
      )}`
    );
  } else {
    console.log('Your transaction has not been included in the canonical chain');
  }
};

main();
import ethers from 'ethers';

// Define the TxHash to Check Finality
const txHash = '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: 'RPC-API-ENDPOINT-HERE', // Insert your RPC URL here
    chainId: 1284,
  }
};

// Define the Web3 provider
const web3Provider = new ethers.providers.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 () => {
  // Get the latest finalized block of the Substrate chain
  // Uses Polkadot JSON-RPC
  const finalizedHeadHash = await customWeb3Request(web3Provider, 'chain_getFinalizedHead', []);

  // Get finalized block header to retrieve number
  // Uses Polkadot JSON-RPC
  const finalizedBlockHeader = await customWeb3Request(web3Provider, 'chain_getHeader', [
    finalizedHeadHash,
  ]);
  const finalizedBlockNumber = parseInt(finalizedBlockHeader.number, 16);

  // Get the transaction receipt of the given tx hash
  // Uses Ethereum JSON-RPC
  const txReceipt = await customWeb3Request(web3Provider, 'eth_getTransactionReceipt', [txHash]);

  // If block number of receipt is not null, compare it against finalized head
  if (txReceipt) {
    // Convert to Number
    const txBlockNumber = parseInt(txReceipt.blockNumber, 16);

    // As a safety check, get given block to check if transaction is included
    // Uses Ethereum JSON-RPC
    const txBlock = await customWeb3Request(web3Provider, 'eth_getBlockByNumber', [
      txBlockNumber,
      false,
    ]);

    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 TxHash to Check Finality
txHash = 'tx_hash'

# Set the RPC_address for Moonbeam
# This can also be adapted for Moonriver or Moonbase Alpha
RPC_address = 'RPC-API-ENDPOINT-HERE' # Insert your RPC URL here

# Define the Web3 provider
web3Provider = Web3(Web3.HTTPProvider(RPC_address))

# asynchronous JSON RPC API request
def customWeb3Request(method, params):
    response = web3Provider.provider.make_request(method, params)
    return response

if __name__ == "__main__":
    # Get the latest finalized block of the Substrate chain
    # Uses Polkadot JSON-RPC
    finalizedHeadHash = customWeb3Request('chain_getFinalizedHead', [])

    # Get finalized block header to retrieve number
    # Uses Polkadot JSON-RPC
    finalizedBlockHeader =  customWeb3Request('chain_getHeader', [finalizedHeadHash["result"]])
    finalizedBlockNumber = int(finalizedBlockHeader["result"]["number"], 16)

    # Get the transaction receipt of the given tx hash
    # Uses Ethereum JSON-RPC
    txReceipt =  customWeb3Request('eth_getTransactionReceipt', [txHash])

    # If block number of receipt is not null, compare it against finalized head
    if txReceipt is not None :
        txBlockNumber = int(txReceipt["result"]["blockNumber"], 16)

        # As a safety check, get given block to check if transaction is included
        # Uses Ethereum JSON-RPC
        txBlock = customWeb3Request('eth_getBlockByNumber', [txBlockNumber, bool(0)])

        print('Current finalized block number is ' + str(finalizedBlockNumber))
        print('Your transaction in block ' +str(txBlockNumber) + ' is finalized? ' + str(finalizedBlockNumber >= txBlockNumber))
        print('Your transaction is indeed in block ' +str(txBlockNumber) + '? ' + str(txHash in txBlock["result"]["transactions"]) )
    else:
        print("Your transaction has not been included in the canonical chain")

Checking Tx Finality with Substrate Libraries

Polkadot.js API组件Python Substrate Interface组件提供开发者使用Javascript操作Substrate链的方法。

给定一个交易哈希(tx_hash),以下代码片段会获取当前的最终区块,并将其与您提供的交易的区块高度进行比较。该代码依赖于来自 Substrate JSON-RPC的三个RPC请求:

  • chain_getFinalizedHead - 第一个请求获取最新的最终确认区块的区块哈希
  • chain_getHeader - 第二个请求获取给定区块哈希的块头
  • eth_getTransactionReceipt - 检索给定交易哈希的ETH交易收据

您可以在Polkadot.js官方文档网站Python Substrate Interface官方文档网站查询所有关于这两个库的详细JSON RPC信息。

import { ApiPromise, WsProvider } from '@polkadot/api';
import { typesBundle } from 'moonbeam-types-bundle';

// Define the TxHash to Check Finality
const txHash = 'tx_hash';

// Define the provider for Moonbeam
const wsProvider = new WsProvider('wss://wss.api.moonbeam.network');

const main = async () => {
  // Create the provider using Moonbeam types
  const polkadotApi = await ApiPromise.create({
    provider: wsProvider,
    typesBundle: typesBundle,
  });
  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 hash

  // 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');
  }
};

main();
from substrateinterface import SubstrateInterface

# Define the Ethereum TxHash to Check Finality
txHash = 'txHash'

# Point API Provider to Moonbeam
# This can also be adapted for Moonriver or Moonbase Alpha
moonbeamAPIProvider = SubstrateInterface(
    url="wss://wss.api.moonbeam.network",
)

if __name__ == "__main__":

    # Get the latest finalized block header of the chain
    finalizedBlockHeader =  moonbeamAPIProvider.get_block_header(finalized_only = True)
    # Get the finalized block number from the block header
    finalizedBlockNumber = finalizedBlockHeader["header"]["number"]
    # Get the transaction receipt of the given tx hash through a custom RPC request
    txReceipt = moonbeamAPIProvider.rpc_request('eth_getTransactionReceipt', [txHash])

    # Check if txReceipt is null 
    if txReceipt is None:
        print('The transaction hash cannot be found in the canonical chain.')
    else:
        #Get the block number of the tx
        txBlockNumber = int(txReceipt["result"]["blockNumber"], 16)
        #Get the transaction block through a custom RPC request
        txBlock = moonbeamAPIProvider.rpc_request('eth_getBlockByNumber', [txBlockNumber, False])

        print("Current finalized block number is", finalizedBlockNumber)
        print("Your transaction in block", txBlockNumber, "is finalized? " + str(txBlockNumber <= finalizedBlockNumber))
        print("Your transaction is indeed in block", txBlockNumber, "? "+ str(txHash in txBlock["result"]["transactions"]))