WEB3DEV

Cover image for Como Implementar o SPV da Arbitrum com Solidity
Panegali
Panegali

Posted on

Como Implementar o SPV da Arbitrum com Solidity

Conhecimento Antecedente:


Ciclo de vida

Sequenciador (Sequencer) Arbitrum

1

Fonte: offchainlabs

TxHash

TxHash
=>
InboxMessageg
=>
SequencerMessage
=>
SequencerBatchItem
=>
SequencerFeedItem
=>
BroadcastFeedMessage
=>
BroadcastMessage
=>
SubmissionInputData
Enter fullscreen mode Exit fullscreen mode
  • Na parte SequencerBatchItem => SequencerFeedItem, Classic e Nitro usam o formato base64 para processar dados.
  • Na parte SequencerFeedItem => BroadcastFeedMessage, Nitro usa um algoritmo geral de compactação de dados chamado brotli para compactar lotes.

Implementação Concreta

Procedimentos

1.TxHash => InputData => SubmissionInputData
//Prove que o InputData gerado com base no TxHash enviado pelo usuário está incluído no SubmissionInputData fornecido pelo cliente local

2.SubmissionInputData => SubmissionTxHashRLP => NewSubmissionTxHash
//Prove que o NewSubmissionTxHash gerado por SubmissionTxHashRLP com base em SubmissionInputData é consistente com o SubmissionTxHash enviado pelo usuário

3.NewSubmissionTxHash + "MPT_proof" => L1Blockhash => Next
//Prove que o NewSubmissionTxHash gerado é comprovado pelo MPT em combinação com os dados do bloco e o L1Blockhash é passado para o próximo processo

4.L1Blockhash + "OtherProof" => NowL1BlockHash
//Prove que L1Blockhash é um bloco anterior de NowL1BlockHash
Enter fullscreen mode Exit fullscreen mode

Detalhes

O usuário precisa fornecer TxHash e SubmissionTxHash.

3.1.1 TxHash => InputData => SubmissionInputData

Finalidade: Verificar a validade de uma transação L2 nos parâmetros da transação L1.

Análise: Tendo em vista a importância desta etapa, precisamos analisá-la detalhadamente.


Classic

Os métodos de prova a seguir são aplicáveis ​​apenas ao Classic, mas também são pré-requisitos para entender o Nitro.

  • Sugira uma transação na Arbitrum: 0x6a1d, registre o hash como TxHash0x6a1d
  • Batches contendo TxHash0x6a1d são empacotados, compactados e publicados em L1 pelo Sequencer (Sequenciador), e esse hash é registrado como SubmissionTxHash0xb150
  • O InputData pode ser obtido por meio de SubmissionTxHash0xb150, que é registrado como SubmissionInputData0xb150.
  • As informações de transação correspondentes podem ser obtidas por meio de TxHash0x6a1d, que é registrado como TxInfo0x6a1d
// TxInfo0x6a1d
{
  hash: '0x6a1d569ca0a8a64d868092ed42ad7d194293c3059cf9780991174f43dd8d3ba5',
  type: 0,
  accessList: null,
  blockHash: '0x4dd97d8cbb5ef3e4fdddbc99ae813e8713f478902038dee8d1c672117ed35cfd',
  blockNumber: 17237171,
  transactionIndex: 0,
  confirmations: 22649108,
  from: '0xC0aF3690cCEdcc353AEb33cC0bB3eF63EF1070ba',
  gasPrice: BigNumber { _hex: '0x1c16ab5a', _isBigNumber: true },
  gasLimit: BigNumber { _hex: '0x079366', _isBigNumber: true },
  to: '0xC266551ef06b976AbC403F998Bd3Ae2398403937',
  value: BigNumber { _hex: '0x07911ad5a14714', _isBigNumber: true },
  nonce: 2,
  data: '0x',
  r: '0x8698fd9762461c6b042007e99a236d50f6cdaf5b6498e56832cbdfb470de6669',
  s: '0x6058d9801636a3f6c90db74b55c8dc611030715989a3b896e1fa66b30d8f2c9e',
  v: 84358,
  creates: null,
  chainId: 42161,
  wait: [Function (anonymous)]
}
Enter fullscreen mode Exit fullscreen mode

Estrutura de dados L2Message e processamento de Tx Info na Arbitrum:

txData := []interface{}{
 t.SequenceNum, //Nonce
 t.GasPrice,   //Preço do gás
 t.GasLimit,   //Limite do gás
 dest,      //Para
 t.Payment,   //Valor
 t.Calldata,   //Dado
 v,       //V
 t.R,      //R
 t.S,      //S
}
Enter fullscreen mode Exit fullscreen mode

Manipulando Nonce, GasPrice, GasLimit, To, Value, Data de Tx Info:

