Coinweb computation system
The Coinweb computation system can be broken down into creation of blocks in shards, processing of blocks, and processing of transactions.
Processing blocks
At a high level, computation in Coinweb consists of processing blocks. Blocks
are produced by the shuffler. The shuffler in turn gets information from
underlying L1 blockchains, such as Bitcoin or Ethereum, as well as information
(in the form of Jump
transactions) from other Coinweb shards.
The processing of blocks is broken down into Computations. Computations are similar to smart contracts, but:
-
They are extensions to the Coinweb protocol, meaning third parties are NOT free to include arbitrary computations.
-
They are not restricted by gas or resources.
-
They are provided a richer API to interact with the ClaimDB.
-
Instead of being called by transactions, they are executed after every block.
This way, computations are intended to extend the core of Coinweb, rather than specific dApps
The current Computations in Coinweb are as follows:
The computations in yellow are run in parallel and consist of:
- Computing statistics about a block.
- Computing block information.
- Computing wCWEB staking information.
The computations in red are parallel internally.
The computation block for staking will be moved into the smart contract layer in a future update.
After the yellow computations are run, a fixed number of steps are performed, where each step executes the Coinweb VM computation and the Tokenization Block computation.
Transaction execution
In Coinweb, the execution of smart contracts is done in parallel, and the Coinweb architecture also supports multiple smart contract virtual machines running side-by-side with full interoperability between the smart contract VMs.
This is done by separating I/O operations (communicating, reading, and writing claims) as well as transactionality, from smart contract execution. I/O and smart contract execution are interleaved in defined steps.
Smart contracts
In Coinweb, a smart contract is a function that takes as input a transaction, and either fails or returns a list of transactions. The smart contract does not do I/O directly but does so indirectly through the output transaction it produces.
Smart contracts are invoked using the Call
operation which identifies the
smart contract virtual machine type (currently only WebAssembly is supported),
dynamically loads the code to be executed, and executes the smart contract in
its own VM.
Smart contract execution overview
Transactions are arrays of Coinweb VM operations.
In Transactions (v0), the available operations are:
-
Data
- Data passed alongside a chain of transactions. Commonly used as input for smart contracts. -
Read
- Database lookups -
Block
- Block transaction on condition. -
Take
- Remove from the database a claim owned by the transaction issuer, and add the claim to the transaction. If the claim doesn't exist the transaction fails. -
Store
- Store into the database a claim owned by the transaction issuer. -
Call
- Invokes a smart contract.
Conceptually a smart contract is a function that takes as input a slice of a transaction and produces as output either a set of new transactions or a failure.
From the point of view of smart contract executions there are two types of operations:
-
Call
: specify which smart contract to call, and the number of arguments (slice) to use -
Any other op: operations such as
Data
,Read
,Take
,Agg
, orStore
will become arguments to the invoked smart contract. Each of them has some special behavior, that gets executed before the smart contract is invoked; for example, ReadOp will read a claim. For each op on the inputs slice, the smart contract will be able to see the op and the result of the op execution. -
Given a parent transaction, for each call op, a smart contract will be called, and the combined output of all these contracts will become the transaction's children.
The first example is a transaction with a call to a smart contract that takes 3 parameters. Two of the parameters are read as claims, and the 3rd parameter is inlined into the transaction. The transaction also includes a write (the write will be aborted if the smart contract fails).
Note that there is an operation that will not be present on any slice of a smart
contract (the Store
).
The second example is a transaction with 4 smart contract calls, all of them succeeding, and producing 11 children.
Note that there will be parent transactions with no calls to smart contracts,
and transactions with operations that will not be present on any slice to a
smart contract. All operations, even those that are not input to smart
contracts, get executed; notice that the execution of an operation has side
effects that are useful by themselves (for example, Take
removes a claim from
the DB and increases the CWEB balance of the transaction, Read
could be used
to force the transaction to fail conditionally, Store
creates a new
transaction in the DB...etc).
Note that it is valid for a successful smart contract execution to produce 0 children.
Note that a transaction made up of 0 operations is considered valid (though not that useful).
Transactionality rules
If any operation or any contract execution fails, the whole parent transaction and its children are aborted (that's why it is called a transaction!).
The transaction also gets aborted if:
-
The child transaction carry more CWEB than the parent transaction.
- Otherwise, transactions would be allowed to create CWEB out of nowhere.
- The CWEB carried by a transaction is the CWEB inside executed
Take
plus the CWEB insideData
minus the CWEB insideStore
.
-
The CWEB difference between parent and child transactions is not enough to cover the network fees.
-
Child transactions contain (counting repetitions) some claim inside a
Data
that is neither issued by the called smart contract nor present on the parent transaction.
If there's no reason to abort it, the changes are committed and the child transactions will become "parent transaction" on the next execution iteration, producing their own new child transactions (or failing).
Once a child transaction becomes a parent transaction, it will be "detached" from its "siblings", meaning that if it gets aborted, it will not cascade to its children or its previous parent (which was already committed).
This means that individual transactions are transactional, but the overall execution and results of on-chain computation is NOT transactional! This property (or lack thereof) is unique to Coinweb and implies big tradeoffs:
-
Pros:
- Scalability.
- "Never-ending" contracts are possible.
- Custom transactional semantics become a possibility.
-
Cons: Correctness of smart contracts is more tricky.
In this example, we show two transactions being executed in a (parallel) step, and the second smart contract from the first transaction fails.
Things to note: The first (parent) transaction, including any external I/O
effects, such as Take
or Store
are aborted. Also all children of any smart
contract call from that transaction are aborted. This is shown in black.
The second transaction is unaffected by the failing smart contract in the first transaction.
In this example, both parent transactions are aborted when there is a Store
conflict in the child transactions. Everything in black is aborted because of
the conflict in the red boxes.
For simplicity, the claims are called x
, and y
. When both 1
and 2
is
simultaneously stored into y
, this causes a conflict and both transactions are
aborted.
On the other hand, two simultaneous writes to x
would not cause transactions
to be aborted as both Store
operations update x
with the same value and thus
no conflict exists.
Creating an explicit conflict is a useful technique to enforce failure of otherwise unrelated transactions.
Step loop
On each shard, the loop of "parent transaction creates children, then children become new parents" iterates forever, we call it the "execution loop".
On every block, we will execute a fixed number of "execution loop" iterations; After these iterations, the intermediate result is saved on disk/memory and resumed when the next block is found.
The number of iterations is determined by the constant
shard_genesis_state.exec_iterations_per_block
.