Baixar
Fechar menu -

Teste de estresse de estado mundial do Rif Consensus Node

Published on: 22 Junho, 2020

Por Pablo Pedemonte

Introdução

Em 2016, a RSK propôs a Unitrie como uma alternativa às árvores Merkle Patricia para modelar o estado mundial da Ethereum (consulte o Ethereum Yellow Paper, apêndice D). Em janeiro de 2020, adicionamos como recurso de inscrição a Unitrie ao Rif Consensus Node da RSK (com base no código do Besu). Consequentemente, os usuários agora podem escolher entre duas implementações de estado mundial:

  1. Estado mundial do Besu, conforme descrito no Livro Amarelo.
  2. Estado mundial da Unitrie, apoiado pela Unitrie.

Como tivemos a chance de executar um Rif Consensus Node selecionando de duas implementações de estado mundial diferentes, estamos em condições de comparar os méritos de cada variante de estado mundial. Neste artigo, compararemos as duas implementações, analisando os tempos de construção/pesquisa e a capacidade de alocação de contas antes de esgotar o armazenamento disponível.

Experiência 1: Tempos de construção e pesquisa

Para analisar os tempos de construção e pesquisa, executamos uma referência que consiste em adicionar um determinado número de contas a um estado mundial Ethereum inicialmente vazio e confirmar alterações (ou seja, armazenar as contas criadas na trie/Unitrie de estado mundial) cinquenta vezes durante a execução da referência. Depois de todas as contas serem confirmadas, procuramos um milhão de contas em duas variantes: 

  1. Pesquisamos um milhão de contas existentes (todas as contas encontradas).
  2. Pesquisamos um milhão de contas aleatórias (nenhuma conta encontrada na prática).

O benchmark finalmente relata os tempos de construção e pesquisa. Executamos o benchmark em uma pilha em Java de 4 GB por um intervalo que varia de 3M a 10M de contas externas. Lidamos com a variação executando o benchmark seis vezes para cada número de contas. A tabela a seguir mostra os tempos médios de construção e pesquisa para as contas 3M, 6M e 9M. CT significa Tempo de Construção, ELT Tempo de Pesquisa de Contas Existentes e RLT significa Tempo de Pesquisa de Contas Aleatórias. Todos os tempos são em segundos.

Tabela 1. Tempos de construção e pesquisa

Nossa implementação da Unitrie foi projetada para ser eficiente em termos de memória, na maioria dos casos, trocando felizmente o tempo de execução por menos espaço ocupado na memória. Essa escolha faz com que a Unitrie se compare desfavoravelmente com a tentativa da Ethereum no que diz respeito aos tempos de pesquisa, sendo a Unitrie 1,2 a 1,8 vezes mais lenta. Entretanto, vale a pena considerar o número de contas alocadas. A Unitrie pode lidar com 9 milhões de contas na memória, enquanto a implementação de estado mundial do Besu mal atinge a barreira dos 6 milhões. De fato, construir uma trie com 6 milhões de contas é 1,47 vezes mais lento que a trie clássica. Isso mostra que o Besu está debulhando a JVM para operar no limite de 6M de contas.

 

Tabela 2. Taxa de transferência de construção e pesquisa

A Tabela 2 resume a taxa de transferência de construção e pesquisa. O acrônimo CTp é um formato abreviado de Construction Throughput (Taxa de transferência de construção) e mede o número de atualizações no estado mundial da Unitrie/Ethereum por segundo. Da mesma forma, ELTp e RLTp representam Exiting Account Lookup e Random Account Lookup Throughput (Saída de pesquisa de conta e Taxa de transferência de pesquisa aleatória de conta (pesquisas por segundo)), respectivamente. De um modo geral, a Unitrie pode realizar entre 55% a 80% das operações que o estado mundial do Besu executaria em um determinado período de tempo. Notavelmente e por causa da lixeira, a taxa de transferência da Unitrie se torna 1,5 vezes a taxa de transferência de estado mundial do Besu ao inserir 6M contas. Além disso, para esse número de contas, a taxa de transferência de pesquisa da Unitrie para um milhão de elementos aleatórios correspondia ao estado mundial do Besu. 

Observe que, na pior das hipóteses, a taxa de transferência de pesquisa é de 67K pesquisas/s (9 milhões de contas, pesquisa de contas existentes). Para se convencer de que isso é aceitável, considere a operação BALANCE, cujo custo é 400 Gas. Dado o limite atual de gás de um bloco RSK de 6,8M de gás, o ideal é que você possa executar no máximo 6,8M/400 = 17K BALANCE operações em um bloco, que, mesmo em 67K pesquisas por segundo, a Unitrie funcionará em 0,25 s, assumindo que a trie seja armazenada na memória.

