nSPV reference cli client

jl777
5 min readJul 6, 2019

--

Above is the first ever transaction created by nSPV! nSPV went from idea to initial implementation during this last week. With the rest of the komodo team handling all the day to day aspects of things, i was free to code a simple cli reference client with the following rpc calls:

nspv_getinfo

nspv_listunspent address

nspv_notarizations height

nspv_hdrsproof prevheight nextheight

nspv_txproof txid height

nspv_spentinfo txid vout

nspv_login wif

nspv_spend destaddr amount

nspv_broadcast hex

For each of these rpc calls, there is a corresponding p2p message that is sent to a fullnode and the response is waited for. The reference implementation just uses a “synchronous” model, to simplify the codeflow. It is expected that production versions of the superlite client will implement local caching, multithreading, etc. However to demonstrate nSPV, none of that complexity is needed. The fullnode side is fully multithreaded and barring bugs, should be ready for production use, though a lot more error handling would need to be added.

Believe it or not, the above is all that is needed to implement basic wallet functionality. It even has a useful spentinfo, that allows to find out where a utxo has been spent. Due to the blockchain design of only having prev pointers, it is not easy to go forward. the spentinfo allows to traverse utxo spends in a forward fashion.

How did this come together so fast? When the nSPV blog post was published last Sunday, within a day it became the most popular blog post i ever wrote. Usually, when a technical post is made, maybe a couple hundred people read it. nSPV post got nearly 1000 views, in a day! Based on this interest, i started to think a bit more how to implement nSPV. Originally the plan was to just let someone else implement it as there didnt seem to be anything very complex.

On Tuesday, i had the idea of adding some nSPV limitations in the komodod deamon. Since it already handles all the p2p network peer handling and the bitcoin protocol messages, if it is possible to have an nSPV=1 mode to the komodod become a superlite client, then very quickly things can move to adding the nSPV specific features, instead of spending (wasting) time reinventing yet another p2p peering. By the end of Tuesday, the nSPV=1 mode was “working”, by that i mean it started up instantly as it didnt use any local DB, established contact with nSPV fullnodes, behaved as a good peer but just handling a limited subset of them with the addition of getnSPV and nSPV messages.

On Wednesday, the goal was to implement the fullnode side to provide the required data to the superlite side. I got most of the way there, but needed Thursday to get to where most of the required client side was in place.

Friday, it was about debugging the data being returned and writing a simple wallet, for now just a way to spend funds. Had a bit of an unexpected hangup where sendrawtransaction didnt work at all on the superlite side.

Today, i finally got all the basics working, including a way to broadcast from the superlite side. There are still validations that need to be added, but all the required data is already right there on the client side, and of course whatever bug fixes.

The superlite client is not doing anything fancy. When i noticed that the response was very fast between the superlite and the fullnode, i decided to just make a stateless wallet. so for each transaction, the listunspent it issued, then a transaction is constructed, with each vin validated. Assuming the transaction is created properly, you broadcast it.

npsv_login <wif> allows you to spend funds in the corresponding address for 60 seconds. During this time, you do a nspv_spend <address> <amount> and it returns the signed transaction. (this is so much easier than fiddling with rawtransactions!!). At this point you should have a valid transaction that can be broadcast with nspv_broadcast or just the normal sendrawtransaction.

Of course, to do the nSPV validations, there needs to be more work than just making, signing and broadcasting a transaction. For that there are the three special rpc calls:

nspv_notarizations height -> returns the notarizations that are just before and just after this height.

nspv_hdrsproof prevheight nextheight -> returns all the headers between the prevheight and nextheight, along with the rawtx of the two notarizations and their txproofs.

nspv_txproof txid height -> returns the rawtx and txproof for the txid at height

For a given utxo that you want to verify, you first find the notarizations that validate the height of the utxo. Once you get the range of blocks needed, with notarizations at the end and the utxo somewhere in between. Then all the headers can be validated to link to the previous one and both ends be validated as being notarized. The notarization can also be independently validated by checking the signatures in the rawtx for the notarization have valid notary signatures. Finally, with the segment of the blockchain dPoW validated, then the txproof for the utxo is verified to be in the merkleroot using the txproof.

The nspv_getinfo rpc call returns the most recent notarization, and it would be possible to track the longest notarized chain, in the event conflicting data is presented by peers.

For KMD, the interest per utxo is returned, that needs to be validated locally to make sure all the available rewards are claimed.

The one area where there is reliance on peers that a fullnode wont have is getting the list of utxos. It is possible to get an incomplete list and thus not know about all the funds that are available. To minimize this, multiple peers can be polled to create a union of all the reported utxos. Local caching will go a long way toward mitigating this reliance on external nodes to know how many utxos an address has.

The attack vectors are ones of omission, rather than fake data, as all the utxo can be validated to be notarized. The utxo that are not notarized yet are of course subject to be reorganized and care must be taken to not trust non-notarized utxo for large amounts. Proper maintenance of the latest blockheaders will allow to know the valid chain tip to a very accurate degree.

source code is in my nSPV branch https://github.com/jl777/komodo

in the src/komodo_nSPV* files you can see the full implementation.

--

--