UNPKG

@ceramicnetwork/core

Version:

Typescript implementation of the Ceramic protocol

171 lines • 7.47 kB
import * as uint8arrays from 'uint8arrays'; import * as providers from '@ethersproject/providers'; import { LRUCache } from 'least-recent'; import { Interface } from '@ethersproject/abi'; import { create as createMultihash } from 'multiformats/hashes/digest'; import { CID } from 'multiformats/cid'; import { convertCidToEthHash } from '@ceramicnetwork/anchor-utils'; const SHA256_CODE = 0x12; const DAG_CBOR_CODE = 0x71; const ETH_CHAIN_ID_MAPPINGS = { 'eip155:1': { network: 'mainnet', chain: 'ETH', chainId: 1, networkId: 1, type: 'Production' }, 'eip155:3': { network: 'ropsten', chain: 'ETH', chainId: 3, networkId: 3, type: 'Test' }, 'eip155:4': { network: 'rinkeby', chain: 'ETH', chainId: 4, networkId: 4, type: 'Test' }, 'eip155:5': { network: 'goerli', chain: 'ETH', chainId: 5, networkId: 5, type: 'Test' }, 'eip155:100': { network: 'mainnet', chain: 'Gnosis', chainId: 100, networkId: 100, type: 'Test', endpoint: 'https://rpc.ankr.com/gnosis', }, 'eip155:11155111': { network: 'sepolia', chain: 'ETH', chainId: 11155111, networkId: 11155111, type: 'Test', }, }; const BASE_CHAIN_ID = 'eip155'; const MAX_PROVIDERS_COUNT = 100; const TRANSACTION_CACHE_SIZE = 50; const BLOCK_CACHE_SIZE = 50; const V1_PROOF_TYPE = 'f(bytes32)'; const ABI = ['function anchorDagCbor(bytes32)']; const iface = new Interface(ABI); const BLOCK_THRESHHOLDS = { 'eip155:1': 16688195, 'eip155:3': 1000000000, 'eip155:5': 8498671, 'eip155:100': 26509835, 'eip155:11155111': 5518585, 'eip155:1337': 1, }; const ANCHOR_CONTRACT_ADDRESS = '0x231055A0852D67C7107Ad0d0DFeab60278fE6AdC'; const getCidFromV0Transaction = (txResponse) => { const withoutPrefix = txResponse.data.replace(/^(0x0?)/, ''); return CID.decode(uint8arrays.fromString(withoutPrefix.slice(1), 'base16')); }; const getCidFromV1Transaction = (txResponse) => { const decodedArgs = iface.decodeFunctionData('anchorDagCbor', txResponse.data); const rootCID = decodedArgs[0]; const multihash = createMultihash(SHA256_CODE, uint8arrays.fromString(rootCID.slice(2), 'base16')); return CID.create(1, DAG_CBOR_CODE, multihash); }; const getCidFromTransaction = (txType, txResponse) => { if (txType === V1_PROOF_TYPE) { return getCidFromV1Transaction(txResponse); } else { return getCidFromV0Transaction(txResponse); } }; export class EthereumAnchorValidator { constructor(ethereumRpcEndpoint, logger) { this.ethereumRpcEndpoint = ethereumRpcEndpoint; this.providersCache = new LRUCache(MAX_PROVIDERS_COUNT); this._transactionCache = new LRUCache(TRANSACTION_CACHE_SIZE); this._blockCache = new LRUCache(BLOCK_CACHE_SIZE); this._logger = logger; } async init(chainId) { if (!chainId) { return; } const provider = this._getEthProvider(chainId); const provider_chain_idnum = (await provider.getNetwork()).chainId; const provider_chain = BASE_CHAIN_ID + ':' + provider_chain_idnum; if (chainId != provider_chain) { throw new Error(`Configured eth provider is for chainId ${provider_chain}, but our anchor service uses chain ${chainId}`); } this._chainId = chainId; } get chainId() { return this._chainId; } async _getTransaction(provider, txHash) { let tx = this._transactionCache.get(txHash); if (!tx) { tx = await provider.getTransaction(txHash); this._transactionCache.set(txHash, tx); } return tx; } async _getTransactionAndBlockInfo(chainId, txHash) { try { const provider = this._getEthProvider(chainId); const transaction = await this._getTransaction(provider, txHash); if (!transaction) { if (!this.ethereumRpcEndpoint) { throw new Error(`Failed to load transaction data for transaction ${txHash}. Try providing an ethereum rpc endpoint`); } else { throw new Error(`Failed to load transaction data for transaction ${txHash}`); } } let block = this._blockCache.get(transaction.blockHash); if (!block) { block = await provider.getBlock(transaction.blockHash); this._blockCache.set(transaction.blockHash, block); } if (!block) { if (!this.ethereumRpcEndpoint) { throw new Error(`Failed to load transaction data for block with block number ${transaction.blockNumber} and block hash ${transaction.blockHash}. Try providing an ethereum rpc endpoint`); } else { throw new Error(`Failed to load transaction data for block with block number ${transaction.blockNumber} and block hash ${transaction.blockHash}`); } } return [transaction, block]; } catch (e) { this._logger.err(`Error loading transaction info for transaction ${txHash} from Ethereum: ${e}`); throw e; } } async validateChainInclusion(anchorProof) { const txHash = convertCidToEthHash(anchorProof.txHash); const [txResponse, block] = await this._getTransactionAndBlockInfo(anchorProof.chainId, txHash); const txCid = getCidFromTransaction(anchorProof.txType, txResponse); if (!txCid.equals(anchorProof.root)) { throw new Error(`The root CID ${anchorProof.root} is not in the transaction`); } if (txResponse.blockNumber <= BLOCK_THRESHHOLDS[anchorProof.chainId]) { return block.timestamp; } if (anchorProof.txType !== V1_PROOF_TYPE) { throw new Error(`Any anchor proofs created after block ${BLOCK_THRESHHOLDS[anchorProof.chainId]} must include the txType field. Anchor txn blockNumber: ${txResponse.blockNumber}`); } if (txResponse.to != ANCHOR_CONTRACT_ADDRESS) { throw new Error(`Anchor was created using address ${txResponse.to}. This is not the official anchoring contract address ${ANCHOR_CONTRACT_ADDRESS}`); } return block.timestamp; } _getEthProvider(chain) { const fromCache = this.providersCache.get(chain); if (fromCache) return fromCache; if (!chain.startsWith('eip155')) { throw new Error(`Unsupported chainId '${chain}' - must be eip155 namespace`); } if (this._chainId && this._chainId != chain) { throw new Error(`Unsupported chainId '${chain}'. Configured anchor service only supports '${this._chainId}'`); } const ethNetwork = ETH_CHAIN_ID_MAPPINGS[chain]; const endpoint = this.ethereumRpcEndpoint || ethNetwork?.endpoint; if (endpoint) { const provider = new providers.StaticJsonRpcProvider(endpoint); this.providersCache.set(chain, provider); return provider; } if (!ethNetwork) { throw new Error(`No ethereum provider available for chainId ${chain}`); } const provider = providers.getDefaultProvider(ethNetwork.network); this.providersCache.set(chain, provider); return provider; } } //# sourceMappingURL=ethereum-anchor-validator.js.map