uspv

package
v0.0.0-...-2cbe605 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 8, 2016 License: MIT Imports: 26 Imported by: 0

README

uspv - micro-SPV library

The uspv library implements simplified SPV wallet functionality. It connects to full nodes using the standard port 8333 bitcoin protocol, gets headers, uses bloom filters, gets blocks and transactions, and has functions to send and receive coins.

Files

Three files are used by the library:

Key file (currently testkey.hex)

This file contains the secret seed which creates all private keys used by the wallet. It is stored in ascii hexadecimal for easy copying and pasting. If you don't enter a password when prompted, you'll get a warning and the key file will be saved in the clear with no encryption. You shouldn't do that though. When using a password, the key file will be longer and use the scrypt KDF and nacl/secretbox to protect the secret seed.

Header file (currently headers.bin)

This is a file storing all the block headers. Headers are 80 bytes long, so this file's size will always be an even multiple of 80. All blockchain-technology verifications are performed when appending headers to the file. In the case of re-orgs, since it's so quick to get headers, it just truncates a bit and tries again.

Database file (currently utxo.db)

This file more complex. It uses bolt DB to store wallet information needed to send and receive bitcoins. The database file is organized into 4 main "buckets":

  • Utxos ("DuffelBag")

This bucket stores all the utxos. The goal of bitcoin is to get lots of utxos, earning a high score.

  • Stxos ("SpentTxs")

For record keeping, this bucket stores what used to be utxos, but are no longer "u"txos, and are spent outpoints. It references the spending txid.

  • Txns ("Txns")

This bucket stores full serialized transactions which are refenced in the Stxos bucket. These can be used to re-play transactions in the case of re-orgs.

  • State ("MiscState")

This has describes some miscellaneous global state variables of the database, such as what height it has synchronized up to, and how many addresses have been created. (Currently those are the only 2 things stored)

Synchronization overview

Currently uspv only connects to one hard-coded node, as address messages and storage are not yet implemented. It first asks for headers, providing the last known header (writing the genesis header if needed). It loops through asking for headers until it receives an empty header message, which signals that headers are fully synchronized.

After header synchronization is complete, it requests merkle blocks starting at the keyfile birthday. (This is currently hard-coded; add new db key?) Bloom filters are generated for the addresses and utxos known to the wallet. If too many false positives are received, a new filter is generated and sent. (This happens fairly often because the filter exponentially saturates with false positives when using BloomUpdateAll.) Once the merkle blocks have been received up to the header height, the wallet is considered synchronized and it will listen for new inv messages from the remote node. An inv message describing a block will trigger a request for headers, starting the same synchronization process of headers then merkle-blocks.

TODO

There's still quite a bit left, though most of it hopefully won't be too hard.

Problems / still to do:

  • Only connects to one node, and that node is hard-coded.
  • Re-orgs affect only headers, and don't evict confirmed transactions.
  • Double spends are not detected; Double spent txs will stay at height 0.
  • Tx creation and signing is still very rudimentary.
  • There may be wire-protocol irregularities which can get it kicked off.

Hopefully I can get most of that list deleted soon.

(Now sanity checks txs, but can't check sigs... because it's SPV. Right.)

Later functionality to implement:

  • "Desktop Mode" SPV, or "Unfiltered" SPV or some other name

This would be a mode where uspv doesn't use bloom filters and request merkle blocks, but instead grabs everything in the block and discards most of the data. This prevents nodes from learning about your utxo set. To further enhance this, it should connect to multiple nodes and relay txs and inv messages to blend in.

  • Ironman SPV

Never request txs. Only merkleBlocks (or in above mode, blocks). No unconfirmed transactions are presented to the user, which makes a whole lot of sense as with unconfirmed SPV transactions you're relying completely on the honesty of the reporting node.

Documentation

Index

Constants

View Source
const (

	// version hardcoded for now, probably ok...?
	VERSION = 70011
)

Variables