As Figuras 1, 2 e 3, respectivamente, mostram os tempos de construção e de pesquisa de contas existentes/aleatórias ao alocar contas no intervalo [3M, 10M]. Os gráficos exibem a tendência central e os intervalos de confiança para cada ponto de dados. Observe que, para uma pilha em Java de 4 GB, a Unitrie começa a lixeira no limite da conta de 9M (cf., estado mundial do Besu, lixeira no limite de 6M). Além disso, a Unitrie exibe intervalos de confiança mais amplos. Isso pode ser atribuído à implementação da Unitrie, que armazena caminhos compartilhados como referências simples, adicionando de maneira não determinística sobrecarga de recomputação de caminho ao tempo de execução.

Experiência 2: Contas de contrato

A Unitrie armazena cada conta em uma chave derivada de seu hash de endereço. Por construção, as chaves da conta têm um prefixo aleatório e comprimento igual; portanto, o resultado será, em média, uma árvore equilibrada binária com contas nas folhas. Isso significa que N contas de propriedade externa exigirão 2N – 1 nós.

Ao considerar as contas de contrato, a capacidade de armazenamento da Unitrie será necessariamente reduzida em relação ao cálculo anterior. Isso ocorre porque a Unitrie armazena o código como o filho certo do nó da conta correspondente. Portanto, para N contas em que α é a razão entre o contrato

Figura 1. Tempo de construção do estado mundial

Figura 2. Tempos de pesquisa (contas existentes)

Figura 3. Tempos de pesquisa (contas aleatórias)

e contas de propriedade externa, o tamanho da Unitrie se torna (2 + α) N – 1 nós. Isso varia de 2N – 1 nós quando α = 0 (incluindo o cálculo inicial de N contas externas) a 3N – 1 nós quando α = 1 (todas as contas são contratos). Por outro lado, o estado mundial da Ethereum armazena o código da conta fora da trie, para que a capacidade de armazenamento da trie não seja afetada ao considerar as contas de contrato.

Para medir a variação da capacidade de armazenamento da Unitrie induzida por diferentes valores de α, executamos um benchmark alocando o maior número possível de contas em uma pilha em Java de 4 GB antes da lixeira. Executamos a experiência para vários valores de α no intervalo [0,1]. A referência usa um contrato ERC20 de 2114 bytes no mundo real para o código da conta. A Figura 4 mostra os resultados, identificando α = 0,8 como o ponto em que a Unitrie cruza a capacidade máxima de armazenamento do estado mundial da Besu Ethereum. Também mostra o intervalo operacional da Unitrie em um heap de 4 GB: pode armazenar entre 9,5M/9M de contas externas (α = 0) e 4,9M de contas de contrato (α = 1).

Figura 4. Variação da capacidade de armazenamento induzida por contas de contrato

Conclusões

A Unitrie negocia o tempo de computação quanto à pegada de memória. As figuras 2 e 3 mostram que os tempos de pesquisa da Unitrie são cerca de 1,5 a 2 vezes mais lentos para contas no intervalo [3M, 5M]. No entanto, essa diferença desapareceria em um cenário em que o estado mundial do Besu acabasse lendo nós do disco porque não cabia na memória o suficiente do estado mundial. Nesse caso, a latência de E/S do disco dominaria os tempos de pesquisa. Nossas experiências indicam que a Unitrie pode armazenar em uma pilha de 4 GB um estado mundial que consiste em 9 milhões de contas de propriedade externa, enquanto o estado mundial do Besu começa a se degradar no limite de 6 milhões de contas. No geral, a Unitrie tem um potencial maior de evitar E/S de disco, às custas de pesquisas 1.8X mais lentas (no pior caso) em comparação com o estado mundial do Besu.

Ao considerar as contas de contrato, a capacidade de armazenamento é determinada por α, a razão entre o contrato e as contas de propriedade externa. Quanto menor o valor de α, mais nós podem caber na memória, maximizando novamente as chances de evitar a latência de E/S do disco. A experiência dois mostra que a capacidade de armazenamento da Unitrie permanece acima da do Besu por α <0,8.

O que é uma estimativa para α? Em novembro de 2020, o Etherscan registra 80,28 milhões de contas exclusivas na Mainnet. Na mesma data, o número de contas de contrato foi de 12,4 milhões. Isso dá α = 0,154. No momento da redação deste artigo, a Mainnet tinha 94,14 milhões de endereços únicos. Supondo de forma irrealista que todas as contas criadas desde novembro de 2020 eram um contrato, isso fornece um limite superior extremamente superestimado para α = 0,28. Mesmo assim, isso ainda está na zona de conforto da Unitrie!

Apêndice A: Executando as experiências

Todo o código da experiência está hospedado no projeto Github rif-consensus-node e requer JDK 11 ou posterior. Após a clonagem do projeto, você pode executar o teste de estresse da Unitrie ou o clássico estado mundial da Ethereum, indo para a pasta raiz do projeto e executando:

Em que accounts_to_create define o número de contas a serem criadas e o parâmetro alpha_ratio corresponde à proporção entre o contrato e as contas de propriedade externa definidas na Experiência dois.

Realizamos nossas experiências no MacBook Pro com um processador dual core Intel i5 de 2,3 GHz, 16 GB de RAM e um SSD de 250 GB. Todas as experiências foram executadas em uma pilha de JVM de tamanho máximo de 4 GB, usando o coletor de lixo G1.