Skip to content

Commit e71decb

Browse files
committed
Trickle tickets during mixed buying
This introduces a random delay during the publishing of tickets during mixed ticket buying. After the mixed split transaction has been created and published, random times picked between 20-60s are created for each ticket being created, and publishing of these tickets will not occur until the trickle duration has been reached. Backport of 6db21ea.
1 parent 82f5982 commit e71decb

File tree

2 files changed

+103
-29
lines changed

2 files changed

+103
-29
lines changed

internal/uniformprng/prng.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,3 +87,36 @@ func (s *Source) Int63n(n int64) int64 {
8787
}
8888
}
8989
}
90+
91+
// Int63 returns a random non-negative int64, with randomness read from rand.
92+
func Int63(rand io.Reader) (int64, error) {
93+
buf := make([]byte, 8)
94+
_, err := io.ReadFull(rand, buf)
95+
if err != nil {
96+
return 0, err
97+
}
98+
return int64(binary.LittleEndian.Uint64(buf) &^ (1 << 63)), nil
99+
100+
}
101+
102+
// Int63n returns, as an int64, a pseudo-random 63-bit positive integer in [0,n)
103+
// without modulo bias.
104+
// Randomness is read from rand.
105+
// It panics if n <= 0.
106+
func Int63n(rand io.Reader, n int64) (int64, error) {
107+
if n <= 0 {
108+
panic("invalid argument to Int63n")
109+
}
110+
n--
111+
mask := int64(^uint64(0) >> bits.LeadingZeros64(uint64(n)))
112+
for {
113+
v, err := Int63(rand)
114+
if err != nil {
115+
return 0, err
116+
}
117+
v &= mask
118+
if v <= n {
119+
return v, nil
120+
}
121+
}
122+
}

wallet/createtx.go

Lines changed: 70 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"decred.org/cspp/v2/coinjoin"
1919
"decred.org/dcrwallet/v3/deployments"
2020
"decred.org/dcrwallet/v3/errors"
21+
"decred.org/dcrwallet/v3/internal/uniformprng"
2122
"decred.org/dcrwallet/v3/rpc/client/dcrd"
2223
"decred.org/dcrwallet/v3/wallet/txauthor"
2324
"decred.org/dcrwallet/v3/wallet/txrules"
@@ -1614,6 +1615,29 @@ func (w *Wallet) purchaseTickets(ctx context.Context, op errors.Op,
16141615
}
16151616
}
16161617