View Source
var (
	BKTUtxos = []byte("DuffelBag") // leave the rest to collect interest
	BKTStxos = []byte("SpentTxs")  // for bookkeeping
	BKTTxns  = []byte("Txns")      // all txs we care about, for replays
	BKTState = []byte("MiscState") // last state of DB
	// these are in the state bucket
	KEYNumKeys   = []byte("NumKeys")   // number of keys used
	KEYTipHeight = []byte("TipHeight") // height synced to
)

Functions

func BlockRootOK

func BlockRootOK(blk wire.MsgBlock) bool

BlockRootOK checks that all the txs in the block match the merkle root. Only checks merkle root; it doesn't look at txs themselves.

func CheckDoubleSpends

func CheckDoubleSpends(
	argTx *wire.MsgTx, txs []*wire.MsgTx) ([]*wire.ShaHash, error)

GetDoubleSpends takes a transaction and compares it with all transactions in the db. It returns a slice of all txids in the db which are double spent by the received tx.

func CheckHeader

func CheckHeader(r io.ReadSeeker, height int32, p *chaincfg.Params) bool

func CheckRange

func CheckRange(r io.ReadSeeker, first, last int32, p *chaincfg.Params) bool
checkrange verifies a range of headers.  it checks their proof of work,

difficulty adjustments, and that they all link in to each other properly. This is the only blockchain technology in the whole code base. Returns false if anything bad happens. Returns true if the range checks out with no errors.

func LoadKeyFromFileArg

func LoadKeyFromFileArg(filename string, pass []byte) (*[32]byte, error)

LoadKeyFromFileArg opens the file and returns the key. If the key is unencrypted it will ignore the password argument.

func LoadKeyFromFileInteractive

func LoadKeyFromFileInteractive(filename string) (*[32]byte, error)

LoadKeyFromFileInteractive opens the file 'filename' and presents a keyboard prompt for the passphrase to decrypt it. It returns the key if decryption works, or errors out.

func MakeMerkleParent

func MakeMerkleParent(left *wire.ShaHash, right *wire.ShaHash) *wire.ShaHash

func OutPointsEqual

func OutPointsEqual(a, b wire.OutPoint) bool

need this because before I was comparing pointers maybe? so they were the same outpoint but stored in 2 places so false negative?

func ReadKeyFileToECPriv

func ReadKeyFileToECPriv(
	filename string, p *chaincfg.Params) (*hdkeychain.ExtendedKey, error)

ReadKeyFileToECPriv returns an extendedkey from a file. If there's no file there, it'll make one. If there's a password needed, it'll prompt for one. One stop function.

func SaveKeyToFileArg

func SaveKeyToFileArg(filename string, priv32 *[32]byte, pass []byte) error

saves a 32 byte key to a file, encrypting with pass. if pass is nil or zero length, doesn't encrypt and just saves in hex.

func SaveKeyToFileInteractive

func SaveKeyToFileInteractive(filename string, priv32 *[32]byte) error

saves a 32 byte key to file, prompting for passphrase. if user enters empty passphrase (hits enter twice), will be saved in the clear.

func TxToString

func TxToString(tx *wire.MsgTx) string

TxToString prints out some info about a transaction. for testing / debugging

Types

type HashAndHeight

type HashAndHeight struct {
	// contains filtered or unexported fields
}

HashAndHeight is needed instead of just height in case a fullnode responds abnormally (?) by sending out of order merkleblocks. we cache a merkleroot:height pair in the queue so we don't have to look them up from the disk. Also used when inv messages indicate blocks so we can add the header and parse the txs in one request instead of requesting headers first.

func NewRootAndHeight

func NewRootAndHeight(b wire.ShaHash, h int32) (hah HashAndHeight)

NewRootAndHeight saves like 2 lines.

type MyAdr

type MyAdr struct {
	PkhAdr btcutil.Address
	KeyIdx uint32 // index for private key needed to sign / spend

}

