import Arweave from 'arweave' ; import { loadContract } from './contract-load' ; import { arrayToHex , evalSettings , hasMultipleinteractions , log } from './utils' ; import { execute , ContractInteraction } from './contract-step' ; import { InteractionTx } from './interaction-tx' ; import GQLResultInterface , { GQLEdgeInterface , GQLTransactionsResultInterface } from './interfaces/gqlResult' ; import SmartWeaveError , { SmartWeaveErrorType } from './errors' ; param arweave param contractId param height param returnValidity export async function readContract ( arweave , contractId , height ? , returnValidity ? ) { if ( ! height ) { const networkInfo = await arweave . network . getInfo ( ) ; height = networkInfo . height ; } const loadPromise = loadContract ( arweave , contractId ) . catch ( ( err ) => { const error = new SmartWeaveError ( SmartWeaveErrorType . CONTRACT_NOT_FOUND , { message : `Contract having txId: ${ contractId } not found` , requestedTxId : contractId , } ) ; throw error ; } ) ; const fetchTxPromise = fetchTransactions ( arweave , contractId , height ) . catch ( ( err ) => err ) ; let [ contractInfo , txInfos ] = await Promise . all ( [ loadPromise , fetchTxPromise ] ) ; if ( contractInfo instanceof Error ) throw contractInfo ; if ( txInfos instanceof Error ) throw txInfos ; let state ; let contractSrcTXID = contractInfo . contractSrcTXID ; try { state = JSON . parse ( contractInfo . initState ) ; } catch ( e ) { throw new Error ( `Unable to parse initial state for contract: ${ contractId }` ) ; } log ( arweave , `Replaying ${ txInfos . length } confirmed interactions` ) ; await sortTransactions ( arweave , txInfos ) ; let { handler , swGlobal } = contractInfo ; const validity = { } ; for ( const txInfo of txInfos ) { const currentTx = txInfo . node ; let input = txInfo . node . tags [ txInfo . node . tags . findIndex ( ( tag ) => tag . name === 'Input' ) ] . value ; if ( hasMultipleinteractions ( txInfo ) ) { const contractIndex = txInfo . node . tags . findIndex ( ( tag ) => tag . name === 'Contract' && tag . value === contractId ) ; const inputTag = txInfo . node . tags [ contractIndex + 1 ] ; if ( ! inputTag || inputTag . name !== 'Input' ) { log ( arweave , `Skipping tx with missing or invalid Input tag - ${ currentTx . id }` ) ; continue ; } input = inputTag . value ; } try { input = JSON . parse ( input ) ; } catch ( e ) { log ( arweave , e ) ; continue ; } if ( ! input ) { log ( arweave , `Skipping tx with missing or invalid Input tag - ${ currentTx . id }` ) ; continue ; } const interaction = { input , caller : currentTx . owner . address , } ; swGlobal . _activeTx = currentTx ; const result = await execute ( handler , interaction , state ) ; if ( result . type === 'exception' ) { log ( arweave , `${ result . result }` ) ; log ( arweave , `Executing of interaction: ${ currentTx . id } threw exception.` ) ; } if ( result . type === 'error' ) { log ( arweave , `${ result . result }` ) ; log ( arweave , `Executing of interaction: ${ currentTx . id } returned error.` ) ; } validity [ currentTx . id ] = result . type === 'ok' ; state = result . state ; const settings = evalSettings ( state ) ; const evolve = state . evolve || settings . get ( 'evolve' ) ; let canEvolve = state . canEvolve || settings . get ( 'canEvolve' ) ; if ( canEvolve == null ) { canEvolve = true ; } if ( evolve && /[a-z0-9_-]{43}/i . test ( evolve ) && canEvolve && contractSrcTXID !== evolve ) { try { contractInfo = await loadContract ( arweave , contractId , evolve ) ; swGlobal = contractInfo . swGlobal ; handler = contractInfo . handler ; contractSrcTXID = evolve ; } catch ( e ) { const error = new SmartWeaveError ( SmartWeaveErrorType . CONTRACT_NOT_FOUND , { message : `Contract having txId: ${ contractId } not found` , requestedTxId : contractId , } ) ; console . log ( error ) ; } } } return returnValidity ? { state , validity } : state ; } async function sortTransactions ( arweave , txInfos ) { const addKeysFuncs = txInfos . map ( ( tx ) => addSortKey ( arweave , tx ) ) ; await Promise . all ( addKeysFuncs ) ; txInfos . sort ( ( a , b ) => a . sortKey . localeCompare ( b . sortKey ) ) ; } async function addSortKey ( arweave , txInfo ) { const { node } = txInfo ; const blockHashBytes = arweave . utils . b64UrlToBuffer ( node . block . id ) ; const txIdBytes = arweave . utils . b64UrlToBuffer ( node . id ) ; const concatted = arweave . utils . concatBuffers ( [ blockHashBytes , txIdBytes ] ) ; const hashed = arrayToHex ( await arweave . crypto . hash ( concatted ) ) ; const blockHeight = `000000${ node . block . height }` . slice ( - 12 ) ; txInfo . sortKey = `${ blockHeight },${ hashed }` ; } const MAX_REQUEST = 100 ; interface TagFilter { name : string ; values : string [ ] ; } interface BlockFilter { max : number ; } interface ReqVariables { tags : TagFilter [ ] ; blockFilter : BlockFilter ; first : number ; after ? : string ; } async function fetchTransactions ( arweave , contractId , height ) { let variables = { tags : [ { name : 'App-Name' , values : [ 'SmartWeaveAction' ] , } , { name : 'Contract' , values : [ contractId ] , } , ] , blockFilter : { max : height , } , first : MAX_REQUEST , } ; let transactions = await getNextPage ( arweave , variables ) ; const txInfos = transactions . edges . filter ( ( tx ) => ! tx . node . bundledIn || ! tx . node . bundledIn ?. id || ! tx . node . parent || ! tx . node . parent ?. id ) ; while ( transactions . pageInfo . hasNextPage ) { const cursor = transactions . edges [ MAX_REQUEST - 1 ] . cursor ; variables = { ... variables , after : cursor , } ; transactions = await getNextPage ( arweave , variables ) ; txInfos . push ( ... transactions . edges . filter ( ( tx ) => ! tx . node . bundledIn || ! tx . node . bundledIn ?. id || ! tx . node . parent || ! tx . node . parent ?. id ) ) ; } return txInfos ; } async function getNextPage ( arweave , variables ) { const query = `query Transactions($tags: [TagFilter!]!, $blockFilter: BlockFilter!, $first: Int!, $after: String) {
    transactions(tags: $tags, block: $blockFilter, first: $first, sort: HEIGHT_ASC, after: $after) {
      pageInfo {
        hasNextPage
      }
      edges {
        node {
          id
          owner { address }
          recipient
          tags {
            name
            value
          }
          block {
            height
            id
            timestamp
          }
          fee { winston }
          quantity { winston }
          parent { id }
        }
        cursor
      }
    }
  }` ; const response = await arweave . api . post ( 'graphql' , { query , variables , } ) ; if ( response . status !== 200 ) { throw new Error ( `Unable to retrieve transactions. Arweave gateway responded with status ${ response . status }.` ) ; } const data = response . data ; const txs = data . data . transactions ; return txs ; }