1618+
// Calculate trickle times for published mixed tickets.
1619+
// Random times between 20s to 1m from now are chosen for each ticket,
1620+
// and tickets will not be published until their trickle time is reached.
1621+
var trickleTickets []time.Time
1622+
if req.CSPPServer != "" {
1623+
now := time.Now()
1624+
trickleTickets = make([]time.Time, 0, len(splitOutputIndexes))
1625+
for range splitOutputIndexes {
1626+
delay, err := uniformprng.Int63n(rand.Reader,
1627+
int64(40*time.Second))
1628+
if err != nil {
1629+
return nil, err
1630+
}
1631+
t := now.Add(time.Duration(delay) + 20*time.Second)
1632+
trickleTickets = append(trickleTickets, t)
1633+
}
1634+
sort.Slice(trickleTickets, func(i, j int) bool {
1635+
t1 := trickleTickets[i]
1636+
t2 := trickleTickets[j]
1637+
return t1.Before(t2)
1638+
})
1639+
}
1640+
16171641
// Create each ticket.
16181642
ticketHashes := make([]*chainhash.Hash, 0, req.Count)
16191643
tickets := make([]*wire.MsgTx, 0, req.Count)
@@ -1670,7 +1694,8 @@ func (w *Wallet) purchaseTickets(ctx context.Context, op errors.Op,
16701694
if err != nil {
16711695
return nil, err
16721696
}
1673-
_, err := w.signingAddressAtIdx(ctx, op, w.persistReturnedChild(ctx, nil), req.VotingAccount, idx)
1697+
_, err := w.signingAddressAtIdx(ctx, op, w.persistReturnedChild(ctx, nil),
1698+
req.VotingAccount, idx)
16741699
if err != nil {
16751700
return nil, err
16761701
}
@@ -1762,39 +1787,55 @@ func (w *Wallet) purchaseTickets(ctx context.Context, op errors.Op,
17621787
if err != nil {
17631788
return purchaseTicketsResponse, errors.E(op, err)
17641789
}
1765-
// TODO: Send all tickets, and all split transactions, together. Purge
1766-
// transactions from DB if tickets cannot be sent.
1767-
if !req.DontSignTx {
1768-
err = n.PublishTransactions(ctx, ticket)
1769-
if err != nil {
1770-
return purchaseTicketsResponse, errors.E(op, err)
1771-
}
1772-
log.Infof("Published ticket purchase %v", ticket.TxHash())
1773-
}
17741790
}
17751791

1776-
if !req.DontSignTx && req.VSPFeePaymentProcess != nil {
1777-
unlockCredits = false
1778-
for i, ticketHash := range purchaseTicketsResponse.TicketHashes {
1779-
feeTx := wire.NewMsgTx()
1780-
for j := range vspFeeCredits[i] {
1781-
in := &vspFeeCredits[i][j]
1782-
feeTx.AddTxIn(wire.NewTxIn(&in.OutPoint, in.PrevOut.Value, nil))
1783-
}
1784-
1785-
err = req.VSPFeePaymentProcess(ctx, ticketHash, feeTx)
1786-
if err != nil {
1787-
// unlock outpoints in case of error
1788-
for _, outpoint := range vspFeeCredits[i] {
1789-
w.UnlockOutpoint(&outpoint.OutPoint.Hash, outpoint.OutPoint.Index)
1792+
for i, ticket := range tickets {
1793+
// Wait for trickle time if this was a mixed buy.
1794+
if len(trickleTickets) > 0 {
1795+
t := trickleTickets[0]
1796+
trickleTickets = trickleTickets[1:]
1797+
timer := time.NewTimer(time.Until(t))
1798+
select {
1799+
case <-ctx.Done():
1800+
if !timer.Stop() {
1801+
<-timer.C
17901802
}
1791-
continue
1803+
return purchaseTicketsResponse, errors.E(op, ctx.Err())
1804+
case <-timer.C:
17921805
}
1793-
// watch for outpoints change.
1794-
_, err = udb.NewTxRecordFromMsgTx(feeTx, time.Now())
1795-
if err != nil {
1796-
return nil, err
1806+
}
1807+
1808+
// Publish transaction
1809+
err = n.PublishTransactions(ctx, ticket)
1810+
if err != nil {
1811+
return purchaseTicketsResponse, errors.E(op, err)
1812+
}
1813+
log.Infof("Published ticket purchase %v", ticket.TxHash())
1814+
1815+
// Pay VSP fee when configured to do so.
1816+
if req.VSPFeePaymentProcess == nil {
1817+
continue
1818+
}
1819+
unlockCredits = false
1820+
feeTx := wire.NewMsgTx()
1821+
for j := range vspFeeCredits[i] {
1822+
in := &vspFeeCredits[i][j]
1823+
feeTx.AddTxIn(wire.NewTxIn(&in.OutPoint, in.PrevOut.Value, nil))
1824+
}
1825+
ticketHash := purchaseTicketsResponse.TicketHashes[i]
1826+
err = req.VSPFeePaymentProcess(ctx, ticketHash, feeTx)
1827+
if err != nil {
1828+
// unlock outpoints in case of error
1829+
for _, outpoint := range vspFeeCredits[i] {
1830+
w.UnlockOutpoint(&outpoint.OutPoint.Hash,
1831+
outpoint.OutPoint.Index)
17971832
}
1833+
continue
1834+
}
1835+
// watch for outpoints change.
1836+
_, err = udb.NewTxRecordFromMsgTx(feeTx, time.Now())
1837+
if err != nil {
1838+
return nil, err
17981839
}
17991840
}
18001841

0 commit comments

Comments
 (0)