type SPVCon

type SPVCon struct {

	// Enhanced SPV modes for users who have outgrown easy mode SPV
	// but have not yet graduated to full nodes.
	HardMode bool // hard mode doesn't use filters.
	Ironman  bool // ironman only gets blocks, never requests txs.

	WBytes uint64 // total bytes written
	RBytes uint64 // total bytes read

	TS *TxStore // transaction store to write to
	// contains filtered or unexported fields
}

func OpenSPV

func OpenSPV(remoteNode string, hfn, dbfn string,
	inTs *TxStore, hard bool, iron bool, p *chaincfg.Params) (SPVCon, error)

OpenPV starts a

func (*SPVCon) AskForBlocks

func (s *SPVCon) AskForBlocks() error

AskForMerkBlocks requests blocks from current to last right now this asks for 1 block per getData message. Maybe it's faster to ask for many in a each message?

func (*SPVCon) AskForHeaders

func (s *SPVCon) AskForHeaders() error

func (*SPVCon) AskForOneBlock

func (s *SPVCon) AskForOneBlock(h int32) error

func (*SPVCon) AskForTx

func (s *SPVCon) AskForTx(txid wire.ShaHash)

AskForTx requests a tx we heard about from an inv message. It's one at a time but should be fast enough. I don't like this function because SPV shouldn't even ask...

func (*SPVCon) GetDataHandler

func (s *SPVCon) GetDataHandler(m *wire.MsgGetData)

GetDataHandler responds to requests for tx data, which happen after advertising our txs via an inv message

func (*SPVCon) HeaderHandler

func (s *SPVCon) HeaderHandler(m *wire.MsgHeaders)

func (*SPVCon) IngestBlock

func (s *SPVCon) IngestBlock(m *wire.MsgBlock)

IngestBlock is like IngestMerkleBlock but aralphic different enough that it's better to have 2 separate functions

func (*SPVCon) IngestHeaders

func (s *SPVCon) IngestHeaders(m *wire.MsgHeaders) (bool, error)

IngestHeaders takes in a bunch of headers and appends them to the local header file, checking that they fit. If there's no headers, it assumes we're done and returns false. If it worked it assumes there's more to request and returns true.

func (*SPVCon) IngestMerkleBlock

func (s *SPVCon) IngestMerkleBlock(m *wire.MsgMerkleBlock)

func (*SPVCon) InvHandler

func (s *SPVCon) InvHandler(m *wire.MsgInv)

func (*SPVCon) NewOutgoingTx

func (s *SPVCon) NewOutgoingTx(tx *wire.MsgTx) error

func (*SPVCon) PongBack

func (s *SPVCon) PongBack(nonce uint64)

func (*SPVCon) Rebroadcast

func (s *SPVCon) Rebroadcast()

Rebroadcast sends an inv message of all the unconfirmed txs the db is aware of. This is called after every sync. Only txids so hopefully not too annoying for nodes.

func (*SPVCon) RemoveHeaders

func (s *SPVCon) RemoveHeaders(r int32) error

func (*SPVCon) SendFilter

func (s *SPVCon) SendFilter(f *bloom.Filter)

func (*SPVCon) TxHandler

func (s *SPVCon) TxHandler(m *wire.MsgTx)

TxHandler takes in transaction messages that come in from either a request after an inv message or after a merkle block message.

type Stxo

type Stxo struct {
	Utxo                     // when it used to be a utxo
	SpendHeight int32        // height at which it met its demise
	SpendTxid   wire.ShaHash // the tx that consumed it
}

Stxo is a utxo that has moved on.

func StxoFromBytes

func StxoFromBytes(b []byte) (Stxo, error)

StxoFromBytes turns bytes into a Stxo.

func (*Stxo) ToBytes

func (s *Stxo) ToBytes() ([]byte, error)

ToBytes turns an Stxo into some bytes. outpoint txid, outpoint idx, height, key idx, amt, spendheight, spendtxid

