2013-09-09 20:14:57 +02:00
|
|
|
/*
|
2014-01-09 20:12:20 +01:00
|
|
|
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
|
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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2014-06-13 05:28:30 +02:00
|
|
|
badrand "math/rand"
|
2014-05-23 04:16:50 +02:00
|
|
|
"sort"
|
|
|
|
"time"
|
|
|
|
|
2014-04-14 15:51:47 +02:00
|
|
|
"github.com/conformal/btcchain"
|
2013-09-09 20:14:57 +02:00
|
|
|
"github.com/conformal/btcscript"
|
|
|
|
"github.com/conformal/btcutil"
|
2014-07-08 20:17:24 +02:00
|
|
|
"github.com/conformal/btcwallet/keystore"
|
2014-05-08 21:48:42 +02:00
|
|
|
"github.com/conformal/btcwallet/txstore"
|
2013-09-09 20:14:57 +02:00
|
|
|
"github.com/conformal/btcwire"
|
|
|
|
)
|
|
|
|
|
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
|
|
|
|
2014-07-29 00:12:01 +02:00
|
|
|
var UnsupportedTransactionType = errors.New("Only P2PKH transactions are supported")
|
|
|
|
|
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-07-07 16:35:54 +02:00
|
|
|
// defaultFeeIncrement is the default minimum transation fee (0.0001 BTC,
|
2013-12-04 01:22:47 +01:00
|
|
|
// measured in satoshis) added to transactions requiring a fee.
|
2014-07-07 16:35:54 +02:00
|
|
|
const defaultFeeIncrement = 10000
|
2013-10-07 21:14:39 +02:00
|
|
|
|
2013-11-05 00:58:41 +01:00
|
|
|
type CreatedTx struct {
|
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
|
|
|
tx *btcutil.Tx
|
|
|
|
changeAddr btcutil.Address
|
|
|
|
changeIndex int // negative if no change
|
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.
|
2014-06-18 07:16:08 +02:00
|
|
|
type ByAmount []txstore.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) }
|
|
|
|
func (u ByAmount) Less(i, j int) bool { return u[i].Amount() < u[j].Amount() }
|
|
|
|
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.
|
|
|
|
func (w *Wallet) txToPairs(pairs map[string]btcutil.Amount, minconf int) (*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
|
|
|
|
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
|
|
|
// Key store must be unlocked to compose transaction. Grab the
|
|
|
|
// unlock if possible (to prevent future unlocks), or return the
|
|
|
|
// error if the keystore is already locked.
|
|
|
|
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
|
|
|
|
2013-12-04 01:22:47 +01:00
|
|
|
// Get current block's height and hash.
|
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
|
|
|
bs, err := w.chainSvr.BlockStamp()
|
2013-12-04 01:22:47 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
eligible, err := w.findEligibleOutputs(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
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
return createTx(eligible, pairs, bs, w.FeeIncrement, w.KeyStore, w.changeAddress)
|
|
|
|
}
|
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.
|
|
|
|
func createTx(
|
|
|
|
eligible []txstore.Credit,
|
|
|
|
outputs map[string]btcutil.Amount,
|
|
|
|
bs *keystore.BlockStamp,
|
|
|
|
feeIncrement btcutil.Amount,
|
|
|
|
keys *keystore.Store,
|
|
|
|
changeAddress func(*keystore.BlockStamp) (btcutil.Address, error)) (
|
|
|
|
*CreatedTx, error) {
|
2014-07-29 00:12:01 +02:00
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
msgtx := btcwire.NewMsgTx()
|
|
|
|
minAmount, err := addOutputs(msgtx, outputs)
|
|
|
|
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.
|
|
|
|
var input txstore.Credit
|
|
|
|
var inputs []txstore.Credit
|
|
|
|
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)
|
|
|
|
msgtx.AddTxIn(btcwire.NewTxIn(input.OutPoint(), nil))
|
|
|
|
totalAdded += input.Amount()
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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))
|
|
|
|
feeEst := minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height)
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
msgtx.AddTxIn(btcwire.NewTxIn(input.OutPoint(), nil))
|
|
|
|
szEst += txInEstimate
|
|
|
|
totalAdded += input.Amount()
|
|
|
|
feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height)
|
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
|
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-09-25 19:27:18 +02:00
|
|
|
changeAddr, err = changeAddress(bs)
|
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
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
if err = signMsgTx(msgtx, inputs, keys); 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 {
|
|
|
|
// 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)
|
|
|
|
msgtx.AddTxIn(btcwire.NewTxIn(input.OutPoint(), nil))
|
|
|
|
szEst += txInEstimate
|
|
|
|
totalAdded += input.Amount()
|
|
|
|
feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height)
|
|
|
|
}
|
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{
|
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
|
|
|
tx: btcutil.NewTx(msgtx),
|
|
|
|
changeAddr: changeAddr,
|
|
|
|
changeIndex: changeIdx,
|
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.
|
|
|
|
func addChange(msgtx *btcwire.MsgTx, change btcutil.Amount, changeAddr btcutil.Address) (int, error) {
|
|
|
|
pkScript, err := btcscript.PayToAddrScript(changeAddr)
|
|
|
|
if err != nil {
|
|
|
|
return 0, fmt.Errorf("cannot create txout script: %s", err)
|
|
|
|
}
|
|
|
|
msgtx.AddTxOut(btcwire.NewTxOut(int64(change), pkScript))
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// changeAddress obtains a new btcutil.Address to be used as a change
|
|
|
|
// transaction output. It will also mark the KeyStore as dirty and
|
|
|
|
// tells chainSvr to watch that address.
|
|
|
|
func (w *Wallet) changeAddress(bs *keystore.BlockStamp) (btcutil.Address, error) {
|
|
|
|
changeAddr, err := w.KeyStore.ChangeAddress(bs)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get change address: %s", err)
|
|
|
|
}
|
|
|
|
w.KeyStore.MarkDirty()
|
|
|
|
err = w.chainSvr.NotifyReceived([]btcutil.Address{changeAddr})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot request updates for "+
|
|
|
|
"change address: %v", err)
|
|
|
|
}
|
|
|
|
return changeAddr, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// addOutputs adds the given address/amount pairs as outputs to msgtx,
|
|
|
|
// returning their total amount.
|
|
|
|
func addOutputs(msgtx *btcwire.MsgTx, pairs map[string]btcutil.Amount) (btcutil.Amount, error) {
|
|
|
|
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
|
2014-07-29 00:12:01 +02:00
|
|
|
addr, err := btcutil.DecodeAddress(addrStr, activeNet.Params)
|
|
|
|
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.
|
|
|
|
pkScript, err := btcscript.PayToAddrScript(addr)
|
|
|
|
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
|
|
|
}
|
|
|
|
txout := btcwire.NewTxOut(int64(amt), pkScript)
|
|
|
|
msgtx.AddTxOut(txout)
|
|
|
|
}
|
2014-09-25 19:27:18 +02:00
|
|
|
return minAmount, nil
|
2014-07-29 00:12:01 +02:00
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
func (w *Wallet) findEligibleOutputs(minconf int, bs *keystore.BlockStamp) ([]txstore.Credit, error) {
|
2014-07-29 00:12:01 +02:00
|
|
|
unspent, err := w.TxStore.UnspentOutputs()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
// 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).
|
|
|
|
eligible := make([]txstore.Credit, 0, len(unspent))
|
|
|
|
for i := range unspent {
|
|
|
|
switch btcscript.GetScriptClass(unspent[i].TxOut().PkScript) {
|
|
|
|
case btcscript.PubKeyHashTy:
|
|
|
|
if !unspent[i].Confirmed(minconf, bs.Height) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
// Coinbase transactions must have have reached maturity
|
|
|
|
// before their outputs may be spent.
|
|
|
|
if unspent[i].IsCoinbase() {
|
|
|
|
target := btcchain.CoinbaseMaturity
|
|
|
|
if !unspent[i].Confirmed(target, bs.Height) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Locked unspent outputs are skipped.
|
|
|
|
if w.LockedOutpoint(*unspent[i].OutPoint()) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
eligible = append(eligible, unspent[i])
|
|
|
|
}
|
|
|
|
}
|
|
|
|
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.
|
|
|
|
func signMsgTx(msgtx *btcwire.MsgTx, prevOutputs []txstore.Credit, store *keystore.Store) error {
|
|
|
|
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.
|
|
|
|
_, addrs, _, _ := output.Addresses(activeNet.Params)
|
|
|
|
if len(addrs) != 1 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
apkh, ok := addrs[0].(*btcutil.AddressPubKeyHash)
|
|
|
|
if !ok {
|
|
|
|
return UnsupportedTransactionType
|
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
ai, err := store.Address(apkh)
|
2014-07-29 00:12:01 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot get address info: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
pka := ai.(keystore.PubKeyAddress)
|
|
|
|
privkey, err := pka.PrivKey()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot get private key: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
sigscript, err := btcscript.SignatureScript(
|
|
|
|
msgtx, i, output.TxOut().PkScript, btcscript.SigHashAll, privkey, ai.Compressed())
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2014-09-25 19:27:18 +02:00
|
|
|
func validateMsgTx(msgtx *btcwire.MsgTx, prevOutputs []txstore.Credit) error {
|
2014-08-01 19:50:31 +02:00
|
|
|
flags := btcscript.ScriptCanonicalSignatures | btcscript.ScriptStrictMultiSig
|
2014-07-29 00:12:01 +02:00
|
|
|
bip16 := time.Now().After(btcscript.Bip16Activation)
|
|
|
|
if bip16 {
|
|
|
|
flags |= btcscript.ScriptBip16
|
|
|
|
}
|
|
|
|
for i, txin := range msgtx.TxIn {
|
|
|
|
engine, err := btcscript.NewScript(
|
2014-09-25 19:27:18 +02:00
|
|
|
txin.SignatureScript, prevOutputs[i].TxOut().PkScript, i, msgtx, flags)
|
2014-07-29 00:12:01 +02:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot create script engine: %s", err)
|
|
|
|
}
|
|
|
|
if err = engine.Execute(); err != nil {
|
|
|
|
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.
|
|
|
|
func minimumFee(incr btcutil.Amount, txLen int, outputs []*btcwire.TxOut, prevOutputs []txstore.Credit, height int32) btcutil.Amount {
|
|
|
|
allowFree := false
|
|
|
|
if !cfg.DisallowFree {
|
|
|
|
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.
|
2014-09-25 19:27:18 +02:00
|
|
|
func allowNoFeeTx(curHeight int32, txouts []txstore.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 {
|
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
|
|
|
depth := chainDepth(txout.BlockHeight, 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
|
|
|
|
}
|