spvwallet

package module
v0.0.0-...-a64e35e Latest Latest
Warning

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

Go to latest
Published: Jul 15, 2016 License: MIT Imports: 32 Imported by: 0

README

spvwallet

Lightweight p2p SPV wallet in Go. It connects directly to the bitcoin p2p network to fetch headers, merkle blocks, and transactions. It mostly utilizes utilities from btcd and is partially based on https://github.com/LightningNetwork/lnd/tree/master/uspv.

Interfaces

These are the used interfaces:

Database

A sqlite implementation is included for testing but you should probably implement your own custom solution. Note there is no encryption in the example db.

  • Keys

The wallet manages an hd keychain (m/0'/change/index/) and stores they keys in this database. Used keys are marked in the db and a lookahead window is maintained to ensure all transactions can be recovered when restoring from seed.

  • Utxos

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

  • Stxos

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

  • 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

This has describes some miscellaneous global state variables of the database, such as what height it has synchronized up to.

Header file (currently headers.bin)

This is currently a bolt db which stores the chain of headers as well as orphans. A separate bucks tracks the tip of the chain. Cumulative work is calculated for each header and enables us to smoothly handle reorgs.

Synchronization overview

At startup addresses are gathered from the DNS seeds and it will maintain one or more connections (determined by the MAX_PEERS constant). It first asks for headers, providing the last known header, then 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 last db height recorded in state. If the height is zero it requests from the last checkpoiint. 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. 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.

TODO

  • Evict confirmed transactions from the db when a reorg is triggered.
  • Cache peer addresses and request more using getaddr. Only use the DNS seeds if we absolutely need to.
  • General refactoring, comments, and clean up.
  • Unit tests.

Documentation

Index

Constants

View Source
const (
	MAX_HEADERS                = 2000
	MAINNET_CHECKPOINT_HEIGHT  = 407232
	TESTNET3_CHECKPOINT_HEIGHT = 895104
)
View Source
const (
	EXTERNAL = 0
	INTERNAL = 1
)
View Source
const (
	CONNECTING = 0
	CONNECTED  = 1
	DEAD       = 2
)
View Source
const (
	PRIOIRTY = 0
	NORMAL   = 1
	ECONOMIC = 2
)
View Source
const (
	SYNCING = 0
	WAITING = 1
	REORG   = 2
)
View Source
const LOOKAHEADWINDOW = 100
View Source
const MAX_PEERS = 10
View Source
const (
	VERSION = 70012
)
View Source
const WALLET_VERSION = "0.1.0"

Variables

View Source
var (
	BKTHeaders  = []byte("Headers")
	BKTChainTip = []byte("ChainTip")
	KEYChainTip = []byte("ChainTip")
)
View Source
var MainnetCheckpoint wire.BlockHeader
View Source
var Testnet3Checkpoint wire.BlockHeader

Functions

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 MakeMerkleParent

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

func NewCoin

func NewCoin(txid []byte, index uint32, value btc.Amount, numConfs int64, scriptPubKey []byte) coinset.Coin

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?

Types

type Blockchain

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

func NewBlockchain

func NewBlockchain(filePath string, params *chaincfg.Params) *Blockchain

func (*Blockchain) CheckHeader

func (b *Blockchain) CheckHeader(header wire.BlockHeader, prevHeader StoredHeader) (bool, uint32)

func (*Blockchain) CommitHeader

func (b *Blockchain) CommitHeader(header wire.BlockHeader) (bool, error)

func (*Blockchain) GetBlockLocatorHashes

func (b *Blockchain) GetBlockLocatorHashes() []*wire.ShaHash

func (*Blockchain) GetEpoch

func (b *Blockchain) GetEpoch() (*wire.BlockHeader, error)

func (*Blockchain) GetNPrevBlockHashes

func (b *Blockchain) GetNPrevBlockHashes(n int) []*wire.ShaHash

type ChainState

type ChainState int

type Coin

type Coin struct {
	TxHash       *wire.ShaHash
	TxIndex      uint32
	TxValue      btc.Amount
	TxNumConfs   int64
	ScriptPubKey []byte
}

func (*Coin) Hash

func (c *Coin) Hash() *wire.ShaHash

func (*Coin) Index

func (c *Coin) Index() uint32

func (*Coin) NumConfs

func (c *Coin) NumConfs() int64

func (*Coin) PkScript

func (c *Coin) PkScript() []byte

func (*Coin) Value

func (c *Coin) Value() btc.Amount

func (*Coin) ValueAge

func (c *Coin) ValueAge() int64

type ConnectionState

type ConnectionState int

type Datastore

type Datastore interface {
	Utxos() Utxos
	Stxos() Stxos
	Txns() Txns
	Keys() Keys
	State() State
}

type FeeLevel

type FeeLevel int

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 HeaderDB

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

func NewHeaderDB

func NewHeaderDB(filePath string) *HeaderDB

func (*HeaderDB) GetBestHeader

func (h *HeaderDB) GetBestHeader() (sh StoredHeader, err error)

func (*HeaderDB) GetPreviousHeader

func (h *HeaderDB) GetPreviousHeader(header wire.BlockHeader) (sh StoredHeader, err error)

func (*HeaderDB) Height

func (h *HeaderDB) Height() (uint32, error)

func (*HeaderDB) Print

func (h *HeaderDB) Print()

func (*HeaderDB) Prune

func (h *HeaderDB) Prune() error

func (*HeaderDB) Put

func (h *HeaderDB) Put(sh StoredHeader, newBestHeader bool) error

type Headers

type Headers interface {
	// Put a block header to the database
	// Total work and height are required to be calculated prior to insertion
	// If this is the new best header, the chain tip should also be updated
	Put(header StoredHeader, newBestHeader bool) error

	// Delete all headers after the MAX_HEADERS most recent
	Prune() error

	// Returns all information about the previous header
	GetPreviousHeader(header wire.BlockHeader) (StoredHeader, error)

	// Retreive the best header from the database
	GetBestHeader() (StoredHeader, error)

	// Get the height of chain
	Height() (uint32, error)

	// Print all headers
	Print()
}

type KeyPath

type KeyPath struct {
	Purpose KeyPurpose
	Index   int
}

type KeyPurpose

type KeyPurpose int

type Keys

type Keys interface {
	// Put a bip32 key to the database
	Put(scriptPubKey []byte, keyPath KeyPath) error

	// Mark the script as used
	MarkKeyAsUsed(scriptPubKey []byte) error

	// Fetch the last index for the given key purpose
	// The bool should state whether the key has been used or not
	GetLastKeyIndex(purpose KeyPurpose) (int, bool, error)

	// Returns the first unused path for the given purpose
	GetPathForScript(scriptPubKey []byte) (KeyPath, error)

	// Get the first unused index for the given purpose
	GetUnused(purpose KeyPurpose) (int, error)

	// Fetch all key paths
	GetAll() ([]KeyPath, error)

	// Get the number of unused keys following the last used key
	// for each key purpose.
	GetLookaheadWindows() map[KeyPurpose]int
}

Keys provides a database interface for the wallet to save key material, track used keys, and manage the look ahead window.

type Peer

type Peer struct {
	WBytes uint64 // total bytes written
	RBytes uint64 // total bytes read

	TS *TxStore // transaction store to write to

	// known good txids and their heights
	OKTxids map[wire.ShaHash]int32
	OKMutex sync.Mutex
	// contains filtered or unexported fields
}

func NewPeer

func NewPeer(remoteNode string, blockchain *Blockchain, inTs *TxStore, params *chaincfg.Params, userAgent string, diconnectChan chan string, downloadPeer bool) (*Peer, error)

OpenPV starts a

func (*Peer) AskForBlocks

func (p *Peer) 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 (*Peer) AskForHeaders

func (p *Peer) AskForHeaders() error

func (*Peer) AskForMerkleBlock

func (p *Peer) AskForMerkleBlock(hash wire.ShaHash)

func (*Peer) AskForTx

func (p *Peer) 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 (*Peer) GetDataHandler

func (p *Peer) GetDataHandler(m *wire.MsgGetData)

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

func (*Peer) HeaderHandler

func (p *Peer) HeaderHandler(m *wire.MsgHeaders)

func (*Peer) IngestBlockAndHeader

func (p *Peer) IngestBlockAndHeader(m *wire.MsgMerkleBlock)

func (*Peer) IngestHeaders

func (p *Peer) 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 (*Peer) IngestMerkleBlock

func (p *Peer) IngestMerkleBlock(m *wire.MsgMerkleBlock)

func (*Peer) InvHandler

func (p *Peer) InvHandler(m *wire.MsgInv)

func (*Peer) NewOutgoingTx

func (p *Peer) NewOutgoingTx(tx *wire.MsgTx) error

func (*Peer) PongBack

func (p *Peer) PongBack(nonce uint64)

func (*Peer) Rebroadcast

func (p *Peer) 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 (*Peer) SendFilter

func (p *Peer) SendFilter(f *bloom.Filter)

func (*Peer) TxHandler

func (p *Peer) 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.

func (*Peer) UpdateFilterAndSend

func (p *Peer) UpdateFilterAndSend()

type SPVWallet

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

func NewSPVWallet

func NewSPVWallet(mnemonic string, params *chaincfg.Params, maxFee uint64, lowFee uint64, mediumFee uint64, highFee uint64, feeApi,
	repoPath string, db Datastore, userAgent string, logger logging.LeveledBackend) *SPVWallet

func (*SPVWallet) Balance

func (w *SPVWallet) Balance() (confirmed, unconfirmed int64)

func (*SPVWallet) CurrencyCode

func (w *SPVWallet) CurrencyCode() string

func (*SPVWallet) CurrentAddress

func (w *SPVWallet) CurrentAddress(purpose KeyPurpose) *btc.AddressPubKeyHash

func (*SPVWallet) MasterPrivateKey

func (w *SPVWallet) MasterPrivateKey() *hd.ExtendedKey

func (*SPVWallet) MasterPublicKey

func (w *SPVWallet) MasterPublicKey() *hd.ExtendedKey

func (*SPVWallet) Params

func (w *SPVWallet) Params() *chaincfg.Params

func (*SPVWallet) Spend

func (w *SPVWallet) Spend(amount int64, addr btc.Address, feeLevel FeeLevel) error

func (*SPVWallet) Start

func (w *SPVWallet) Start()

type State

type State interface {
	// Put a key/value pair to the database
	Put(key, value string) error

	// Get a value given the key
	Get(key string) (string, error)
}

type StoredHeader

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

type Stxo

type Stxo struct {
	Utxo        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.

type Stxos

type Stxos interface {
	// Put a stxo to the database
	Put(stxo Stxo) error

	// Fetch all stxos from the db
	GetAll() ([]Stxo, error)

	// Delete a stxo from the db
	Delete(stxo Stxo) error
}

type TxStore

type TxStore struct {
	Adrs []btcutil.Address

	Param *chaincfg.Params
	// contains filtered or unexported fields
}

func NewTxStore

func NewTxStore(p *chaincfg.Params, db Datastore, masterPrivKey *hd.ExtendedKey) *TxStore

func (*TxStore) GetCurrentKey

func (t *TxStore) GetCurrentKey(purpose KeyPurpose) *hd.ExtendedKey

func (*TxStore) GetDBSyncHeight

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

SyncHeight returns the chain height to which the db has synced

func (*TxStore) GetFreshKey

func (t *TxStore) GetFreshKey(purpose KeyPurpose) *hd.ExtendedKey

func (*TxStore) GetKeyForScript

func (t *TxStore) GetKeyForScript(scriptPubKey []byte) (*hd.ExtendedKey, error)

func (*TxStore) GetKeys

func (t *TxStore) GetKeys() []*hd.ExtendedKey

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) 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) PopulateAdrs

func (ts *TxStore) PopulateAdrs() 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.

type Txns

type Txns interface {

	// Put a new transaction to the database
	Put(txn *wire.MsgTx) error

	// Fetch a tx given it's hash
	Get(txid wire.ShaHash) (*wire.MsgTx, error)

	// Fetch all transactions from the db
	GetAll() ([]*wire.MsgTx, error)

	// Delete a transactions from the db
	Delete(txid *wire.ShaHash) 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
	Value    int64 // higher is better

	ScriptPubkey []byte
}

type Utxos

type Utxos interface {
	// Put a utxo to the database
	Put(utxo Utxo) error

	// Fetch all utxos from the db
	GetAll() ([]Utxo, error)

	// Delete a utxo from the db
	Delete(utxo Utxo) error
}

Directories

Path Synopsis
db

Jump to

Keyboard shortcuts

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