type TxStore

type TxStore struct {
	OKTxids map[wire.ShaHash]int32 // known good txids and their heights
	OKMutex sync.Mutex

	Adrs    []MyAdr  // endeavouring to acquire capital
	StateDB *bolt.DB // place to write all this down

	// Params live here, not SCon
	Param *chaincfg.Params // network parameters (testnet3, testnetL)
	// contains filtered or unexported fields
}

func NewTxStore

func NewTxStore(rootkey *hdkeychain.ExtendedKey, p *chaincfg.Params) TxStore

func (*TxStore) AddTxid

func (t *TxStore) AddTxid(txid *wire.ShaHash, height int32) error

add txid of interest

func (*TxStore) GetAllStxos

func (ts *TxStore) GetAllStxos() ([]*Stxo, error)

GetAllStxos returns a slice of all stxos known to the db. empty slice is OK.

func (*TxStore) GetAllTxs

func (ts *TxStore) GetAllTxs() ([]*wire.MsgTx, error)

GetTx takes a txid and returns the transaction. If we have it.

func (*TxStore) GetAllUtxos

func (ts *TxStore) GetAllUtxos() ([]*Utxo, error)

GetAllUtxos returns a slice of all utxos known to the db. empty slice is OK.

func (*TxStore) GetDBSyncHeight

func (ts *TxStore) GetDBSyncHeight() (int32, error)

SyncHeight returns the chain height to which the db has synced

func (*TxStore) GetPendingInv

func (ts *TxStore) GetPendingInv() (*wire.MsgInv, error)

GetPendingInv returns an inv message containing all txs known to the db which are at height 0 (not known to be confirmed). This can be useful on startup or to rebroadcast unconfirmed txs.

func (*TxStore) GetTx

func (ts *TxStore) GetTx(txid *wire.ShaHash) (*wire.MsgTx, error)

GetTx takes a txid and returns the transaction. If we have it.

func (*TxStore) GimmeFilter

func (t *TxStore) GimmeFilter() (*bloom.Filter, error)

... or I'm gonna fade away

func (*TxStore) Ingest

func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error)

Ingest puts a tx into the DB atomically. This can result in a gain, a loss, or no result. Gain or loss in satoshis is returned.

func (*TxStore) NewAdr

func (ts *TxStore) NewAdr() (*btcutil.AddressPubKeyHash, error)

NewAdr creates a new, never before seen address, and increments the DB counter as well as putting it in the ram Adrs store, and returns it

func (*TxStore) OpenDB

func (ts *TxStore) OpenDB(filename string) error

func (*TxStore) PopulateAdrs

func (ts *TxStore) PopulateAdrs(lastKey uint32) error

PopulateAdrs just puts a bunch of adrs in ram; it doesn't touch the DB

func (*TxStore) SetDBSyncHeight

func (ts *TxStore) SetDBSyncHeight(n int32) error

SetDBSyncHeight sets sync height of the db, indicated the latest block of which it has ingested all the transactions.

func (*TxStore) SignThis

func (t *TxStore) SignThis(tx *wire.MsgTx) error

type Utxo

type Utxo struct {
	Op wire.OutPoint // where

	// all the info needed to spend
	AtHeight int32  // block height where this tx was confirmed, 0 for unconf
	KeyIdx   uint32 // index for private key needed to sign / spend
	Value    int64  // higher is better

}

func UtxoFromBytes

func UtxoFromBytes(b []byte) (Utxo, error)

UtxoFromBytes turns bytes into a Utxo. Note it wants the txid and outindex in the first 36 bytes, which isn't stored that way in the boldDB, but can be easily appended.

func (*Utxo) ToBytes

func (u *Utxo) ToBytes() ([]byte, error)

ToBytes turns a Utxo into some bytes. note that the txid is the first 36 bytes and in our use cases will be stripped off, but is left here for other applications

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL