Skip to content
This repository has been archived by the owner on Nov 2, 2018. It is now read-only.

[WIP] Improve unconfirmed txn handling in wallet #2641

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
10 changes: 10 additions & 0 deletions build/errors.go
Expand Up @@ -24,6 +24,16 @@ func ComposeErrors(errs ...error) error {
return nil
}

// If there is only a single error return it directly. This still allows
// callers of functions to check for specific errors
if len(errStrings) == 1 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will have to be added to NebulousLabs/errors as well. We should make a PR that strips out build/errors.go.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should create an issue for that to make sure we don't forget about it.

for _, err := range errs {
if err != nil {
return err
}
}
}

// Combine all of the non-nil errors into one larger return value.
return errors.New(strings.Join(errStrings, "; "))
}
Expand Down
18 changes: 12 additions & 6 deletions modules/transactionpool/consts.go
Expand Up @@ -28,12 +28,6 @@ const (
// default size during times of congestion.
TransactionPoolExponentiation = 3

// TransactionPoolSizeForFee defines how large the transaction pool needs to
// be before it starts expecting fees to be on the transaction. This initial
// limit is to help the network grow and provide some wiggle room for
// wallets that are not yet able to operate via a fee market.
TransactionPoolSizeForFee = 500e3

// TransactionPoolSizeTarget defines the target size of the pool when the
// transactions are paying 1 SC / kb in fees.
TransactionPoolSizeTarget = 3e6
Expand Down Expand Up @@ -71,6 +65,18 @@ var (
minEstimation = types.SiacoinPrecision.Div64(100).Div64(1e3)
)

var (
// TransactionPoolSizeForFee defines how large the transaction pool needs to
// be before it starts expecting fees to be on the transaction. This initial
// limit is to help the network grow and provide some wiggle room for
// wallets that are not yet able to operate via a fee market.
TransactionPoolSizeForFee = build.Select(build.Var{
Standard: int(500e3),
Dev: int(500e3),
Testing: int(30e3),
}).(int)
)

// Variables related to propagating transactions through the network.
var (
// relayTransactionSetTimeout establishes the timeout for a relay
Expand Down
6 changes: 6 additions & 0 deletions modules/wallet/consts.go
Expand Up @@ -15,6 +15,12 @@ const (
// defragThreshold is the number of outputs a wallet is allowed before it is
// defragmented.
defragThreshold = 50

// respendTimeout records the number of blocks that the wallet will wait
// before spending an output that has been spent in the past. If the
// transaction spending the output has not made it to the transaction pool
// after the limit, the assumption is that it never will.
respendTimeout = 40
)

var (
Expand Down
27 changes: 26 additions & 1 deletion modules/wallet/database.go
Expand Up @@ -3,9 +3,11 @@ package wallet
import (
"encoding/binary"
"errors"
"log"
"reflect"
"time"

"github.com/NebulousLabs/Sia/build"
"github.com/NebulousLabs/Sia/encoding"
"github.com/NebulousLabs/Sia/modules"
"github.com/NebulousLabs/Sia/types"
Expand Down Expand Up @@ -41,6 +43,8 @@ var (
// bucketWallet contains various fields needed by the wallet, such as its
// UID, EncryptionVerification, and PrimarySeedFile.
bucketWallet = []byte("bucketWallet")
// bucketUnconfirmedSets contains the unconfirmedSets field of the wallet
bucketUnconfirmedSets = []byte("bucketUnconfirmedSets")

dbBuckets = [][]byte{
bucketProcessedTransactions,
Expand All @@ -49,6 +53,7 @@ var (
bucketSiacoinOutputs,
bucketSiafundOutputs,
bucketSpentOutputs,
bucketUnconfirmedSets,
bucketWallet,
}

Expand Down Expand Up @@ -211,14 +216,34 @@ func dbGetSpentOutput(tx *bolt.Tx, id types.OutputID) (height types.BlockHeight,
func dbDeleteSpentOutput(tx *bolt.Tx, id types.OutputID) error {
return dbDelete(tx.Bucket(bucketSpentOutputs), id)
}

func dbPutAddrTransactions(tx *bolt.Tx, addr types.UnlockHash, txns []uint64) error {
return dbPut(tx.Bucket(bucketAddrTransactions), addr, txns)
}
func dbGetAddrTransactions(tx *bolt.Tx, addr types.UnlockHash) (txns []uint64, err error) {
err = dbGet(tx.Bucket(bucketAddrTransactions), addr, &txns)
return
}
func dbPutUnconfirmedSet(tx *bolt.Tx, tSetID modules.TransactionSetID, ids []types.TransactionID) error {
return dbPut(tx.Bucket(bucketUnconfirmedSets), tSetID, ids)
}
func dbDeleteUnconfirmedSet(tx *bolt.Tx, tSetID modules.TransactionSetID) error {
return dbDelete(tx.Bucket(bucketUnconfirmedSets), tSetID)
}
func dbLoadUnconfirmedSets(tx *bolt.Tx) (map[modules.TransactionSetID][]types.TransactionID, error) {
sets := make(map[modules.TransactionSetID][]types.TransactionID)
err := tx.Bucket(bucketUnconfirmedSets).ForEach(func(k []byte, v []byte) error {
var tSetID modules.TransactionSetID
var ids []types.TransactionID
err := build.ComposeErrors(encoding.Unmarshal(k, &tSetID), encoding.Unmarshal(v, &ids))
if err != nil {
log.Println(err)
return err
}
sets[tSetID] = ids
return nil
})
return sets, err
}

// dbAddAddrTransaction appends a single transaction index to the set of
// transactions associated with addr. If the index is already in the set, it is
Expand Down
2 changes: 1 addition & 1 deletion modules/wallet/defrag.go
Expand Up @@ -175,7 +175,7 @@ func (w *Wallet) threadedDefragWallet() {
return
}
// Submit the defrag to the transaction pool.
err = w.tpool.AcceptTransactionSet(txnSet)
err = w.managedCommitTransactionSet(txnSet)
if err != nil {
w.log.Println("WARN: defrag transaction was rejected:", err)
return
Expand Down
4 changes: 2 additions & 2 deletions modules/wallet/defrag_test.go
Expand Up @@ -96,7 +96,7 @@ func TestDefragWalletDust(t *testing.T) {
t.Fatal(err)
}

err = wt.tpool.AcceptTransactionSet(txns)
err = wt.wallet.managedCommitTransactionSet(txns)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -179,7 +179,7 @@ func TestDefragOutputExhaustion(t *testing.T) {
if err != nil {
t.Error("Error signing fragmenting transaction:", err)
}
err = wt.tpool.AcceptTransactionSet(txns)
err = wt.wallet.managedCommitTransactionSet(txns)
if err != nil {
t.Error("Error accepting fragmenting transaction:", err)
}
Expand Down
6 changes: 3 additions & 3 deletions modules/wallet/money.go
Expand Up @@ -126,7 +126,7 @@ func (w *Wallet) SendSiacoins(amount types.Currency, dest types.UnlockHash) (txn
if w.deps.Disrupt("SendSiacoinsInterrupted") {
return nil, errors.New("failed to accept transaction set (SendSiacoinsInterrupted)")
}
err = w.tpool.AcceptTransactionSet(txnSet)
err = w.managedCommitTransactionSet(txnSet)
if err != nil {
w.log.Println("Attempt to send coins has failed - transaction pool rejected transaction:", err)
return nil, build.ExtendErr("unable to get transaction accepted", err)
Expand Down Expand Up @@ -195,7 +195,7 @@ func (w *Wallet) SendSiacoinsMulti(outputs []types.SiacoinOutput) (txns []types.
return nil, errors.New("failed to accept transaction set (SendSiacoinsInterrupted)")
}
w.log.Println("Attempting to broadcast a multi-send over the network")
err = w.tpool.AcceptTransactionSet(txnSet)
err = w.managedCommitTransactionSet(txnSet)
if err != nil {
w.log.Println("Attempt to send coins has failed - transaction pool rejected transaction:", err)
return nil, build.ExtendErr("unable to get transaction accepted", err)
Expand Down Expand Up @@ -247,7 +247,7 @@ func (w *Wallet) SendSiafunds(amount types.Currency, dest types.UnlockHash) ([]t
if err != nil {
return nil, err
}
err = w.tpool.AcceptTransactionSet(txnSet)
err = w.managedCommitTransactionSet(txnSet)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion modules/wallet/seed.go
Expand Up @@ -510,7 +510,7 @@ func (w *Wallet) SweepSeed(seed modules.Seed) (coins, funds types.Currency, err
txnSet := append(parents, txn)

// submit the transactions
err = w.tpool.AcceptTransactionSet(txnSet)
err = w.managedCommitTransactionSet(txnSet)
if err != nil {
return
}
Expand Down
6 changes: 3 additions & 3 deletions modules/wallet/transactionbuilder.go
Expand Up @@ -100,7 +100,7 @@ func (w *Wallet) checkOutput(tx *bolt.Tx, currentHeight types.BlockHeight, id ty
// Check that this output has not recently been spent by the wallet.
spendHeight, err := dbGetSpentOutput(tx, types.OutputID(id))
if err == nil {
if spendHeight+RespendTimeout > currentHeight {
if spendHeight+respendTimeout > currentHeight {
return errSpendHeightTooHigh
}
}
Expand Down Expand Up @@ -287,8 +287,8 @@ func (tb *transactionBuilder) FundSiafunds(amount types.Currency) error {
spendHeight = 0
}
// Prevent an underflow error.
allowedHeight := consensusHeight - RespendTimeout
if consensusHeight < RespendTimeout {
allowedHeight := consensusHeight - respendTimeout
if consensusHeight < respendTimeout {
allowedHeight = 0
}
if spendHeight > allowedHeight {
Expand Down
18 changes: 9 additions & 9 deletions modules/wallet/transactionbuilder_test.go
Expand Up @@ -93,7 +93,7 @@ func TestViewAdded(t *testing.T) {
t.Error("seems like there's memory sharing happening between txn calls")
}
// Set1 should be missing some signatures.
err = wt.tpool.AcceptTransactionSet(set1)
err = wt.wallet.managedCommitTransactionSet(set1)
if err == nil {
t.Fatal(err)
}
Expand All @@ -108,7 +108,7 @@ func TestViewAdded(t *testing.T) {
b2.AddTransactionSignature(unfinishedTxn3.TransactionSignatures[sigIndex])
}
set2, err := b2.Sign(true)
err = wt.tpool.AcceptTransactionSet(set2)
err = wt.wallet.managedCommitTransactionSet(set2)
if err != nil {
t.Fatal(err)
}
Expand All @@ -122,7 +122,7 @@ func TestViewAdded(t *testing.T) {
b.AddTransactionSignature(finishedTxn.TransactionSignatures[sigIndex])
}
set3Txn, set3Parents := b.View()
err = wt.tpool.AcceptTransactionSet(append(set3Parents, set3Txn))
err = wt.wallet.managedCommitTransactionSet(append(set3Parents, set3Txn))
if err != modules.ErrDuplicateTransactionSet {
t.Fatal(err)
}
Expand Down Expand Up @@ -159,7 +159,7 @@ func TestDoubleSignError(t *testing.T) {
if err != nil && txnSet2 != nil {
t.Error("errored call to sign did not return a nil txn set")
}
err = wt.tpool.AcceptTransactionSet(txnSet)
err = wt.wallet.managedCommitTransactionSet(txnSet)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -235,11 +235,11 @@ func TestConcurrentBuilders(t *testing.T) {
if err != nil {
t.Fatal(err)
}
err = wt.tpool.AcceptTransactionSet(tset1)
err = wt.wallet.managedCommitTransactionSet(tset1)
if err != nil {
t.Fatal(err)
}
err = wt.tpool.AcceptTransactionSet(tset2)
err = wt.wallet.managedCommitTransactionSet(tset2)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -294,7 +294,7 @@ func TestConcurrentBuildersSingleOutput(t *testing.T) {
if err != nil {
t.Fatal(err)
}
err = wt.tpool.AcceptTransactionSet(tSet)
err = wt.wallet.managedCommitTransactionSet(tSet)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -347,7 +347,7 @@ func TestConcurrentBuildersSingleOutput(t *testing.T) {
if err != nil {
t.Fatal(err)
}
err = wt.tpool.AcceptTransactionSet(tset1)
err = wt.wallet.managedCommitTransactionSet(tset1)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -422,7 +422,7 @@ func TestParallelBuilders(t *testing.T) {
if err != nil {
t.Fatal(err)
}
err = wt.tpool.AcceptTransactionSet(tset)
err = wt.wallet.managedCommitTransactionSet(tset)
if err != nil {
t.Fatal(err)
}
Expand Down