zmitton / eth-proof

Get a merkle-proof from the blockchain. Verify it locally.
MIT License
191 stars 44 forks source link

Question: could this theoretically deliver a proof of eth_call result? #16

Open stskeeps opened 5 years ago

stskeeps commented 5 years ago

Been playing with verifying ENS resolver results against a known good block hash, but that often takes a bit of poking around the contract to find right getStorageAt

Could this theoretically use ethereumjs-vm or the likes + storageAgainstBlockHash for SLOAD queries?

stskeeps commented 5 years ago

Answering my own question, this is a sample tx eth_call style done against a ERC20 token asking for balance -- running the tx locally instead of remote node:

This uses ethereumjs-vm 2.6.0. Bits and pieces liberally borrowed from https://github.com/MetaMask/eth-json-rpc-middleware/blob/master/vm.js


async function run() {
  const createVm = require('ethereumjs-vm/dist/hooked')
  const blockFromRpc = require('ethereumjs-block/from-rpc')
  const FakeTransaction = require('ethereumjs-tx').FakeTransaction
  const ethers = require('ethers');

  var provider = new ethers.providers.JsonRpcProvider('https://foundation.query.zippie.org/rpc')
  const { GetAndVerify, GetProof, VerifyProof } = require('eth-proof')
  let blockNumber = await provider.getBlockNumber()
  let block = await provider.getBlock(blockNumber)
  let knownHash = block.hash
  let getAndVerify = new GetAndVerify('https://foundation.query.zippie.org/rpc')

  let vm = createVm({homestead: true}, {
    fetchAccountBalance: function(addressHex, cb) {
      getAndVerify.accountAgainstBlockHash(addressHex, knownHash).then((account) => {
        console.log('fetchAccountBalance: ' + addressHex)
        cb(null, account.balance)
      })
    },  
    fetchAccountNonce: function(addressHex, cb) {
      console.log('fetchAccountNonce: '  + addressHex)
      getAndVerify.accountAgainstBlockHash(addressHex, knownHash).then((account) => {
        cb(null, account.nonce)
      })
    },
    fetchAccountCode: function(addressHex, cb) {
      console.log('fetchAccountCode: ' + addressHex)
      getAndVerify.accountAgainstBlockHash(addressHex, knownHash).then((account) => {
        provider.getBlock(knownHash).then((block) => {
         provider.getCode(addressHex, block.number).then((code) => {           
           let providerCodeHash = ethers.utils.keccak256(code)
           console.log('providerCodeHash: ' + providerCodeHash)
           console.log('account.codeHash: ' + account.codeHash.toString('hex'))

           if ('0x' + account.codeHash.toString('hex') !== providerCodeHash) {
             throw "Provider code hash failed verification"
           }
           cb(null, code)
         })
       })
      })
    },
    fetchAccountStorage: function(addressHex, keyHex, cb) {
      console.log('fetchAccountStorage: ' + addressHex + ' - ' + keyHex)
      getAndVerify.storageAgainstBlockHash(addressHex, keyHex, knownHash).then((result) => {
        cb(null, result)
      })
    }
  }) 
  var txParams = {}  
  txParams.from = '0xf4eb9bb30a8f61991220cb31762bf2f456bc7fee'
  txParams.gasLimit = '0x' + Number('32175').toString(16) 
  txParams.to = '0xedd7c94fd7b4971b916d15067bc454b9e1bad980'
  txParams.value = '0x00'
  txParams.nonce = '0x2d'
  txParams.data = '0x70a082310000000000000000000000002a0c0dbecc7e4d658f48e01e3fa353f44050c208'
  const tx = new FakeTransaction(txParams)
  vm.runTx({
    tx: tx,
//    block: block, XXX fixme?
    skipNonce: true,
      skipBalance: true
    }, function (err, results) {
      if (err) throw err
      if (results.error) {
        throw new Error('VM error: ' + results.error)
      }
      if (results.vm && results.vm.exception !== 1 && results.vm.exceptionError !== 'invalid opcode') {
        return new Error('VM Exception while executing ' + req.method + ': ' + results.vm.exceptionError)
      }

      console.log(results)
      console.log(results.vm.runState.returnValue.toString('hex'))
    })
}

run().then(() => {}).catch((err) => { console.log(err) })
zmitton commented 5 years ago

holy shit

zmitton commented 5 years ago

this is what I wanted someone to do. actually this would return a single proof for each value looked up - resulting in lots or repeated data. I've been hoping someone would do this though: In theory, you could combine all the data that must be "touched" by a tx into a special MPT that has only the proof nodes needed. Then the VM could use that as if it were the normal MPT.

This code is cool. It's a great start.

stskeeps commented 5 years ago

Yeah, I think there's a lot of room for making centralised nodes more trustable (really needed for UX)

I'm currently working on next proof of concept in line -- running eth_call against just a known good block hash -- and no actual Ehereum node to talk to.

.. where the block hash would be downloaded through IPFS, state trie navigated with IPLD and then storage trie same way (https://github.com/ipld/js-ipld-ethereum )

There's some partial support in Parity to act as such a source of IPFS objects but working to get storage trie available too.

zmitton commented 5 years ago

Yeah, I think there's a lot of room for making centralised nodes more trustable (really needed for UX)

agreed. If you find anything that fits this repo feel free to make PR.

If you are communicating with an eth-node we can maybe add some sort of eth_callWithProof functionality to geth/parity that returns the proofNodes that got touched during the ethcall (that way you can rerun the eth_call locally to verify).

If you are use IPFS you cant do that, but you could hack the MPT software to request from IPFS durring each _lookupNode operation. Of course this is lots if slow remote lookups! just an idea