func encodeUnsignedTx(tx CompressedTx) ([]byte, error) {
   nonceData, err := rlp.EncodeToBytes(tx.SequenceNum)
   if err != nil {
       return nil, err
    }
   gasPriceData, err := rlp.EncodeToBytes(tx.GasPrice)
   if err != nil {
       return nil, err
   }
   gasLimitData, err := rlp.EncodeToBytes(tx.GasLimit)
   if err != nil {
       return nil, err
   }
   paymentData, err := encodeAmount(tx.Payment)
   if err != nil {
       return nil, err
   }

   var data []byte
   data = append(data, nonceData...)
   data = append(data, gasPriceData...)
   data = append(data, gasLimitData...)
   if tx.To == nil {
       data = append(data, 0x80)
   } else {
       destData, err := tx.To.Encode()
       if err != nil {
           return nil, err
       }
       data = append(data, destData...)
   }
   data = append(data, paymentData...)
   data = append(data, tx.Calldata...)
   return data, nil
}
Enter fullscreen mode Exit fullscreen mode
func encodeECDSASig(v byte, r, s *big.Int) []byte {
   data := make([]byte, 0, 65)
   data = append(data, ethmath.U256Bytes(new(big.Int).Set(r))...)
   data = append(data, ethmath.U256Bytes(new(big.Int).Set(s))...)
   data = append(data, v)
   return data
}
Enter fullscreen mode Exit fullscreen mode
  • Aplicando RLP (Recursive Length Prefix ou Prefixo de Comprimento Recursivo) aos dados parciais de TxInfo0x6a1d e processando V, R, S, obteremos os seguintes dados, anotados como TxInfoRLP0x6a1d
// TxInfoRLP0x6a1d
02841c16ab5a8307936694c266551ef06b976abc403f998bd3ae23984039378707911ad5a14714008698fd9762461c6b042007e99a236d50f6cdaf5b6498e56832cbdfb470de66696058d9801636a3f6c90db74b55c8dc611030715989a3b896e1fa66b30d8f2c9e
Enter fullscreen mode Exit fullscreen mode
  • Correspondente:
02 // SequenceNum/Nonce
84 // 0x84 - 0x80 = 4 bytes
1c16ab5a // Preço do Gás
83 // 0x83 - 0x80 = 3 bytes
079366 // Limite do Gás
94 // 0x94 - 0x80 = 20 bytes
c266551ef06b976abc403f998bd3ae2398403937 // Para
87 // 0x87 - 0x80 = 7 bytes
07911ad5a14714 // Valor   
00 // V
8698fd9762461c6b042007e99a236d50f6cdaf5b6498e56832cbdfb470de6669 // R
6058d9801636a3f6c90db74b55c8dc611030715989a3b896e1fa66b30d8f2c9e // S
Enter fullscreen mode Exit fullscreen mode
  • Encontre-o em SubmissionInputData0xb150:

2

  • Assim, no Classic, você pode usar o TxHash para provar se existe uma transação L2 em L1, ou seja, você pode verificar a validade de uma transação L2 nos parâmetros da transação L1.

Nitro

  • O ciclo de vida do Nitro é basicamente o mesmo do Classic, mas no processo de SequencerFeedItem => BroadcastFeedMessage, o algoritmo brotli é usado para compactar todo o batch. Por causa disso, o método de prova aplicável ao Classic não é aplicável ao Nitro.

  • Esquema de certificação aplicável ao Nitro (o Esquema 3 é válido após o teste):

  1. Obtenha SubmissionInputData0xb150, desconstrua seus dados, obtenha dados de Batch nele e, finalmente, julgue se ele contém TxInfoRLP0x6a1d. (Falhou)
  2. Obtenha todos os outros txs no mesmo bloco Batch de Tx0x6a1d, base64 todos eles e, em seguida, monte-os em dados Batch. Use o brotli para comprimi-los. Finalmente, compare os dados compactados com SubmissionInputData0xb150. (Falhou)
  3. A execução de um Nó Arbitrum Nitro pode sincronizar diretamente o estado L1 de cada transação L2 sem provar por meio do 'Sequenciador' e outras etapas.

Argumentos do Nitro:

  • TxHash: Transação da Arbitrum. (Fornecida pelo usuário)
  • SubmissionTxHash: SubmissionTx na Ethereum L1. (Fornecida pelo usuário)
  • SubmissionInputData: InputData de SubmissionTxHash. (Cliente local)
  • Execute o Nó Arbitrum Nitro fora da cadeia (off-chain), forneça o TxHash fornecido pelo usuário e comprove se o TxHash está incluído no SubmissionInputData por meio dos dados do nó. Se for comprovado, vá para a próxima etapa.

Nota complementar — Premissa na verificação da eficácia do TxHash:

* Planejamos adicionar uma nova etapa para verificar a eficácia do TxHash. Esta etapa será após a Etapa 1 e antes da Etapa 2.

* Uma vez que o status da transação de TxHash em L1 existe no Nó Arbitrum Nitro, pode-se supor que seja baseado nos dados fornecidos pelo Nó Arbitrum Nitro.

* Descartou as transações com falha e construiu uma árvore de Merkel fora da cadeia usando o hash da transação bem-sucedida. Simultaneamente, construir uma árvore de Merkel na cadeia. Depois que o usuário fornecer o TxHash e provar na primeira etapa, verifique os dados MPT fornecidos pelo cliente TxHash na cadeia. Primeiro, verifique se as duas árvores de Merkel são consistentes e, em seguida, verifique se TxHash existe na árvore de Merkel.

Pergunta:

1. De que forma o hash da transação é empacotado e compactado?
2. Com que frequência é empacotado e compactado?

Referência: cirom e snarkjs


3.1.2 SubmissionInputData => SubmissionTxHashRLP => NewSubmissionTxHash

Finalidade: Verificar a validade do parâmetro de transação L1 submetido que existe na transação L1.

Argumentos:

  • SubmissionTxHash: do último procedimento
  • SubmissionInputData: do último procedimento
  • SubmissionTxHashRLP: Codificação RLP de SubmissionInputData

Procedimentos:

Carregue SubmissionTxHash e SubmissionTxHashRLP na cadeia para verificação. Como SubmissionTxHashRLP contém SubmissionInputData, Keccak256 é usado no contrato para calcular o hash de SubmissionTxHashRLP, que é registrado como NewSubmissionTxHash, e para verificar se NewSubmissionTxHash é consistente com SubmissionTxHash. Se for consistente, a verificação é aprovada.

//Solidity
function validateRLPTxInfoBySubTxHash(
       bytes memory rlpTxInfo,
       bytes[] memory header
   ) internal view;
Enter fullscreen mode Exit fullscreen mode

3.1.3 NewSubmissionTxHash + MPT_proof => L1Blockhash => Next

Finalidade: Verificar a validade da transação L1 submetida que existe no Bloco L1.

Argumentos:

  • SubmissionTxHashRLP: do último procedimento
  • NewSubmissionTxHash: do último procedimento
  • L1BlockNumberMPTProof: dados MPT do bloco L1 de SubmissionTx
  • L1BlockTxRootHash: Hash raiz do bloco L1 de SubmissionTx
  • L1BlockHash: Bloco Hash do bloco L1 de SubmissionTx

Carregue cinco argumentos na cadeia para verificação. A verificação aqui é dividida em quatro partes. Se todas as quatro partes forem aprovadas, a verificação será aprovada.

  1. Verifique a conexão entre SubmissionTxHashRLP e L1BlockNumberMPTProof
// Solidity
function validateRLPTxInfoByProof(
       bytes memory rlpTxInfo,
       bytes[] memory proof
   ) internal view;
Enter fullscreen mode Exit fullscreen mode
  1. Verifique L1BlockTxRootHash
// Solidity
function validateTxRootHash(bytes[] memory proof, bytes[] memory header)
       internal
       view
       returns (bytes32 rootHash);
Enter fullscreen mode Exit fullscreen mode
  1. Verifique L1BlockHash
// Solidity
function validateBlockHash(bytes[] memory header) internal view;
Enter fullscreen mode Exit fullscreen mode
  1. Verifique L1BlockNumberMPTProof
// Solidity
function validateMPTProof(
       bytes32 rootHash,
       bytes memory mptKey,
       bytes[] memory proof
   ) internal view returns (bytes memory value);
Enter fullscreen mode Exit fullscreen mode

3.1.4 L1Blockhash + OtherProof => NowL1BlockHash

Finalidade: Verificar a validade do L1 Block existente na L1 Blockchain.

Argumentos:

  • L1Blockhash: do último procedimento
  • L1BlockNumber: número do bloco correspondente

Aqui estamos divididos em duas partes:

Fora da rede (Off-chain):

  1. Obtenha as informações do bloco de L1BlockNumber para o número do bloco mais recente e classifique-os em ordem, 1.. 2.. 3… n.
  2. Processe os dados brutos do segundo bloco para obter o código RLP e, em seguida, use Keccak256 para hash para obter o BlockHash do segundo bloco.
  3. Compare este BlockHash fora da cadeia com o BlockHash na cadeia. Se for o mesmo, a verificação é aprovada.
  4. Obtenha ParentHash do segundo bloco e compare-o com o primeiro BlockHash. Se for o mesmo, a verificação é aprovada.
  5. Prove para o enésimo bloco sucessivamente.
  6. É necessário que o tempo de cálculo da parte fora da cadeia seja inferior a 1h e, se for superior a 1h, continuará a provar do enésimo bloco até o último número de bloco.
  7. Argumentos:
  • ChainOffL1BlockHash: BlockHash do enésimo bloco.
  • ChainOffL1BlockNumber: BlockNumber do enésimo bloco.

Na cadeia (On-chain):

  • Use a função blockhash (uint blockNumber) no contrato para obter o hash do bloco com o número do bloco ChainOffL1BlockNumber, que é registrado como NowL1BlockHash.
  • Compare se NowL1BlockHash é igual a ChainOffL1BlockHash. Se for o mesmo, a verificação é aprovada.
// Solidity
function validateBlockNumberhash(
    uint256 blockNumberPast,
    bytes32 blockHashPast
    ) internal view;
Enter fullscreen mode Exit fullscreen mode

3.1.5 O hash da transação L2 submetida pelo usuário poderá ser considerada válida após a comprovação das 4 etapas acima.


Artigo escrito por celestialRiver e traduzido por Marcelo Panegali.

Top comments (0)