Skip to content

Commit

Permalink
pool: filter out dust payments.
Browse files Browse the repository at this point in the history
This updates payment generation to filter out
dust payments. These dust payments will be
forfeited by the accounts generating them
and be added to the pool fee payout. This is
being done since dust outputs will cause the
payout transaction to error and also serve as
a deterrent to miners sporadically contributing
to a pool.
  • Loading branch information
dnldd committed Oct 9, 2020
1 parent 45b9b20 commit 197fc90
Show file tree
Hide file tree
Showing 2 changed files with 140 additions and 8 deletions.
32 changes: 25 additions & 7 deletions pool/paymentmgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,7 @@ func (pm *PaymentMgr) calculatePayments(ratios map[string]*big.Rat, source *Paym
amtSansFees := total - fee
sansFees := new(big.Rat).SetInt64(int64(amtSansFees))
paymentTotal := dcrutil.Amount(0)
dustAmts := make([]dcrutil.Amount, 0)

// Calculate each participating account's portion of the amount after fees.
payments := make([]*Payment, 0)
Expand All @@ -544,8 +545,23 @@ func (pm *PaymentMgr) calculatePayments(ratios map[string]*big.Rat, source *Paym
}

paymentTotal += amt
payments = append(payments, NewPayment(account, source, amt, height,
estMaturity))

// The script size of the output is assumed to be the worst possible,
// which is txsizes.P2PKHOutputSize, to avoid a lower size estimation.
if txrules.IsDustAmount(amt, txsizes.P2PKHOutputSize,
txrules.DefaultRelayFeePerKb) {
// Since dust payments will cause the payout transaction to error
// and are also most likely to be generated by participating
// accounts contributing sporadic work to pool, for these reasons
// dust payments will be forfeited by the accounts that generated
// them and be added to the pool fee payout. This intended
// to serve as a deterrent for contributinf sporadic work
// to the pool.
dustAmts = append(dustAmts, amt)
} else {
payments = append(payments, NewPayment(account, source, amt, height,
estMaturity))
}
}

if amtSansFees < paymentTotal {
Expand All @@ -556,12 +572,14 @@ func (pm *PaymentMgr) calculatePayments(ratios map[string]*big.Rat, source *Paym
return nil, 0, poolError(ErrPaymentSource, desc)
}

// TODO: Need to check each payment to ensure its not dust if it is it
// should be added to the pool fee. Will be resolving this in a seperate
// PR.
// Add a payout entry for pool fees, which includes any dust payments
// collected.
var dustTotal dcrutil.Amount
for _, amt := range dustAmts {
dustTotal += amt
}

// Add a payout entry for pool fees.
feePayment := NewPayment(PoolFeesK, source, fee, height, estMaturity)
feePayment := NewPayment(PoolFeesK, source, fee+dustTotal, height, estMaturity)
payments = append(payments, feePayment)

return payments, feePayment.CreatedOn, nil
Expand Down
116 changes: 115 additions & 1 deletion pool/paymentmgr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import (
"time"

"decred.org/dcrwallet/rpc/walletrpc"
txrules "decred.org/dcrwallet/wallet/txrules"
"decred.org/dcrwallet/wallet/txsizes"
"github.com/decred/dcrd/chaincfg/chainhash"
"github.com/decred/dcrd/chaincfg/v3"
"github.com/decred/dcrd/dcrutil/v3"
Expand Down Expand Up @@ -817,7 +819,7 @@ func testPaymentMgr(t *testing.T, db *bolt.DB) {
// payment entry.
pmts, err = mgr.pendingPayments()
if err != nil {
t.Fatalf("[PPLNS] fetchPendingPayments error: %v", err)
t.Fatalf("[PPLNS] pendingPayments error: %v", err)
}

xt = dcrutil.Amount(0)
Expand Down Expand Up @@ -1695,4 +1697,116 @@ func testPaymentMgr(t *testing.T, db *bolt.DB) {
if err != nil {
t.Fatal(err)
}

// Ensure dust payments are forfeited by their originating accounts and
// added to the pool fee payout.
now = time.Now()
pCfg.PaymentMethod = PPLNS
coinbaseValue = 1
mul := 100000
yWeight := new(big.Rat).Mul(weight, new(big.Rat).SetInt64(int64(mul)))

// Create shares for account x and y.
err = persistShare(db, xID, weight, now.UnixNano())
if err != nil {
t.Fatal(err)
}
err = persistShare(db, yID, yWeight, now.UnixNano())
if err != nil {
t.Fatal(err)
}

coinbase, err = dcrutil.NewAmount(float64(coinbaseValue))
if err != nil {
t.Fatalf("[NewAmount] unexpected error: %v", err)
}

// Ensure the expected payout amount for account x is dust.
expectedDustAmt := coinbase.MulF64(1 / float64(mul))
if !txrules.IsDustAmount(expectedDustAmt, txsizes.P2PKHOutputSize,
txrules.DefaultRelayFeePerKb) {
t.Fatal("expected dust amount for account x")
}

err = mgr.generatePayments(height, zeroSource, coinbase, now.UnixNano())
if err != nil {
t.Fatalf("unable to generate payments: %v", err)
}

// Ensure the payments created are for accounts x, y and a fee
// payment entry.
pmts, err = mgr.pendingPayments()
if err != nil {
t.Fatalf("pendingPayments error: %v", err)
}

// Ensure only two pending payments were generated.
if len(pmts) != 2 {
t.Fatalf("expected 2 pending payments, got %d", len(pmts))
}

xt = dcrutil.Amount(0)
yt = dcrutil.Amount(0)
ft = dcrutil.Amount(0)
for _, pmt := range pmts {
if pmt.Account == xID {
xt += pmt.Amount
}
if pmt.Account == yID {
yt += pmt.Amount
}
if pmt.Account == PoolFeesK {
ft += pmt.Amount
}
}

// Ensure account x has no payments for it since it generated dust.
if xt != dcrutil.Amount(0) {
t.Fatalf("expected no payment amounts for account x, got %v", xt)
}

// Ensure the updated pool fee includes the dust amount from account x.
expectedFeeAmt = coinbase.MulF64(mgr.cfg.PoolFee)
if ft-maxRoundingDiff < expectedFeeAmt {
t.Fatalf("expected the updated pool fee (%v) to be greater "+
"than the initial (%v)", ft, expectedFeeAmt)
}

// Empty the share bucket.
err = emptyBucket(db, shareBkt)
if err != nil {
t.Fatalf("emptyBucket error: %v", err)
}

// Empty the payment bucket.
err = emptyBucket(db, paymentBkt)
if err != nil {
t.Fatalf("emptyBucket error: %v", err)
}

// Reset backed up values to their defaults.
mgr.setLastPaymentHeight(0)
mgr.setLastPaymentPaidOn(0)
mgr.setLastPaymentCreatedOn(0)
err = db.Update(func(tx *bolt.Tx) error {
err := mgr.persistLastPaymentHeight(tx)
if err != nil {
return fmt.Errorf("unable to persist default last "+
"payment height: %v", err)
}
err = mgr.persistLastPaymentPaidOn(tx)
if err != nil {
return fmt.Errorf("unable to persist default last "+
"payment paid on: %v", err)
}
err = mgr.persistLastPaymentCreatedOn(tx)
if err != nil {
return fmt.Errorf("unable to persist default last "+
"payment created on: %v", err)
}
return nil
})
if err != nil {
t.Fatal(err)
}
}

0 comments on commit 197fc90

Please sign in to comment.