Para verificar la disponibilidad de datos en Ethereum, primero es necesario enviar datos a Avail como una transacci贸n de env铆o de datos. Los datos enviados de esta manera se incluir谩n en los bloques de disponibilidad, pero no se interpretar谩n ni ejecutar谩n de ninguna manera. El env铆o se puede realizar utilizando Polkadot-JSuna colecci贸n de herramientas para la comunicaci贸n con cadenas basadas en Substrate (que ahora forma parte del SDK de Polkadot).
async function submitData(availApi, data, account) { let submit = await availApi.tx.dataAvailability.submitData(data); return await sendTx(availApi, account, submit);}
La funci贸n submitDatarecibe availApila instancia de API, dataque se enviar谩 y accountque env铆a la transacci贸n. Para crear una cuenta es necesario crear un par de llaveros para la cuenta que desea enviar los datos. Esto se puede hacer con lo que crea un par de llaveros mediante suri (el secreto puede ser una cadena hexadecimal, una frase mnemot茅cnica o una cadena). Despu茅s de crear un par de llaveros, es posible enviar datos en una transacci贸n a la red Avail con . Una vez que la transacci贸n se incluye en un bloque de disponibilidad, es posible iniciar el env铆o de la ra铆z de datos creando una transacci贸n de env铆o con los par谩metros:keyring.addFromUri(secret)availApi.tx.dataAvailability.submitData(data);availApi.tx.daBridge.tryDispatchDataRoot(destinationDomain, bridgeRouterEthAddress, header);
destinationDomainDominio de destino 1000.
bridgeRouterEthAddressDirecci贸n del contrato principal de enrutador de disponibilidad de datos implementado en la red Sepolia para Goldberg ( 0x305222c4DdB86FfA9fa9Aa0A479705577E3c4d33), para Kate 0xbD824890A51ed8bda53F51F27303b14EFfEbC152.
headerProporcionado desde el bloque cuando se env铆an los datos.
Ejemplo de env铆o de datos a Avail y env铆o de la ra铆z de datos mediante Polkadot-JS.
Variables de entorno:
AVAIL_RPC= # avail network websocket urlSURI= # mnemonicDA_BRIDGE_ADDRESS= # main da bridge contract address deployed to Sepolia test network in format (Kate) 0x000000000000000000000000bD824890A51ed8bda53F51F27303b14EFfEbC152 (Goldberg) 0x000000000000000000000000305222c4DdB86FfA9fa9Aa0A479705577E3c4d33DESTINATION_DOMAIN= # destination domain is 1000DATA= # data sending to avail
Ejemplo de JavaScript ra铆z de datos de env铆o
import { ApiPromise, Keyring, WsProvider } from '@polkadot/api';import * as dotenv from 'dotenv'; dotenv.config(); /** * Creates api instance. * * @param url websocket address */async function createApi(url) { const provider = new WsProvider(url); return ApiPromise.create({ provider, rpc: { kate: { queryDataProof: { description: 'Generate the data proof for the given `index`', params: [ { name: 'transaction_index', type: 'u32', }, { name: 'at', type: 'Hash', isOptional: true, }, ], type: 'DataProof', }, }, }, types: { AppId: 'Compact<u32>', DataLookupIndexItem: { appId: 'AppId', start: 'Compact<u32>', }, DataLookup: { size: 'Compact<u32>', index: 'Vec<DataLookupIndexItem>', }, KateCommitment: { rows: 'Compact<u16>', cols: 'Compact<u16>', dataRoot: 'H256', commitment: 'Vec<u8>', }, V1HeaderExtension: { commitment: 'KateCommitment', appLookup: 'DataLookup', }, VTHeaderExtension: { newField: 'Vec<u8>', commitment: 'KateCommitment', appLookup: 'DataLookup', }, HeaderExtension: { _enum: { V1: 'V1HeaderExtension', VTest: 'VTHeaderExtension', }, }, DaHeader: { parentHash: 'Hash', number: 'Compact<BlockNumber>', stateRoot: 'Hash', extrinsicsRoot: 'Hash', digest: 'Digest', extension: 'HeaderExtension', }, Header: 'DaHeader', CheckAppIdExtra: { appId: 'AppId', }, CheckAppIdTypes: {}, CheckAppId: { extra: 'CheckAppIdExtra', types: 'CheckAppIdTypes', }, DataProof: { root: 'H256', proof: 'Vec<H256>', numberOfLeaves: 'Compact<u32>', leafIndex: 'Compact<u32>', leaf: 'H256', }, Cell: { row: 'u32', col: 'u32', }, }, signedExtensions: { CheckAppId: { extrinsic: { appId: 'AppId', }, payload: {}, }, }, });} /** * Sends transaction to Avail. * * @param api instance of the api * @param account sending the transaction * @param tx transaction */async function sendTx(api, account, tx) { return new Promise(async (resolve) => { try { const res = await tx.signAndSend(account, (result) => { if (result.status.isReady) { console.log(`Txn has been sent to the mempool`); } if (result.status.isInBlock) { console.log( `Tx hash: ${result.txHash} is in block ${result.status.asInBlock}`, ); res(); resolve(result); } }); } catch (e) { console.log(e); process.exit(1); } });} /** * Submitting data to Avail as a transaction. * * @param availApi api instance * @param data payload to send * @param account that is sending transaction * @returns {Promise<unknown>} */async function submitData(availApi, data, account) { let submit = await availApi.tx.dataAvailability.submitData(data); return await sendTx(availApi, account, submit);} /** * Sending dispatch data root transaction. * * @param availApi api instance * @param blockHash hash of the block * @param account sending transaction * @returns {Promise<unknown>} */async function dispatchDataRoot(availApi, blockHash, account) { const destinationDomain = process.env.DESTINATION_DOMAIN; const bridgeRouterEthAddress = process.env.DA_BRIDGE_ADDRESS; const header = await availApi.rpc.chain.getHeader(blockHash); console.log(`Block Number: ${header.number}`); console.log(`State Root: ${header.stateRoot}`); let tx = await availApi.tx.daBridge.tryDispatchDataRoot( destinationDomain, bridgeRouterEthAddress, header, ); return await sendTx(availApi, account, tx);} /** * Returns data root for the particular block. * * @param availApi api instance * @param blockHash hash of the block * @returns {Promise<(*)[]>} */async function getDataRoot(availApi, blockHash) { const header = JSON.parse(await availApi.rpc.chain.getHeader(blockHash)); return [header.extension.v1.commitment.dataRoot, header.number];} (async function dataRootDispatch() { const availApi = await createApi(process.env.AVAIL_RPC); const keyring = new Keyring({ type: 'sr25519' }); const account = keyring.addFromMnemonic(process.env.SURI); console.log('Submitting data to Avail...'); let result = await submitData(availApi, process.env.DATA, account); const txIndex = JSON.parse(result.events[0].phase).applyExtrinsic; const blockHash = result.status.asInBlock; console.log( `Transaction: ${result.txHash}. Block hash: ${blockHash}. Transaction index: ${txIndex}.`, ); console.log('Triggering Home...'); result = await dispatchDataRoot(availApi, blockHash, account); console.log(`Sent txn on Avail. Txn Hash: ${result.txHash}.`); let [root, blockNum] = await getDataRoot(availApi, blockHash); console.log('Data Root:' + root + ' and Block number: ' + blockNum); await availApi.disconnect();})() .then(() => { console.log('Done'); }) .catch((err) => { console.error(err); process.exit(1); });
El env铆o de la ra铆z de datos activar谩 un puente optimista que conectar谩 la ra铆z de datos con la red Ethereum. Dado que el puente es optimista, es necesario esperar 30 minutos antes de que la ra铆z de datos est茅 disponible en Ethereum.
Despu茅s de conectar con 茅xito la ra铆z de datos con el contrato principal de certificaci贸n de disponibilidad de datos en Ethereum, es posible demostrar que los datos est谩n disponibles en la red Avail enviando una prueba Merkle al contrato de verificaci贸n. La obtenci贸n de pruebas de Avail se puede realizar mediante una llamada RPC, kate_queryDataProofpor ejemplo, availApi.rpc.kate.queryDataProof(transactionIndex, hashBlock); d贸nde transactionIndexest谩 el 铆ndice de la transacci贸n en el bloque y hashBlockcu谩l es un hash del bloque en el que se incluyen los datos. Este punto final RPC devuelve DataProofun objeto que se puede utilizar para demostrar en Ethereum que los datos est谩n disponibles en la red Avail. Ejemplo:
proofArt铆culos a prueba de Merkle (no contiene el hash de hoja ni la ra铆z).
numberOfLeavesN煤mero de hojas del 谩rbol original.
leafIndex脥ndice de la hoja para la que es la prueba (comienza desde 0).
leafHoja de la cual es la prueba.
EJEMPLO
Ejemplo de contrato de verificaci贸n
// SPDX-License-Identifier: Apache-2.0// Modified from https://github.com/QEDK/solidity-misc/blob/master/contracts/Merkle.solpragma solidity ^0.8.21; import "@openzeppelin/contracts/access/Ownable.sol";// or for foundry:// import "openzeppelin-contracts/contracts/access/Ownable.sol"; interface IDataAvailabilityRouter { function roots(uint32 blockNumber) external view returns (bytes32 root);} contract ValidiumContract is Ownable { IDataAvailabilityRouter private router; function setRouter( IDataAvailabilityRouter _router ) public virtual onlyOwner { router = _router; } function checkDataRootMembership( uint32 blockNumber, bytes32[] calldata proof, uint256 width, // number of leaves uint256 index, bytes32 leaf ) public view virtual returns (bool isMember) { bytes32 rootHash = router.roots(blockNumber); // if root hash is 0, block does not have a root (yet) require(rootHash != bytes32(0), "INVALID_ROOT"); assembly ("memory-safe") { if proof.length { let end := add(proof.offset, shl(5, proof.length)) let i := proof.offset for {} 1 {} { let leafSlot := shl(5, and(0x1, index)) if eq(add(index, 1), width) { leafSlot := 0x20 } mstore(leafSlot, leaf) mstore(xor(leafSlot, 32), calldataload(i)) leaf := keccak256(0, 64) index := shr(1, index) i := add(i, 32) width := add(shr(1, sub(width, 1)), 1) if iszero(lt(i, end)) { break } } } // checks if the calculated root matches the expected root isMember := eq(leaf, rootHash) } }}
Al presentar prueba al contrato de verificaci贸n es posible verificar que los datos est谩n disponibles en Avail. La prueba de Merkle es una lista de hashes que se pueden usar para demostrar que una hoja determinada es miembro del 谩rbol de Merkle. Se puede consultar un ejemplo de env铆o de una prueba al contrato de verificaci贸n implementado en la red Sepolia para Kate ( 0xA06386C65B1f56De57CE6aB9CeEB2552fa811529) y Goldberg ( 0x67044689F7e274a4aC7b818FDea64Cb4604c6875) llamando a la funci贸n de membres铆a ra铆z de datos async function checkProof(sepoliaApi, blockNumber, proof, numberOfLeaves, leafIndex, leafHash);donde
sepoliaApiInstancia de API de red Sepolia.
blockNumberN煤mero de bloque de disponibilidad.
proofPrueba de Merkle para la hoja.
numberOfLeavesN煤mero de hojas del 谩rbol original.
leafIndex脥ndice de la hoja del 谩rbol Merkle.
leafHashHach铆s de la hoja del 谩rbol Merkle.
Esto llamar谩 a la funci贸n de los contratos implementados verificationContract.checkDataRootMembership(blockNumber, proof, numberOfLeaves, leafIndex, leafHash) y devolver谩 trueo falsedependiendo de la prueba proporcionada.
EJEMPLO DE OBTENER LA PRUEBA Y VERIFICARLA CON EL CONTRATO DE VERIFICACI脫N UTILIZANDO POLKADOT-JSY ETHERS.JS.
Variables de entorno:
AVAIL_RPC= # avail websocket addressINFURA_KEY= # rpc provider key if neededVALIDIUM_ADDRESS= # address of the verification contract, one such is deployed on Sepolia network for Kate 0xA06386C65B1f56De57CE6aB9CeEB2552fa811529 or Goldberg 0x67044689F7e274a4aC7b818FDea64Cb4604c6875VALIDIYM_ABI_PATH= # path to abi file e.g. abi/ValidiumContract.jsonBLOCK_NUMBER= # number of the block for which to get Merkle proofBLOCK_HASH= # hash of the block for which to get Merkle proofTRANSACTION_INDEX= # index of the transaction in the block
Enviar ejemplo de prueba
import { ethers } from 'ethers';import * as dotenv from 'dotenv';import { hexlify } from 'ethers/lib/utils.js';import { readFileSync } from 'fs';import { ApiPromise, WsProvider } from '@polkadot/api'; dotenv.config(); /** * Creates api instance. * * @param url websocket address * @returns {Promise<ApiPromise>} */async function createApi(url) { const provider = new WsProvider(url); // Create the API and wait until ready return ApiPromise.create({ provider, rpc: { kate: { queryDataProof: { description: 'Generate the data proof for the given `index`', params: [ { name: 'data_index', type: 'u32', }, { name: 'at', type: 'Hash', isOptional: true, }, ], type: 'DataProof', }, }, }, types: { DataProof: { root: 'H256', proof: 'Vec<H256>', numberOfLeaves: 'Compact<u32>', leafIndex: 'Compact<u32>', leaf: 'H256', }, }, });} /** * Returns Merkle proof for the particular data. * * @param availApi Api instance * @param hashBlock Hash of the block * @param transactionIndex Index of the transaction in the block * @returns {Promise<*>} */async function getProof(availApi, hashBlock, transactionIndex) { const daHeader = await availApi.rpc.kate.queryDataProof( transactionIndex, hashBlock, ); console.log( `Fetched proof from Avail for txn index ${transactionIndex} inside block ${hashBlock}`, ); return daHeader;} /** * Checks if the provided Merkle proof is valid by checking on Ethereum deployed validation contract. * * @param sepoliaApi Sepolia network api instance * @param blockNumber Avail block number * @param proof Merkle proof for the leaf * @param numberOfLeaves Number of leaves in the original tree * @param leafIndex Index of the leaf in the Merkle tree * @param leafHash Hash of the leaf in the Merkle tree * @returns {Promise<*>} */async function checkProof( sepoliaApi, blockNumber, proof, numberOfLeaves, leafIndex, leafHash,) { const abi = JSON.parse( readFileSync(process.env.VALIDIYM_ABI_PATH).toString(), ); const verificationContract = new ethers.Contract( process.env.VALIDIUM_ADDRESS, abi, sepoliaApi, ); return await verificationContract.checkDataRootMembership( BigInt(blockNumber), proof, BigInt(numberOfLeaves), BigInt(leafIndex), leafHash, );} (async function submitProof() { // connect to Sepolia through Infura but can be used any other available provider const sepoliaApi = new ethers.providers.InfuraProvider.getWebSocketProvider( 'sepolia', process.env.INFURA_KEY, ); const availApi = await createApi(process.env.AVAIL_RPC); console.log( `Getting proof for transaction index ${process.env.TRANSACTION_INDEX} block number ${process.env.BLOCK_NUMBER} and block hash ${process.env.BLOCK_HASH}`, ); const daHeader = await getProof( availApi, process.env.BLOCK_HASH, process.env.TRANSACTION_INDEX, ); console.log(`Data Root: ${hexlify(daHeader.root)}`); console.log(`Proof: ${daHeader.proof}`); console.log(`Leaf to prove: ${hexlify(daHeader.leaf)}`); console.log(`Leaf index : ${daHeader.leafIndex}`); console.log(`Number of leaves: ${daHeader.numberOfLeaves}`); const isDataAccepted = await checkProof( sepoliaApi, process.env.BLOCK_NUMBER, daHeader.proof, daHeader.numberOfLeaves, daHeader.leafIndex, daHeader.leaf, ); console.log('Data is: ' + (isDataAccepted ? 'available' : 'not available')); await availApi.disconnect(); await sepoliaApi.destroy();})() .then(() => { console.log('Done'); }) .catch((err) => { console.error(err); process.exit(1); });