Skip to content

Commit

Permalink
feat(core): parse smart contract tx that anchors a 32 byte hash (#2379)
Browse files Browse the repository at this point in the history
  • Loading branch information
stephhuynh18 authored Aug 16, 2022
1 parent 80cce90 commit 0cd3a36
Show file tree
Hide file tree
Showing 2 changed files with 42 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const ANCHOR_PROOF: AnchorProof = {
const TX_HASH =
'0x' + uint8arrays.toString(decode(ANCHOR_PROOF.txHash.multihash.bytes).digest, 'base16')
const TEST_TRANSACTION = {
data: '0x7d3b0ca2000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000250f017112205d7fcd1a0999befdb062e6762c1f0f902f729b98304a2ef539412f53360d3d6a000000000000000000000000000000000000000000000000000000',
data: '0x97ad09eb5d7fcd1a0999befdb062e6762c1f0f902f729b98304a2ef539412f53360d3d6a',
blockNumber: ANCHOR_PROOF.blockNumber,
blockHash: '0x752fcd3593c140db9f206b421c47d875e0f92e425983f8c72ab04c0e969b072a',
to: '0xD3f84Cf6Be3DD0EB16dC89c972f7a27B441A39f2',
Expand Down
97 changes: 41 additions & 56 deletions packages/core/src/anchor/ethereum/ethereum-anchor-validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,12 @@ import * as providers from '@ethersproject/providers'
import lru from 'lru_map'
import { AnchorProof, AnchorValidator, DiagnosticsLogger } from '@ceramicnetwork/common'
import { Block, TransactionResponse } from '@ethersproject/providers'
import { base16 } from 'multiformats/bases/base16'
import { Interface } from '@ethersproject/abi'
import { create as createMultihash } from 'multiformats/hashes/digest'
import { CID } from 'multiformats/cid'

const SHA256_CODE = 0x12
const DAG_CBOR_CODE = 0x71

/**
* Ethereum network configuration
Expand Down Expand Up @@ -42,7 +46,7 @@ const MAX_PROVIDERS_COUNT = 100
const TRANSACTION_CACHE_SIZE = 50
const BLOCK_CACHE_SIZE = 50

const ABI = ['function anchor(bytes)']
const ABI = ['function anchorDagCbor(bytes32)']

const iface = new Interface(ABI)

Expand All @@ -62,14 +66,30 @@ const ANCHOR_CONTRACT_ADDRESSES = {
'eip155:1337': '0xD3f84Cf6Be3DD0EB16dC89c972f7a27B441A39f2', //ganache
}

/*
type for overall validation result
*/
type ValidationResult = {
txResponse: TransactionResponse
block: Block
txValueHexNumber: number
rootValueHexNumber: number
const getCidFromV0Transaction = (txResponse: TransactionResponse): CID => {
const withoutPrefix = txResponse.data.replace(/^(0x0?)/, '')
return CID.decode(uint8arrays.fromString(withoutPrefix.slice(1), 'base16'))
}

const getCidFromV1Transaction = (txResponse: TransactionResponse): CID => {
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)
}

/**
* Parses the transaction data to recover the CID.
* @param version version of the anchor proof. Version 1 anchor proofs are created using the official anchoring smart contract and must be parsed accordingly
* @param txResponse the retrieved transaction from the ethereum blockchain
* @returns
*/
const getCidFromTransaction = (version: number, txResponse: TransactionResponse): CID => {
if (version === 1) {
return getCidFromV1Transaction(txResponse)
} else {
return getCidFromV0Transaction(txResponse)
}
}

/**
Expand Down Expand Up @@ -186,63 +206,31 @@ export class EthereumAnchorValidator implements AnchorValidator {
}
}

/**
* Validate version 0 anchor proof on the chain by the reading tx data directly
* @param anchorProof - Anchor proof instance
*/
async parseAnchorProofV0(anchorProof: AnchorProof): Promise<ValidationResult> {
const decoded = decode(anchorProof.txHash.multihash.bytes)
const txHash = '0x' + uint8arrays.toString(decoded.digest, 'base16')
const [transaction, block] = await this._getTransactionAndBlockInfo(anchorProof.chainId, txHash)
const txValueHexNumber = parseInt(transaction.data, 16)
const rootValueHexNumber = parseInt('0x' + anchorProof.root.toString(base16), 16)
return { txResponse: transaction, block, txValueHexNumber, rootValueHexNumber }
}

/**
* Validate version 1 anchor proof on the chain by parsing first encoded parameter
* @param anchorProof - Anchor proof instance
*/
async parseAnchorProofV1(anchorProof: AnchorProof): Promise<ValidationResult> {
async validateChainInclusion(anchorProof: AnchorProof): Promise<void> {
const decoded = decode(anchorProof.txHash.multihash.bytes)
const txHash = '0x' + uint8arrays.toString(decoded.digest, 'base16')
const [transaction, block] = await this._getTransactionAndBlockInfo(anchorProof.chainId, txHash)
const decodedArgs = iface.decodeFunctionData('anchor', transaction.data)
const rootCID = decodedArgs[0]
const txValueHexNumber = parseInt(rootCID, 16)
const rootValueHexNumber = parseInt('0x' + anchorProof.root.toString(base16), 16)
return { txResponse: transaction, block, txValueHexNumber, rootValueHexNumber }
}
const [txResponse, block] = await this._getTransactionAndBlockInfo(anchorProof.chainId, txHash)
const txCid = getCidFromTransaction(anchorProof.version, txResponse)

async parseAnchorProof(anchorProof: AnchorProof): Promise<ValidationResult> {
if (anchorProof.version === 1) {
return this.parseAnchorProofV1(anchorProof)
} else {
return this.parseAnchorProofV0(anchorProof)
}
}

async validateChainInclusion(anchorProof: AnchorProof): Promise<void> {
const validationResult: ValidationResult = await this.parseAnchorProof(anchorProof)
if (validationResult.txValueHexNumber !== validationResult.rootValueHexNumber) {
if (!txCid.equals(anchorProof.root)) {
throw new Error(`The root CID ${anchorProof.root.toString()} is not in the transaction`)
}

if (anchorProof.blockNumber !== validationResult.txResponse.blockNumber) {
if (anchorProof.blockNumber !== txResponse.blockNumber) {
throw new Error(
`Block numbers are not the same. AnchorProof blockNumber: ${anchorProof.blockNumber}, eth txn blockNumber: ${validationResult.txResponse.blockNumber}`
`Block numbers are not the same. AnchorProof blockNumber: ${anchorProof.blockNumber}, eth txn blockNumber: ${txResponse.blockNumber}`
)
}

if (anchorProof.blockTimestamp !== validationResult.block.timestamp) {
if (anchorProof.blockTimestamp !== block.timestamp) {
throw new Error(
`Block timestamps are not the same. AnchorProof blockTimestamp: ${anchorProof.blockTimestamp}, eth txn blockTimestamp: ${validationResult.block.timestamp}`
`Block timestamps are not the same. AnchorProof blockTimestamp: ${anchorProof.blockTimestamp}, eth txn blockTimestamp: ${block.timestamp}`
)
}

// if the block number is greater than the threshold and the version is 0 or non existent
if (
validationResult.txResponse.blockNumber > BLOCK_THRESHHOLDS[this._chainId] &&
txResponse.blockNumber > BLOCK_THRESHHOLDS[this._chainId] &&
(anchorProof.version === 0 || !anchorProof.version)
) {
throw new Error(
Expand All @@ -252,13 +240,10 @@ export class EthereumAnchorValidator implements AnchorValidator {
)
}

if (
anchorProof.version === 1 &&
validationResult.txResponse.to !== ANCHOR_CONTRACT_ADDRESSES[this._chainId]
) {
if (anchorProof.version === 1 && txResponse.to !== ANCHOR_CONTRACT_ADDRESSES[this._chainId]) {
throw new Error(
`Anchor was created using address ${
validationResult.txResponse.to
txResponse.to
}. This is not the official anchoring contract address ${
ANCHOR_CONTRACT_ADDRESSES[this._chainId]
}`
Expand Down

0 comments on commit 0cd3a36

Please sign in to comment.