2013-09-09 20:14:57 +02:00
|
|
|
/*
|
2015-05-01 19:10:23 +02:00
|
|
|
* Copyright (c) 2013-2015 The btcsuite developers
|
2013-09-09 20:14:57 +02:00
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
2015-04-02 20:13:38 +02:00
|
|
|
package wallet
|
2013-09-09 20:14:57 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2014-06-13 05:28:30 +02:00
|
|
|
badrand "math/rand"
|
2014-05-23 04:16:50 +02:00
|
|
|
"sort"
|
|
|
|
"time"
|
|
|
|
|
2015-01-30 23:30:16 +01:00
|
|
|
"github.com/btcsuite/btcd/blockchain"
|
2015-04-02 20:13:38 +02:00
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
2015-01-30 19:31:29 +01:00
|
|
|
"github.com/btcsuite/btcd/txscript"
|
2015-02-05 22:41:38 +01:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
2015-01-15 17:48:58 +01:00
|
|
|
"github.com/btcsuite/btcutil"
|
2014-10-29 07:43:29 +01:00
|
|
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
2015-04-06 21:03:24 +02:00
|
|
|
"github.com/btcsuite/btcwallet/wtxmgr"
|
2013-09-09 20:14:57 +02:00
|
|
|
)
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
const (
|
|
|
|
// All transactions have 4 bytes for version, 4 bytes of locktime,
|
|
|
|
// and 2 varints for the number of inputs and outputs.
|
|
|
|
txOverheadEstimate = 4 + 4 + 1 + 1
|
|
|
|
|
|
|
|
// A best case signature script to redeem a P2PKH output for a
|
|
|
|
// compressed pubkey has 70 bytes of the smallest possible DER signature
|
|
|
|
// (with no leading 0 bytes for R and S), 33 bytes of serialized pubkey,
|
|
|
|
// and data push opcodes for both, plus one byte for the hash type flag
|
|
|
|
// appended to the end of the signature.
|
|
|
|
sigScriptEstimate = 1 + 70 + 1 + 33 + 1
|
|
|
|
|
|
|
|
// A best case tx input serialization cost is 32 bytes of sha, 4 bytes
|
|
|
|
// of output index, 4 bytes of sequnce, and the estimated signature
|
|
|
|
// script size.
|
|
|
|
txInEstimate = 32 + 4 + 4 + sigScriptEstimate
|
|
|
|
|
|
|
|
// A P2PKH pkScript contains the following bytes:
|
|
|
|
// - OP_DUP
|
|
|
|
// - OP_HASH160
|
|
|
|
// - OP_DATA_20 + 20 bytes of pubkey hash
|
|
|
|
// - OP_EQUALVERIFY
|
|
|
|
// - OP_CHECKSIG
|
|
|
|
pkScriptEstimate = 1 + 1 + 1 + 20 + 1 + 1
|
|
|
|
|
|
|
|
// A best case tx output serialization cost is 8 bytes of value, one
|
|
|
|
// byte of varint, and the pkScript size.
|
|
|
|
txOutEstimate = 8 + 1 + pkScriptEstimate
|
|
|
|
)
|
|
|
|
|
|
|
|
func estimateTxSize(numInputs, numOutputs int) int {
|
|
|
|
return txOverheadEstimate + txInEstimate*numInputs + txOutEstimate*numOutputs
|
|
|
|
}
|
|
|
|
|
|
|
|
func feeForSize(incr btcutil.Amount, sz int) btcutil.Amount {
|
|
|
|
return btcutil.Amount(1+sz/1000) * incr
|
|
|
|
}
|
|
|
|
|
|
|
|
// InsufficientFundsError represents an error where there are not enough
|
2013-09-09 20:14:57 +02:00
|
|
|
// funds from unspent tx outputs for a wallet to create a transaction.
|
2014-06-20 18:58:21 +02:00
|
|
|
// This may be caused by not enough inputs for all of the desired total
|
|
|
|
// transaction output amount, or due to
|
2014-09-25 19:27:18 +02:00
|
|
|
type InsufficientFundsError struct {
|
2014-06-20 18:58:21 +02:00
|
|
|
in, out, fee btcutil.Amount
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error satisifies the builtin error interface.
|
2014-09-25 19:27:18 +02:00
|
|
|
func (e InsufficientFundsError) Error() string {
|
2014-06-20 18:58:21 +02:00
|
|
|
total := e.out + e.fee
|
|
|
|
if e.fee == 0 {
|
|
|
|
return fmt.Sprintf("insufficient funds: transaction requires "+
|
|
|
|
"%s input but only %v spendable", total, e.in)
|
|
|
|
}
|
|
|
|
return fmt.Sprintf("insufficient funds: transaction requires %s input "+
|
|
|
|
"(%v output + %v fee) but only %v spendable", total, e.out,
|
|
|
|
e.fee, e.in)
|
|
|
|
}
|
2013-09-09 20:14:57 +02:00
|
|
|
|
2015-02-06 06:12:48 +01:00
|
|
|
// ErrUnsupportedTransactionType represents an error where a transaction
|
|
|
|
// cannot be signed as the API only supports spending P2PKH outputs.
|
|
|
|
var ErrUnsupportedTransactionType = errors.New("Only P2PKH transactions are supported")
|
2014-07-29 00:12:01 +02:00
|
|
|
|
2013-11-12 18:01:32 +01:00
|
|
|
// ErrNonPositiveAmount represents an error where a bitcoin amount is
|
|
|
|
// not positive (either negative, or zero).
|
|
|
|
var ErrNonPositiveAmount = errors.New("amount is not positive")
|
|
|
|
|
|
|
|
// ErrNegativeFee represents an error where a fee is erroneously
|
|
|
|
// negative.
|
|
|
|
var ErrNegativeFee = errors.New("fee is negative")
|
|
|
|
|
2014-12-16 03:36:38 +01:00
|
|
|
// defaultFeeIncrement is the default minimum transation fee (0.00001 BTC,
|
2013-12-04 01:22:47 +01:00
|
|
|
// measured in satoshis) added to transactions requiring a fee.
|
2014-12-16 03:36:38 +01:00
|
|
|
const defaultFeeIncrement = 1e3
|
2013-10-07 21:14:39 +02:00
|
|
|
|
2015-02-06 06:12:48 +01:00
|
|
|
// CreatedTx holds the state of a newly-created transaction and the change
|
|
|
|
// output (if one was added).
|
2013-11-05 00:58:41 +01:00
|
|
|
type CreatedTx struct {
|
2015-04-06 21:03:24 +02:00
|
|
|
MsgTx *wire.MsgTx
|
2015-04-02 20:13:38 +02:00
|
|
|
ChangeAddr btcutil.Address
|
|
|
|
ChangeIndex int // negative if no change
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
Fee btcutil.Amount
|
2013-10-24 00:23:20 +02:00
|
|
|
}
|
|
|
|
|
2013-09-09 20:14:57 +02:00
|
|
|
// ByAmount defines the methods needed to satisify sort.Interface to
|
|
|
|
// sort a slice of Utxos by their amount.
|
2015-04-06 21:03:24 +02:00
|
|
|
type ByAmount []wtxmgr.Credit
|
2013-09-09 20:14:57 +02:00
|
|
|
|
2014-05-23 04:16:50 +02:00
|
|
|
func (u ByAmount) Len() int { return len(u) }
|
2015-04-06 21:03:24 +02:00
|
|
|
func (u ByAmount) Less(i, j int) bool { return u[i].Amount < u[j].Amount }
|
2014-05-23 04:16:50 +02:00
|
|
|
func (u ByAmount) Swap(i, j int) { u[i], u[j] = u[j], u[i] }
|
2013-09-09 20:14:57 +02:00
|
|
|
|
|
|
|
// txToPairs creates a raw transaction sending the amounts for each
|
|
|
|
// address/amount pair and fee to each address and the miner. minconf
|
|
|
|
// specifies the minimum number of confirmations required before an
|
|
|
|
// unspent output is eligible for spending. Leftover input funds not sent
|
|
|
|
// to addr or as a fee for the miner are sent to a newly generated
|
2014-09-25 19:27:18 +02:00
|
|
|
// address. InsufficientFundsError is returned if there are not enough
|
|
|
|
// eligible unspent outputs to create the transaction.
|
2015-04-06 21:03:24 +02:00
|
|
|
func (w *Wallet) txToPairs(pairs map[string]btcutil.Amount, account uint32, minconf int32) (*CreatedTx, error) {
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
|
2014-10-29 07:43:29 +01:00
|
|
|
// Address manager must be unlocked to compose transaction. Grab
|
|
|
|
// the unlock if possible (to prevent future unlocks), or return the
|
|
|
|
// error if already locked.
|
Remove account support, fix races on btcd connect.
This commit is the result of several big changes being made to the
wallet. In particular, the "handshake" (initial sync to the chain
server) was quite racy and required proper synchronization. To make
fixing this race easier, several other changes were made to the
internal wallet data structures and much of the RPC server ended up
being rewritten.
First, all account support has been removed. The previous Account
struct has been replaced with a Wallet structure, which includes a
keystore for saving keys, and a txstore for storing relevant
transactions. This decision has been made since it is the opinion of
myself and other developers that bitcoind accounts are fundamentally
broken (as accounts implemented by bitcoind support both arbitrary
address groupings as well as moving balances between accounts -- these
are fundamentally incompatible features), and since a BIP0032 keystore
is soon planned to be implemented (at which point, "accounts" can
return as HD extended keys). With the keystore handling the grouping
of related keys, there is no reason have many different Account
structs, and the AccountManager has been removed as well. All RPC
handlers that take an account option will only work with "" (the
default account) or "*" if the RPC allows specifying all accounts.
Second, much of the RPC server has been cleaned up. The global
variables for the RPC server and chain server client have been moved
to part of the rpcServer struct, and the handlers for each RPC method
that are looked up change depending on which components have been set.
Passthrough requests are also no longer handled specially, but when
the chain server is set, a handler to perform the passthrough will be
returned if the method is not otherwise a wallet RPC. The
notification system for websocket clients has also been rewritten so
wallet components can send notifications through channels, rather than
requiring direct access to the RPC server itself, or worse still,
sending directly to a websocket client's send channel. In the future,
this will enable proper registration of notifications, rather than
unsolicited broadcasts to every connected websocket client (see
issue #84).
Finally, and the main reason why much of this cleanup was necessary,
the races during intial sync with the chain server have been fixed.
Previously, when the 'Handshake' was run, a rescan would occur which
would perform modifications to Account data structures as
notifications were received. Synchronization was provided with a
single binary semaphore which serialized all access to wallet and
account data. However, the Handshake itself was not able to run with
this lock (or else notifications would block), and many data races
would occur as both notifications were being handled. If GOMAXPROCS
was ever increased beyond 1, btcwallet would always immediately crash
due to invalid addresses caused by the data races on startup. To fix
this, the single lock for all wallet access has been replaced with
mutexes for both the keystore and txstore. Handling of btcd
notifications and client requests may now occur simultaneously.
GOMAXPROCS has also been set to the number of logical CPUs at the
beginning of main, since with the data races fixed, there's no reason
to prevent the extra parallelism gained by increasing it.
Closes #78.
Closes #101.
Closes #110.
2014-07-09 05:17:38 +02:00
|
|
|
heldUnlock, err := w.HoldUnlock()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2014-03-20 17:21:52 +01:00
|
|
|
}
|
Remove account support, fix races on btcd connect.
This commit is the result of several big changes being made to the
wallet. In particular, the "handshake" (initial sync to the chain
server) was quite racy and required proper synchronization. To make
fixing this race easier, several other changes were made to the
internal wallet data structures and much of the RPC server ended up
being rewritten.
First, all account support has been removed. The previous Account
struct has been replaced with a Wallet structure, which includes a
keystore for saving keys, and a txstore for storing relevant
transactions. This decision has been made since it is the opinion of
myself and other developers that bitcoind accounts are fundamentally
broken (as accounts implemented by bitcoind support both arbitrary
address groupings as well as moving balances between accounts -- these
are fundamentally incompatible features), and since a BIP0032 keystore
is soon planned to be implemented (at which point, "accounts" can
return as HD extended keys). With the keystore handling the grouping
of related keys, there is no reason have many different Account
structs, and the AccountManager has been removed as well. All RPC
handlers that take an account option will only work with "" (the
default account) or "*" if the RPC allows specifying all accounts.
Second, much of the RPC server has been cleaned up. The global
variables for the RPC server and chain server client have been moved
to part of the rpcServer struct, and the handlers for each RPC method
that are looked up change depending on which components have been set.
Passthrough requests are also no longer handled specially, but when
the chain server is set, a handler to perform the passthrough will be
returned if the method is not otherwise a wallet RPC. The
notification system for websocket clients has also been rewritten so
wallet components can send notifications through channels, rather than
requiring direct access to the RPC server itself, or worse still,
sending directly to a websocket client's send channel. In the future,
this will enable proper registration of notifications, rather than
unsolicited broadcasts to every connected websocket client (see
issue #84).
Finally, and the main reason why much of this cleanup was necessary,
the races during intial sync with the chain server have been fixed.
Previously, when the 'Handshake' was run, a rescan would occur which
would perform modifications to Account data structures as
notifications were received. Synchronization was provided with a
single binary semaphore which serialized all access to wallet and
account data. However, the Handshake itself was not able to run with
this lock (or else notifications would block), and many data races
would occur as both notifications were being handled. If GOMAXPROCS
was ever increased beyond 1, btcwallet would always immediately crash
due to invalid addresses caused by the data races on startup. To fix
this, the single lock for all wallet access has been replaced with
mutexes for both the keystore and txstore. Handling of btcd
notifications and client requests may now occur simultaneously.
GOMAXPROCS has also been set to the number of logical CPUs at the
beginning of main, since with the data races fixed, there's no reason
to prevent the extra parallelism gained by increasing it.
Closes #78.
Closes #101.
Closes #110.
2014-07-09 05:17:38 +02:00
|
|
|
defer heldUnlock.Release()
|
2014-03-20 17:21:52 +01:00
|
|
|
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
chainClient, err := w.requireChainClient()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2013-12-04 01:22:47 +01:00
|
|
|
// Get current block's height and hash.
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
bs, err := chainClient.BlockStamp()
|
2013-12-04 01:22:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-12-12 09:54:26 +01:00
|
|
|
eligible, err := w.findEligibleOutputs(account, minconf, bs)
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-06-13 21:52:49 +02:00
|
|
|
|
2015-04-02 20:13:38 +02:00
|
|
|
return createTx(eligible, pairs, bs, w.FeeIncrement, w.Manager, account, w.NewChangeAddress, w.chainParams, w.DisallowFree)
|
2014-09-25 19:27:18 +02:00
|
|
|
}
|
2013-11-21 17:57:28 +01:00
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
// createTx selects inputs (from the given slice of eligible utxos)
|
|
|
|
// whose amount are sufficient to fulfil all the desired outputs plus
|
|
|
|
// the mining fee. It then creates and returns a CreatedTx containing
|
|
|
|
// the selected inputs and the given outputs, validating it (using
|
|
|
|
// validateMsgTx) as well.
|
2015-04-06 21:03:24 +02:00
|
|
|
func createTx(eligible []wtxmgr.Credit,
|
2015-04-02 20:13:38 +02:00
|
|
|
outputs map[string]btcutil.Amount, bs *waddrmgr.BlockStamp,
|
|
|
|
feeIncrement btcutil.Amount, mgr *waddrmgr.Manager, account uint32,
|
|
|
|
changeAddress func(account uint32) (btcutil.Address, error),
|
|
|
|
chainParams *chaincfg.Params, disallowFree bool) (*CreatedTx, error) {
|
2014-07-29 00:12:01 +02:00
|
|
|
|
2015-02-05 22:41:38 +01:00
|
|
|
msgtx := wire.NewMsgTx()
|
2015-04-02 20:13:38 +02:00
|
|
|
minAmount, err := addOutputs(msgtx, outputs, chainParams)
|
2014-09-25 19:27:18 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2013-12-04 01:22:47 +01:00
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
// Sort eligible inputs so that we first pick the ones with highest
|
|
|
|
// amount, thus reducing number of inputs.
|
|
|
|
sort.Sort(sort.Reverse(ByAmount(eligible)))
|
|
|
|
|
|
|
|
// Start by adding enough inputs to cover for the total amount of all
|
|
|
|
// desired outputs.
|
2015-04-06 21:03:24 +02:00
|
|
|
var input wtxmgr.Credit
|
|
|
|
var inputs []wtxmgr.Credit
|
2014-09-25 19:27:18 +02:00
|
|
|
totalAdded := btcutil.Amount(0)
|
|
|
|
for totalAdded < minAmount {
|
|
|
|
if len(eligible) == 0 {
|
|
|
|
return nil, InsufficientFundsError{totalAdded, minAmount, 0}
|
Perform smarter UTXO tracking.
This change fixes many issues with the tracking of unspent transaction
outputs. First, notifications for when UTXOs arse spent are now
requested from btcd, and when spent, will be removed from the
UtxoStore.
Second, when transactions are created, the unconfirmed (not yet in a
block) Utxo (with block height -1 and zeroed block hash) is added to
the wallet's UtxoStore. Notifications for when this UTXO is spent are
also requested from btcd. After the tx appears in a block, because
the UTXO has a pkScript to be spent by another owned wallet address, a
notification with the UTXO will be sent to btcwallet. We already
store the unconfirmed UTXO, so at this point the actual block height
and hash are filled in.
Finally, when calculating the balance, if confirmations is zero,
unconfirmed UTXOs (block height -1) will be included in the balance.
Otherwise, they are ignored.
2013-10-22 15:55:53 +02:00
|
|
|
}
|
2014-09-25 19:27:18 +02:00
|
|
|
input, eligible = eligible[0], eligible[1:]
|
|
|
|
inputs = append(inputs, input)
|
2015-04-06 21:03:24 +02:00
|
|
|
msgtx.AddTxIn(wire.NewTxIn(&input.OutPoint, nil))
|
|
|
|
totalAdded += input.Amount
|
2014-09-25 19:27:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get an initial fee estimate based on the number of selected inputs
|
|
|
|
// and added outputs, with no change.
|
|
|
|
szEst := estimateTxSize(len(inputs), len(msgtx.TxOut))
|
2015-04-02 20:13:38 +02:00
|
|
|
feeEst := minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree)
|
2014-09-25 19:27:18 +02:00
|
|
|
|
|
|
|
// Now make sure the sum amount of all our inputs is enough for the
|
|
|
|
// sum amount of all outputs plus the fee. If necessary we add more,
|
|
|
|
// inputs, but in that case we also need to recalculate the fee.
|
|
|
|
for totalAdded < minAmount+feeEst {
|
|
|
|
if len(eligible) == 0 {
|
|
|
|
return nil, InsufficientFundsError{totalAdded, minAmount, feeEst}
|
|
|
|
}
|
|
|
|
input, eligible = eligible[0], eligible[1:]
|
|
|
|
inputs = append(inputs, input)
|
2015-04-06 21:03:24 +02:00
|
|
|
msgtx.AddTxIn(wire.NewTxIn(&input.OutPoint, nil))
|
2014-09-25 19:27:18 +02:00
|
|
|
szEst += txInEstimate
|
2015-04-06 21:03:24 +02:00
|
|
|
totalAdded += input.Amount
|
2015-04-02 20:13:38 +02:00
|
|
|
feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree)
|
2014-09-25 19:27:18 +02:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
|
2016-01-30 06:10:10 +01:00
|
|
|
// If we're spending the outputs of an imported address, we default
|
|
|
|
// to generating change addresses from the default account.
|
|
|
|
prevAccount := account
|
|
|
|
if account == waddrmgr.ImportedAddrAccount {
|
|
|
|
account = waddrmgr.DefaultAccountNum
|
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
var changeAddr btcutil.Address
|
|
|
|
// changeIdx is -1 unless there's a change output.
|
|
|
|
changeIdx := -1
|
|
|
|
|
|
|
|
for {
|
|
|
|
change := totalAdded - minAmount - feeEst
|
2013-12-04 01:22:47 +01:00
|
|
|
if change > 0 {
|
2014-01-06 18:24:29 +01:00
|
|
|
if changeAddr == nil {
|
2014-12-12 09:54:26 +01:00
|
|
|
changeAddr, err = changeAddress(account)
|
Remove account support, fix races on btcd connect.
This commit is the result of several big changes being made to the
wallet. In particular, the "handshake" (initial sync to the chain
server) was quite racy and required proper synchronization. To make
fixing this race easier, several other changes were made to the
internal wallet data structures and much of the RPC server ended up
being rewritten.
First, all account support has been removed. The previous Account
struct has been replaced with a Wallet structure, which includes a
keystore for saving keys, and a txstore for storing relevant
transactions. This decision has been made since it is the opinion of
myself and other developers that bitcoind accounts are fundamentally
broken (as accounts implemented by bitcoind support both arbitrary
address groupings as well as moving balances between accounts -- these
are fundamentally incompatible features), and since a BIP0032 keystore
is soon planned to be implemented (at which point, "accounts" can
return as HD extended keys). With the keystore handling the grouping
of related keys, there is no reason have many different Account
structs, and the AccountManager has been removed as well. All RPC
handlers that take an account option will only work with "" (the
default account) or "*" if the RPC allows specifying all accounts.
Second, much of the RPC server has been cleaned up. The global
variables for the RPC server and chain server client have been moved
to part of the rpcServer struct, and the handlers for each RPC method
that are looked up change depending on which components have been set.
Passthrough requests are also no longer handled specially, but when
the chain server is set, a handler to perform the passthrough will be
returned if the method is not otherwise a wallet RPC. The
notification system for websocket clients has also been rewritten so
wallet components can send notifications through channels, rather than
requiring direct access to the RPC server itself, or worse still,
sending directly to a websocket client's send channel. In the future,
this will enable proper registration of notifications, rather than
unsolicited broadcasts to every connected websocket client (see
issue #84).
Finally, and the main reason why much of this cleanup was necessary,
the races during intial sync with the chain server have been fixed.
Previously, when the 'Handshake' was run, a rescan would occur which
would perform modifications to Account data structures as
notifications were received. Synchronization was provided with a
single binary semaphore which serialized all access to wallet and
account data. However, the Handshake itself was not able to run with
this lock (or else notifications would block), and many data races
would occur as both notifications were being handled. If GOMAXPROCS
was ever increased beyond 1, btcwallet would always immediately crash
due to invalid addresses caused by the data races on startup. To fix
this, the single lock for all wallet access has been replaced with
mutexes for both the keystore and txstore. Handling of btcd
notifications and client requests may now occur simultaneously.
GOMAXPROCS has also been set to the number of logical CPUs at the
beginning of main, since with the data races fixed, there's no reason
to prevent the extra parallelism gained by increasing it.
Closes #78.
Closes #101.
Closes #110.
2014-07-09 05:17:38 +02:00
|
|
|
if err != nil {
|
2014-09-25 19:27:18 +02:00
|
|
|
return nil, err
|
Remove account support, fix races on btcd connect.
This commit is the result of several big changes being made to the
wallet. In particular, the "handshake" (initial sync to the chain
server) was quite racy and required proper synchronization. To make
fixing this race easier, several other changes were made to the
internal wallet data structures and much of the RPC server ended up
being rewritten.
First, all account support has been removed. The previous Account
struct has been replaced with a Wallet structure, which includes a
keystore for saving keys, and a txstore for storing relevant
transactions. This decision has been made since it is the opinion of
myself and other developers that bitcoind accounts are fundamentally
broken (as accounts implemented by bitcoind support both arbitrary
address groupings as well as moving balances between accounts -- these
are fundamentally incompatible features), and since a BIP0032 keystore
is soon planned to be implemented (at which point, "accounts" can
return as HD extended keys). With the keystore handling the grouping
of related keys, there is no reason have many different Account
structs, and the AccountManager has been removed as well. All RPC
handlers that take an account option will only work with "" (the
default account) or "*" if the RPC allows specifying all accounts.
Second, much of the RPC server has been cleaned up. The global
variables for the RPC server and chain server client have been moved
to part of the rpcServer struct, and the handlers for each RPC method
that are looked up change depending on which components have been set.
Passthrough requests are also no longer handled specially, but when
the chain server is set, a handler to perform the passthrough will be
returned if the method is not otherwise a wallet RPC. The
notification system for websocket clients has also been rewritten so
wallet components can send notifications through channels, rather than
requiring direct access to the RPC server itself, or worse still,
sending directly to a websocket client's send channel. In the future,
this will enable proper registration of notifications, rather than
unsolicited broadcasts to every connected websocket client (see
issue #84).
Finally, and the main reason why much of this cleanup was necessary,
the races during intial sync with the chain server have been fixed.
Previously, when the 'Handshake' was run, a rescan would occur which
would perform modifications to Account data structures as
notifications were received. Synchronization was provided with a
single binary semaphore which serialized all access to wallet and
account data. However, the Handshake itself was not able to run with
this lock (or else notifications would block), and many data races
would occur as both notifications were being handled. If GOMAXPROCS
was ever increased beyond 1, btcwallet would always immediately crash
due to invalid addresses caused by the data races on startup. To fix
this, the single lock for all wallet access has been replaced with
mutexes for both the keystore and txstore. Handling of btcd
notifications and client requests may now occur simultaneously.
GOMAXPROCS has also been set to the number of logical CPUs at the
beginning of main, since with the data races fixed, there's no reason
to prevent the extra parallelism gained by increasing it.
Closes #78.
Closes #101.
Closes #110.
2014-07-09 05:17:38 +02:00
|
|
|
}
|
2013-12-04 01:22:47 +01:00
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
changeIdx, err = addChange(msgtx, change, changeAddr)
|
2013-12-04 01:22:47 +01:00
|
|
|
if err != nil {
|
2014-09-25 19:27:18 +02:00
|
|
|
return nil, err
|
2013-12-04 01:22:47 +01:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
}
|
2013-09-09 20:14:57 +02:00
|
|
|
|
2015-04-02 20:13:38 +02:00
|
|
|
if err = signMsgTx(msgtx, inputs, mgr, chainParams); err != nil {
|
2014-07-29 00:12:01 +02:00
|
|
|
return nil, err
|
2013-11-04 17:50:32 +01:00
|
|
|
}
|
2013-09-09 20:14:57 +02:00
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
if feeForSize(feeIncrement, msgtx.SerializeSize()) <= feeEst {
|
2016-01-30 06:10:10 +01:00
|
|
|
if change > 0 && prevAccount == waddrmgr.ImportedAddrAccount {
|
|
|
|
log.Warnf("Spend from imported account produced change: moving"+
|
|
|
|
" %v from imported account into default account.", change)
|
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
// The required fee for this size is less than or equal to what
|
|
|
|
// we guessed, so we're done.
|
2013-12-04 01:22:47 +01:00
|
|
|
break
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
2014-09-25 19:27:18 +02:00
|
|
|
|
|
|
|
if change > 0 {
|
|
|
|
// Remove the change output since the next iteration will add
|
|
|
|
// it again (with a new amount) if necessary.
|
|
|
|
tmp := msgtx.TxOut[:changeIdx]
|
|
|
|
tmp = append(tmp, msgtx.TxOut[changeIdx+1:]...)
|
|
|
|
msgtx.TxOut = tmp
|
|
|
|
}
|
|
|
|
|
|
|
|
feeEst += feeIncrement
|
|
|
|
for totalAdded < minAmount+feeEst {
|
|
|
|
if len(eligible) == 0 {
|
|
|
|
return nil, InsufficientFundsError{totalAdded, minAmount, feeEst}
|
|
|
|
}
|
|
|
|
input, eligible = eligible[0], eligible[1:]
|
|
|
|
inputs = append(inputs, input)
|
2015-04-06 21:03:24 +02:00
|
|
|
msgtx.AddTxIn(wire.NewTxIn(&input.OutPoint, nil))
|
2014-09-25 19:27:18 +02:00
|
|
|
szEst += txInEstimate
|
2015-04-06 21:03:24 +02:00
|
|
|
totalAdded += input.Amount
|
2015-04-02 20:13:38 +02:00
|
|
|
feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree)
|
2014-09-25 19:27:18 +02:00
|
|
|
}
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
if err := validateMsgTx(msgtx, inputs); err != nil {
|
2014-07-29 00:12:01 +02:00
|
|
|
return nil, err
|
Perform smarter UTXO tracking.
This change fixes many issues with the tracking of unspent transaction
outputs. First, notifications for when UTXOs arse spent are now
requested from btcd, and when spent, will be removed from the
UtxoStore.
Second, when transactions are created, the unconfirmed (not yet in a
block) Utxo (with block height -1 and zeroed block hash) is added to
the wallet's UtxoStore. Notifications for when this UTXO is spent are
also requested from btcd. After the tx appears in a block, because
the UTXO has a pkScript to be spent by another owned wallet address, a
notification with the UTXO will be sent to btcwallet. We already
store the unconfirmed UTXO, so at this point the actual block height
and hash are filled in.
Finally, when calculating the balance, if confirmations is zero,
unconfirmed UTXOs (block height -1) will be included in the balance.
Otherwise, they are ignored.
2013-10-22 15:55:53 +02:00
|
|
|
}
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
info := &CreatedTx{
|
2015-04-06 21:03:24 +02:00
|
|
|
MsgTx: msgtx,
|
2015-04-02 20:13:38 +02:00
|
|
|
ChangeAddr: changeAddr,
|
|
|
|
ChangeIndex: changeIdx,
|
Modernize the RPC server.
This is a rather monolithic commit that moves the old RPC server to
its own package (rpc/legacyrpc), introduces a new RPC server using
gRPC (rpc/rpcserver), and provides the ability to defer wallet loading
until request at a later time by an RPC (--noinitialload).
The legacy RPC server remains the default for now while the new gRPC
server is not enabled by default. Enabling the new server requires
setting a listen address (--experimenalrpclisten). This experimental
flag is used to effectively feature gate the server until it is ready
to use as a default. Both RPC servers can be run at the same time,
but require binding to different listen addresses.
In theory, with the legacy RPC server now living in its own package it
should become much easier to unit test the handlers. This will be
useful for any future changes to the package, as compatibility with
Core's wallet is still desired.
Type safety has also been improved in the legacy RPC server. Multiple
handler types are now used for methods that do and do not require the
RPC client as a dependency. This can statically help prevent nil
pointer dereferences, and was very useful for catching bugs during
refactoring.
To synchronize the wallet loading process between the main package
(the default) and through the gRPC WalletLoader service (with the
--noinitialload option), as well as increasing the loose coupling of
packages, a new wallet.Loader type has been added. All creating and
loading of existing wallets is done through a single Loader instance,
and callbacks can be attached to the instance to run after the wallet
has been opened. This is how the legacy RPC server is associated with
a loaded wallet, even after the wallet is loaded by a gRPC method in a
completely unrelated package.
Documentation for the new RPC server has been added to the
rpc/documentation directory. The documentation includes a
specification for the new RPC API, addresses how to make changes to
the server implementation, and provides short example clients in
several different languages.
Some of the new RPC methods are not implementated exactly as described
by the specification. These are considered bugs with the
implementation, not the spec. Known bugs are commented as such.
2015-06-01 21:57:50 +02:00
|
|
|
Fee: feeEst, // Last estimate is the actual fee
|
2013-11-22 19:42:25 +01:00
|
|
|
}
|
|
|
|
return info, nil
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
2013-12-04 01:22:47 +01:00
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
// addChange adds a new output with the given amount and address, and
|
|
|
|
// randomizes the index (and returns it) of the newly added output.
|
2015-02-05 22:41:38 +01:00
|
|
|
func addChange(msgtx *wire.MsgTx, change btcutil.Amount, changeAddr btcutil.Address) (int, error) {
|
2015-01-30 19:31:29 +01:00
|
|
|
pkScript, err := txscript.PayToAddrScript(changeAddr)
|
2014-09-25 19:27:18 +02:00
|
|
|
if err != nil {
|
|
|
|
return 0, fmt.Errorf("cannot create txout script: %s", err)
|
|
|
|
}
|
2015-02-05 22:41:38 +01:00
|
|
|
msgtx.AddTxOut(wire.NewTxOut(int64(change), pkScript))
|
2014-09-25 19:27:18 +02:00
|
|
|
|
|
|
|
// Randomize index of the change output.
|
|
|
|
rng := badrand.New(badrand.NewSource(time.Now().UnixNano()))
|
|
|
|
r := rng.Int31n(int32(len(msgtx.TxOut))) // random index
|
|
|
|
c := len(msgtx.TxOut) - 1 // change index
|
|
|
|
msgtx.TxOut[r], msgtx.TxOut[c] = msgtx.TxOut[c], msgtx.TxOut[r]
|
|
|
|
return int(r), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// addOutputs adds the given address/amount pairs as outputs to msgtx,
|
|
|
|
// returning their total amount.
|
2015-04-02 20:13:38 +02:00
|
|
|
func addOutputs(msgtx *wire.MsgTx, pairs map[string]btcutil.Amount, chainParams *chaincfg.Params) (btcutil.Amount, error) {
|
2014-09-25 19:27:18 +02:00
|
|
|
var minAmount btcutil.Amount
|
2014-07-29 00:12:01 +02:00
|
|
|
for addrStr, amt := range pairs {
|
2014-09-25 19:27:18 +02:00
|
|
|
if amt <= 0 {
|
|
|
|
return minAmount, ErrNonPositiveAmount
|
|
|
|
}
|
|
|
|
minAmount += amt
|
2015-04-02 20:13:38 +02:00
|
|
|
addr, err := btcutil.DecodeAddress(addrStr, chainParams)
|
2014-07-29 00:12:01 +02:00
|
|
|
if err != nil {
|
2014-09-25 19:27:18 +02:00
|
|
|
return minAmount, fmt.Errorf("cannot decode address: %s", err)
|
2014-07-29 00:12:01 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Add output to spend amt to addr.
|
2015-01-30 19:31:29 +01:00
|
|
|
pkScript, err := txscript.PayToAddrScript(addr)
|
2014-07-29 00:12:01 +02:00
|
|
|
if err != nil {
|
2014-09-25 19:27:18 +02:00
|
|
|
return minAmount, fmt.Errorf("cannot create txout script: %s", err)
|
2014-07-29 00:12:01 +02:00
|
|
|
}
|
2015-02-05 22:41:38 +01:00
|
|
|
txout := wire.NewTxOut(int64(amt), pkScript)
|
2014-07-29 00:12:01 +02:00
|
|
|
msgtx.AddTxOut(txout)
|
|
|
|
}
|
2014-09-25 19:27:18 +02:00
|
|
|
return minAmount, nil
|
2014-07-29 00:12:01 +02:00
|
|
|
}
|
|
|
|
|
2015-04-06 21:03:24 +02:00
|
|
|
func (w *Wallet) findEligibleOutputs(account uint32, minconf int32, bs *waddrmgr.BlockStamp) ([]wtxmgr.Credit, error) {
|
2014-07-29 00:12:01 +02:00
|
|
|
unspent, err := w.TxStore.UnspentOutputs()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-04-06 21:03:24 +02:00
|
|
|
|
|
|
|
// TODO: Eventually all of these filters (except perhaps output locking)
|
|
|
|
// should be handled by the call to UnspentOutputs (or similar).
|
|
|
|
// Because one of these filters requires matching the output script to
|
|
|
|
// the desired account, this change depends on making wtxmgr a waddrmgr
|
|
|
|
// dependancy and requesting unspent outputs for a single account.
|
|
|
|
eligible := make([]wtxmgr.Credit, 0, len(unspent))
|
2014-07-29 00:12:01 +02:00
|
|
|
for i := range unspent {
|
2015-04-06 21:03:24 +02:00
|
|
|
output := &unspent[i]
|
2014-07-29 00:12:01 +02:00
|
|
|
|
2015-04-06 21:03:24 +02:00
|
|
|
// Only include this output if it meets the required number of
|
|
|
|
// confirmations. Coinbase transactions must have have reached
|
|
|
|
// maturity before their outputs may be spent.
|
|
|
|
if !confirmed(minconf, output.Height, bs.Height) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if output.FromCoinBase {
|
|
|
|
const target = blockchain.CoinbaseMaturity
|
|
|
|
if !confirmed(target, output.Height, bs.Height) {
|
2014-07-29 00:12:01 +02:00
|
|
|
continue
|
|
|
|
}
|
2015-04-06 21:03:24 +02:00
|
|
|
}
|
2014-07-29 00:12:01 +02:00
|
|
|
|
2015-04-06 21:03:24 +02:00
|
|
|
// Locked unspent outputs are skipped.
|
|
|
|
if w.LockedOutpoint(output.OutPoint) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter out unspendable outputs, that is, remove those that
|
|
|
|
// (at this time) are not P2PKH outputs. Other inputs must be
|
|
|
|
// manually included in transactions and sent (for example,
|
|
|
|
// using createrawtransaction, signrawtransaction, and
|
|
|
|
// sendrawtransaction).
|
|
|
|
class, addrs, _, err := txscript.ExtractPkScriptAddrs(
|
|
|
|
output.PkScript, w.chainParams)
|
|
|
|
if err != nil || class != txscript.PubKeyHashTy {
|
|
|
|
continue
|
2014-07-29 00:12:01 +02:00
|
|
|
}
|
2015-04-06 21:03:24 +02:00
|
|
|
|
|
|
|
// Only include the output if it is associated with the passed
|
|
|
|
// account. There should only be one address since this is a
|
|
|
|
// P2PKH script.
|
|
|
|
addrAcct, err := w.Manager.AddrAccount(addrs[0])
|
|
|
|
if err != nil || addrAcct != account {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
eligible = append(eligible, *output)
|
2014-07-29 00:12:01 +02:00
|
|
|
}
|
|
|
|
return eligible, nil
|
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
// signMsgTx sets the SignatureScript for every item in msgtx.TxIn.
|
|
|
|
// It must be called every time a msgtx is changed.
|
|
|
|
// Only P2PKH outputs are supported at this point.
|
2015-04-06 21:03:24 +02:00
|
|
|
func signMsgTx(msgtx *wire.MsgTx, prevOutputs []wtxmgr.Credit, mgr *waddrmgr.Manager, chainParams *chaincfg.Params) error {
|
2014-09-25 19:27:18 +02:00
|
|
|
if len(prevOutputs) != len(msgtx.TxIn) {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Number of prevOutputs (%d) does not match number of tx inputs (%d)",
|
|
|
|
len(prevOutputs), len(msgtx.TxIn))
|
2014-07-29 00:12:01 +02:00
|
|
|
}
|
2014-09-25 19:27:18 +02:00
|
|
|
for i, output := range prevOutputs {
|
2014-07-29 00:12:01 +02:00
|
|
|
// Errors don't matter here, as we only consider the
|
|
|
|
// case where len(addrs) == 1.
|
2015-04-06 21:03:24 +02:00
|
|
|
_, addrs, _, _ := txscript.ExtractPkScriptAddrs(output.PkScript,
|
|
|
|
chainParams)
|
2014-07-29 00:12:01 +02:00
|
|
|
if len(addrs) != 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash)
|
|
|
|
if !ok {
|
2015-02-06 06:12:48 +01:00
|
|
|
return ErrUnsupportedTransactionType
|
2014-07-29 00:12:01 +02:00
|
|
|
}
|
|
|
|
|
2014-10-29 07:43:29 +01:00
|
|
|
ai, err := mgr.Address(apkh)
|
2014-07-29 00:12:01 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot get address info: %v", err)
|
|
|
|
}
|
|
|
|
|
2014-10-29 07:43:29 +01:00
|
|
|
pka := ai.(waddrmgr.ManagedPubKeyAddress)
|
2014-07-29 00:12:01 +02:00
|
|
|
privkey, err := pka.PrivKey()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot get private key: %v", err)
|
|
|
|
}
|
|
|
|
|
2014-10-29 07:43:29 +01:00
|
|
|
sigscript, err := txscript.SignatureScript(msgtx, i,
|
2015-04-06 21:03:24 +02:00
|
|
|
output.PkScript, txscript.SigHashAll, privkey,
|
2014-10-29 07:43:29 +01:00
|
|
|
ai.Compressed())
|
2014-07-29 00:12:01 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot create sigscript: %s", err)
|
|
|
|
}
|
|
|
|
msgtx.TxIn[i].SignatureScript = sigscript
|
|
|
|
}
|
2014-09-25 19:27:18 +02:00
|
|
|
|
2014-07-29 00:12:01 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-04-06 21:03:24 +02:00
|
|
|
func validateMsgTx(msgtx *wire.MsgTx, prevOutputs []wtxmgr.Credit) error {
|
2015-04-20 22:52:26 +02:00
|
|
|
for i := range msgtx.TxIn {
|
2015-04-06 21:03:24 +02:00
|
|
|
vm, err := txscript.NewEngine(prevOutputs[i].PkScript,
|
2015-10-12 19:09:40 +02:00
|
|
|
msgtx, i, txscript.StandardVerifyFlags, nil)
|
2014-07-29 00:12:01 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot create script engine: %s", err)
|
|
|
|
}
|
2015-04-20 23:21:19 +02:00
|
|
|
if err = vm.Execute(); err != nil {
|
2014-07-29 00:12:01 +02:00
|
|
|
return fmt.Errorf("cannot validate transaction: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
// minimumFee estimates the minimum fee required for a transaction.
|
|
|
|
// If cfg.DisallowFree is false, a fee may be zero so long as txLen
|
|
|
|
// s less than 1 kilobyte and none of the outputs contain a value
|
|
|
|
// less than 1 bitcent. Otherwise, the fee will be calculated using
|
|
|
|
// incr, incrementing the fee for each kilobyte of transaction.
|
2015-04-06 21:03:24 +02:00
|
|
|
func minimumFee(incr btcutil.Amount, txLen int, outputs []*wire.TxOut, prevOutputs []wtxmgr.Credit, height int32, disallowFree bool) btcutil.Amount {
|
2014-09-25 19:27:18 +02:00
|
|
|
allowFree := false
|
2015-04-02 20:13:38 +02:00
|
|
|
if !disallowFree {
|
2014-09-25 19:27:18 +02:00
|
|
|
allowFree = allowNoFeeTx(height, prevOutputs, txLen)
|
|
|
|
}
|
|
|
|
fee := feeForSize(incr, txLen)
|
2013-12-04 01:22:47 +01:00
|
|
|
|
|
|
|
if allowFree && txLen < 1000 {
|
|
|
|
fee = 0
|
|
|
|
}
|
|
|
|
|
|
|
|
if fee < incr {
|
2014-09-25 19:27:18 +02:00
|
|
|
for _, txOut := range outputs {
|
2013-12-04 01:22:47 +01:00
|
|
|
if txOut.Value < btcutil.SatoshiPerBitcent {
|
|
|
|
return incr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
// How can fee be smaller than 0 here?
|
2014-07-08 18:22:09 +02:00
|
|
|
if fee < 0 || fee > btcutil.MaxSatoshi {
|
|
|
|
fee = btcutil.MaxSatoshi
|
2013-12-04 01:22:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return fee
|
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
// allowNoFeeTx calculates the transaction priority and checks that the
|
2014-07-08 18:22:09 +02:00
|
|
|
// priority reaches a certain threshold. If the threshhold is
|
2013-12-04 01:22:47 +01:00
|
|
|
// reached, a free transaction fee is allowed.
|
2015-04-06 21:03:24 +02:00
|
|
|
func allowNoFeeTx(curHeight int32, txouts []wtxmgr.Credit, txSize int) bool {
|
2014-07-08 18:22:09 +02:00
|
|
|
const blocksPerDayEstimate = 144.0
|
|
|
|
const txSizeEstimate = 250.0
|
|
|
|
const threshold = btcutil.SatoshiPerBitcoin * blocksPerDayEstimate / txSizeEstimate
|
2013-12-04 01:22:47 +01:00
|
|
|
|
|
|
|
var weightedSum int64
|
2014-02-24 20:35:30 +01:00
|
|
|
for _, txout := range txouts {
|
2015-04-06 21:03:24 +02:00
|
|
|
depth := chainDepth(txout.Height, curHeight)
|
|
|
|
weightedSum += int64(txout.Amount) * int64(depth)
|
2013-12-04 01:22:47 +01:00
|
|
|
}
|
|
|
|
priority := float64(weightedSum) / float64(txSize)
|
2014-07-08 18:22:09 +02:00
|
|
|
return priority > threshold
|
2013-12-04 01:22:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// chainDepth returns the chaindepth of a target given the current
|
|
|
|
// blockchain height.
|
|
|
|
func chainDepth(target, current int32) int32 {
|
|
|
|
if target == -1 {
|
|
|
|
// target is not yet in a block.
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// target is in a block.
|
|
|
|
return current - target + 1
|
|
|
|
}
|