Comprender las estructuras de datos que componen una cadena de bloques nos ayuda a pensar en formas creativas de analizar estos datos.
**Escrito por:**NOXX
Compilar: vaciar
La navegación de datos en cadena es una habilidad esencial para cualquier persona que desee comprender el espacio Web3. Comprender las estructuras de datos que componen una cadena de bloques nos ayuda a pensar en formas creativas de analizar estos datos. Al mismo tiempo, estos datos en cadena constituyen una gran parte de los datos disponibles. Esta publicación profundizará en una estructura de datos clave en el EVM, el recibo de la transacción y su registro de eventos asociado.
Por qué iniciar sesión
Antes de comenzar, hablemos brevemente sobre por qué necesitamos usar registros de eventos como desarrollador de solidez:
El registro de eventos es una opción más económica para el almacenamiento de datos a la que no es necesario acceder mediante contrato, y también puede reconstruir el estado almacenado probando variables específicas en el contrato inteligente, variables de indexación.
El registro de eventos es una forma de activar una aplicación Web3 que escucha un registro de eventos específico.
Los nodos de EVM no necesitan mantener registros para siempre y pueden ahorrar espacio eliminando registros antiguos. Los contratos no tienen acceso al almacenamiento de registros, por lo que los nodos no los necesitan para ejecutar contratos. El almacenamiento del contrato, por otro lado, es necesario para la ejecución y, por lo tanto, no se puede eliminar.
Raíz Merkle del bloque Ethereum
En la Parte 4, profundizamos en el marco Ethereum, especialmente en la parte raíz del estado de Merkle. State Root es una de las tres raíces de Merkle incluidas en el encabezado del bloque. Los otros dos son Raíz de transacciones y Raíz de recibos.
Para obtener información sobre la creación de este marco, nos referiremos al bloque 15001871 en Ethereum, que contiene 5 transacciones con sus recibos asociados y registros de eventos enviados.
encabezado de bloque
Comenzaremos con 3 partes en el encabezado del bloque, Transaction Root, Receipt Root y Logs Bloom (se puede revisar una breve introducción al encabezado del bloque en la Parte 4).
Fuente:
En el cliente de Ethereum en Raíz de transacciones y Raíz de recibos, Merkle Patricia Tries contiene todos los datos de transacciones y recibos en el bloque. Este artículo solo se centrará en todas las transacciones y recibos a los que puede acceder un nodo.
La información del encabezado del bloque del bloque 15001871 que se encuentra a través del nodo Ethereum es la siguiente:
El logsBloom en el encabezado del bloque es una estructura de datos clave, que se mencionará más adelante en este artículo. Primero, comencemos con los datos ubicados debajo de Transaction Root, Transaction Trie.
Árbol de transacciones Transacción Trie
Transaction Trie es un conjunto de datos que genera transaccionesRoot y registra los vectores de solicitud de transacción. Los vectores de solicitud de transacción son piezas de información necesarias para ejecutar una transacción. Los campos de datos contenidos en una transacción son los siguientes:
Tipo: tipo de transacción (transacción tradicional LegacyTxType, introducción de AccessListTxType EIP-2930, introducción de DynamicFeeTxType EIP-1559)
ChainId - ID de cadena EIP155 de la transacción
Datos - datos de entrada de la transacción
AccessList - lista de acceso para transacciones
Gas - el límite de gas de la transacción
GasPrice - el precio del gas de la transacción
GasTipCap: la prima de incentivo para los mineros cuya unidad de transacción de gas supera la tarifa base para empaquetar primero, maxPriorityFeePerGas en Geth está definido por EIP1559
GasFeeCap: el límite superior de la tarifa de gas por unidad de transacción, maxFeePerGas en Geth (GasFeeCap ≥ baseFee + GasTipCap)
Valor: la cantidad de Ethereum negociada
Nonce - el nonce del originador de la cuenta comercial
Para: la dirección del destinatario de la transacción. Para transacciones de creación de contratos, To devuelve un valor nulo
RawSignaturues - Valores de firma V, R, S de datos de transacción
Después de comprender los campos de datos anteriores, echemos un vistazo a la primera transacción del bloque 15001871
A través de la consulta de ethclient de Geth, puede ver que tanto ChainId como AccessList tienen "omiempty", lo que significa que si el campo está vacío, se omitirá en la respuesta para reducir o acortar el tamaño de los datos serializados.
código fuente:
Esta transacción representa la transferencia de tokens USDT a la dirección 0xec23e787ea25230f74a3da0f515825c1d820f47a. La dirección Para es la dirección del contrato ERC20 USDT 0xdac17f958d2ee523a2206206994597c13d831ec7. A través de INPUT DATA, podemos ver que la firma de función 0xa9059cbb corresponde a la función Transferir (Dirección, UINT256), y 42.251 USDT (precisión de 6) a 0x2b279b8 (45251000) se transfiere a 0xEC23E787EA25230F a 0xEC23E787EA25230F.74A3 Dirección DA0F515825C1D820F47A.
Es posible que haya notado que esta estructura de datos de transacción no nos dice nada sobre el resultado de la transacción, entonces, ¿la transacción tuvo éxito? ¿Cuanto gas consume? ¿Qué registros de eventos se activan? En este punto introduciremos el Recibo Trie.
Recepción de prueba
Así como un recibo de compras registra el resultado de una transacción, un objeto en Receipt Trie hace lo mismo para una transacción de Ethereum pero también registra algunos detalles adicionales. Volviendo a hacer la pregunta anterior sobre los recibos de transacciones, nos centraremos en los registros que desencadenaron los siguientes eventos.
Consulta los datos en cadena de 0x311b nuevamente y obtén su recibo de transacción. En este momento, se obtendrán los siguientes campos:
código fuente:
Tipo: tipo de transacción (LegacyTxType, AccessListTxType, DynamicFeeTxType)
PostState(root) - StateRoot, el nodo raíz del árbol de estado generado después de ejecutar la transacción, el valor correspondiente que se encuentra en la figura es 0x, probablemente debido a EIP98
AcumulativeGasUsed - Gas total acumulativo consumido por esta transacción y todas las transacciones anteriores en el mismo bloque
Bloom(logsBloom): filtro Bloom para registros de eventos, que se utiliza para buscar y acceder de manera eficiente a los registros de eventos de contratos en la cadena de bloques, lo que permite que los nodos recuperen rápidamente si un determinado evento ocurrió en un bloque sin analizar completamente el bloque Todos los recibos de transacciones en el bloque
Registros: una matriz de objetos de registro que contienen entradas de registro generadas por eventos de contrato activados durante la ejecución de la transacción
TxHash: el hash de transacción asociado con el recibo
ContractAddress: si la transacción es para crear un contrato, la dirección donde se implementó el contrato. Si la transacción no es la creación de un contrato, sino una transferencia o interacción con un contrato inteligente implementado, el campo Dirección del contrato estará vacío.
GasUsed - el gas consumido por esta transacción
BlockNumber - el número de bloque del bloque donde ocurrió esta transacción
TransactionIndex: el índice de transacción dentro del bloque, el índice determina qué transacción se ejecuta primero. Esta transacción está en la parte superior del bloque, por lo que el índice 0
Ahora que conocemos la composición del recibo de la transacción, echemos un vistazo más de cerca a los logsBloom y los registros de la matriz de registros en el recibo de la transacción.
Registros de eventos
A través del código del contrato USDT en la red principal de Ethereum, podemos ver que el evento Transfer se declara en la línea 86 del contrato y los dos parámetros de entrada tienen la palabra clave "indexado".
(código fuente:
Cuando una entrada de evento está "indexada", nos permite encontrar rápidamente registros a través de esa entrada. Por ejemplo, al usar el índice "desde" anterior, es posible obtener todos los registros de eventos de tipo Transferencia con la dirección "desde" 0x5041ed759dd4afc3a72b8192c143f72f4724081a entre los bloques X e Y. También podemos ver que cuando se llama a la función de transferencia en la línea 138, se activa el registro de eventos. Vale la pena señalar que el contrato actual usa una versión anterior de solidity, por lo que falta la palabra clave emit.
Regrese a los datos obtenidos en la cadena:
código fuente:
Profundicemos un poco más en los campos de dirección, temas y datos.
Temas temáticos
Temas es un valor de índice. En la figura anterior, podemos ver que hay 3 parámetros de índice de temas en los datos de consulta en la cadena, mientras que el evento Transfer solo tiene 2 parámetros de índice (desde y hasta). Esto se debe a que el primer tema siempre es el hash de la firma de la función del evento. La firma de la función de evento en el ejemplo actual es Transfer(address, address, uint256). Al codificarlo con keccak256, obtenemos el resultado ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.
(Herramienta en línea:
Cuando consultamos el campo from como se mencionó anteriormente, pero al mismo tiempo queremos limitar el tipo de registro de eventos de consulta a solo registros de eventos de tipo Transfer, debemos filtrar por tipo de evento mediante la indexación de firmas de eventos.
Podemos tener hasta 4 temas, cada tema tiene un tamaño de 32 bytes (si el tipo del parámetro de índice es mayor a 32 bytes (es decir, cadena y bytes), los datos reales no se almacenan, sino un resumen keccak256 de los datos se almacena). Podemos declarar 3 parámetros de índice porque el primer parámetro lo toma la firma del evento. Pero hay una situación en la que el primer tema no es una firma de evento hash. Este es el caso cuando se declaran eventos anónimos. Esto abre la posibilidad de usar 4 parámetros de indexación en lugar de los 3 anteriores, pero pierde la capacidad de indexar nombres de eventos. Otra ventaja de los eventos anónimos es que son menos costosos de implementar ya que no imponen un tema adicional. Los otros temas son los valores de los índices "from" y "to" del evento Transfer.
DatosDatos
La sección de datos contiene los parámetros restantes (no indexados) del registro de eventos. En el ejemplo anterior, hay un valor 0x000000000000000000000000000000000000000000000000000000002b279b8, que es 45251000 en decimal, que es la cantidad antes mencionada de $ 45,251. Si hay más parámetros de este tipo, se agregarán al elemento de datos. El siguiente ejemplo mostrará el caso de más de 1 parámetro no indexado.
El ejemplo actual agrega un campo adicional de "impuestos" al evento Transferir. Supongamos que el impuesto establecido es del 20%, entonces el valor del impuesto debe ser 45251000 * 20% = 9050200, su valor hexadecimal es 0x8a1858, ya que el tipo de este número es uint256 y el tipo de datos es de 32 bytes, necesita El el valor hexadecimal se completa con 32 bytes, y el resultado del elemento de datos es 0x000000000000000000000000000000000000000000000000000000002b279b8000000000000000000000000000000000000 000 00000000000000000000008a1858.
DIRECCIÓN
El campo de dirección es la dirección del contrato que emitió el evento, una nota importante sobre este campo es que se indexará aunque no esté incluido en la sección de tema. La razón es que el evento Transfer es parte del estándar ERC20, lo que significa que cuando sea necesario filtrar los registros de los eventos de transferencia ERC20, los eventos de transferencia se obtendrán de todos los contratos ERC20. Y al indexar la dirección del contrato, la búsqueda se puede reducir a un contrato/token específico, como USDT en el ejemplo.
Códigos de operación Códigos de operación
Finalmente está el código de operación LOG. Van desde LOG0 cuando no hay temas hasta LOG4 cuando hay 4 temas. LOG3 es lo que usamos en nuestro ejemplo. Contiene lo siguiente:
desplazamiento: desplazamiento de memoria, que indica la posición inicial de la entrada del campo de datos
longitud - longitud de datos para leer de la memoria
tema x (0 - 4) - el valor del tema x
(Fuente:
el desplazamiento y la longitud definen dónde se ubican los datos en la sección de datos en la memoria.
Después de comprender la estructura del registro y cómo se indexa un tema, comprendamos cómo se buscan los elementos del índice.
Filtros de floración Filtros de floración
El secreto para indexar elementos que se buscan más rápido es el filtro Bloom.
El artículo de Llimllib tiene una buena definición y explicación de esta estructura de datos.
"Bloom filter es una estructura de datos que se puede usar para determinar si un elemento está en una colección. Tiene las características de una operación rápida y una pequeña huella de memoria. El costo de una inserción y consulta eficientes es que Bloom Filter es un filtro de datos basado en la probabilidad. estructura: solo puede decirnos que un elemento definitivamente no está en el conjunto o posiblemente en el conjunto.La estructura de datos subyacente de un filtro Bloom es un vector de bits ".
A continuación se muestra un ejemplo de un vector de bits. Las celdas blancas representan bits con un valor de 0 y las celdas verdes representan bits con un valor de 1.
Estos bits se establecen en 1 tomando alguna entrada y hashing, el valor hash resultante se usa como un índice de bit en el que se debe actualizar el bit. El vector de bits anterior es el resultado de aplicar 2 hashes diferentes al valor "ethereum" para obtener un índice de 2 bits. El hash representa un número hexadecimal, y para obtener el índice, toma el número y lo convierte a un valor entre 0 y 14. Hay muchas formas de hacer esto, como el mod 14.
Revisar
Con un filtro Bloom para transacciones, es decir, un vector de bits, se puede codificar en Ethereum para determinar qué bits del vector de bits actualizar.La entrada es el campo de dirección y el tema del registro de eventos. Revisemos los logsBloom en el recibo de la transacción, que es un filtro Bloom específico de la transacción. Una transacción puede tener varios registros, que contienen la dirección/tema de todos los registros.
Si vuelve a mirar el encabezado del bloque, encontrará otro logsBloom. Este es un filtro Bloom para todas las transacciones dentro del bloque. Que contiene todas las direcciones/temas en cada registro para cada transacción.
Estos filtros Bloom se expresan en hexadecimal, no en binario. Tienen una longitud de 256 bytes y representan un vector de 2048 bits. Si nos referimos al ejemplo anterior de Llimllib, la longitud de nuestro vector de bits es 15, y los índices de bits 2 y 13 se invierten como 1. Veamos qué obtenemos cuando convertimos eso a hexadecimal.
Aunque la representación hexadecimal no parece un vector de bits, sí lo hace en logsBloom.
Consulta Consultas
Anteriormente se mencionó una consulta, "Encuentre todos los registros de eventos del tipo Transferir cuya dirección "de" sea 0x5041ed759dd4afc3a72b8192c143f72f4724081a entre los bloques X e Y". Podemos obtener el tema de la firma del evento, que representa un tema de tipo Transferencia y desde (0x5041...) valor, y determinar qué índices de bits en el filtro Bloom deben establecerse en 1.
Si usa logsBloom en el encabezado del bloque, puede verificar si alguno de estos bits no está configurado en 1. De lo contrario, se puede determinar que no hay registros que coincidan con la condición en el bloque. Y si se encuentra que esos bits están configurados, sabemos que probablemente haya un registro coincidente en el bloque. Pero no del todo seguro, porque el encabezado del bloque logsBloom consta de varias direcciones y temas. Otros registros de eventos pueden tener bits de coincidencia establecidos. Es por eso que un filtro Bloom es una estructura de datos probabilísticos. Cuanto mayor sea el vector de bits, es menos probable que se produzcan colisiones de índice de bits con otros registros. Una vez que tenga un filtro Bloom coincidente, puede usar el mismo método para consultar logsBloom para recibos individuales. Cuando se obtiene una coincidencia, se puede ver la entrada de registro real para recuperar el objeto.
Ejecute las operaciones anteriores en los bloques X a Y para encontrar y recuperar rápidamente todos los registros que cumplan con los criterios. Así es como funciona conceptualmente el filtro Bloom.
Ahora veamos la implementación utilizada en Ethereum.
Implementación Geth - Filtros Bloom
Ahora que sabemos cómo funciona el filtro Bloom, aprendamos cómo el filtro Bloom completa la selección paso a paso desde la dirección/tema hasta logsBloom en un bloque real.
En primer lugar, de la definición del Libro Amarillo de Ethereum:
Fuente:
"Definimos una función de filtro Bloom M que reduce las entradas de registro en un solo hash de 256 bytes:
en es un filtro Bloom especializado que establece tres bits en 2048 dada una secuencia arbitraria de bytes. Esto se hace tomando los 11 bits inferiores de cada uno de los primeros tres pares de bytes en el hash Keccak-256 de la secuencia de bytes. "
A continuación se proporciona un ejemplo y una referencia a la implementación de un cliente Geth para simplificar la comprensión de las definiciones anteriores.
Aquí está el registro de transacciones que vimos en Etherscan.
El primer tema es la firma del evento 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef y convierte este valor en el índice de bits que debe actualizarse.
A continuación se muestra la función bloomValues del código base de Geth.
Esta función recibe el tema de la firma del evento, como: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef y otros datos, y devuelve el índice de bits que debe actualizarse en el filtro Bloom.
fuente del código:
La función bloomValues recibe como entrada un tema (firma de evento en el ejemplo) y un hashbuf (una matriz de bytes vacía de longitud 6).
Consulte el fragmento de Yellow Paper, "Los primeros tres pares de bytes en un hash Keccak-256 de una secuencia de bytes". Estos tres pares de bytes son 6 bytes, que es la longitud de hashbuf.
Datos de muestra: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.
El comando sha entre las líneas 140 - 144 procesa los datos de entrada y carga la salida en hashbuf.
El resultado hexadecimal de la salida sha usando keccak256 es (cuando se usa keccak 256 como firma de función, la entrada es de tipo texto, pero aquí es de tipo hexadecimal): ada389e1fc24a8587c776340efb91b36e675792ab631816100d55df0b5cf3cbc.
El contenido actual de hasbuf [ad, a3, 89, e1, fc, 24] (hexadecimal). Cada carácter hexadecimal representa 4 bits.
3 Calcula v1.
1)hashbuf [1] = 0xa3 = 10100011 para AND bit a bit con 0x7. 0x7 = 00000111.
Un byte consta de 8 bits. Si desea obtener un índice de bits, debe asegurarse de que el valor obtenido esté entre 0 y 7 de la matriz de índice cero. Use AND bit a bit para hashbuf [1] Restringido a valores entre 0 y 7. Calculado en el ejemplo, 10100011 y 00000111 = 00000011 = 3.
Este valor de índice de bit se utiliza con un operador de desplazamiento de bit, es decir, se desplaza 3 bits a la izquierda, lo que da como resultado un índice de byte de 8 bits 00001000, para crear un bit invertido.
v1 es el byte completo en lugar del índice de bits real, ya que este valor será un OR bit a bit en el filtro Bloom más adelante. La operación OR asegurará que todos los bits correspondientes en el filtro Bloom también se inviertan.
Ahora tenemos valores de bytes, pero aún necesitamos índices de bytes. El filtro Bloom tiene una longitud de 256 bytes (2048 bits), por lo que necesitamos saber en qué byte ejecutar el OR bit a bit. El valor i1 representa este índice de bytes.
Coloque el hashbuf a través del orden de bytes big-endian uint16, lo que hace que limite los primeros 2 bytes de la matriz de bits, que es 0xada3 = 1010110110100011 en el ejemplo.
Bitwise Y este valor con 0x7ff = 0000011111111111. Hay 11 bits donde 0x7ff se establece en 1. Como se menciona en el documento amarillo, "lo hace tomando los 11 bits inferiores de cada uno de los primeros tres pares". Esto dará como resultado el valor 0000010110100011 que es 1010110110100011 y 0000011111111111.
Luego cambie el valor a la derecha por 3 bits. Esto convierte un número de 11 dígitos en un número de 8 dígitos. Queremos un índice de bytes, y la longitud de bytes del filtro Bloom es 256, por lo que el valor del índice de bytes debe estar en este rango. Y un número de 8 bits puede ser cualquier valor entre 0 y 255. En nuestro ejemplo, este valor es 0000010110100011 desplazado a la derecha 3 bits 10110100 = 180.
Calcular nuestro índice de bytes por BloomByteLength, sabiendo que es 256 menos los 180 calculados, menos 1. Resta 1 para mantener el resultado entre 0 y 255. Esto nos da el índice de bytes a actualizar, que en este caso resulta ser el byte 75, que es como calculamos i1.
Actualice el índice de bits 3 en el byte 75 del filtro Bloom (0 es el índice por lo que el cuarto bit), lo que se puede hacer realizando una operación OR bit a bit de v1 en el byte 75 en el filtro Bloom.
Solo cubrimos el primer par de bytes 0xada3, lo cual se hizo nuevamente para los pares de bytes 2 y 3. Cada dirección/tema actualizará 3 bits en un vector de 2048 bits. Como se menciona en el Libro Amarillo, "el filtro Bloom establece tres bits en 2048 dada una secuencia arbitraria de bytes".
El estado del par de bytes 2 actualiza el índice de bits 1 en el byte 195 (ejecutar de acuerdo con los procedimientos 3 y 4, el resultado se muestra en la figura).
Bit de actualización de estado del par de bytes 3, índice 4 en el byte 123.
Si el bit a actualizar ya ha sido volteado por otro tema, permanecerá como está. Cambiará a 1 si no.
A través del proceso de operación anterior, se puede determinar que el tema de la firma del evento cambiará los siguientes bits en el filtro Bloom:
Índice de bits 3 en el byte 75
índice de bits 1 en el byte 195
índice de bits 4 en el byte 123
Mirando los logBlooms en el recibo de la transacción, convertidos a binario, verifica que estos índices de bits estén establecidos.
Mientras tanto, para aquellos lectores que estén interesados en obtener más información sobre la implementación de la búsqueda de registros y el filtro Bloom, pueden consultar el artículo BloomBits Trie.
En este punto, nuestra discusión en profundidad de la serie de artículos EVM ha llegado a su fin, y le traeremos más artículos técnicos de alta calidad en el futuro.
Ver originales
El contenido es solo de referencia, no una solicitud u oferta. No se proporciona asesoramiento fiscal, legal ni de inversión. Consulte el Descargo de responsabilidad para obtener más información sobre los riesgos.
Sumérjase en estructuras de datos EVM, recibos de transacciones y registros de eventos
**Escrito por:**NOXX
Compilar: vaciar
La navegación de datos en cadena es una habilidad esencial para cualquier persona que desee comprender el espacio Web3. Comprender las estructuras de datos que componen una cadena de bloques nos ayuda a pensar en formas creativas de analizar estos datos. Al mismo tiempo, estos datos en cadena constituyen una gran parte de los datos disponibles. Esta publicación profundizará en una estructura de datos clave en el EVM, el recibo de la transacción y su registro de eventos asociado.
Por qué iniciar sesión
Antes de comenzar, hablemos brevemente sobre por qué necesitamos usar registros de eventos como desarrollador de solidez:
Los nodos de EVM no necesitan mantener registros para siempre y pueden ahorrar espacio eliminando registros antiguos. Los contratos no tienen acceso al almacenamiento de registros, por lo que los nodos no los necesitan para ejecutar contratos. El almacenamiento del contrato, por otro lado, es necesario para la ejecución y, por lo tanto, no se puede eliminar.
Raíz Merkle del bloque Ethereum
En la Parte 4, profundizamos en el marco Ethereum, especialmente en la parte raíz del estado de Merkle. State Root es una de las tres raíces de Merkle incluidas en el encabezado del bloque. Los otros dos son Raíz de transacciones y Raíz de recibos.
Para obtener información sobre la creación de este marco, nos referiremos al bloque 15001871 en Ethereum, que contiene 5 transacciones con sus recibos asociados y registros de eventos enviados.
encabezado de bloque
Comenzaremos con 3 partes en el encabezado del bloque, Transaction Root, Receipt Root y Logs Bloom (se puede revisar una breve introducción al encabezado del bloque en la Parte 4).
Fuente:
En el cliente de Ethereum en Raíz de transacciones y Raíz de recibos, Merkle Patricia Tries contiene todos los datos de transacciones y recibos en el bloque. Este artículo solo se centrará en todas las transacciones y recibos a los que puede acceder un nodo.
La información del encabezado del bloque del bloque 15001871 que se encuentra a través del nodo Ethereum es la siguiente:
El logsBloom en el encabezado del bloque es una estructura de datos clave, que se mencionará más adelante en este artículo. Primero, comencemos con los datos ubicados debajo de Transaction Root, Transaction Trie.
Árbol de transacciones Transacción Trie
Transaction Trie es un conjunto de datos que genera transaccionesRoot y registra los vectores de solicitud de transacción. Los vectores de solicitud de transacción son piezas de información necesarias para ejecutar una transacción. Los campos de datos contenidos en una transacción son los siguientes:
Después de comprender los campos de datos anteriores, echemos un vistazo a la primera transacción del bloque 15001871
A través de la consulta de ethclient de Geth, puede ver que tanto ChainId como AccessList tienen "omiempty", lo que significa que si el campo está vacío, se omitirá en la respuesta para reducir o acortar el tamaño de los datos serializados.
código fuente:
Esta transacción representa la transferencia de tokens USDT a la dirección 0xec23e787ea25230f74a3da0f515825c1d820f47a. La dirección Para es la dirección del contrato ERC20 USDT 0xdac17f958d2ee523a2206206994597c13d831ec7. A través de INPUT DATA, podemos ver que la firma de función 0xa9059cbb corresponde a la función Transferir (Dirección, UINT256), y 42.251 USDT (precisión de 6) a 0x2b279b8 (45251000) se transfiere a 0xEC23E787EA25230F a 0xEC23E787EA25230F.74A3 Dirección DA0F515825C1D820F47A.
Es posible que haya notado que esta estructura de datos de transacción no nos dice nada sobre el resultado de la transacción, entonces, ¿la transacción tuvo éxito? ¿Cuanto gas consume? ¿Qué registros de eventos se activan? En este punto introduciremos el Recibo Trie.
Recepción de prueba
Así como un recibo de compras registra el resultado de una transacción, un objeto en Receipt Trie hace lo mismo para una transacción de Ethereum pero también registra algunos detalles adicionales. Volviendo a hacer la pregunta anterior sobre los recibos de transacciones, nos centraremos en los registros que desencadenaron los siguientes eventos.
Consulta los datos en cadena de 0x311b nuevamente y obtén su recibo de transacción. En este momento, se obtendrán los siguientes campos:
código fuente:
Ahora que conocemos la composición del recibo de la transacción, echemos un vistazo más de cerca a los logsBloom y los registros de la matriz de registros en el recibo de la transacción.
Registros de eventos
A través del código del contrato USDT en la red principal de Ethereum, podemos ver que el evento Transfer se declara en la línea 86 del contrato y los dos parámetros de entrada tienen la palabra clave "indexado".
(código fuente:
Cuando una entrada de evento está "indexada", nos permite encontrar rápidamente registros a través de esa entrada. Por ejemplo, al usar el índice "desde" anterior, es posible obtener todos los registros de eventos de tipo Transferencia con la dirección "desde" 0x5041ed759dd4afc3a72b8192c143f72f4724081a entre los bloques X e Y. También podemos ver que cuando se llama a la función de transferencia en la línea 138, se activa el registro de eventos. Vale la pena señalar que el contrato actual usa una versión anterior de solidity, por lo que falta la palabra clave emit.
Regrese a los datos obtenidos en la cadena:
código fuente:
Profundicemos un poco más en los campos de dirección, temas y datos.
Temas temáticos
Temas es un valor de índice. En la figura anterior, podemos ver que hay 3 parámetros de índice de temas en los datos de consulta en la cadena, mientras que el evento Transfer solo tiene 2 parámetros de índice (desde y hasta). Esto se debe a que el primer tema siempre es el hash de la firma de la función del evento. La firma de la función de evento en el ejemplo actual es Transfer(address, address, uint256). Al codificarlo con keccak256, obtenemos el resultado ddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.
(Herramienta en línea:
Cuando consultamos el campo from como se mencionó anteriormente, pero al mismo tiempo queremos limitar el tipo de registro de eventos de consulta a solo registros de eventos de tipo Transfer, debemos filtrar por tipo de evento mediante la indexación de firmas de eventos.
Podemos tener hasta 4 temas, cada tema tiene un tamaño de 32 bytes (si el tipo del parámetro de índice es mayor a 32 bytes (es decir, cadena y bytes), los datos reales no se almacenan, sino un resumen keccak256 de los datos se almacena). Podemos declarar 3 parámetros de índice porque el primer parámetro lo toma la firma del evento. Pero hay una situación en la que el primer tema no es una firma de evento hash. Este es el caso cuando se declaran eventos anónimos. Esto abre la posibilidad de usar 4 parámetros de indexación en lugar de los 3 anteriores, pero pierde la capacidad de indexar nombres de eventos. Otra ventaja de los eventos anónimos es que son menos costosos de implementar ya que no imponen un tema adicional. Los otros temas son los valores de los índices "from" y "to" del evento Transfer.
DatosDatos
La sección de datos contiene los parámetros restantes (no indexados) del registro de eventos. En el ejemplo anterior, hay un valor 0x000000000000000000000000000000000000000000000000000000002b279b8, que es 45251000 en decimal, que es la cantidad antes mencionada de $ 45,251. Si hay más parámetros de este tipo, se agregarán al elemento de datos. El siguiente ejemplo mostrará el caso de más de 1 parámetro no indexado.
El ejemplo actual agrega un campo adicional de "impuestos" al evento Transferir. Supongamos que el impuesto establecido es del 20%, entonces el valor del impuesto debe ser 45251000 * 20% = 9050200, su valor hexadecimal es 0x8a1858, ya que el tipo de este número es uint256 y el tipo de datos es de 32 bytes, necesita El el valor hexadecimal se completa con 32 bytes, y el resultado del elemento de datos es 0x000000000000000000000000000000000000000000000000000000002b279b8000000000000000000000000000000000000 000 00000000000000000000008a1858.
DIRECCIÓN
El campo de dirección es la dirección del contrato que emitió el evento, una nota importante sobre este campo es que se indexará aunque no esté incluido en la sección de tema. La razón es que el evento Transfer es parte del estándar ERC20, lo que significa que cuando sea necesario filtrar los registros de los eventos de transferencia ERC20, los eventos de transferencia se obtendrán de todos los contratos ERC20. Y al indexar la dirección del contrato, la búsqueda se puede reducir a un contrato/token específico, como USDT en el ejemplo.
Códigos de operación Códigos de operación
Finalmente está el código de operación LOG. Van desde LOG0 cuando no hay temas hasta LOG4 cuando hay 4 temas. LOG3 es lo que usamos en nuestro ejemplo. Contiene lo siguiente:
(Fuente:
el desplazamiento y la longitud definen dónde se ubican los datos en la sección de datos en la memoria.
Después de comprender la estructura del registro y cómo se indexa un tema, comprendamos cómo se buscan los elementos del índice.
Filtros de floración Filtros de floración
El secreto para indexar elementos que se buscan más rápido es el filtro Bloom.
El artículo de Llimllib tiene una buena definición y explicación de esta estructura de datos.
"Bloom filter es una estructura de datos que se puede usar para determinar si un elemento está en una colección. Tiene las características de una operación rápida y una pequeña huella de memoria. El costo de una inserción y consulta eficientes es que Bloom Filter es un filtro de datos basado en la probabilidad. estructura: solo puede decirnos que un elemento definitivamente no está en el conjunto o posiblemente en el conjunto.La estructura de datos subyacente de un filtro Bloom es un vector de bits ".
A continuación se muestra un ejemplo de un vector de bits. Las celdas blancas representan bits con un valor de 0 y las celdas verdes representan bits con un valor de 1.
Estos bits se establecen en 1 tomando alguna entrada y hashing, el valor hash resultante se usa como un índice de bit en el que se debe actualizar el bit. El vector de bits anterior es el resultado de aplicar 2 hashes diferentes al valor "ethereum" para obtener un índice de 2 bits. El hash representa un número hexadecimal, y para obtener el índice, toma el número y lo convierte a un valor entre 0 y 14. Hay muchas formas de hacer esto, como el mod 14.
Revisar
Con un filtro Bloom para transacciones, es decir, un vector de bits, se puede codificar en Ethereum para determinar qué bits del vector de bits actualizar.La entrada es el campo de dirección y el tema del registro de eventos. Revisemos los logsBloom en el recibo de la transacción, que es un filtro Bloom específico de la transacción. Una transacción puede tener varios registros, que contienen la dirección/tema de todos los registros.
Si vuelve a mirar el encabezado del bloque, encontrará otro logsBloom. Este es un filtro Bloom para todas las transacciones dentro del bloque. Que contiene todas las direcciones/temas en cada registro para cada transacción.
Estos filtros Bloom se expresan en hexadecimal, no en binario. Tienen una longitud de 256 bytes y representan un vector de 2048 bits. Si nos referimos al ejemplo anterior de Llimllib, la longitud de nuestro vector de bits es 15, y los índices de bits 2 y 13 se invierten como 1. Veamos qué obtenemos cuando convertimos eso a hexadecimal.
Aunque la representación hexadecimal no parece un vector de bits, sí lo hace en logsBloom.
Consulta Consultas
Anteriormente se mencionó una consulta, "Encuentre todos los registros de eventos del tipo Transferir cuya dirección "de" sea 0x5041ed759dd4afc3a72b8192c143f72f4724081a entre los bloques X e Y". Podemos obtener el tema de la firma del evento, que representa un tema de tipo Transferencia y desde (0x5041...) valor, y determinar qué índices de bits en el filtro Bloom deben establecerse en 1.
Si usa logsBloom en el encabezado del bloque, puede verificar si alguno de estos bits no está configurado en 1. De lo contrario, se puede determinar que no hay registros que coincidan con la condición en el bloque. Y si se encuentra que esos bits están configurados, sabemos que probablemente haya un registro coincidente en el bloque. Pero no del todo seguro, porque el encabezado del bloque logsBloom consta de varias direcciones y temas. Otros registros de eventos pueden tener bits de coincidencia establecidos. Es por eso que un filtro Bloom es una estructura de datos probabilísticos. Cuanto mayor sea el vector de bits, es menos probable que se produzcan colisiones de índice de bits con otros registros. Una vez que tenga un filtro Bloom coincidente, puede usar el mismo método para consultar logsBloom para recibos individuales. Cuando se obtiene una coincidencia, se puede ver la entrada de registro real para recuperar el objeto.
Ejecute las operaciones anteriores en los bloques X a Y para encontrar y recuperar rápidamente todos los registros que cumplan con los criterios. Así es como funciona conceptualmente el filtro Bloom.
Ahora veamos la implementación utilizada en Ethereum.
Implementación Geth - Filtros Bloom
Ahora que sabemos cómo funciona el filtro Bloom, aprendamos cómo el filtro Bloom completa la selección paso a paso desde la dirección/tema hasta logsBloom en un bloque real.
En primer lugar, de la definición del Libro Amarillo de Ethereum:
Fuente:
"Definimos una función de filtro Bloom M que reduce las entradas de registro en un solo hash de 256 bytes:
en
es un filtro Bloom especializado que establece tres bits en 2048 dada una secuencia arbitraria de bytes. Esto se hace tomando los 11 bits inferiores de cada uno de los primeros tres pares de bytes en el hash Keccak-256 de la secuencia de bytes. "
A continuación se proporciona un ejemplo y una referencia a la implementación de un cliente Geth para simplificar la comprensión de las definiciones anteriores.
Aquí está el registro de transacciones que vimos en Etherscan.
El primer tema es la firma del evento 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef y convierte este valor en el índice de bits que debe actualizarse.
A continuación se muestra la función bloomValues del código base de Geth.
Esta función recibe el tema de la firma del evento, como: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef y otros datos, y devuelve el índice de bits que debe actualizarse en el filtro Bloom.
fuente del código:
Consulte el fragmento de Yellow Paper, "Los primeros tres pares de bytes en un hash Keccak-256 de una secuencia de bytes". Estos tres pares de bytes son 6 bytes, que es la longitud de hashbuf.
Datos de muestra: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.
3 Calcula v1.
1)hashbuf [1] = 0xa3 = 10100011 para AND bit a bit con 0x7. 0x7 = 00000111.
Un byte consta de 8 bits. Si desea obtener un índice de bits, debe asegurarse de que el valor obtenido esté entre 0 y 7 de la matriz de índice cero. Use AND bit a bit para hashbuf [1] Restringido a valores entre 0 y 7. Calculado en el ejemplo, 10100011 y 00000111 = 00000011 = 3.
Este valor de índice de bit se utiliza con un operador de desplazamiento de bit, es decir, se desplaza 3 bits a la izquierda, lo que da como resultado un índice de byte de 8 bits 00001000, para crear un bit invertido.
v1 es el byte completo en lugar del índice de bits real, ya que este valor será un OR bit a bit en el filtro Bloom más adelante. La operación OR asegurará que todos los bits correspondientes en el filtro Bloom también se inviertan.
Coloque el hashbuf a través del orden de bytes big-endian uint16, lo que hace que limite los primeros 2 bytes de la matriz de bits, que es 0xada3 = 1010110110100011 en el ejemplo.
Bitwise Y este valor con 0x7ff = 0000011111111111. Hay 11 bits donde 0x7ff se establece en 1. Como se menciona en el documento amarillo, "lo hace tomando los 11 bits inferiores de cada uno de los primeros tres pares". Esto dará como resultado el valor 0000010110100011 que es 1010110110100011 y 0000011111111111.
Luego cambie el valor a la derecha por 3 bits. Esto convierte un número de 11 dígitos en un número de 8 dígitos. Queremos un índice de bytes, y la longitud de bytes del filtro Bloom es 256, por lo que el valor del índice de bytes debe estar en este rango. Y un número de 8 bits puede ser cualquier valor entre 0 y 255. En nuestro ejemplo, este valor es 0000010110100011 desplazado a la derecha 3 bits 10110100 = 180.
Calcular nuestro índice de bytes por BloomByteLength, sabiendo que es 256 menos los 180 calculados, menos 1. Resta 1 para mantener el resultado entre 0 y 255. Esto nos da el índice de bytes a actualizar, que en este caso resulta ser el byte 75, que es como calculamos i1.
Solo cubrimos el primer par de bytes 0xada3, lo cual se hizo nuevamente para los pares de bytes 2 y 3. Cada dirección/tema actualizará 3 bits en un vector de 2048 bits. Como se menciona en el Libro Amarillo, "el filtro Bloom establece tres bits en 2048 dada una secuencia arbitraria de bytes".
El estado del par de bytes 2 actualiza el índice de bits 1 en el byte 195 (ejecutar de acuerdo con los procedimientos 3 y 4, el resultado se muestra en la figura).
Bit de actualización de estado del par de bytes 3, índice 4 en el byte 123.
Si el bit a actualizar ya ha sido volteado por otro tema, permanecerá como está. Cambiará a 1 si no.
A través del proceso de operación anterior, se puede determinar que el tema de la firma del evento cambiará los siguientes bits en el filtro Bloom:
Mirando los logBlooms en el recibo de la transacción, convertidos a binario, verifica que estos índices de bits estén establecidos.
Mientras tanto, para aquellos lectores que estén interesados en obtener más información sobre la implementación de la búsqueda de registros y el filtro Bloom, pueden consultar el artículo BloomBits Trie.
En este punto, nuestra discusión en profundidad de la serie de artículos EVM ha llegado a su fin, y le traeremos más artículos técnicos de alta calidad en el futuro.