What is a Valid Transaction in BigchainDB?

Troy McConaghy
The BigchainDB Blog
5 min readJun 20, 2017

--

This post is a bit more technical than others in this blog. It outlines what it means for a transaction to be valid as of BigchainDB Server version 1.0.0 (which will be released soon).

BigchainDB checks many aspects of each transaction

We strive to be open and transparent about what we’re doing and blog posts like this are part of how we do that. If you’re technically-minded, we hope this post will help you better-understand and make decisions about BigchainDB.

Assumptions

In BigchainDB version 1.0.0, we assume that the BigchainDB cluster has a fixed set of BigchainDB nodes, that each node is running the same version of BigchainDB Server, and the nodes all agree on the public keys of all the other nodes.

If a user wants to ask a BigchainDB cluster about the validity of a transaction, they must ask enough of the nodes to determine what a majority of the nodes think. The non-faulty nodes should all agree on the transaction’s validity. We assume that a majority of nodes are not faulty (i.e. not broken or somehow compromised).

A valid transaction sent to a BigchainDB cluster won’t necessarily end up being stored. Several things can go wrong: It might get lost or corrupted en route to the node where it was sent. The node it was sent to might go down before assigning the transaction to a node for consideration. The node it was assigned to might go down before including it in a block, or before writing the block to the database. However, once a transaction gets written to a block and that block gets stored in the database, it’s stored. It’s up to the user to check what happened to their transaction. If a sent transaction goes missing, the user should just re-send it.

Transaction Validity

A BigchainDB transaction is valid if it is structurally-valid and graph-valid.

Structurally-Valid Transactions

A transaction is structurally-valid if it satisfies some constraints, all of which can be checked by looking at the internal structure (content) of the transaction in isolation.

For example, the transaction must conform to the BigchainDB transaction schema, which is codified in transaction.yaml, transaction_create.yaml and transaction_transfer.yaml.

There are differences between the schemas of CREATE and TRANSFER transactions. For example, in a CREATE transaction, the "asset" must contain a "data" key (i.e. the asset payload, which can be an arbitrary JSON object, or null) and no other keys. In a TRANSFER transaction, the "asset" must contain an "id" key (i.e. the ID of the transaction where the asset was first created) and no other keys.

Another difference between CREATE and TRANSFER transactions is in the expected "inputs" array. A CREATE transaction should have exactly one input. That input can contain one or more “owners_before”, a fulfillment (with one signature from each of the owners-before), and it shouldn’t point to any previous transaction output (i.e. the value of"fulfills"should be null). A TRANSFER transaction should have at least one input, and the value of "fulfills" must not be null (because it should point to a transaction output).

The maximum "amount" in an output of a transaction, once converted from a string to a number, is 9×10¹⁸. The reason is to keep the value within what a server can comfortably represent using a 64-bit signed integer, i.e. 9×1o¹⁸ is less than 2⁶³.

Each of the crypto-conditions (and sub-conditions) in an output must be one of the two allowed types: an ED25519 signature condition or a threshold condition. They must also be valid according to version 02 of the crypto-conditions spec. (Hashlock/preimage conditions, timelock conditions and inverted threshold conditions are not supported in version 1.0.0, but support for them may return in future versions.)

Transactions must always be signed and hashed, and the stored signatures and hashes must be valid. The details of what gets signed and hashed are beyond the scope of this overview; suffice it to say that almost everything gets signed or hashed, except for things that can’t be (e.g. things that depend on the signatures or hashes themselves). One consequence is that transactions aren’t malleable.

Graph-Valid Transactions

A transaction is graph-valid, roughly speaking, if it satisfies some constraints on the directed graph of transactions, where the graph nodes are transactions and the directed edges connect transaction inputs and outputs.

One constraint is that the transaction doesn’t double-spend: it doesn’t have two or more inputs spending (transferring) the same output, and it doesn’t spend an output that is spent by another transaction.

An aside on block order. If two transactions spend the same output and they’re in blocks that are far-enough apart in time, then the one in the later block is the double-spend, as determined by a block order. That block order is determined by the underlying database backend (because the database backend determines the order of block insertion). In the case of MongoDB, the block order is determined from the oplog (operations log) of the replica set storing the blocks, and the order of the oplog is determined by MongoDB’s replication protocol version 1. There’s a nice post about that replication protocol at jepsen.io.

Another constraint is that the transaction isn’t a duplicate of another transaction. (This means that if you want to create some more of an asset, in a second CREATE transaction, you can’t. Either the CREATE transaction is identical to a previous one, which this constraint rules out, or it’s a different CREATE transaction, so it has a different hash, so it has a different transaction ID, so it has a different asset ID.)

The other graph-validity constraints, all specific to TRANSFER transactions, are:

  • All inputs point to outputs that exist on transactions that exist.
  • All inputs point to outputs on valid transactions. (Note the recursion.)
  • All inputs correctly fulfill (satisfy) the crypto-conditions on the outputs that they spend.
  • All inputs point to transactions with the same asset ID, and that asset ID is the same as the asset ID of the TRANSFER transaction itself. (This constraint exists because a TRANSFER transaction can only transfer shares of one asset at a time).
  • The sum of the amounts on the inputs must equal the sum of the amounts on the outputs. This is a graph constraint because getting the amounts on the inputs requires checking output amounts in other transactions.

Tests

We have unit tests to ensure that the above checks work as expected, but of course we may have missed something. We also have integration tests. You can find all the details in the tests directory of the BigchainDB Server repo on GitHub. If you find flaws in the tests, or indeed in any of the above logic, then please contact us or create an issue on GitHub.

More Details

If you want the details about how BigchainDB checks transaction validity (or does anything), you can read the BigchainDB Server code (Python code) on GitHub. You can also ask us on Gitter. The BigchainDB whitepaper is no longer a good source; we plan to update it to reflect our intentions for an upcoming version of BigchainDB. If you’re curious about our intentions for 2017, check out the BigchainDB 2017 Roadmap post.

Be sure to follow this blog to stay up to date on future releases and to get a first-hand look at additions to the system, what has changed and why. Finally, if you’re a developer using BigchainDB, we want to hear from you. Send us an email at contact@bigchaindb.com and tell us your story.

--

--

user advocate, technical writer, infrastructure plumber, information architect, developer