wtxmgr

package
v0.0.0-...-dfc2b99 Latest Latest
Warning

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

Go to latest
Published: Jan 23, 2024 License: ISC Imports: 20 Imported by: 0

README

wtxmgr

Package wtxmgr provides storage and spend tracking of wallet transactions and their relevant input and outputs.

Feature overview

  • Storage for relevant wallet transactions
  • Ability to mark outputs as controlled by wallet
  • Unspent transaction output index
  • Balance tracking
  • Automatic spend tracking for transaction inserts and removals
  • Double spend detection and correction after blockchain reorgs
  • Scalable design:
    • Utilizes similar prefixes to allow cursor iteration over relevant transaction inputs and outputs
    • Programmatically detectable errors, including encapsulation of errors from packages it relies on
    • Operates under its own walletdb namespace

License

Package wtxmgr is licensed under the Copyfree ISC License.

Documentation

Overview

Package wtxmgr provides an implementation of a transaction database handling spend tracking for a bitcoin wallet. Its primary purpose is to save transactions with outputs spendable with wallet keys and transactions that are signed by wallet keys in memory, handle spend tracking for unspent outputs and newly-inserted transactions, and report the spendable balance from each unspent transaction output. It uses walletdb as the backend for storing the serialized transaction objects in buckets.

Transaction outputs which are spendable by wallet keys are called credits (because they credit to a wallet's total spendable balance). Transaction inputs which spend previously-inserted credits are called debits (because they debit from the wallet's spendable balance).

Spend tracking is mostly automatic. When a new transaction is inserted, if it spends from any unspent credits, they are automatically marked spent by the new transaction, and each input which spent a credit is marked as a debit. However, transaction outputs of inserted transactions must manually marked as credits, as this package has no knowledge of wallet keys or addresses, and therefore cannot determine which outputs may be spent.

Details regarding individual transactions and their credits and debits may be queried either by just a transaction hash, or by hash and block. When querying for just a transaction hash, the most recent transaction with a matching hash will be queried. However, because transaction hashes may collide with other transaction hashes, methods to query for specific transactions in the chain (or unmined) are provided as well.

Index

Constants

View Source
const (
	// TxLabelLimit is the length limit we impose on transaction labels.
	TxLabelLimit = 500

	// DefaultLockDuration is the default duration used to lock outputs.
	DefaultLockDuration = 10 * time.Minute
)

Variables

View Source
var (
	// ErrDatabase indicates an error with the underlying database.  When
	// this error code is set, the Err field of the Error will be
	// set to the underlying error returned from the database.
	ErrDatabase = Err.Code("ErrDatabase")

	// ErrData describes an error where data stored in the transaction
	// database is incorrect.  This may be due to missing values, values of
	// wrong sizes, or data from different buckets that is inconsistent with
	// itself.  Recovering from an ErrData requires rebuilding all
	// transaction history or manual database surgery.  If the failure was
	// not due to data corruption, this error category indicates a
	// programming error in this package.
	ErrData = Err.Code("ErrData")

	// ErrInput describes an error where the variables passed into this
	// function by the caller are obviously incorrect.  Examples include
	// passing transactions which do not serialize, or attempting to insert
	// a credit at an index for which no transaction output exists.
	ErrInput = Err.Code("ErrInput")

	// ErrAlreadyExists describes an error where creating the store cannot
	// continue because a store already exists in the namespace.
	ErrAlreadyExists = Err.Code("ErrAlreadyExists")

	// ErrNoExists describes an error where the store cannot be opened due to
	// it not already existing in the namespace.  This error should be
	// handled by creating a new store.
	ErrNoExists = Err.Code("ErrNoExists")

	// ErrNeedsUpgrade describes an error during store opening where the
	// database contains an older version of the store.
	ErrNeedsUpgrade = Err.Code("ErrNeedsUpgrade")

	// ErrUnknownVersion describes an error where the store already exists
	// but the database version is newer than latest version known to this
	// software.  This likely indicates an outdated binary.
	ErrUnknownVersion = Err.Code("ErrUnknownVersion")
)

These constants are used to identify a specific Error.

View Source
var (
	// ErrEmptyLabel is returned when an attempt to write a label that is
	// empty is made.
	ErrEmptyLabel = Err.CodeWithDetail("ErrEmptyLabel", "empty transaction label not allowed")

	// ErrLabelTooLong is returned when an attempt to write a label that is
	// to long is made.
	ErrLabelTooLong = Err.CodeWithDetail("ErrLabelTooLong", "transaction label exceeds limit")

	// ErrNoLabelBucket is returned when the bucket holding optional
	// transaction labels is not found. This occurs when no transactions
	// have been labelled yet.
	ErrNoLabelBucket = Err.CodeWithDetail("ErrNoLabelBucket", "labels bucket does not exist")

	// ErrTxLabelNotFound is returned when no label is found for a
	// transaction hash.
	ErrTxLabelNotFound = Err.CodeWithDetail("ErrTxLabelNotFound",
		"label for transaction not found")

	// ErrUnknownOutput is an error returned when an output not known to the
	// wallet is attempted to be locked.
	ErrUnknownOutput = Err.CodeWithDetail("ErrUnknownOutput", "unknown output")

	// ErrOutputAlreadyLocked is an error returned when an output has
	// already been locked to a different ID.
	ErrOutputAlreadyLocked = Err.CodeWithDetail("ErrOutputAlreadyLocked",
		"output already locked")

	// ErrOutputUnlockNotAllowed is an error returned when an output unlock
	// is attempted with a different ID than the one which locked it.
	ErrOutputUnlockNotAllowed = Err.CodeWithDetail("ErrOutputUnlockNotAllowed",
		"output unlock not alowed")
)
View Source
var Err er.ErrorType = er.NewErrorType("wtxmgr.Err")

Err identifies a category of error.

Functions

func AddressForOutPoint

func AddressForOutPoint(ns walletdb.ReadBucket, prevOut *wire.OutPoint) ([]byte, er.R)

func Create

func Create(ns walletdb.ReadWriteBucket) er.R

Create creates a new persistent transaction store in the walletdb namespace. Creating the store when one already exists in this namespace will error with ErrAlreadyExists.

func DependencySort

func DependencySort(txs map[chainhash.Hash]*wire.MsgTx) []*wire.MsgTx

DependencySort topologically sorts a set of transactions by their dependency order. It is implemented using Kahn's algorithm.

func DeserializeLabel

func DeserializeLabel(v []byte) (string, er.R)

DeserializeLabel reads a deserializes a length-value encoded label from the byte array provided.

func DropTransactionHistory

func DropTransactionHistory(ns walletdb.ReadWriteBucket) er.R

DropTransactionHistory is a migration that attempts to recreate the transaction store with a clean state.

func ExtendUnspent

func ExtendUnspent(ns walletdb.ReadWriteBucket) er.R

func FetchTxLabel

func FetchTxLabel(ns walletdb.ReadBucket, txid chainhash.Hash) (string, er.R)

FetchTxLabel reads a transaction label from the tx labels bucket. If a label with 0 length was written, we return an error, since this is unexpected.

func PutTxLabel

func PutTxLabel(labelBucket walletdb.ReadWriteBucket, txid chainhash.Hash,
	label string) er.R

PutTxLabel writes a label for a tx to the bucket provided. Note that it does not perform any validation on the label provided, or check whether there is an existing label for the txid.

Types

type BlockMeta

type BlockMeta struct {
	dbstructs.Block
	Time time.Time
}

BlockMeta contains the unique identification for a block and any metadata pertaining to the block. At the moment, this additional metadata only includes the block time from the block header.

type Credit

type Credit struct {
	wire.OutPoint
	BlockMeta
	Amount       btcutil.Amount
	PkScript     []byte
	Received     time.Time
	FromCoinBase bool
}

Credit is the type representing a transaction output which was spent or is still spendable by wallet. A UTXO is an unspent Credit, but not all Credits are UTXOs.

type CreditRecord

type CreditRecord struct {
	Amount btcutil.Amount
	Index  uint32
	Spent  bool
	Change bool
}

CreditRecord contains metadata regarding a transaction credit for a known transaction. Further details may be looked up by indexing a wire.MsgTx.TxOut with the Index field.

type DbNsVote2

type DbNsVote2 struct {
	// True this the address has indicated it's candidacy in the most recent vote
	IsCandidate bool `json:"is_candidate,omitempty"`
	// Who the address voted for, empty string if they voted for themselves or nobody
	VoteFor string `json:"vote_for,omitempty"`
	// The transaction ID of the vote
	VoteTxid string `json:"vote_txid,omitempty"`
	// The number of the block where this address voted
	VoteBlock int32 `json:"vote_block,omitempty"`
}

func FetchAddressNsVote

func FetchAddressNsVote(
	ns walletdb.ReadBucket,
	address btcutil.Address,
) (*DbNsVote2, er.R)

type DebitRecord

type DebitRecord struct {
	Amount btcutil.Amount
	Index  uint32
}

DebitRecord contains metadata regarding a transaction debit for a known transaction. Further details may be looked up by indexing a wire.MsgTx.TxIn with the Index field.

type LockID

type LockID [32]byte

LockID represents a unique context-specific ID assigned to an output lock.

type MigrationManager

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

MigrationManager is an implementation of the migration.Manager interface that will be used to handle migrations for the address manager. It exposes the necessary parameters required to successfully perform migrations.

func NewMigrationManager

func NewMigrationManager(ns walletdb.ReadWriteBucket) *MigrationManager

