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.
This commit is contained in:
parent
e956d0b290
commit
e9bdf2a094
10 changed files with 2997 additions and 1546 deletions
146
account.go
146
account.go
|
@ -79,14 +79,11 @@ func (a *Account) AddressUsed(addr btcutil.Address) bool {
|
|||
|
||||
pkHash := addr.ScriptAddress()
|
||||
|
||||
for _, record := range a.TxStore.SortedRecords() {
|
||||
txout, ok := record.(*tx.RecvTxOut)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, r := range a.TxStore.Records() {
|
||||
credits := r.Credits()
|
||||
for _, c := range credits {
|
||||
// Extract addresses from this output's pkScript.
|
||||
_, addrs, _, err := txout.Addresses(cfg.Net())
|
||||
_, addrs, _, err := c.Addresses(cfg.Net())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
@ -97,6 +94,7 @@ func (a *Account) AddressUsed(addr btcutil.Address) bool {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -115,8 +113,12 @@ func (a *Account) CalculateBalance(confirms int) float64 {
|
|||
return 0.
|
||||
}
|
||||
|
||||
bal := a.TxStore.Balance(confirms, bs.Height)
|
||||
return float64(bal) / float64(btcutil.SatoshiPerBitcoin)
|
||||
bal, err := a.TxStore.Balance(confirms, bs.Height)
|
||||
if err != nil {
|
||||
log.Errorf("Cannot calculate balance: %v", err)
|
||||
return 0
|
||||
}
|
||||
return bal.ToUnit(btcutil.AmountBTC)
|
||||
}
|
||||
|
||||
// CalculateAddressBalance sums the amounts of all unspent transaction
|
||||
|
@ -134,23 +136,25 @@ func (a *Account) CalculateAddressBalance(addr btcutil.Address, confirms int) fl
|
|||
return 0.
|
||||
}
|
||||
|
||||
var bal int64 // Measured in satoshi
|
||||
for _, txout := range a.TxStore.UnspentOutputs() {
|
||||
// Utxos not yet in blocks (height -1) should only be
|
||||
// added if confirmations is 0.
|
||||
if confirmed(confirms, txout.Height(), bs.Height) {
|
||||
var bal btcutil.Amount
|
||||
unspent, err := a.TxStore.UnspentOutputs()
|
||||
if err != nil {
|
||||
return 0.
|
||||
}
|
||||
for _, credit := range unspent {
|
||||
if confirmed(confirms, credit.BlockHeight, bs.Height) {
|
||||
// We only care about the case where len(addrs) == 1, and err
|
||||
// will never be non-nil in that case
|
||||
_, addrs, _, _ := txout.Addresses(cfg.Net())
|
||||
_, addrs, _, _ := credit.Addresses(cfg.Net())
|
||||
if len(addrs) != 1 {
|
||||
continue
|
||||
}
|
||||
if addrs[0].EncodeAddress() == addr.EncodeAddress() {
|
||||
bal += txout.Value()
|
||||
bal += credit.Amount()
|
||||
}
|
||||
}
|
||||
}
|
||||
return float64(bal) / float64(btcutil.SatoshiPerBitcoin)
|
||||
return bal.ToUnit(btcutil.AmountBTC)
|
||||
}
|
||||
|
||||
// CurrentAddress gets the most recently requested Bitcoin payment address
|
||||
|
@ -171,23 +175,28 @@ func (a *Account) CurrentAddress() (btcutil.Address, error) {
|
|||
// ListSinceBlock returns a slice of objects with details about transactions
|
||||
// since the given block. If the block is -1 then all transactions are included.
|
||||
// This is intended to be used for listsinceblock RPC replies.
|
||||
func (a *Account) ListSinceBlock(since, curBlockHeight int32, minconf int) ([]btcjson.ListTransactionsResult, error) {
|
||||
func (a *Account) ListSinceBlock(since, curBlockHeight int32,
|
||||
minconf int) ([]btcjson.ListTransactionsResult, error) {
|
||||
|
||||
var txList []btcjson.ListTransactionsResult
|
||||
for _, txRecord := range a.TxStore.SortedRecords() {
|
||||
for _, txRecord := range a.TxStore.Records() {
|
||||
// Transaction records must only be considered if they occur
|
||||
// after the block height since.
|
||||
if since != -1 && txRecord.Height() <= since {
|
||||
if since != -1 && txRecord.BlockHeight <= since {
|
||||
continue
|
||||
}
|
||||
|
||||
// Transactions that have not met minconf confirmations are to
|
||||
// be ignored.
|
||||
if !confirmed(minconf, txRecord.Height(), curBlockHeight) {
|
||||
if !confirmed(minconf, txRecord.BlockHeight, curBlockHeight) {
|
||||
continue
|
||||
}
|
||||
|
||||
txList = append(txList,
|
||||
txRecord.TxInfo(a.name, curBlockHeight, a.Net())...)
|
||||
jsonResults, err := txRecord.ToJSON(a.name, curBlockHeight, a.Net())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txList = append(txList, jsonResults...)
|
||||
}
|
||||
|
||||
return txList, nil
|
||||
|
@ -206,12 +215,15 @@ func (a *Account) ListTransactions(from, count int) ([]btcjson.ListTransactionsR
|
|||
|
||||
var txList []btcjson.ListTransactionsResult
|
||||
|
||||
records := a.TxStore.SortedRecords()
|
||||
records := a.TxStore.Records()
|
||||
lastLookupIdx := len(records) - count
|
||||
// Search in reverse order: lookup most recently-added first.
|
||||
for i := len(records) - 1; i >= from && i >= lastLookupIdx; i-- {
|
||||
txList = append(txList,
|
||||
records[i].TxInfo(a.name, bs.Height, a.Net())...)
|
||||
jsonResults, err := records[i].ToJSON(a.name, bs.Height, a.Net())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txList = append(txList, jsonResults...)
|
||||
}
|
||||
|
||||
return txList, nil
|
||||
|
@ -231,14 +243,11 @@ func (a *Account) ListAddressTransactions(pkHashes map[string]struct{}) (
|
|||
}
|
||||
|
||||
var txList []btcjson.ListTransactionsResult
|
||||
for _, txRecord := range a.TxStore.SortedRecords() {
|
||||
txout, ok := txRecord.(*tx.RecvTxOut)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// We only care about the case where len(addrs) == 1, and err
|
||||
// will never be non-nil in that case
|
||||
_, addrs, _, _ := txout.Addresses(cfg.Net())
|
||||
for _, r := range a.TxStore.Records() {
|
||||
for _, c := range r.Credits() {
|
||||
// We only care about the case where len(addrs) == 1,
|
||||
// and err will never be non-nil in that case.
|
||||
_, addrs, _, _ := c.Addresses(cfg.Net())
|
||||
if len(addrs) != 1 {
|
||||
continue
|
||||
}
|
||||
|
@ -247,9 +256,14 @@ func (a *Account) ListAddressTransactions(pkHashes map[string]struct{}) (
|
|||
continue
|
||||
}
|
||||
|
||||
if _, ok := pkHashes[string(apkh.ScriptAddress())]; ok {
|
||||
info := txout.TxInfo(a.name, bs.Height, a.Net())
|
||||
txList = append(txList, info...)
|
||||
if _, ok := pkHashes[string(apkh.ScriptAddress())]; !ok {
|
||||
continue
|
||||
}
|
||||
jsonResult, err := c.ToJSON(a.name, bs.Height, a.Net())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txList = append(txList, jsonResult)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -268,11 +282,14 @@ func (a *Account) ListAllTransactions() ([]btcjson.ListTransactionsResult, error
|
|||
}
|
||||
|
||||
// Search in reverse order: lookup most recently-added first.
|
||||
records := a.TxStore.SortedRecords()
|
||||
records := a.TxStore.Records()
|
||||
var txList []btcjson.ListTransactionsResult
|
||||
for i := len(records) - 1; i >= 0; i-- {
|
||||
info := records[i].TxInfo(a.name, bs.Height, a.Net())
|
||||
txList = append(txList, info...)
|
||||
jsonResults, err := records[i].ToJSON(a.name, bs.Height, a.Net())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txList = append(txList, jsonResults...)
|
||||
}
|
||||
|
||||
return txList, nil
|
||||
|
@ -429,12 +446,16 @@ func (a *Account) Track() {
|
|||
i++
|
||||
}
|
||||
|
||||
err := NotifyReceived(CurrentServerConn(), addrstrs)
|
||||
if err != nil {
|
||||
jsonErr := NotifyReceived(CurrentServerConn(), addrstrs)
|
||||
if jsonErr != nil {
|
||||
log.Error("Unable to request transaction updates for address.")
|
||||
}
|
||||
|
||||
for _, txout := range a.TxStore.UnspentOutputs() {
|
||||
unspent, err := a.TxStore.UnspentOutputs()
|
||||
if err != nil {
|
||||
log.Errorf("Unable to access unspent outputs: %v", err)
|
||||
}
|
||||
for _, txout := range unspent {
|
||||
ReqSpentUtxoNtfn(txout)
|
||||
}
|
||||
}
|
||||
|
@ -443,7 +464,7 @@ func (a *Account) Track() {
|
|||
// account. This is needed for catching btcwallet up to a long-running
|
||||
// btcd process, as otherwise it would have missed notifications as
|
||||
// blocks are attached to the main chain.
|
||||
func (a *Account) RescanActiveJob() *RescanJob {
|
||||
func (a *Account) RescanActiveJob() (*RescanJob, error) {
|
||||
// Determine the block necesary to start the rescan for all active
|
||||
// addresses.
|
||||
height := int32(0)
|
||||
|
@ -463,21 +484,25 @@ func (a *Account) RescanActiveJob() *RescanJob {
|
|||
addrs = append(addrs, actives[i].Address())
|
||||
}
|
||||
|
||||
unspents := a.TxStore.UnspentOutputs()
|
||||
unspents, err := a.TxStore.UnspentOutputs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
outpoints := make([]*btcwire.OutPoint, 0, len(unspents))
|
||||
for i := range unspents {
|
||||
outpoints = append(outpoints, unspents[i].OutPoint())
|
||||
for _, c := range unspents {
|
||||
outpoints = append(outpoints, c.OutPoint())
|
||||
}
|
||||
|
||||
return &RescanJob{
|
||||
job := &RescanJob{
|
||||
Addresses: map[*Account][]btcutil.Address{a: addrs},
|
||||
OutPoints: outpoints,
|
||||
StartHeight: height,
|
||||
}
|
||||
return job, nil
|
||||
}
|
||||
|
||||
func (a *Account) ResendUnminedTxs() {
|
||||
txs := a.TxStore.UnminedSignedTxs()
|
||||
txs := a.TxStore.UnminedDebitTxs()
|
||||
txbuf := new(bytes.Buffer)
|
||||
for _, tx_ := range txs {
|
||||
tx_.MsgTx().Serialize(txbuf)
|
||||
|
@ -628,8 +653,8 @@ func (a *Account) ReqNewTxsForAddress(addr btcutil.Address) {
|
|||
|
||||
// ReqSpentUtxoNtfn sends a message to btcd to request updates for when
|
||||
// a stored UTXO has been spent.
|
||||
func ReqSpentUtxoNtfn(t *tx.RecvTxOut) {
|
||||
op := t.OutPoint()
|
||||
func ReqSpentUtxoNtfn(c *tx.Credit) {
|
||||
op := c.OutPoint()
|
||||
log.Debugf("Requesting spent UTXO notifications for Outpoint hash %s index %d",
|
||||
op.Hash, op.Index)
|
||||
|
||||
|
@ -645,25 +670,22 @@ func (a *Account) TotalReceived(confirms int) (float64, error) {
|
|||
return 0, err
|
||||
}
|
||||
|
||||
var totalSatoshis int64
|
||||
for _, record := range a.TxStore.SortedRecords() {
|
||||
txout, ok := record.(*tx.RecvTxOut)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
var amount btcutil.Amount
|
||||
for _, r := range a.TxStore.Records() {
|
||||
for _, c := range r.Credits() {
|
||||
// Ignore change.
|
||||
if txout.Change() {
|
||||
if c.Change() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Tally if the appropiate number of block confirmations have passed.
|
||||
if confirmed(confirms, txout.Height(), bs.Height) {
|
||||
totalSatoshis += txout.Value()
|
||||
if confirmed(confirms, c.BlockHeight, bs.Height) {
|
||||
amount += c.Amount()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return float64(totalSatoshis) / float64(btcutil.SatoshiPerBitcoin), nil
|
||||
return amount.ToUnit(btcutil.AmountBTC), nil
|
||||
}
|
||||
|
||||
// confirmed checks whether a transaction at height txHeight has met
|
||||
|
|
68
acctmgr.go
68
acctmgr.go
|
@ -27,7 +27,6 @@ import (
|
|||
"github.com/conformal/btcwire"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Errors relating to accounts.
|
||||
|
@ -563,22 +562,22 @@ func (am *AccountManager) BlockNotify(bs *wallet.BlockStamp) {
|
|||
// the transaction IDs match, the record in the TxStore is updated with
|
||||
// the full information about the newly-mined tx, and the TxStore is
|
||||
// scheduled to be written to disk..
|
||||
func (am *AccountManager) RecordSpendingTx(tx_ *btcutil.Tx, block *tx.BlockDetails) {
|
||||
now := time.Now()
|
||||
var created time.Time
|
||||
if block != nil && now.After(block.Time) {
|
||||
created = block.Time
|
||||
} else {
|
||||
created = now
|
||||
}
|
||||
|
||||
func (am *AccountManager) RecordSpendingTx(tx_ *btcutil.Tx, block *tx.Block) error {
|
||||
for _, a := range am.AllAccounts() {
|
||||
// TODO(jrick): This needs to iterate through each txout's
|
||||
// addresses and find whether this account's keystore contains
|
||||
// any of the addresses this tx sends to.
|
||||
a.TxStore.InsertSignedTx(tx_, created, block)
|
||||
txr, err := a.TxStore.InsertTx(tx_, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// When received as a notification, we don't know what the inputs are.
|
||||
if _, err := txr.AddDebits(nil); err != nil {
|
||||
return err
|
||||
}
|
||||
am.ds.ScheduleTxStoreWrite(a)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CalculateBalance returns the balance, calculated using minconf block
|
||||
|
@ -744,7 +743,9 @@ func (am *AccountManager) ListAccounts(minconf int) map[string]float64 {
|
|||
// ListSinceBlock returns a slice of objects representing all transactions in
|
||||
// the wallets since the given block.
|
||||
// To be used for the listsinceblock command.
|
||||
func (am *AccountManager) ListSinceBlock(since, curBlockHeight int32, minconf int) ([]btcjson.ListTransactionsResult, error) {
|
||||
func (am *AccountManager) ListSinceBlock(since, curBlockHeight int32,
|
||||
minconf int) ([]btcjson.ListTransactionsResult, error) {
|
||||
|
||||
// Create and fill a map of account names and their balances.
|
||||
var txList []btcjson.ListTransactionsResult
|
||||
for _, a := range am.AllAccounts() {
|
||||
|
@ -761,18 +762,18 @@ func (am *AccountManager) ListSinceBlock(since, curBlockHeight int32, minconf in
|
|||
// GetTransaction.
|
||||
type accountTx struct {
|
||||
Account string
|
||||
Tx tx.Record
|
||||
Tx *tx.TxRecord
|
||||
}
|
||||
|
||||
// GetTransaction returns an array of accountTx to fully represent the effect of
|
||||
// a transaction on locally known wallets. If we know nothing about a
|
||||
// transaction an empty array will be returned.
|
||||
func (am *AccountManager) GetTransaction(txsha *btcwire.ShaHash) []accountTx {
|
||||
func (am *AccountManager) GetTransaction(txSha *btcwire.ShaHash) []accountTx {
|
||||
accumulatedTxen := []accountTx{}
|
||||
|
||||
for _, a := range am.AllAccounts() {
|
||||
for _, record := range a.TxStore.SortedRecords() {
|
||||
if *record.TxSha() != *txsha {
|
||||
for _, record := range a.TxStore.Records() {
|
||||
if *record.Tx().Sha() != *txSha {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -794,6 +795,7 @@ func (am *AccountManager) GetTransaction(txsha *btcwire.ShaHash) []accountTx {
|
|||
// transaction an empty array will be returned.
|
||||
func (am *AccountManager) ListUnspent(minconf, maxconf int,
|
||||
addresses map[string]bool) ([]*btcjson.ListUnSpentResult, error) {
|
||||
|
||||
bs, err := GetCurBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -803,14 +805,18 @@ func (am *AccountManager) ListUnspent(minconf, maxconf int,
|
|||
|
||||
var results []*btcjson.ListUnSpentResult
|
||||
for _, a := range am.AllAccounts() {
|
||||
for _, rtx := range a.TxStore.UnspentOutputs() {
|
||||
confs := confirms(rtx.Height(), bs.Height)
|
||||
unspent, err := a.TxStore.UnspentOutputs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, credit := range unspent {
|
||||
confs := confirms(credit.BlockHeight, bs.Height)
|
||||
switch {
|
||||
case int(confs) < minconf, int(confs) > maxconf:
|
||||
continue
|
||||
}
|
||||
|
||||
_, addrs, _, _ := rtx.Addresses(cfg.Net())
|
||||
_, addrs, _, _ := credit.Addresses(cfg.Net())
|
||||
if filter {
|
||||
for _, addr := range addrs {
|
||||
_, ok := addresses[addr.EncodeAddress()]
|
||||
|
@ -821,13 +827,12 @@ func (am *AccountManager) ListUnspent(minconf, maxconf int,
|
|||
continue
|
||||
}
|
||||
include:
|
||||
outpoint := rtx.OutPoint()
|
||||
result := &btcjson.ListUnSpentResult{
|
||||
TxId: outpoint.Hash.String(),
|
||||
Vout: float64(outpoint.Index),
|
||||
TxId: credit.Tx().Sha().String(),
|
||||
Vout: float64(credit.OutputIndex),
|
||||
Account: a.Name(),
|
||||
ScriptPubKey: hex.EncodeToString(rtx.PkScript()),
|
||||
Amount: float64(rtx.Value()) / 1e8,
|
||||
ScriptPubKey: hex.EncodeToString(credit.TxOut().PkScript),
|
||||
Amount: credit.Amount().ToUnit(btcutil.AmountBTC),
|
||||
Confirmations: float64(confs),
|
||||
}
|
||||
|
||||
|
@ -847,25 +852,28 @@ func (am *AccountManager) ListUnspent(minconf, maxconf int,
|
|||
|
||||
// RescanActiveAddresses begins a rescan for all active addresses for
|
||||
// each account.
|
||||
func (am *AccountManager) RescanActiveAddresses() {
|
||||
func (am *AccountManager) RescanActiveAddresses() error {
|
||||
var job *RescanJob
|
||||
for _, a := range am.AllAccounts() {
|
||||
acctJob := a.RescanActiveJob()
|
||||
acctJob, err := a.RescanActiveJob()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if job == nil {
|
||||
job = acctJob
|
||||
} else {
|
||||
job.Merge(acctJob)
|
||||
}
|
||||
}
|
||||
if job == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if job != nil {
|
||||
// Submit merged job and block until rescan completes.
|
||||
jobFinished := am.rm.SubmitJob(job)
|
||||
<-jobFinished
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (am *AccountManager) ResendUnminedTxs() {
|
||||
for _, a := range am.AllAccounts() {
|
||||
a.ResendUnminedTxs()
|
||||
|
|
69
createtx.go
69
createtx.go
|
@ -56,27 +56,27 @@ const minTxFee = 10000
|
|||
// miner. i is measured in satoshis.
|
||||
var TxFeeIncrement = struct {
|
||||
sync.Mutex
|
||||
i int64
|
||||
i btcutil.Amount
|
||||
}{
|
||||
i: minTxFee,
|
||||
}
|
||||
|
||||
type CreatedTx struct {
|
||||
tx *btcutil.Tx
|
||||
time time.Time
|
||||
inputs []*tx.Credit
|
||||
changeAddr btcutil.Address
|
||||
}
|
||||
|
||||
// ByAmount defines the methods needed to satisify sort.Interface to
|
||||
// sort a slice of Utxos by their amount.
|
||||
type ByAmount []*tx.RecvTxOut
|
||||
type ByAmount []*tx.Credit
|
||||
|
||||
func (u ByAmount) Len() int {
|
||||
return len(u)
|
||||
}
|
||||
|
||||
func (u ByAmount) Less(i, j int) bool {
|
||||
return u[i].Value() < u[j].Value()
|
||||
return u[i].Amount() < u[j].Amount()
|
||||
}
|
||||
|
||||
func (u ByAmount) Swap(i, j int) {
|
||||
|
@ -89,8 +89,8 @@ func (u ByAmount) Swap(i, j int) {
|
|||
// is the total number of satoshis which would be spent by the combination
|
||||
// of all selected previous outputs. err will equal ErrInsufficientFunds if there
|
||||
// are not enough unspent outputs to spend amt.
|
||||
func selectInputs(utxos []*tx.RecvTxOut, amt int64,
|
||||
minconf int) (selected []*tx.RecvTxOut, btcout int64, err error) {
|
||||
func selectInputs(utxos []*tx.Credit, amt btcutil.Amount,
|
||||
minconf int) (selected []*tx.Credit, out btcutil.Amount, err error) {
|
||||
|
||||
bs, err := GetCurBlock()
|
||||
if err != nil {
|
||||
|
@ -100,13 +100,13 @@ func selectInputs(utxos []*tx.RecvTxOut, amt int64,
|
|||
// Create list of eligible unspent previous outputs to use as tx
|
||||
// inputs, and sort by the amount in reverse order so a minimum number
|
||||
// of inputs is needed.
|
||||
eligible := make([]*tx.RecvTxOut, 0, len(utxos))
|
||||
eligible := make([]*tx.Credit, 0, len(utxos))
|
||||
for _, utxo := range utxos {
|
||||
if confirmed(minconf, utxo.Height(), bs.Height) {
|
||||
if confirmed(minconf, utxo.BlockHeight, bs.Height) {
|
||||
// Coinbase transactions must have have reached maturity
|
||||
// before their outputs may be spent.
|
||||
if utxo.IsCoinbase() {
|
||||
confs := confirms(utxo.Height(), bs.Height)
|
||||
confs := confirms(utxo.BlockHeight, bs.Height)
|
||||
if confs < btcchain.CoinbaseMaturity {
|
||||
continue
|
||||
}
|
||||
|
@ -117,20 +117,20 @@ func selectInputs(utxos []*tx.RecvTxOut, amt int64,
|
|||
sort.Sort(sort.Reverse(ByAmount(eligible)))
|
||||
|
||||
// Iterate throguh eligible transactions, appending to outputs and
|
||||
// increasing btcout. This is finished when btcout is greater than the
|
||||
// increasing out. This is finished when out is greater than the
|
||||
// requested amt to spend.
|
||||
for _, e := range eligible {
|
||||
selected = append(selected, e)
|
||||
btcout += e.Value()
|
||||
if btcout >= amt {
|
||||
return selected, btcout, nil
|
||||
out += e.Amount()
|
||||
if out >= amt {
|
||||
return selected, out, nil
|
||||
}
|
||||
}
|
||||
if btcout < amt {
|
||||
if out < amt {
|
||||
return nil, 0, ErrInsufficientFunds
|
||||
}
|
||||
|
||||
return selected, btcout, nil
|
||||
return selected, out, nil
|
||||
}
|
||||
|
||||
// txToPairs creates a raw transaction sending the amounts for each
|
||||
|
@ -142,7 +142,9 @@ func selectInputs(utxos []*tx.RecvTxOut, amt int64,
|
|||
// address, changeUtxo will point to a unconfirmed (height = -1, zeroed
|
||||
// block hash) Utxo. ErrInsufficientFunds is returned if there are not
|
||||
// enough eligible unspent outputs to create the transaction.
|
||||
func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, error) {
|
||||
func (a *Account) txToPairs(pairs map[string]btcutil.Amount,
|
||||
minconf int) (*CreatedTx, error) {
|
||||
|
||||
// Wallet must be unlocked to compose transaction.
|
||||
if a.IsLocked() {
|
||||
return nil, wallet.ErrWalletLocked
|
||||
|
@ -152,7 +154,7 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er
|
|||
msgtx := btcwire.NewMsgTx()
|
||||
|
||||
// Calculate minimum amount needed for inputs.
|
||||
var amt int64
|
||||
var amt btcutil.Amount
|
||||
for _, v := range pairs {
|
||||
// Error out if any amount is negative.
|
||||
if v <= 0 {
|
||||
|
@ -188,21 +190,25 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er
|
|||
// a higher fee if not enough was originally chosen.
|
||||
txNoInputs := msgtx.Copy()
|
||||
|
||||
var selectedInputs []*tx.RecvTxOut
|
||||
unspent, err := a.TxStore.UnspentOutputs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var selectedInputs []*tx.Credit
|
||||
// These are nil/zeroed until a change address is needed, and reused
|
||||
// again in case a change utxo has already been chosen.
|
||||
var changeAddr btcutil.Address
|
||||
|
||||
// Get the number of satoshis to increment fee by when searching for
|
||||
// the minimum tx fee needed.
|
||||
fee := int64(0)
|
||||
fee := btcutil.Amount(0)
|
||||
for {
|
||||
msgtx = txNoInputs.Copy()
|
||||
|
||||
// Select unspent outputs to be used in transaction based on the amount
|
||||
// neededing to sent, and the current fee estimation.
|
||||
inputs, btcin, err := selectInputs(a.TxStore.UnspentOutputs(),
|
||||
amt+fee, minconf)
|
||||
inputs, btcin, err := selectInputs(unspent, amt+fee, minconf)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -262,7 +268,7 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er
|
|||
}
|
||||
|
||||
sigscript, err := btcscript.SignatureScript(msgtx, i,
|
||||
input.PkScript(), btcscript.SigHashAll, privkey,
|
||||
input.TxOut().PkScript, btcscript.SigHashAll, privkey,
|
||||
ai.Compressed())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create sigscript: %s", err)
|
||||
|
@ -290,7 +296,7 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er
|
|||
}
|
||||
for i, txin := range msgtx.TxIn {
|
||||
engine, err := btcscript.NewScript(txin.SignatureScript,
|
||||
selectedInputs[i].PkScript(), i, msgtx, flags)
|
||||
selectedInputs[i].TxOut().PkScript, i, msgtx, flags)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot create script engine: %s", err)
|
||||
}
|
||||
|
@ -304,7 +310,7 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er
|
|||
msgtx.BtcEncode(buf, btcwire.ProtocolVersion)
|
||||
info := &CreatedTx{
|
||||
tx: btcutil.NewTx(msgtx),
|
||||
time: time.Now(),
|
||||
inputs: selectedInputs,
|
||||
changeAddr: changeAddr,
|
||||
}
|
||||
return info, nil
|
||||
|
@ -316,12 +322,12 @@ func (a *Account) txToPairs(pairs map[string]int64, minconf int) (*CreatedTx, er
|
|||
// and none of the outputs contain a value less than 1 bitcent.
|
||||
// Otherwise, the fee will be calculated using TxFeeIncrement,
|
||||
// incrementing the fee for each kilobyte of transaction.
|
||||
func minimumFee(tx *btcwire.MsgTx, allowFree bool) int64 {
|
||||
func minimumFee(tx *btcwire.MsgTx, allowFree bool) btcutil.Amount {
|
||||
txLen := tx.SerializeSize()
|
||||
TxFeeIncrement.Lock()
|
||||
incr := TxFeeIncrement.i
|
||||
TxFeeIncrement.Unlock()
|
||||
fee := int64(1+txLen/1000) * incr
|
||||
fee := btcutil.Amount(int64(1+txLen/1000) * int64(incr))
|
||||
|
||||
if allowFree && txLen < 1000 {
|
||||
fee = 0
|
||||
|
@ -335,8 +341,9 @@ func minimumFee(tx *btcwire.MsgTx, allowFree bool) int64 {
|
|||
}
|
||||
}
|
||||
|
||||
if fee < 0 || fee > btcutil.MaxSatoshi {
|
||||
fee = btcutil.MaxSatoshi
|
||||
max := btcutil.Amount(btcutil.MaxSatoshi)
|
||||
if fee < 0 || fee > max {
|
||||
fee = max
|
||||
}
|
||||
|
||||
return fee
|
||||
|
@ -345,14 +352,14 @@ func minimumFee(tx *btcwire.MsgTx, allowFree bool) int64 {
|
|||
// allowFree calculates the transaction priority and checks that the
|
||||
// priority reaches a certain threshhold. If the threshhold is
|
||||
// reached, a free transaction fee is allowed.
|
||||
func allowFree(curHeight int32, txouts []*tx.RecvTxOut, txSize int) bool {
|
||||
func allowFree(curHeight int32, txouts []*tx.Credit, txSize int) bool {
|
||||
const blocksPerDayEstimate = 144
|
||||
const txSizeEstimate = 250
|
||||
|
||||
var weightedSum int64
|
||||
for _, txout := range txouts {
|
||||
depth := chainDepth(txout.Height(), curHeight)
|
||||
weightedSum += txout.Value() * int64(depth)
|
||||
depth := chainDepth(txout.BlockHeight, curHeight)
|
||||
weightedSum += int64(txout.Amount()) * int64(depth)
|
||||
}
|
||||
priority := float64(weightedSum) / float64(txSize)
|
||||
return priority > float64(btcutil.SatoshiPerBitcoin)*blocksPerDayEstimate/txSizeEstimate
|
||||
|
|
55
ntfns.go
55
ntfns.go
|
@ -33,20 +33,20 @@ import (
|
|||
"github.com/conformal/btcws"
|
||||
)
|
||||
|
||||
func parseBlock(block *btcws.BlockDetails) (*tx.BlockDetails, error) {
|
||||
func parseBlock(block *btcws.BlockDetails) (*tx.Block, int, error) {
|
||||
if block == nil {
|
||||
return nil, nil
|
||||
return nil, btcutil.TxIndexUnknown, nil
|
||||
}
|
||||
blksha, err := btcwire.NewShaHashFromStr(block.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, btcutil.TxIndexUnknown, err
|
||||
}
|
||||
return &tx.BlockDetails{
|
||||
b := &tx.Block{
|
||||
Height: block.Height,
|
||||
Hash: *blksha,
|
||||
Index: int32(block.Index),
|
||||
Time: time.Unix(block.Time, 0),
|
||||
}, nil
|
||||
}
|
||||
return b, block.Index, nil
|
||||
}
|
||||
|
||||
type notificationHandler func(btcjson.Cmd) error
|
||||
|
@ -80,13 +80,11 @@ func NtfnRecvTx(n btcjson.Cmd) error {
|
|||
return fmt.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err)
|
||||
}
|
||||
|
||||
var block *tx.BlockDetails
|
||||
if rtx.Block != nil {
|
||||
block, err = parseBlock(rtx.Block)
|
||||
block, txIdx, err := parseBlock(rtx.Block)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v handler: bad block: %v", n.Method(), err)
|
||||
}
|
||||
}
|
||||
tx_.SetIndex(txIdx)
|
||||
|
||||
// For transactions originating from this wallet, the sent tx history should
|
||||
// be recorded before the received history. If wallet created this tx, wait
|
||||
|
@ -106,14 +104,6 @@ func NtfnRecvTx(n btcjson.Cmd) error {
|
|||
SendTxHistSyncChans.remove <- *tx_.Sha()
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
var received time.Time
|
||||
if block != nil && now.After(block.Time) {
|
||||
received = block.Time
|
||||
} else {
|
||||
received = now
|
||||
}
|
||||
|
||||
// For every output, find all accounts handling that output address (if any)
|
||||
// and record the received txout.
|
||||
for outIdx, txout := range tx_.MsgTx().TxOut {
|
||||
|
@ -128,7 +118,11 @@ func NtfnRecvTx(n btcjson.Cmd) error {
|
|||
}
|
||||
|
||||
for _, a := range accounts {
|
||||
record, err := a.TxStore.InsertRecvTxOut(tx_, uint32(outIdx), false, received, block)
|
||||
txr, err := a.TxStore.InsertTx(tx_, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cred, err := txr.AddCredit(uint32(outIdx), false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -139,21 +133,23 @@ func NtfnRecvTx(n btcjson.Cmd) error {
|
|||
// has already been notified and is now in a block, a txmined notifiction
|
||||
// should be sent once to let frontends that all previous send/recvs
|
||||
// for this unconfirmed tx are now confirmed.
|
||||
recvTxOP := btcwire.NewOutPoint(tx_.Sha(), uint32(outIdx))
|
||||
op := *cred.OutPoint()
|
||||
previouslyNotifiedReq := NotifiedRecvTxRequest{
|
||||
op: *recvTxOP,
|
||||
op: op,
|
||||
response: make(chan NotifiedRecvTxResponse),
|
||||
}
|
||||
NotifiedRecvTxChans.access <- previouslyNotifiedReq
|
||||
if <-previouslyNotifiedReq.response {
|
||||
NotifiedRecvTxChans.remove <- *recvTxOP
|
||||
NotifiedRecvTxChans.remove <- op
|
||||
} else {
|
||||
// Notify frontends of new recv tx and mark as notified.
|
||||
NotifiedRecvTxChans.add <- *recvTxOP
|
||||
NotifiedRecvTxChans.add <- op
|
||||
|
||||
// need access to the RecvTxOut to get the json info object
|
||||
NotifyNewTxDetails(allClients, a.Name(),
|
||||
record.TxInfo(a.Name(), bs.Height, a.Wallet.Net())[0])
|
||||
ltr, err := cred.ToJSON(a.Name(), bs.Height, a.Wallet.Net())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
NotifyNewTxDetails(allClients, a.Name(), ltr)
|
||||
}
|
||||
|
||||
// Notify frontends of new account balance.
|
||||
|
@ -255,13 +251,12 @@ func NtfnRedeemingTx(n btcjson.Cmd) error {
|
|||
return fmt.Errorf("%v handler: bad transaction bytes: %v", n.Method(), err)
|
||||
}
|
||||
|
||||
block, err := parseBlock(cn.Block)
|
||||
block, txIdx, err := parseBlock(cn.Block)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%v handler: bad block: %v", n.Method(), err)
|
||||
}
|
||||
AcctMgr.RecordSpendingTx(tx_, block)
|
||||
|
||||
return nil
|
||||
tx_.SetIndex(txIdx)
|
||||
return AcctMgr.RecordSpendingTx(tx_, block)
|
||||
}
|
||||
|
||||
// NtfnRescanProgress handles btcd rescanprogress notifications resulting
|
||||
|
|
108
rpcserver.go
108
rpcserver.go
|
@ -223,7 +223,7 @@ func WalletRequestProcessor() {
|
|||
err := f(n)
|
||||
AcctMgr.Release()
|
||||
switch err {
|
||||
case tx.ErrInconsistantStore:
|
||||
case tx.ErrInconsistentStore:
|
||||
// Assume this is a broken btcd reordered
|
||||
// notifications. Restart the connection
|
||||
// to reload accounts files from their last
|
||||
|
@ -949,9 +949,9 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
return nil, &btcjson.ErrNoTxInfo
|
||||
}
|
||||
|
||||
var sr *tx.SignedTx
|
||||
var srAccount string
|
||||
var amountReceived int64
|
||||
received := btcutil.Amount(0)
|
||||
var debitTx *tx.TxRecord
|
||||
var debitAccount string
|
||||
|
||||
ret := btcjson.GetTransactionResult{
|
||||
Details: []btcjson.GetTransactionDetailsResult{},
|
||||
|
@ -959,19 +959,20 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
}
|
||||
details := []btcjson.GetTransactionDetailsResult{}
|
||||
for _, e := range accumulatedTxen {
|
||||
switch record := e.Tx.(type) {
|
||||
case *tx.RecvTxOut:
|
||||
if record.Change() {
|
||||
for _, cred := range e.Tx.Credits() {
|
||||
// Change is ignored.
|
||||
if cred.Change() {
|
||||
continue
|
||||
}
|
||||
|
||||
amountReceived += record.Value()
|
||||
_, addrs, _, _ := record.Addresses(cfg.Net())
|
||||
received += cred.Amount()
|
||||
|
||||
var addr string
|
||||
_, addrs, _, _ := cred.Addresses(cfg.Net())
|
||||
if len(addrs) == 1 {
|
||||
addr = addrs[0].EncodeAddress()
|
||||
}
|
||||
|
||||
details = append(details, btcjson.GetTransactionDetailsResult{
|
||||
Account: e.Account,
|
||||
// TODO(oga) We don't mine for now so there
|
||||
|
@ -980,28 +981,31 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
// specially with the category depending on
|
||||
// whether it is an orphan or in the blockchain.
|
||||
Category: "receive",
|
||||
Amount: float64(record.Value()) / float64(btcutil.SatoshiPerBitcoin),
|
||||
Amount: cred.Amount().ToUnit(btcutil.AmountBTC),
|
||||
Address: addr,
|
||||
})
|
||||
case *tx.SignedTx:
|
||||
// there should only be a single SignedTx record, if any.
|
||||
// If found, it will be added to the beginning.
|
||||
sr = record
|
||||
srAccount = e.Account
|
||||
}
|
||||
|
||||
if e.Tx.Debits() != nil {
|
||||
// There should only be a single debits record for any
|
||||
// of the account's transaction records.
|
||||
debitTx = e.Tx
|
||||
debitAccount = e.Account
|
||||
}
|
||||
}
|
||||
|
||||
totalAmount := amountReceived
|
||||
if sr != nil {
|
||||
totalAmount -= sr.TotalSent()
|
||||
totalAmount := received
|
||||
if debitTx != nil {
|
||||
debits := debitTx.Debits()
|
||||
totalAmount -= debits.InputAmount()
|
||||
info := btcjson.GetTransactionDetailsResult{
|
||||
Account: srAccount,
|
||||
Account: debitAccount,
|
||||
Category: "send",
|
||||
// negative since it is a send
|
||||
Amount: float64(-(sr.TotalSent() - amountReceived)) / float64(btcutil.SatoshiPerBitcoin),
|
||||
Fee: float64(sr.Fee()) / float64(btcutil.SatoshiPerBitcoin),
|
||||
Amount: (-debits.OutputAmount(true)).ToUnit(btcutil.AmountBTC),
|
||||
Fee: debits.Fee().ToUnit(btcutil.AmountBTC),
|
||||
}
|
||||
_, addrs, _, _ := btcscript.ExtractPkScriptAddrs(sr.Tx().MsgTx().TxOut[0].PkScript, cfg.Net())
|
||||
_, addrs, _, _ := debitTx.Credits()[0].Addresses(cfg.Net())
|
||||
if len(addrs) == 1 {
|
||||
info.Address = addrs[0].EncodeAddress()
|
||||
}
|
||||
|
@ -1012,10 +1016,11 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
}
|
||||
ret.Details = append(ret.Details, details...)
|
||||
|
||||
ret.Amount = totalAmount.ToUnit(btcutil.AmountBTC)
|
||||
|
||||
// Generic information should be the same, so just use the first one.
|
||||
first := accumulatedTxen[0]
|
||||
ret.Amount = float64(totalAmount) / float64(btcutil.SatoshiPerBitcoin)
|
||||
ret.TxID = first.Tx.TxSha().String()
|
||||
ret.TxID = first.Tx.Tx().Sha().String()
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
buf.Grow(first.Tx.Tx().MsgTx().SerializeSize())
|
||||
|
@ -1032,12 +1037,16 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
// timereceived depending on if a transaction was send or
|
||||
// receive. We ideally should provide the correct numbers for
|
||||
// both. Right now they will always be the same
|
||||
ret.Time = first.Tx.Time().Unix()
|
||||
ret.TimeReceived = first.Tx.Time().Unix()
|
||||
if details := first.Tx.Block(); details != nil {
|
||||
ret.BlockIndex = int64(details.Index)
|
||||
ret.BlockHash = details.Hash.String()
|
||||
ret.BlockTime = details.Time.Unix()
|
||||
ret.Time = first.Tx.Received().Unix()
|
||||
ret.TimeReceived = first.Tx.Received().Unix()
|
||||
if txr := first.Tx; txr.BlockHeight != -1 {
|
||||
txBlock, err := txr.Block()
|
||||
if err != nil {
|
||||
return nil, &btcjson.Error{
|
||||
Code: btcjson.ErrWallet.Code,
|
||||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
bs, err := GetCurBlock()
|
||||
if err != nil {
|
||||
return nil, &btcjson.Error{
|
||||
|
@ -1045,7 +1054,10 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
Message: err.Error(),
|
||||
}
|
||||
}
|
||||
ret.Confirmations = int64(bs.Height - details.Height + 1)
|
||||
ret.BlockIndex = int64(first.Tx.Tx().Index())
|
||||
ret.BlockHash = txBlock.Hash.String()
|
||||
ret.BlockTime = txBlock.Time.Unix()
|
||||
ret.Confirmations = int64(confirms(txr.BlockHeight, bs.Height))
|
||||
}
|
||||
// TODO(oga) if the tx is a coinbase we should set "generated" to true.
|
||||
// Since we do not mine this currently is never the case.
|
||||
|
@ -1312,7 +1324,7 @@ func ListUnspent(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
|
||||
// sendPairs is a helper routine to reduce duplicated code when creating and
|
||||
// sending payment transactions.
|
||||
func sendPairs(icmd btcjson.Cmd, account string, amounts map[string]int64,
|
||||
func sendPairs(icmd btcjson.Cmd, account string, amounts map[string]btcutil.Amount,
|
||||
minconf int) (interface{}, *btcjson.Error) {
|
||||
// Check that the account specified in the request exists.
|
||||
a, err := AcctMgr.Account(account)
|
||||
|
@ -1401,8 +1413,8 @@ func SendFrom(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
return nil, &e
|
||||
}
|
||||
// Create map of address and amount pairs.
|
||||
pairs := map[string]int64{
|
||||
cmd.ToAddress: cmd.Amount,
|
||||
pairs := map[string]btcutil.Amount{
|
||||
cmd.ToAddress: btcutil.Amount(cmd.Amount),
|
||||
}
|
||||
|
||||
return sendPairs(cmd, cmd.FromAccount, pairs, cmd.MinConf)
|
||||
|
@ -1429,7 +1441,13 @@ func SendMany(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
return nil, &e
|
||||
}
|
||||
|
||||
return sendPairs(cmd, cmd.FromAccount, cmd.Amounts, cmd.MinConf)
|
||||
// Recreate address/amount pairs, using btcutil.Amount.
|
||||
pairs := make(map[string]btcutil.Amount, len(cmd.Amounts))
|
||||
for k, v := range cmd.Amounts {
|
||||
pairs[k] = btcutil.Amount(v)
|
||||
}
|
||||
|
||||
return sendPairs(cmd, cmd.FromAccount, pairs, cmd.MinConf)
|
||||
}
|
||||
|
||||
// SendToAddress handles a sendtoaddress RPC request by creating a new
|
||||
|
@ -1454,8 +1472,8 @@ func SendToAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
}
|
||||
|
||||
// Mock up map of address and amount pairs.
|
||||
pairs := map[string]int64{
|
||||
cmd.Address: cmd.Amount,
|
||||
pairs := map[string]btcutil.Amount{
|
||||
cmd.Address: btcutil.Amount(cmd.Amount),
|
||||
}
|
||||
|
||||
return sendPairs(cmd, "", pairs, 1)
|
||||
|
@ -1518,7 +1536,12 @@ func SendBeforeReceiveHistorySync(add, done, remove chan btcwire.ShaHash,
|
|||
|
||||
func handleSendRawTxReply(icmd btcjson.Cmd, txIDStr string, a *Account, txInfo *CreatedTx) (interface{}, *btcjson.Error) {
|
||||
// Add to transaction store.
|
||||
stx, err := a.TxStore.InsertSignedTx(txInfo.tx, time.Now(), nil)
|
||||
txr, err := a.TxStore.InsertTx(txInfo.tx, nil)
|
||||
if err != nil {
|
||||
log.Warnf("Error adding sent tx history: %v", err)
|
||||
return nil, &btcjson.ErrInternal
|
||||
}
|
||||
debits, err := txr.AddDebits(txInfo.inputs)
|
||||
if err != nil {
|
||||
log.Warnf("Error adding sent tx history: %v", err)
|
||||
return nil, &btcjson.ErrInternal
|
||||
|
@ -1528,7 +1551,12 @@ func handleSendRawTxReply(icmd btcjson.Cmd, txIDStr string, a *Account, txInfo *
|
|||
// Notify frontends of new SendTx.
|
||||
bs, err := GetCurBlock()
|
||||
if err == nil {
|
||||
for _, details := range stx.TxInfo(a.Name(), bs.Height, a.Net()) {
|
||||
ltr, err := debits.ToJSON(a.Name(), bs.Height, a.Net())
|
||||
if err != nil {
|
||||
log.Warnf("Error adding sent tx history: %v", err)
|
||||
return nil, &btcjson.ErrInternal
|
||||
}
|
||||
for _, details := range ltr {
|
||||
NotifyNewTxDetails(allClients, a.Name(), details)
|
||||
}
|
||||
}
|
||||
|
@ -1589,7 +1617,7 @@ func SetTxFee(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|||
|
||||
// Set global tx fee.
|
||||
TxFeeIncrement.Lock()
|
||||
TxFeeIncrement.i = cmd.Amount
|
||||
TxFeeIncrement.i = btcutil.Amount(cmd.Amount)
|
||||
TxFeeIncrement.Unlock()
|
||||
|
||||
// A boolean true result is returned upon success.
|
||||
|
|
16
sockets.go
16
sockets.go
|
@ -740,8 +740,8 @@ func Handshake(rpc ServerConn) error {
|
|||
log.Debugf("Checking for previous saved block with height %v hash %v",
|
||||
bs.Height, bs.Hash)
|
||||
|
||||
_, err := GetBlock(rpc, bs.Hash.String())
|
||||
if err != nil {
|
||||
_, jsonErr := GetBlock(rpc, bs.Hash.String())
|
||||
if jsonErr != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -763,7 +763,14 @@ func Handshake(rpc ServerConn) error {
|
|||
|
||||
// Begin tracking wallets against this btcd instance.
|
||||
AcctMgr.Track()
|
||||
AcctMgr.RescanActiveAddresses()
|
||||
if err := AcctMgr.RescanActiveAddresses(); err != nil {
|
||||
return err
|
||||
}
|
||||
// TODO: Only begin tracking new unspent outputs as a result
|
||||
// of the rescan. This is also pretty racy, as a new block
|
||||
// could arrive between rescan and by the time the new outpoint
|
||||
// is added to btcd's websocket's unspent output set.
|
||||
AcctMgr.Track()
|
||||
|
||||
// (Re)send any unmined transactions to btcd in case of a btcd restart.
|
||||
AcctMgr.ResendUnminedTxs()
|
||||
|
@ -782,6 +789,9 @@ func Handshake(rpc ServerConn) error {
|
|||
a.fullRescan = true
|
||||
AcctMgr.Track()
|
||||
AcctMgr.RescanActiveAddresses()
|
||||
// TODO: only begin tracking new unspent outputs as a result of the
|
||||
// rescan. This is also racy (see comment for second Track above).
|
||||
AcctMgr.Track()
|
||||
AcctMgr.ResendUnminedTxs()
|
||||
return nil
|
||||
}
|
||||
|
|
129
tx/json.go
Normal file
129
tx/json.go
Normal file
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
|
||||
*
|
||||
* 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 tx
|
||||
|
||||
import (
|
||||
"github.com/conformal/btcjson"
|
||||
"github.com/conformal/btcscript"
|
||||
"github.com/conformal/btcutil"
|
||||
"github.com/conformal/btcwire"
|
||||
)
|
||||
|
||||
// ToJSON returns a slice of btcjson listtransaction result types for all credits
|
||||
// and debits of this transaction.
|
||||
func (t *TxRecord) ToJSON(account string, chainHeight int32,
|
||||
net btcwire.BitcoinNet) ([]btcjson.ListTransactionsResult, error) {
|
||||
|
||||
var results []btcjson.ListTransactionsResult
|
||||
if d := t.Debits(); d != nil {
|
||||
r, err := d.ToJSON(account, chainHeight, net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = r
|
||||
}
|
||||
for _, c := range t.Credits() {
|
||||
r, err := c.ToJSON(account, chainHeight, net)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, r)
|
||||
}
|
||||
return results, nil
|
||||
}
|
||||
|
||||
// ToJSON returns a slice of objects that may be marshaled as a JSON array
|
||||
// of JSON objects for a listtransactions RPC reply.
|
||||
func (d *Debits) ToJSON(account string, chainHeight int32,
|
||||
net btcwire.BitcoinNet) ([]btcjson.ListTransactionsResult, error) {
|
||||
|
||||
msgTx := d.Tx().MsgTx()
|
||||
reply := make([]btcjson.ListTransactionsResult, 0, len(msgTx.TxOut))
|
||||
|
||||
for _, txOut := range msgTx.TxOut {
|
||||
address := ""
|
||||
_, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txOut.PkScript, net)
|
||||
if len(addrs) == 1 {
|
||||
address = addrs[0].EncodeAddress()
|
||||
}
|
||||
|
||||
result := btcjson.ListTransactionsResult{
|
||||
Account: account,
|
||||
Address: address,
|
||||
Category: "send",
|
||||
Amount: btcutil.Amount(-txOut.Value).ToUnit(btcutil.AmountBTC),
|
||||
Fee: d.Fee().ToUnit(btcutil.AmountBTC),
|
||||
TxID: d.Tx().Sha().String(),
|
||||
Time: d.txRecord.received.Unix(),
|
||||
TimeReceived: d.txRecord.received.Unix(),
|
||||
WalletConflicts: []string{},
|
||||
}
|
||||
if d.BlockHeight != -1 {
|
||||
b, err := d.s.lookupBlock(d.BlockHeight)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
result.BlockHash = b.Hash.String()
|
||||
result.BlockIndex = int64(d.Tx().Index())
|
||||
result.BlockTime = b.Time.Unix()
|
||||
result.Confirmations = int64(confirms(b.Height, chainHeight))
|
||||
}
|
||||
reply = append(reply, result)
|
||||
}
|
||||
|
||||
return reply, nil
|
||||
}
|
||||
|
||||
// ToJSON returns a slice of objects that may be marshaled as a JSON array
|
||||
// of JSON objects for a listtransactions RPC reply.
|
||||
func (c *Credit) ToJSON(account string, chainHeight int32,
|
||||
net btcwire.BitcoinNet) (btcjson.ListTransactionsResult, error) {
|
||||
|
||||
msgTx := c.Tx().MsgTx()
|
||||
txout := msgTx.TxOut[c.OutputIndex]
|
||||
|
||||
var address string
|
||||
_, addrs, _, _ := btcscript.ExtractPkScriptAddrs(txout.PkScript, net)
|
||||
if len(addrs) == 1 {
|
||||
address = addrs[0].EncodeAddress()
|
||||
}
|
||||
|
||||
result := btcjson.ListTransactionsResult{
|
||||
Account: account,
|
||||
Category: "receive",
|
||||
Address: address,
|
||||
Amount: btcutil.Amount(txout.Value).ToUnit(btcutil.AmountBTC),
|
||||
TxID: c.Tx().Sha().String(),
|
||||
Time: c.received.Unix(),
|
||||
TimeReceived: c.received.Unix(),
|
||||
WalletConflicts: []string{},
|
||||
}
|
||||
if c.BlockHeight != -1 {
|
||||
b, err := c.s.lookupBlock(c.BlockHeight)
|
||||
if err != nil {
|
||||
return btcjson.ListTransactionsResult{}, err
|
||||
}
|
||||
|
||||
result.BlockHash = b.Hash.String()
|
||||
result.BlockIndex = int64(c.Tx().Index())
|
||||
result.BlockTime = b.Time.Unix()
|
||||
result.Confirmations = int64(confirms(b.Height, chainHeight))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
1121
tx/serialization.go
Normal file
1121
tx/serialization.go
Normal file
File diff suppressed because it is too large
Load diff
364
tx/tx_test.go
364
tx/tx_test.go
|
@ -1,18 +1,16 @@
|
|||
/*
|
||||
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
// Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
|
||||
//
|
||||
// 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 tx_test
|
||||
|
||||
|
@ -34,27 +32,29 @@ var (
|
|||
TstRecvTx, _ = btcutil.NewTxFromBytes(TstRecvSerializedTx)
|
||||
TstRecvTxSpendingTxBlockHash, _ = btcwire.NewShaHashFromStr("00000000000000017188b968a371bab95aa43522665353b646e41865abae02a4")
|
||||
TstRecvAmt = int64(10000000)
|
||||
TstRecvTxBlockDetails = &BlockDetails{
|
||||
TstRecvIndex = 684
|
||||
TstRecvTxBlockDetails = &Block{
|
||||
Height: 276425,
|
||||
Hash: *TstRecvTxSpendingTxBlockHash,
|
||||
Index: 684,
|
||||
Time: time.Unix(1387737310, 0),
|
||||
}
|
||||
|
||||
TstRecvCurrentHeight = int32(284498) // mainnet blockchain height at time of writing
|
||||
TstRecvTxOutConfirms = 8074 // hardcoded number of confirmations given the above block height
|
||||
|
||||
TstSpendingSerializedTx, _ = hex.DecodeString("0100000003ad3fba7ebd67c09baa9538898e10d6726dcb8eadb006be0c7388c8e46d69d361000000006b4830450220702c4fbde5532575fed44f8d6e8c3432a2a9bd8cff2f966c3a79b2245a7c88db02210095d6505a57e350720cb52b89a9b56243c15ddfcea0596aedc1ba55d9fb7d5aa0012103cccb5c48a699d3efcca6dae277fee6b82e0229ed754b742659c3acdfed2651f9ffffffffdbd36173f5610e34de5c00ed092174603761595d90190f790e79cda3e5b45bc2010000006b483045022000fa20735e5875e64d05bed43d81b867f3bd8745008d3ff4331ef1617eac7c44022100ad82261fc57faac67fc482a37b6bf18158da0971e300abf5fe2f9fd39e107f58012102d4e1caf3e022757512c204bf09ff56a9981df483aba3c74bb60d3612077c9206ffffffff65536c9d964b6f89b8ef17e83c6666641bc495cb27bab60052f76cd4556ccd0d040000006a473044022068e3886e0299ffa69a1c3ee40f8b6700f5f6d463a9cf9dbf22c055a131fc4abc02202b58957fe19ff1be7a84c458d08016c53fbddec7184ac5e633f2b282ae3420ae012103b4e411b81d32a69fb81178a8ea1abaa12f613336923ee920ffbb1b313af1f4d2ffffffff02ab233200000000001976a91418808b2fbd8d2c6d022aed5cd61f0ce6c0a4cbb688ac4741f011000000001976a914f081088a300c80ce36b717a9914ab5ec8a7d283988ac00000000")
|
||||
TstSpendingTx, _ = btcutil.NewTxFromBytes(TstSpendingSerializedTx)
|
||||
TstSpendingTxBlockHeight = int32(279143)
|
||||
TstSignedTxBlockHash, _ = btcwire.NewShaHashFromStr("00000000000000017188b968a371bab95aa43522665353b646e41865abae02a4")
|
||||
TstSignedTxBlockDetails = &BlockDetails{
|
||||
TstSignedTxIndex = 123
|
||||
TstSignedTxBlockDetails = &Block{
|
||||
Height: TstSpendingTxBlockHeight,
|
||||
Hash: *TstSignedTxBlockHash,
|
||||
Index: 123,
|
||||
Time: time.Unix(1389114091, 0),
|
||||
}
|
||||
)
|
||||
|
||||
func TestTxStore(t *testing.T) {
|
||||
func TestInsertsCreditsDebitsRollbacks(t *testing.T) {
|
||||
// Create a double spend of the received blockchain transaction.
|
||||
dupRecvTx, _ := btcutil.NewTxFromBytes(TstRecvSerializedTx)
|
||||
// Switch txout amount to 1 BTC. Transaction store doesn't
|
||||
|
@ -75,12 +75,12 @@ func TestTxStore(t *testing.T) {
|
|||
spendingTx.AddTxOut(spendingTxOut1)
|
||||
spendingTx.AddTxOut(spendingTxOut2)
|
||||
TstSpendingTx := btcutil.NewTx(spendingTx)
|
||||
var _ = TstSpendingTx
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
f func(*Store) (*Store, error)
|
||||
err error
|
||||
bal, unc int64
|
||||
bal, unc btcutil.Amount
|
||||
unspents map[btcwire.OutPoint]struct{}
|
||||
unmined map[btcwire.ShaHash]struct{}
|
||||
}{
|
||||
|
@ -89,7 +89,6 @@ func TestTxStore(t *testing.T) {
|
|||
f: func(_ *Store) (*Store, error) {
|
||||
return NewStore(), nil
|
||||
},
|
||||
err: nil,
|
||||
bal: 0,
|
||||
unc: 0,
|
||||
unspents: map[btcwire.OutPoint]struct{}{},
|
||||
|
@ -98,19 +97,25 @@ func TestTxStore(t *testing.T) {
|
|||
{
|
||||
name: "txout insert",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, time.Now(), nil)
|
||||
r, err := s.InsertTx(TstRecvTx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.AddCredit(0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Verify that we can create the JSON output without any
|
||||
// errors.
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// If the above succeeded, try using the record. This will
|
||||
// dereference the tx and panic if the above didn't catch
|
||||
// an inconsistant insert.
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: 0,
|
||||
unc: TstRecvTx.MsgTx().TxOut[0].Value,
|
||||
unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
|
||||
},
|
||||
|
@ -119,16 +124,24 @@ func TestTxStore(t *testing.T) {
|
|||
{
|
||||
name: "insert duplicate unconfirmed",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, time.Now(), nil)
|
||||
r, err := s.InsertTx(TstRecvTx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.AddCredit(0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: 0,
|
||||
unc: TstRecvTx.MsgTx().TxOut[0].Value,
|
||||
unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
|
||||
},
|
||||
|
@ -137,15 +150,24 @@ func TestTxStore(t *testing.T) {
|
|||
{
|
||||
name: "confirmed txout insert",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails)
|
||||
TstRecvTx.SetIndex(TstRecvIndex)
|
||||
r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.AddCredit(0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: TstRecvTx.MsgTx().TxOut[0].Value,
|
||||
bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
|
||||
unc: 0,
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
|
||||
|
@ -155,15 +177,24 @@ func TestTxStore(t *testing.T) {
|
|||
{
|
||||
name: "insert duplicate confirmed",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails)
|
||||
TstRecvTx.SetIndex(TstRecvIndex)
|
||||
r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.AddCredit(0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: TstRecvTx.MsgTx().TxOut[0].Value,
|
||||
bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
|
||||
unc: 0,
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
|
||||
|
@ -171,29 +202,43 @@ func TestTxStore(t *testing.T) {
|
|||
unmined: map[btcwire.ShaHash]struct{}{},
|
||||
},
|
||||
{
|
||||
name: "insert duplicate unconfirmed",
|
||||
name: "rollback confirmed credit",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, time.Now(), nil)
|
||||
err := s.Rollback(TstRecvTxBlockDetails.Height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: ErrInconsistantStore,
|
||||
bal: 0,
|
||||
unc: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
|
||||
},
|
||||
unmined: map[btcwire.ShaHash]struct{}{},
|
||||
},
|
||||
{
|
||||
name: "insert double spend with new txout value",
|
||||
name: "insert confirmed double spend",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertRecvTxOut(TstDoubleSpendTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails)
|
||||
TstDoubleSpendTx.SetIndex(TstRecvIndex)
|
||||
r, err := s.InsertTx(TstDoubleSpendTx, TstRecvTxBlockDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.AddCredit(0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: TstDoubleSpendTx.MsgTx().TxOut[0].Value,
|
||||
bal: btcutil.Amount(TstDoubleSpendTx.MsgTx().TxOut[0].Value),
|
||||
unc: 0,
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstDoubleSpendTx.Sha(), 0): {},
|
||||
|
@ -201,16 +246,29 @@ func TestTxStore(t *testing.T) {
|
|||
unmined: map[btcwire.ShaHash]struct{}{},
|
||||
},
|
||||
{
|
||||
name: "insert unconfirmed signed tx",
|
||||
name: "insert unconfirmed debit",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertSignedTx(TstSpendingTx, time.Now(), nil)
|
||||
prev, err := s.InsertTx(TstDoubleSpendTx, TstRecvTxBlockDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := s.InsertTx(TstSpendingTx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.AddDebits(prev.Credits())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: 0,
|
||||
unc: 0,
|
||||
unspents: map[btcwire.OutPoint]struct{}{},
|
||||
|
@ -219,16 +277,29 @@ func TestTxStore(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "insert unconfirmed signed tx again",
|
||||
name: "insert unconfirmed debit again",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertSignedTx(TstSpendingTx, time.Now(), nil)
|
||||
prev, err := s.InsertTx(TstDoubleSpendTx, TstRecvTxBlockDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := s.InsertTx(TstSpendingTx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.AddDebits(prev.Credits())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: 0,
|
||||
unc: 0,
|
||||
unspents: map[btcwire.OutPoint]struct{}{},
|
||||
|
@ -239,16 +310,24 @@ func TestTxStore(t *testing.T) {
|
|||
{
|
||||
name: "insert change (index 0)",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertRecvTxOut(TstSpendingTx, 0, true, time.Now(), nil)
|
||||
r, err := s.InsertTx(TstSpendingTx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.AddCredit(0, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: 0,
|
||||
unc: TstSpendingTx.MsgTx().TxOut[0].Value,
|
||||
unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value),
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
|
||||
},
|
||||
|
@ -259,16 +338,24 @@ func TestTxStore(t *testing.T) {
|
|||
{
|
||||
name: "insert output back to this own wallet (index 1)",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertRecvTxOut(TstSpendingTx, 1, true, time.Now(), nil)
|
||||
r, err := s.InsertTx(TstSpendingTx, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.AddCredit(1, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: 0,
|
||||
unc: TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value,
|
||||
unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
|
||||
*btcwire.NewOutPoint(TstSpendingTx.Sha(), 1): {},
|
||||
|
@ -278,17 +365,21 @@ func TestTxStore(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "confirmed signed tx",
|
||||
name: "confirm signed tx",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertSignedTx(TstSpendingTx, TstSignedTxBlockDetails.Time, TstSignedTxBlockDetails)
|
||||
TstSpendingTx.SetIndex(TstSignedTxIndex)
|
||||
r, err := s.InsertTx(TstSpendingTx, TstSignedTxBlockDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value,
|
||||
bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
|
||||
unc: 0,
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
|
||||
|
@ -299,11 +390,13 @@ func TestTxStore(t *testing.T) {
|
|||
{
|
||||
name: "rollback after spending tx",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
s.Rollback(TstSignedTxBlockDetails.Height + 1)
|
||||
err := s.Rollback(TstSignedTxBlockDetails.Height + 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value,
|
||||
bal: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
|
||||
unc: 0,
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
|
||||
|
@ -314,12 +407,14 @@ func TestTxStore(t *testing.T) {
|
|||
{
|
||||
name: "rollback spending tx block",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
s.Rollback(TstSignedTxBlockDetails.Height)
|
||||
err := s.Rollback(TstSignedTxBlockDetails.Height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: 0,
|
||||
unc: TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value,
|
||||
unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
|
||||
*btcwire.NewOutPoint(TstSpendingTx.Sha(), 1): {},
|
||||
|
@ -331,12 +426,14 @@ func TestTxStore(t *testing.T) {
|
|||
{
|
||||
name: "rollback double spend tx block",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
s.Rollback(TstRecvTxBlockDetails.Height)
|
||||
err := s.Rollback(TstRecvTxBlockDetails.Height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: 0,
|
||||
unc: TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value,
|
||||
unc: btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value + TstSpendingTx.MsgTx().TxOut[1].Value),
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstSpendingTx.Sha(), 0): {},
|
||||
*btcwire.NewOutPoint(TstSpendingTx.Sha(), 1): {},
|
||||
|
@ -348,15 +445,24 @@ func TestTxStore(t *testing.T) {
|
|||
{
|
||||
name: "insert original recv txout",
|
||||
f: func(s *Store) (*Store, error) {
|
||||
r, err := s.InsertRecvTxOut(TstRecvTx, 0, false, TstRecvTxBlockDetails.Time, TstRecvTxBlockDetails)
|
||||
TstRecvTx.SetIndex(TstRecvIndex)
|
||||
r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.AddCredit(0, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = r.ToJSON("", 100, btcwire.MainNet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_ = r.TxInfo("", 100, btcwire.MainNet)
|
||||
return s, nil
|
||||
},
|
||||
err: nil,
|
||||
bal: TstRecvTx.MsgTx().TxOut[0].Value,
|
||||
bal: btcutil.Amount(TstRecvTx.MsgTx().TxOut[0].Value),
|
||||
unc: 0,
|
||||
unspents: map[btcwire.OutPoint]struct{}{
|
||||
*btcwire.NewOutPoint(TstRecvTx.Sha(), 0): {},
|
||||
|
@ -368,29 +474,37 @@ func TestTxStore(t *testing.T) {
|
|||
var s *Store
|
||||
for _, test := range tests {
|
||||
tmpStore, err := test.f(s)
|
||||
if err != test.err {
|
||||
t.Fatalf("%s: error mismatch: expected: %v, got: %v", test.name, test.err, err)
|
||||
}
|
||||
if test.err != nil {
|
||||
continue
|
||||
if err != nil {
|
||||
t.Fatalf("%s: got error: %v", test.name, err)
|
||||
}
|
||||
s = tmpStore
|
||||
bal := s.Balance(1, TstRecvCurrentHeight)
|
||||
if bal != test.bal {
|
||||
t.Errorf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal)
|
||||
bal, err := s.Balance(1, TstRecvCurrentHeight)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Confirmed Balance() failed: %v", test.name, err)
|
||||
}
|
||||
unc := s.Balance(0, TstRecvCurrentHeight) - bal
|
||||
if bal != test.bal {
|
||||
t.Fatalf("%s: balance mismatch: expected: %d, got: %d", test.name, test.bal, bal)
|
||||
}
|
||||
unc, err := s.Balance(0, TstRecvCurrentHeight)
|
||||
if err != nil {
|
||||
t.Fatalf("%s: Unconfirmed Balance() failed: %v", test.name, err)
|
||||
}
|
||||
unc -= bal
|
||||
if unc != test.unc {
|
||||
t.Errorf("%s: unconfimred balance mismatch: expected %d, got %d", test.name, test.unc, unc)
|
||||
t.Errorf("%s: unconfirmed balance mismatch: expected %d, got %d", test.name, test.unc, unc)
|
||||
}
|
||||
|
||||
// Check that unspent outputs match expected.
|
||||
for _, record := range s.UnspentOutputs() {
|
||||
if record.Spent() {
|
||||
unspent, err := s.UnspentOutputs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, r := range unspent {
|
||||
if r.Spent() {
|
||||
t.Errorf("%s: unspent record marked as spent", test.name)
|
||||
}
|
||||
|
||||
op := *record.OutPoint()
|
||||
op := *r.OutPoint()
|
||||
if _, ok := test.unspents[op]; !ok {
|
||||
t.Errorf("%s: unexpected unspent output: %v", test.name, op)
|
||||
}
|
||||
|
@ -400,10 +514,10 @@ func TestTxStore(t *testing.T) {
|
|||
t.Errorf("%s: missing expected unspent output(s)", test.name)
|
||||
}
|
||||
|
||||
// Check that unmined signed txs match expected.
|
||||
for _, tx := range s.UnminedSignedTxs() {
|
||||
// Check that unmined sent txs match expected.
|
||||
for _, tx := range s.UnminedDebitTxs() {
|
||||
if _, ok := test.unmined[*tx.Sha()]; !ok {
|
||||
t.Errorf("%s: unexpected unmined signed tx: %v", test.name, *tx.Sha())
|
||||
t.Fatalf("%s: unexpected unmined signed tx: %v", test.name, *tx.Sha())
|
||||
}
|
||||
delete(test.unmined, *tx.Sha())
|
||||
}
|
||||
|
@ -431,3 +545,51 @@ func TestTxStore(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindingSpentCredits(t *testing.T) {
|
||||
s := NewStore()
|
||||
|
||||
// Insert transaction and credit which will be spent.
|
||||
r, err := s.InsertTx(TstRecvTx, TstRecvTxBlockDetails)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = r.AddCredit(0, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Insert confirmed transaction which spends the above credit.
|
||||
TstSpendingTx.SetIndex(TstSignedTxIndex)
|
||||
r2, err := s.InsertTx(TstSpendingTx, TstSignedTxBlockDetails)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = r2.AddCredit(0, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, err = r2.AddDebits(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
bal, err := s.Balance(1, TstSignedTxBlockDetails.Height)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if bal != btcutil.Amount(TstSpendingTx.MsgTx().TxOut[0].Value) {
|
||||
t.Fatal("bad balance")
|
||||
}
|
||||
unspents, err := s.UnspentOutputs()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
op := btcwire.NewOutPoint(TstSpendingTx.Sha(), 0)
|
||||
if *unspents[0].OutPoint() != *op {
|
||||
t.Fatal("unspent outpoint doesn't match expected")
|
||||
}
|
||||
if len(unspents) > 1 {
|
||||
t.Fatal("has more than one unspent credit")
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue