uspv

package
v0.0.0-...-57c8b1d Latest Latest
Warning

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

Go to latest
Published: Jun 12, 2019 License: MIT Imports: 18 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.

C4:1Lb99OK

Documentation

Index

Constants

View Source
const (

	// VERSION hardcoded for now, probably ok...?
	// 70012 is for segnet... make this an init var?
	VERSION = 70015
)

Variables

This section is empty.

Functions

func CheckHeaderChain

func CheckHeaderChain(
	r io.ReadSeeker, inHeaders []*wire.BlockHeader,
	p *coinparam.Params) (int32, error)

CheckHeaderChain takes in the headers message and sees if they all validate. This function also needs read access to the previous headers. Does not deal with re-orgs; assumes new headers link to tip returns true if *all* headers are cool, false if there is any problem Note we don't know what the height is, just the relative height. returnin nil means it worked returns an int32 usually 0, but if there's a reorg, shows what height to reorg back to before adding on the headers

func FindHeader

func FindHeader(r io.ReadSeeker, hdr wire.BlockHeader) (int32, error)

FindHeader will try to find where the header you give it is. it runs backwards to find it and gives up after 1000 headers

func IP4

func IP4(ipAddress string) bool

IP4 ...

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 chainhash.Hash, h int32) (hah HashAndHeight)

NewRootAndHeight saves like 2 lines.

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.
	ProxyURL string // Optionally the URL of a SOCKS5 proxy to use

	OKTxids map[chainhash.Hash]int32 // known good txids and their heights
	OKMutex sync.Mutex

	// TrackingAdrs and OPs are slices of addresses and outpoints to watch for.
	// Using struct{} saves a byte of RAM but is ugly so I'll use bool.
	TrackingAdrs    map[[20]byte]bool
	TrackingAdrsMtx sync.Mutex

	TrackingOPs    map[wire.OutPoint]bool
	TrackingOPsMtx sync.Mutex

	// TxMap is an in-memory map of all the Txs the SPVCon knows about
	TxMap map[chainhash.Hash]*wire.MsgTx

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

	Param *coinparam.Params // network parameters (testnet3, segnet, etc)

	Synced bool
	// contains filtered or unexported fields
}

SPVCon is a SPV connection to a coin daemon.

func (*SPVCon) AskForBlocks

func (s *SPVCon) AskForBlocks() error

AskForBlocks 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 each message?

func (*SPVCon) AskForHeaders

func (s *SPVCon) AskForHeaders() error

AskForHeaders ...

func (*SPVCon) Connect

func (s *SPVCon) Connect() error

Connect dials out and connects to full nodes. Calls GetListOfNodes to get the list of nodes if the user has specified a YupString. Else, moves on to dial the node to see if its up and establishes a connection followed by Handshake() which sends out wire messages, checks for version string to prevent spam, etc.

func (*SPVCon) DialNode

func (s *SPVCon) DialNode(listOfNodes []string) error

DialNode receives a list of node ips and then tries to connect to them one by one.

func (*SPVCon) GetHeaderAtHeight

func (s *SPVCon) GetHeaderAtHeight(h int32) (*wire.BlockHeader, error)

GetHeaderAtHeight gives back a header at the specified height

func (*SPVCon) GetHeaderByBlockHash

func (s *SPVCon) GetHeaderByBlockHash(hash *chainhash.Hash) (*wire.BlockHeader, error)

func (*SPVCon) GetHeaderTipHeight

func (s *SPVCon) GetHeaderTipHeight() int32

GetHeaderTipHeight gives back a header at the specified height.

func (*SPVCon) GetListOfNodes

func (s *SPVCon) GetListOfNodes() ([]string, error)

GetListOfNodes contacts all DNSSeeds for the coin specified and then contacts each one of them in order to receive a list of ips and then returns a combined list

func (*SPVCon) Handshake

func (s *SPVCon) Handshake(listOfNodes []string) error

Handshake ...

func (*SPVCon) HeaderHandler

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

HeaderHandler ...

func (*SPVCon) IndexHeaders

func (s *SPVCon) IndexHeaders() error

func (*SPVCon) IngestHeaders

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

IngestHeaders takes in a bunch of headers, checks them, and if they're OK, appends them to the local header file. If there are no headers, it assumes we're done and returns false. Otherwise it assumes there's more to request and returns true.

func (*SPVCon) InvHandler

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

InvHandler ...

func (*SPVCon) PongBack

func (s *SPVCon) PongBack(nonce uint64)

PongBack ...

func (*SPVCon) Start

func (s *SPVCon) Start(params *coinparam.Params) error

Jump to

Keyboard shortcuts

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