package lnwallet import ( "errors" "fmt" "sync" "github.com/roasbeef/btcd/btcec" "github.com/roasbeef/btcd/chaincfg/chainhash" "github.com/roasbeef/btcd/wire" "github.com/roasbeef/btcutil" ) // ErrNotMine is an error denoting that a WalletController instance is unable // to spend a specified output. var ErrNotMine = errors.New("the passed output doesn't belong to the wallet") // AddressType is a enum-like type which denotes the possible address types // WalletController supports. type AddressType uint8 const ( // UnknownAddressType represents an output with an unknown or non-standard // script. UnknownAddressType AddressType = iota // WitnessPubKey represents a p2wkh address. WitnessPubKey // NestedWitnessPubKey represents a p2sh output which is itself a // nested p2wkh output. NestedWitnessPubKey // PubKeyHash represents a regular p2pkh output. PubKeyHash ) // ErrDoubleSpend is returned from PublishTransaction in case the // tx being published is spending an output spent by a conflicting // transaction. var ErrDoubleSpend = errors.New("Transaction rejected: output already spent") // Utxo is an unspent output denoted by its outpoint, and output value of the // original output. type Utxo struct { AddressType AddressType Value btcutil.Amount PkScript []byte RedeemScript []byte WitnessScript []byte wire.OutPoint } // TransactionDetail describes a transaction with either inputs which belong to // the wallet, or has outputs that pay to the wallet. type TransactionDetail struct { // Hash is the transaction hash of the transaction. Hash chainhash.Hash // Value is the net value of this transaction (in satoshis) from the // PoV of the wallet. If this transaction purely spends from the // wallet's funds, then this value will be negative. Similarly, if this // transaction credits the wallet, then this value will be positive. Value btcutil.Amount // NumConfirmations is the number of confirmations this transaction // has. If the transaction is unconfirmed, then this value will be // zero. NumConfirmations int32 // BlockHeight is the hash of the block which includes this // transaction. Unconfirmed transactions will have a nil value for this // field. BlockHash *chainhash.Hash // BlockHeight is the height of the block including this transaction. // Unconfirmed transaction will show a height of zero. BlockHeight int32 // Timestamp is the unix timestamp of the block including this // transaction. If the transaction is unconfirmed, then this will be a // timestamp of txn creation. Timestamp int64 // TotalFees is the total fee in satoshis paid by this transaction. TotalFees int64 // DestAddresses are the destinations for a transaction DestAddresses []btcutil.Address } // TransactionSubscription is an interface which describes an object capable of // receiving notifications of new transaction related to the underlying wallet. // TODO(roasbeef): add balance updates? type TransactionSubscription interface { // ConfirmedTransactions returns a channel which will be sent on as new // relevant transactions are confirmed. ConfirmedTransactions() chan *TransactionDetail // UnconfirmedTransactions returns a channel which will be sent on as // new relevant transactions are seen within the network. UnconfirmedTransactions() chan *TransactionDetail // Cancel finalizes the subscription, cleaning up any resources // allocated. Cancel() } // WalletController defines an abstract interface for controlling a local Pure // Go wallet, a local or remote wallet via an RPC mechanism, or possibly even // a daemon assisted hardware wallet. This interface serves the purpose of // allowing LightningWallet to be seamlessly compatible with several wallets // such as: uspv, btcwallet, Bitcoin Core, Electrum, etc. This interface then // serves as a "base wallet", with Lightning Network awareness taking place at // a "higher" level of abstraction. Essentially, an overlay wallet. // Implementors of this interface must closely adhere to the documented // behavior of all interface methods in order to ensure identical behavior // across all concrete implementations. type WalletController interface { // FetchInputInfo queries for the WalletController's knowledge of the // passed outpoint. If the base wallet determines this output is under // its control, then the original txout should be returned. Otherwise, // a non-nil error value of ErrNotMine should be returned instead. FetchInputInfo(prevOut *wire.OutPoint) (*wire.TxOut, error) // ConfirmedBalance returns the sum of all the wallet's unspent outputs // that have at least confs confirmations. If confs is set to zero, // then all unspent outputs, including those currently in the mempool // will be included in the final sum. ConfirmedBalance(confs int32, witness bool) (btcutil.Amount, error) // NewAddress returns the next external or internal address for the // wallet dictated by the value of the `change` parameter. If change is // true, then an internal address should be used, otherwise an external // address should be returned. The type of address returned is dictated // by the wallet's capabilities, and may be of type: p2sh, p2pkh, // p2wkh, p2wsh, etc. NewAddress(addrType AddressType, change bool) (btcutil.Address, error) // GetPrivKey retrieves the underlying private key associated with the // passed address. If the wallet is unable to locate this private key // due to the address not being under control of the wallet, then an // error should be returned. GetPrivKey(a btcutil.Address) (*btcec.PrivateKey, error) // SendOutputs funds, signs, and broadcasts a Bitcoin transaction // paying out to the specified outputs. In the case the wallet has // insufficient funds, or the outputs are non-standard, an error should // be returned. This method also takes the target fee expressed in // sat/vbyte that should be used when crafting the transaction. SendOutputs(outputs []*wire.TxOut, feeRate SatPerVByte) (*chainhash.Hash, error) // ListUnspentWitness returns all unspent outputs which are version 0 // witness programs. The 'confirms' parameter indicates the minimum // number of confirmations an output needs in order to be returned by // this method. Passing -1 as 'confirms' indicates that even // unconfirmed outputs should be returned. ListUnspentWitness(confirms int32) ([]*Utxo, error) // ListTransactionDetails returns a list of all transactions which are // relevant to the wallet. ListTransactionDetails() ([]*TransactionDetail, error) // LockOutpoint marks an outpoint as locked meaning it will no longer // be deemed as eligible for coin selection. Locking outputs are // utilized in order to avoid race conditions when selecting inputs for // usage when funding a channel. LockOutpoint(o wire.OutPoint) // UnlockOutpoint unlocks an previously locked output, marking it // eligible for coin selection. UnlockOutpoint(o wire.OutPoint) // PublishTransaction performs cursory validation (dust checks, etc), // then finally broadcasts the passed transaction to the Bitcoin network. // If the transaction is rejected because it is conflicting with an // already known transaction, ErrDoubleSpend is returned. If the // transaction is already known (published already), no error will be // returned. Other error returned depends on the currently active chain // backend. PublishTransaction(tx *wire.MsgTx) error // SubscribeTransactions returns a TransactionSubscription client which // is capable of receiving async notifications as new transactions // related to the wallet are seen within the network, or found in // blocks. // // NOTE: a non-nil error should be returned if notifications aren't // supported. // // TODO(roasbeef): make distinct interface? SubscribeTransactions() (TransactionSubscription, error) // IsSynced returns a boolean indicating if from the PoV of the wallet, // it has fully synced to the current best block in the main chain. // It also returns an int64 indicating the timestamp of the best block // known to the wallet, expressed in Unix epoch time IsSynced() (bool, int64, error) // Start initializes the wallet, making any necessary connections, // starting up required goroutines etc. Start() error // Stop signals the wallet for shutdown. Shutdown may entail closing // any active sockets, database handles, stopping goroutines, etc. Stop() error // BackEnd returns a name for the wallet's backing chain service, // which could be e.g. btcd, bitcoind, neutrino, or another consensus // service. BackEnd() string } // BlockChainIO is a dedicated source which will be used to obtain queries // related to the current state of the blockchain. The data returned by each of // the defined methods within this interface should always return the most up // to date data possible. // // TODO(roasbeef): move to diff package perhaps? // TODO(roasbeef): move publish txn here? type BlockChainIO interface { // GetBestBlock returns the current height and block hash of the valid // most-work chain the implementation is aware of. GetBestBlock() (*chainhash.Hash, int32, error) // GetUtxo attempts to return the passed outpoint if it's still a // member of the utxo set. The passed height hint should be the "birth // height" of the passed outpoint. In the case that the output is in // the UTXO set, then the output corresponding to that output is // returned. Otherwise, a non-nil error will be returned. GetUtxo(op *wire.OutPoint, heightHint uint32) (*wire.TxOut, error) // GetBlockHash returns the hash of the block in the best blockchain // at the given height. GetBlockHash(blockHeight int64) (*chainhash.Hash, error) // GetBlock returns the block in the main chain identified by the given // hash. GetBlock(blockHash *chainhash.Hash) (*wire.MsgBlock, error) } // Signer represents an abstract object capable of generating raw signatures as // well as full complete input scripts given a valid SignDescriptor and // transaction. This interface fully abstracts away signing paving the way for // Signer implementations such as hardware wallets, hardware tokens, HSM's, or // simply a regular wallet. type Signer interface { // SignOutputRaw generates a signature for the passed transaction // according to the data within the passed SignDescriptor. // // NOTE: The resulting signature should be void of a sighash byte. SignOutputRaw(tx *wire.MsgTx, signDesc *SignDescriptor) ([]byte, error) // ComputeInputScript generates a complete InputIndex for the passed // transaction with the signature as defined within the passed // SignDescriptor. This method should be capable of generating the // proper input script for both regular p2wkh output and p2wkh outputs // nested within a regular p2sh output. // // NOTE: This method will ignore any tweak parameters set within the // passed SignDescriptor as it assumes a set of typical script // templates (p2wkh, np2wkh, etc). ComputeInputScript(tx *wire.MsgTx, signDesc *SignDescriptor) (*InputScript, error) } // MessageSigner represents an abstract object capable of signing arbitrary // messages. The capabilities of this interface are used to sign announcements // to the network, or just arbitrary messages that leverage the wallet's keys // to attest to some message. type MessageSigner interface { // SignMessage attempts to sign a target message with the private key // that corresponds to the passed public key. If the target private key // is unable to be found, then an error will be returned. The actual // digest signed is the double SHA-256 of the passed message. SignMessage(pubKey *btcec.PublicKey, msg []byte) (*btcec.Signature, error) } // PreimageCache is an interface that represents a global cache for preimages. // We'll utilize this cache to communicate the discovery of new preimages // across sub-systems. type PreimageCache interface { // LookupPreimage attempts to look up a preimage according to its hash. // If found, the preimage is returned along with true for the second // argument. Otherwise, it'll return false. LookupPreimage(hash []byte) ([]byte, bool) // AddPreimage attempts to add a new preimage to the global cache. If // successful a nil error will be returned. AddPreimage(preimage []byte) error } // WalletDriver represents a "driver" for a particular concrete // WalletController implementation. A driver is identified by a globally unique // string identifier along with a 'New()' method which is responsible for // initializing a particular WalletController concrete implementation. type WalletDriver struct { // WalletType is a string which uniquely identifies the // WalletController that this driver, drives. WalletType string // New creates a new instance of a concrete WalletController // implementation given a variadic set up arguments. The function takes // a variadic number of interface parameters in order to provide // initialization flexibility, thereby accommodating several potential // WalletController implementations. New func(args ...interface{}) (WalletController, error) // BackEnds returns a list of available chain service drivers for the // wallet driver. This could be e.g. bitcoind, btcd, neutrino, etc. BackEnds func() []string } var ( wallets = make(map[string]*WalletDriver) registerMtx sync.Mutex ) // RegisteredWallets returns a slice of all currently registered notifiers. // // NOTE: This function is safe for concurrent access. func RegisteredWallets() []*WalletDriver { registerMtx.Lock() defer registerMtx.Unlock() registeredWallets := make([]*WalletDriver, 0, len(wallets)) for _, wallet := range wallets { registeredWallets = append(registeredWallets, wallet) } return registeredWallets } // RegisterWallet registers a WalletDriver which is capable of driving a // concrete WalletController interface. In the case that this driver has // already been registered, an error is returned. // // NOTE: This function is safe for concurrent access. func RegisterWallet(driver *WalletDriver) error { registerMtx.Lock() defer registerMtx.Unlock() if _, ok := wallets[driver.WalletType]; ok { return fmt.Errorf("wallet already registered") } wallets[driver.WalletType] = driver return nil } // SupportedWallets returns a slice of strings that represents the wallet // drivers that have been registered and are therefore supported. // // NOTE: This function is safe for concurrent access. func SupportedWallets() []string { registerMtx.Lock() defer registerMtx.Unlock() supportedWallets := make([]string, 0, len(wallets)) for walletName := range wallets { supportedWallets = append(supportedWallets, walletName) } return supportedWallets }