Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@pfrazee
Last active March 22, 2018 09:50
Show Gist options
  • Star 20 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pfrazee/bf13db9dea21936af320c512811c2a2b to your computer and use it in GitHub Desktop.
Save pfrazee/bf13db9dea21936af320c512811c2a2b to your computer and use it in GitHub Desktop.

Can we create a smart contract VM on nodejs using Dat?

Ethereum is a trustless network of VMs which run smart contracts submitted by users. It uses proof-of-work to synchronize state across the network, and has every node execute the contracts in order to verify the state's validity. Each transaction is stored in the blockchain for replayability. Read more about it here.

Ethereum's "trustless network" model has some disadvantages:

  • Transaction processing is slow - it maxes at roughly 25tx/s right now for all contracts combined.
  • Every transaction costs money to execute.
  • The entire blockchain state must be shared across the computing network.
  • No private transactions.
  • Proof of Work burns energy needlessly.
  • It ultimately depends on blackbox oracles, which puts a limit on how trustless the transactions can really be.

However, there are some things that Ethereum does right:

  • Makes it very easy to spin up a "backend" for maintaining centralized state.
  • Provides crypto-auditable transparency on the history of a contract.
  • Enables e-commerce.

The limits of trustless-ness

Ethereum's "trustless" claim refers to the contract VM. All transactions are published on the blockchain, and therefore all of the VMs in the network are tightly audited by the rest of the network. This makes it very hard for a VM to lie about the results of a contract computation.

Trustlessness makes the VMs fungible; you don't care which one executes the code, so long as it's done.

However- the "trustless" claim only works over a closed system: anything not modeled on the Ethereum network itself cannot be verified. This is referred to as the "oracle" problem.

Let's say your contract depends on the current stock-value of GOOG as listed by the NASDAQ. The NASDAQ is not modeled on Ethereum's blockchain. Therefore, the contract will have to consult a third party to get the stock value. That third party is the "oracle," and it's effectively a black box to the network.

In Ethereum, oracles are modeled in the same fashion as a user: when consulted, an oracle creates a new transaction which includes the response data. This makes sense- users are black boxes, and so oracles are basically users in the network. Regardless, oracles must be trusted if they are used in contracts, and so Ethereum contracts tend to avoid them.

This need for auditability is why Ethereum has its own currency. Ether is modeled entirely on the blockchain, and therefore it is fully verifiable.

Changing the requirements: efficiency over uniformity

The trustless network is very slow because it requires full sync between all nodes, and the sync must be timed by Proof-of-Work. I would prefer a network which can transact efficiently. Therefore, let's relax some constraints.

Instead of having all computers in the network model the Single Syncronized VM, let's have each computer in the network model Many Isolated VMs. These isolated VMs do not interact with each other by default. They have no shared state, and therefore they have no shared currency. The Many Isolated VM model is therefore not a financial system: it's simply a system for computing blocks of state.

So:

  • No currency
  • No clustering
  • No consensus algorithm

Each VM host runs on its own. Like in Ethereum, every contract maintains a verifiable log which publishes the transactions auditably. Unlike Ethereum, there's no need to wait for consensus, and so the contract can execute as quickly as the VM host can handle incoming requests.

Usecases for the Many Isolated VMs (MIVM) model

Ethereum is useful for two reasons: 1) it's a convenient way to spin up a backend which maintains state, and 2) it can handle financial transactions. The Many Isolated VMs model cant handle finances, but it can maintain state, and do so at much higher scale.

Therefore, a MIVM contract is extremely useful for running applications. Examples include: matching buyers and sellers in a market (ebay, uber), creating public sale/rental listings (craigslist, airbnb), running discussion forums, CMS, and

Compared to traditional hosted services, a MIVM contract is similar to the "hostless" lambda functions, but with external auditability as a key feature. The ideal execution of an MIVM environment would enable users to quickly spin up a "contract backend" for personal use, and then simply interact with the API without having to maintain the backend.

It's important to note that a MIVM contract can be transfered from one VM host to another and continue operating. This would require a "fork" of the contract as the signing keys would need to change. However, because the transaction history is replayable, a new host can resume operation by reading the history.

Embracing the oracle: trusted but auditable

E-commerce is still possible in the Many Isolated VMs model. It simply requires trust in the host VM.

As with Ethereum, any operations which are not modeled internally would be executed by APIs provided by the host environment. These APIs would be blackboxes: they would require the VM host to (as an oracle) conduct the operation and truthfully respond with the result.

Therefore, we shift the focus from "trustless" to "trusted but auditable." This would mean you, the user, do need to choose the VM which executes your contract.

I consider "Embracing the oracle" an acceptable alternative to inefficiently modeling the financial system within the contract network. Though I acknowledge financial transactions are a critical part of computing, I don't view them as particularly more important than the other forms of computing for which Ethereum still requires oracles- therefore I consider the focus on trustless finances to be somewhat skewed. Further, at this stage I have more personal trust in my existing banking solution than I have in the Ethereum blockchain.

Auditable execution using Dat's log structure

Dat uses a cryptographically auditable log structure called hypercore to distribute changes to a system.

Here I'll propose a potential MIVM model:

  • Use nodejs to provide the Host VM environment (will require a secure sandbox to execute)
  • Users generate new "contract instances" by remote call, and provide the contract script
  • The contract exports an API, and uses the environment API to read/write state
  • The Host VM generates a hypercore log for each contract instance, the call log
  • The Host VM generates a dat archive for each contract instance, the state archive
  • Calling users generate 1 hpercore log for each contract instance they wish to contract, the user log

Here is the process for executing an API call:

  • The call is first written to a user log
  • The host VM receives the call in the log, and writes it to the call log (as an ACK)
  • The host VM executes the call, and writes the return value to the call log
  • Any changes to state made by the contract are written to the state archive
  • The return value is written to the user log (as an ACK)

This process should make it possible for the calling user to verify all non-blackbox changes, and for an external user to verify the history of a contract by syncing and replaying the call log and all user logs. The state archive is used by consumers to read the current values of the contract (though a live 'get' operation would be useful).

Here's an example contract. It's just a node module with an expored API. For fun, let's say that any variable that's exported is automatically persisted to the state archive, so if the host VM shuts down and restarts, that state will persist. Let's also assume a global state variable which represents the state archive:

exports.topicCounter = 0
exports.postCounter = 0
exports.init = async () => {
  state.mkdir('/topics')
}
exports.addTopic = async (title) => {
  const id = ++exports.topicCounter
  await state.mkdir(`/topics/${id}`)
  await state.writeFile(`/topics/${id}/title`, title)
  return id
}
exports.addPost = async (id, content) => {
  const postId = ++exports.postCounter
  await state.writeFile(`/topics/${id}/${postId}`, content)
  return postId
}

A consuming user could now call:

var topicId = await myForumContract.addTopic('Hello world')
var postId = await myForumContract.addPost(topicId, 'My name is bob')

And the files would be readable in the state archive:

/topics/1/title   'Hello world'
/topics/1/1       'My name is bob'

Bob's user log would look something like this:

bob+1 init()
bob+2 ACK host+1
bob+3 addTopic('Hello world')
bob+4 ACK host+2
bob+5 addPost(1, 'My  name is bob')
bob+6 ACK host+3

The call log would look something like this:

host+1 new Forum() (ACK bob+1)
host+2 addTopic('Hello world') (ACK bob+3) result: 1
host+3 addPost(1, 'My name is bob') (ACK bob+5) result: 1

This is a pretty simple example. It doesn't cover permissions, for instance.

@dominictarr
Copy link

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment