The definitive guide to NANO's blocks
Normally, the node manages all the cryptographic stuff for you. But if you want to run a high-availability service, you should consider doing some of this on your own, since the reference implementation of wallets tends to have some issues from time to time, and it's less agile for large-scale services.
Lightwallets use this way too due to security concerns - they manage sensitive keys locally and only use the node for generic ledger management.
Implementing your own wallet logic is not that hard after all, if you have the right cryptographic libraries for your programming language. Then, nodes will be interchangable because they handle the networking part only, and not store any user-specific data.
This guide is work in progress, so it will only cover a portion of the complete process, but the main goal of this guide is to show how blocks are assembled. You may also read developers.nano.org and https://github.com/nanocurrency/raiblocks/wiki/Universal-Blocks-Specification for tips and specifications. The new block concept introduced in spring 2018 is called "universal blocks" but the block type itself is called "state blocks", because the head block ("frontier") represent all current properties, unlike deprecated block types.
The most important rule with state blocks
The purpose of a block is determined by the difference ("delta") between two successive blocks' balances.
Rising balance means, the block is receiving funds
Falling balance means, the block is sending funds
equal balance means, the block is changing representatives (or is an 'epoch' snapshot block)
The main rason why a receive block is necessary: Only the account owner is supposed to alter his chain, so no one can mess up the order of your chain (forks or DOS attacks).
Visual overview of how to generate an outgoing transaction
Errata: Mistook some "byte" for "bit"
The signing is not actually done with the string, but with the byte representation of the hexadecimal string
Annotations
Once the frontier and balance values are known, you don't need to re-query the node after each transaction, because you can use internal variables to append new blocks on the chain. This only works reliably if you don't use the same seed on multiple machines.
Instead if libraries, you can use the block_create command to hash and sign the block in one go, but it requires you to either send the private key along the request, or store the keys on the node.
You can re-use use the account's existing representative (from account_info) instead of overwriting it every time with the wallet's default representative.
When sending the block in the `process` command, it needs to be in a very specific format or it'll get rejected. Avoid line breaks!
Internally, Nano balances uses integers of up to 128bit size. You'll have to multiply and divide balances with 1030 in order to make them human-readable. By default, wallets ignore sends smaller than one milionth Nano.
For computing the new block hash, you have to use Blake2b with an output size of 32 bytes (often represented by 64 hex chars). The output size normally is specified in the blake2binit() method. For the hashing itself, you have 2 options: concatenating the inputs before hashing, or successively blake2b_update() before you blake2b_final(). See, concatenating the input in a single string will have the exact same effect as using blake2b_update() on each subset of input data.
To get the signature, you have to use a modified Ed25519 signing library. Normally, Ed25519 uses an older SHA algorithm for hashing, but nano replaced that part with blake2b hashing. Output length is 64 bytes (= 512 bit = 128 chars hex). You sign with the private key (not with the seed). The seed-to-private-key mechanism uses blake2b hasing too, it is not part of this tutorial.
What's different when generating incoming transactions
The receiving side looks similar. But when creating receiving blocks,
- you will have to get a list of pending blocks e.g. with accounts_pending
- the balance
will be added instead of subtracted.
- link
will contain the corresponding send block's hash. The process command will accept both variants.
In case it's the first block in the receiving account's chain:
- previous
is 0 and
- work
is created from the public key of the receiving account
If you only want to change a representative
- balance
is the same as previous block's balance
- link
is 0
How a depositing mechanism could be implemented
You can either pool funds, or leave each user's funds on a separate account. The advantage of pooling is that you can store the biggest part of the funds on a cold wallet. Separate accounts have the advantage that they allow more concurrency due to PoW-precaching, and a user simply can't withdraw more than he has.
Either way, you'll have distinct deposit addresses for each user, and you'll have to listen for incoming transactions on these accounts. The most scalable way to do the monitoring is to use callback. Just monitor the node's block callback for "is_send" flag being true, and "link_as_account" being one of your deposit accounts. The json-string would look like this:
{ "account": "xrb_3hff58z7txjbo9ijzjxowzm3z9yppi14z39dng1kw1a1yojq8nxrx7jse4n5", "hash": "5F54038C5CB23C4A9F595786611804E16070F52518FDCFE84F224742F3473B27", "block": "{\n \"type\": \"state\",\n \"account\": \"xrb_3hff58z7txjbo9ijzjxowzm3z9yppi14z39dng1kw1a1yojq8nxrx7jse4n5\",\n \"previous\": \"A1E536E22EFB96882761F2E66C24A9CF921BE1697B49102165516A6D1C7C7828\",\n \"representative\": \"xrb_1esmod167m7o38gw4rzt7hz67oq6itejpt4yocrfywujbpatd711cjew8gjj\",\n \"balance\": \"6771200000000000000000000000000000\",\n \"link\": \"01275D55A9C5D3C02522F74F554E1082EB7C27BCB65B5E808AB6F0B3F76D0BB5\",\n \"link_as_account\": \"xrb_11b9doctmjgmr1kk7xthco9331qdhimusfkudt1aofqiphupt4xokkxa97d4\",\n \"signature\": \"A2FAAD365F0EC44110B80870BD5F9836AACCFC2E721D236143C05B753CA36A4107BB44F5922F6819893FAB79EFA1DF632F3208EBA12AF734E6440563FC37790E\",\n \"work\": \"8d9473fcad6e5149\"\n}\n", "amount": "2300000000000000000000000000000", "is_send": "true" }Callback normally is triggered when the quorum is met - you can raise the quorum in config.json for added fund security. Unfortunately, callback misses some blocks, so you need a fallback option utilizing the "accounts_pending" command, which periodically To avoid double deposits, just keep record of every block hash that was already credited, and before you credit user funds, check if that hash was already credited. You only need to create a receiving block in order to be able to spend funds. Once the sending block has reached quorum among the representatives, the money transfer is de-facto irreversible. You can create the receiving blocks whenever you like, but of course before you actually need to access the funds for new transfers. If you chose a pooling option, you can decide whether to forward funds from the depositing account to either your hotwallet or coldwallet. You can also run multiple hotwallet if you have slow PoW on your server - you can then pre-cache PoW for each of them and round-robin outgoing transfers so there's less waiting time in crowded times.
Variables used in the example
TBD
Technical details about hashing and signing
The signing algorithm is Ed25519 with blake2b for hashing instead of the default SHA. If your results don't match, try a different digest size of the blake2b.
WiP
Further reading
https://github.com/nanocurrency/nano-node/wiki/Universal-Blocks-Specification Block specifications