NewMigrationManager creates a new migration manager for the transaction manager. The given bucket should reflect the top-level bucket in which all of the transaction manager's data is contained within.

func (*MigrationManager) CurrentVersion

func (m *MigrationManager) CurrentVersion(ns walletdb.ReadBucket) (uint32, er.R)

CurrentVersion returns the current version of the service's database.

NOTE: This method is part of the migration.Manager interface.

func (*MigrationManager) Name

func (m *MigrationManager) Name() string

Name returns the name of the service we'll be attempting to upgrade.

NOTE: This method is part of the migration.Manager interface.

func (*MigrationManager) Namespace

func (m *MigrationManager) Namespace() walletdb.ReadWriteBucket

Namespace returns the top-level bucket of the service.

NOTE: This method is part of the migration.Manager interface.

func (*MigrationManager) SetVersion

func (m *MigrationManager) SetVersion(ns walletdb.ReadWriteBucket,
	version uint32) er.R

SetVersion sets the version of the service's database.

NOTE: This method is part of the migration.Manager interface.

func (*MigrationManager) Versions

func (m *MigrationManager) Versions() []migration.Version

Versions returns all of the available database versions of the service.

NOTE: This method is part of the migration.Manager interface.

type Store

type Store struct {

	// Event callbacks.  These execute in the same goroutine as the wtxmgr
	// caller.
	NotifyUnspent func(hash *chainhash.Hash, index uint32)
	// contains filtered or unexported fields
}

Store implements a transaction store for storing and managing wallet transactions.

func Open

func Open(ns walletdb.ReadBucket, chainParams *chaincfg.Params) (*Store, er.R)

Open opens the wallet transaction store from a walletdb namespace. If the store does not exist, ErrNoExist is returned.

func (*Store) AddCredit

func (s *Store) AddCredit(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta, index uint32, change bool) er.R

func (*Store) AddCredit2

func (s *Store) AddCredit2(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta, index uint32, change bool) (bool, er.R)

AddCredit marks a transaction record as containing a transaction output spendable by wallet. The output is added unspent, and is marked spent when a new transaction spending the output is inserted into the store.

TODO(jrick): This should not be necessary. Instead, pass the indexes that are known to contain credits when a transaction or merkleblock is inserted into the store.

func (*Store) Balance

func (s *Store) Balance(ns walletdb.ReadBucket, minConf int32, syncHeight int32) (btcutil.Amount, er.R)

func (*Store) DeleteExpiredLockedOutputs

func (s *Store) DeleteExpiredLockedOutputs(ns walletdb.ReadWriteBucket) er.R

DeleteExpiredLockedOutputs iterates through all existing locked outputs and deletes those which have already expired.

func (*Store) ForEachUnspentOutput

func (s *Store) ForEachUnspentOutput(
	ns walletdb.ReadBucket,
	beginKey []byte,
	addrs map[string]struct{},
	visitor func(key []byte, c *dbstructs.Unspent) er.R,
) (int, er.R)

ForEachUnspentOutput runs the visitor over each unspent output (in undefined order) Any error type other than wtxmgr.Err or er.LoopBreak will be wrapped as wtxmgr.ErrDatabase

beginKey can be used to skip entries for pagination, however beware there is no guarantee of never receiving duplicate entries in different pages. In particular, all unconfirmed outputs will be received after the final confirmed output with no regard for what you specify as the beginKey.

func (*Store) GetUnspentOutputs

func (s *Store) GetUnspentOutputs(ns walletdb.ReadBucket) ([]dbstructs.Unspent, er.R)

GetUnspentOutputs returns all unspent received transaction outputs. The order is undefined.

func (*Store) InsertTx

func (s *Store) InsertTx(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) er.R

It is bad to scan the unmined credits and automatically add them, we should be adding anything which is relevant to any of our addresses, but this is used in a ton of tests so we'd better support it.

func (*Store) InsertTx2

func (s *Store) InsertTx2(ns walletdb.ReadWriteBucket, rec *TxRecord, block *BlockMeta) er.R

InsertTx records a transaction as belonging to a wallet's transaction history. If block is nil, the transaction is considered unspent, and the transaction's index must be unset.

func (*Store) LockOutput

func (s *Store) LockOutput(ns walletdb.ReadWriteBucket, id LockID,
	op wire.OutPoint) (time.Time, er.R)

LockOutput locks an output to the given ID, preventing it from being available for coin selection. The absolute time of the lock's expiration is returned. The expiration of the lock can be extended by successive invocations of this call.

Outputs can be unlocked before their expiration through `UnlockOutput`. Otherwise, they are unlocked lazily through calls which iterate through all known outputs, e.g., `Balance`, `UnspentOutputs`.

If the output is not known, ErrUnknownOutput is returned. If the output has already been locked to a different ID, then ErrOutputAlreadyLocked is returned.

func (*Store) PreviousPkScripts

func (s *Store) PreviousPkScripts(ns walletdb.ReadBucket, rec *TxRecord, block *dbstructs.Block) ([][]byte, er.R)

PreviousPkScripts returns a slice of previous output scripts for each credit output this transaction record debits from.

func (*Store) PutTxLabel

func (s *Store) PutTxLabel(ns walletdb.ReadWriteBucket, txid chainhash.Hash,
	label string) er.R

PutTxLabel validates transaction labels and writes them to disk if they are non-zero and within the label length limit. The entry is keyed by the transaction hash: [0:32] Transaction hash (32 bytes)

The label itself is written to disk in length value format: [0:2] Label length [2: +len] Label

func (*Store) RangeTransactions

func (s *Store) RangeTransactions(ns walletdb.ReadBucket, begin, end int32,
	f func([]TxDetails) (bool, er.R)) er.R

RangeTransactions runs the function f on all transaction details between blocks on the best chain over the height range [begin,end]. The special height -1 may be used to also include unmined transactions. If the end height comes before the begin height, blocks are iterated in reverse order and unmined transactions (if any) are processed first.

The function f may return an error which, if non-nil, is propagated to the caller. Additionally, a boolean return value allows exiting the function early without reading any additional transactions early when true.

All calls to f are guaranteed to be passed a slice with more than zero elements. The slice may be reused for multiple blocks, so it is not safe to use it after the loop iteration it was acquired.

func (*Store) RemoveUnminedTx

func (s *Store) RemoveUnminedTx(ns walletdb.ReadWriteBucket, rec *TxRecord) er.R

RemoveUnminedTx attempts to remove an unmined transaction from the transaction store. This is to be used in the scenario that a transaction that we attempt to rebroadcast, turns out to double spend one of our existing inputs. This function we remove the conflicting transaction identified by the tx record, and also recursively remove all transactions that depend on it.

func (*Store) RollbackOne

func (s *Store) RollbackOne(ns walletdb.ReadWriteBucket, height int32) er.R

func (*Store) TxDetails

func (s *Store) TxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash) (*TxDetails, er.R)

TxDetails looks up all recorded details regarding a transaction with some hash. In case of a hash collision, the most recent transaction with a matching hash is returned.

Not finding a transaction with this hash is not an error. In this case, a nil TxDetails is returned.

func (*Store) TxLabel

func (s *Store) TxLabel(ns walletdb.ReadBucket, txHash chainhash.Hash) (string,
	er.R)

TxLabel looks up a transaction label for the txHash provided. If the store has no labels in it, or the specific txHash does not have a label, an empty string and no error are returned.

func (*Store) UniqueTxDetails

func (s *Store) UniqueTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash,
	block *dbstructs.Block) (*TxDetails, er.R)

UniqueTxDetails looks up all recorded details for a transaction recorded mined in some particular block, or an unmined transaction if block is nil.

Not finding a transaction with this hash from this block is not an error. In this case, a nil TxDetails is returned.

func (*Store) UnlockOutput

func (s *Store) UnlockOutput(ns walletdb.ReadWriteBucket, id LockID,
	op wire.OutPoint) er.R

UnlockOutput unlocks an output, allowing it to be available for coin selection if it remains unspent. The ID should match the one used to originally lock the output.

func (*Store) UnminedTxHashes

func (s *Store) UnminedTxHashes(ns walletdb.ReadBucket) ([]*chainhash.Hash, er.R)

UnminedTxHashes returns the hashes of all transactions not known to have been mined in a block.

func (*Store) UnminedTxs

func (s *Store) UnminedTxs(ns walletdb.ReadBucket) ([]*wire.MsgTx, er.R)

UnminedTxs returns the underlying transactions for all unmined transactions which are not known to have been mined in a block. Transactions are guaranteed to be sorted by their dependency order.

type TxDetails

type TxDetails struct {
	TxRecord
	Block   BlockMeta
	Credits []CreditRecord
	Debits  []DebitRecord
	Label   string
}

TxDetails is intended to provide callers with access to rich details regarding a relevant transaction and which inputs and outputs are credit or debits.

type TxRecord

type TxRecord struct {
	MsgTx        wire.MsgTx
	Hash         chainhash.Hash
	Received     time.Time
	SerializedTx []byte // Optional: may be nil
}

TxRecord represents a transaction managed by the Store.

func NewTxRecord

func NewTxRecord(serializedTx []byte, received time.Time) (*TxRecord, er.R)

NewTxRecord creates a new transaction record that may be inserted into the store. It uses memoization to save the transaction hash and the serialized transaction.

func NewTxRecordFromMsgTx

func NewTxRecordFromMsgTx(msgTx *wire.MsgTx, received time.Time) (*TxRecord, er.R)

NewTxRecordFromMsgTx creates a new transaction record that may be inserted into the store.

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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