WEB3DEV Español

Delia Viloria T
Delia Viloria T

Posted on

Registros Ethereum, Manos a la obra con Ethers.js

Al desarrollar en Ethereum, uno de los principales tipos de datos que, inevitablemente, encontrarás son los eventos de contratos inteligentes. Los eventos proporcionan una manera para que un contrato inteligente se comunique con las aplicaciones de firma, ya sea con la finalidad de devolver valores de contrato o para activar una función correspondiente. Además, como cada evento está grabado en la blockchain, también puede servir como un registro histórico (y almacenamiento más barato) de todos los eventos emitidos por un contrato.

Este artículo se enfoca en la decodificación de esta función de registro y asume algunos conocimientos básicos sobre eventos y registros de Ethereum. A modo de repaso:

  • Los eventos son específicos de la aplicación, tal como los define el desarrollador del contrato inteligente. Por ejemplo, los desarrolladores de Larvalabs definen un evento PunkTransfer para notificar a las aplicaciones cuando se ha transferido un Cryptopunk. El evento se emite cuando se invoca PunkTransfer().

event PunkTransfer(address indexed from, address indexed to, uint256 punkIndex);

  • Al consultar los registros de un evento, los valores emitidos del evento se devuelven como un DataHexString. Por lo tanto, necesitamos el contrato ABI para decodificar los datos (recuerda que el evento es específico de la aplicación). A continuación se muestra la respuesta bruta al consultar el evento PunkTransfer, las especificaciones completas pueden encontrar aquí.

Image description

  • Ehereum utiliza filtros bloom para almacenar secciones de los datos de registro, lo que permite una consulta y filtrado eficiente de registros sin la necesidad de descargar el blockchain completo. Los “temas” se utilizan para describir el evento y el primer tema generalmente consistente en el hash Keccak256 de la firma del evento (es decir PunkTransfer(address,address,uint256)). Por lo tanto, los temas permiten la recuperación rápida de registros específicos.

Image description

Ten en cuenta que este hash coincide con el primer tema en la captura de pantalla anterior

Para entender mejor, vamos a pasar por 2 formas de procesamiento de registros, con y sin ayuda de la biblioteca de conveniencia de contrato Ethers.js. Esta guía estará consultando los eventos PunkTransfer que se han emitido en los últimos 1000 bloques. Al hacerlo tendremos una mejor comprensión de lo que Ethers.js está haciendo debajo del capó y cómo los eventos son realmente registrados en Ethereum. El repositorio para este tutorial se puede encontrar aquí.


Decodificando registros en bruto

Para obtener los registros en bruto, usaremos el proveedor Ethers.jspara consultar el blockchain sin la funcionalidad adicional de auxiliares de contrato. Después de configurar tu proveedor y consultar el bloque más reciente, podemos obtener los registros sin procesar utilizando provider.getLogs().

async function getLogs() {
    console.log(`Obtendo os eventos do PunkTransfer...`);

    const cryptopunkContractAddress: string = '0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB'; 

    const eventSignature: string = 'PunkTransfer(address,address,uint256)';
    const eventTopic: string = ethers.utils.id(eventSignature); // Obter a string hexadecimal de dados

    rawLogs = await provider.getLogs({
        address: cryptopunkContractAddress,
        topics: [eventTopic],
        fromBlock: currentBlock - 10000, 
        toBlock: currentBlock
    });
}
Enter fullscreen mode Exit fullscreen mode

getLogs( ) espera que un filtro se pase como parámetro. En este caso, pasamos la dirección del contrato CryptoPunks, tema para filtrar, así como el rango del bloque de filtro. La lista completa de opciones de filtro se puede encontrar en los documentos oficiales de ether.

Ten en cuenta que hemos obtenido el tema mediante el hash de eventSignature con ethers.utils.id, que devuelve el hash Keccak256. Esto apunta al hecho de que los eventos son almacenados como hashes en el blockchain para reducir los requisitos de almacenamiento, pues todos los hashes tienen una longitud fija. Además, dado que los hashes son fáciles de calcular en función de una entrada, pero increíblemente difíciles de aplicarles ingeniería inversa a través de una salida, esto también proporciona un cierto nivel de privacidad en las transacciones.

De esta forma, sin conocer el contrato desde el cual se originó el registro, no podríamos saber mucho sobre el evento ni el tema por el cual filtrar. Como referencia, un ejemplo de respuesta en bruto del evento PunkTransferse puede ver a continuación:

Image description

Solo con la captura de pantalla anterior, no podemos obtener ningún dato útil específico de la aplicación. Sin embargo, dado que somos conscientes del contexto del evento, también podemos decodificarlo usando ABI (Application Binary Interface).

async function processLogsWithInterface() {
    const abi: string = '[{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"punkIndex","type":"uint256"}],"name":"PunkTransfer","type":"event"}]';

    const intrfc = new ethers.utils.Interface(abi);

    rawLogs.forEach((log: any) => {
        console.log(`ANTES DE ANALISAR:`);
        console.debug(log);
        console.log(`\n`);

        console.log(`DEPOIS DE ANALISAR:`);
        let parsedLog = intrfc.parseLog(log);
        console.debug(parsedLog);
        console.log('************************************************');
    })
}
Enter fullscreen mode Exit fullscreen mode

La ABI proporcionó especificaciones sobre cómo interactuar con el registro de eventos en bruto. Al crear una instancia de un objeto de Interface con ABI, podemos decodificar el registro sin procesar con parsedLog(), lo que da como resultado lo siguiente:

Image description

El registro sin procesar se analizó con éxito y podemos extraer los valores emitidos por el contrato inteligente a través del evento PunkTransfer.


Biblioteca de conveniencia Ethers.js

Como ahora comprendemos mejor cómo se manejan los registros, también podemos apreciar mejor la abstracción que permite Ethers.js por medio del objeto Contract. Primero creamos el objeto de contrato CryptoPunk:

async function getContract() {
  console.log(`Obtenção do contrato CryptoPunk...`);

  const cryptopunkContractAddress: string = '0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB'; 
  const cryptopunkContractAbi: string = '[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"punksOfferedForSale","outputs":[{"name":"isForSale","type":"bool"},{"name":"punkIndex","type":"uint256"},{"name":"seller","type":"address"},{"name":"minValue","type":"uint256"},{"name":"onlySellTo","type":"address"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"}],"name":"enterBidForPunk","outputs":[],"payable":true,"type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"},{"name":"minPrice","type":"uint256"}],"name":"acceptBidForPunk","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"addresses","type":"address[]"},{"name":"indices","type":"uint256[]"}],"name":"setInitialOwners","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"withdraw","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"imageHash","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"nextPunkIndexToAssign","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"punkIndexToAddress","outputs":[{"name":"","type":"address"}],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"standard","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"punkBids","outputs":[{"name":"hasBid","type":"bool"},{"name":"punkIndex","type":"uint256"},{"name":"bidder","type":"address"},{"name":"value","type":"uint256"}],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[],"name":"allInitialOwnersAssigned","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"allPunksAssigned","outputs":[{"name":"","type":"bool"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"}],"name":"buyPunk","outputs":[],"payable":true,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"punkIndex","type":"uint256"}],"name":"transferPunk","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"}],"name":"withdrawBidForPunk","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"to","type":"address"},{"name":"punkIndex","type":"uint256"}],"name":"setInitialOwner","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"},{"name":"minSalePriceInWei","type":"uint256"},{"name":"toAddress","type":"address"}],"name":"offerPunkForSaleToAddress","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[],"name":"punksRemainingToAssign","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"},{"name":"minSalePriceInWei","type":"uint256"}],"name":"offerPunkForSale","outputs":[],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"}],"name":"getPunk","outputs":[],"payable":false,"type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"pendingWithdrawals","outputs":[{"name":"","type":"uint256"}],"payable":false,"type":"function"},{"constant":false,"inputs":[{"name":"punkIndex","type":"uint256"}],"name":"punkNoLongerForSale","outputs":[],"payable":false,"type":"function"},{"inputs":[],"payable":true,"type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"punkIndex","type":"uint256"}],"name":"Assign","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"punkIndex","type":"uint256"}],"name":"PunkTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"punkIndex","type":"uint256"},{"indexed":false,"name":"minValue","type":"uint256"},{"indexed":true,"name":"toAddress","type":"address"}],"name":"PunkOffered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"punkIndex","type":"uint256"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"fromAddress","type":"address"}],"name":"PunkBidEntered","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"punkIndex","type":"uint256"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"fromAddress","type":"address"}],"name":"PunkBidWithdrawn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"punkIndex","type":"uint256"},{"indexed":false,"name":"value","type":"uint256"},{"indexed":true,"name":"fromAddress","type":"address"},{"indexed":true,"name":"toAddress","type":"address"}],"name":"PunkBought","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"punkIndex","type":"uint256"}],"name":"PunkNoLongerForSale","type":"event"}]';
  cryptopunkContract = new ethers.Contract(cryptopunkContractAddress, cryptopunkContractAbi, provider);
}
Enter fullscreen mode Exit fullscreen mode

Ten en cuenta que al crear el contrato, también proporcionamos la dirección del contrato y la ABI completa (que se puede encontrar en Etherscan). Con la instancia del contrato configurada, la obtención de los registros se puede hacer de forma sencilla a través de una simple función queryFilter().

async function getEvents() {
  console.log(`Obtendo os eventos do PunkTransfer...`);

  let events = await cryptopunkContract.queryFilter('PunkTransfer', currentBlock - 10000, currentBlock);

  console.log(events);
}
Enter fullscreen mode Exit fullscreen mode

Este método nos permite evitar las complejidades de administrar los temas al consultar un evento, ya que solo necesitamos pasar el nombre del evento. El resultado final es el mismo, pero es mucho más fácil extraer los datos del contrato inteligente:

Image description


Gracias por quedarte hasta el final. Me encantaría escuchar tus pensamientos/comentarios, así que deja un comentario. Si deseas recibir más información relacionada con cripto, estoy activo en twitter @AwKaiShin y también puedes visitar mi sitio web personal si desea mis servicios 🙂.


Artículo escrito por Aw Kai Shin y traducido por Delia Viloria T., su original puede ser leído aquí.

Sería genial escucharte en nuestro Discord, puedes contarnos tus ideas, comentarios, sugerencias y dejarnos saber lo que necesitas.

Si prefieres puedes escribirnos a @web3dev_es en Twitter.

Discussion (0)