Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 36 additions & 24 deletions decred/decred/dcr/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,37 +89,43 @@ def __init__(
spendLimit,
poolAddress,
votingAddress,
ticketFee,
poolFees,
count,
txFee,
ticketFee=0,
):
# minConf is just a placeholder for now. Account minconf is 0 until
# I add the ability to change it.
"""
TicketRequest constructor.

Args:
minConf (int): minConf is just a placeholder for now. Account
minconf is 0 until I add the ability to change it.
expiry (int): expiry can be set to some reasonable block height.
This may be important when approaching the end of a ticket
window.
spendLimit (int): Price is calculated purely from the ticket count,
price, and fees, but cannot go over spendLimit.
poolAddress (str): The VSP fee payment address.
votingAddress (str): The P2SH voting address based on the 1-of-2.
multi-sig script you share with the VSP.
poolFees (int): poolFees are set by the VSP. If you don't set these
correctly, the VSP may not vote for you.
count (int): How many tickets to buy.
txFee (int): txFee is the transaction fee rate to pay the miner for
the split transaction required to fund the ticket.
ticketFee (int): Optional. Default is the network's default relay
fee. ticketFee is the transaction fee rate to pay the miner for
the ticket.
"""
self.minConf = minConf
# expiry can be set to some reasonable block height. This may be
# important when approaching the end of a ticket window.
self.expiry = expiry
# Price is calculated purely from the ticket count, price, and fees, but
# cannot go over spendLimit.
self.spendLimit = spendLimit
# The VSP fee payment address.
self.poolAddress = poolAddress
# The P2SH voting address based on the 1-of-2 multi-sig script you share
# with the VSP.
self.votingAddress = votingAddress
# ticketFee is the transaction fee rate to pay the miner for the ticket.
# Set to zero to use wallet's network default fee rate.
self.ticketFee = ticketFee
# poolFees are set by the VSP. If you don't set these correctly, the
# VSP may not vote for you.
self.poolFees = poolFees
# How many tickets to buy.
self.count = count
# txFee is the transaction fee rate to pay the miner for the split
# transaction required to fund the ticket.
# Set to zero to use wallet's network default fee rate.
self.txFee = txFee
self.ticketFee = ticketFee if ticketFee != 0 else DefaultRelayFeePerKb


class TicketStats:
Expand Down Expand Up @@ -1865,7 +1871,7 @@ def addressSignal(self, addr, txid):
# signal the balance update
self.signals.balance(self.calcBalance())

def sendToAddress(self, value, address, feeRate=None):
def sendToAddress(self, value, address):
"""
Send the value to the address.

Expand All @@ -1880,7 +1886,7 @@ def sendToAddress(self, value, address, feeRate=None):
priv=self.privKeyForAddress, internal=self.nextInternalAddress,
)
tx, spentUTXOs, newUTXOs = self.blockchain.sendToAddress(
value, address, keysource, self.getUTXOs, feeRate
value, address, keysource, self.getUTXOs, self.relayFee
)
self.addMempoolTx(tx)
self.spendUTXOs(spentUTXOs)
Expand All @@ -1895,6 +1901,13 @@ def purchaseTickets(self, qty, price):
Account uses the blockchain to do the heavy lifting, but must prepare
the TicketRequest and KeySource and gather some other account- related
information.

Args:
qty (int): The number of tickets to buy.
price (int): The price per ticket in coins to pay.

Returs:
MsgTx: The sent split transaction.
"""
keysource = KeySource(
priv=self.privKeyForAddress, internal=self.nextInternalAddress,
Expand All @@ -1907,10 +1920,9 @@ def purchaseTickets(self, qty, price):
spendLimit=int(round(price * qty * 1.1 * 1e8)), # convert to atoms here
poolAddress=pi.poolAddress,
votingAddress=pi.ticketAddress,
ticketFee=0, # use network default
poolFees=pi.poolFees,
count=qty,
txFee=0, # use network default
txFee=self.relayFee,
)
txs, spentUTXOs, newUTXOs = self.blockchain.purchaseTickets(
keysource, self.getUTXOs, req
Expand Down Expand Up @@ -1962,7 +1974,7 @@ def revokeTickets(self):
priv=lambda _: self._votingKey,
internal=lambda: "",
)
self.blockchain.revokeTicket(tx, keysource, redeemScript)
self.blockchain.revokeTicket(tx, keysource, redeemScript, self.relayFee)

def sync(self):
"""
Expand Down
89 changes: 53 additions & 36 deletions decred/decred/dcr/dcrdata.py
Original file line number Diff line number Diff line change
Expand Up @@ -841,15 +841,6 @@ def updateTip(self):
log.error("failed to retrieve tip from blockchain: %s" % formatTraceback(e))
raise DecredError("no tip data retrieved")

def relayFee(self):
"""
Return the current transaction fee.

Returns:
int: Atoms per kB of encoded transaction.
"""
return txscript.DefaultRelayFeePerKb

def saveBlockHeader(self, header):
"""
Save the block header to the database.
Expand All @@ -861,13 +852,25 @@ def saveBlockHeader(self, header):
self.heightMap[header.height] = bHash
self.headerDB[bHash] = header

def sendToAddress(self, value, address, keysource, utxosource, feeRate=None):
def checkFeeRate(self, relayFee):
"""
Check that the relay fee is lower than the allowed max of
txscript.HighFeeRate.
"""
if relayFee > txscript.HighFeeRate:
raise DecredError(
f"relay fee of {relayFee} is above the allowed max of {txscript.HighFeeRate}"
)

def sendToAddress(
self, value, address, keysource, utxosource, relayFee, allowHighFees=False
):
"""
Send the amount in atoms to the specified address.

Args:
value int: The amount to send, in atoms.
address str: The base-58 encoded address.
value (int): The amount to send, in atoms.
address (str): The base-58 encoded address.
keysource func(str) -> PrivateKey: A function that returns the
private key for an address.
utxosource func(int, func(UTXO) -> bool) -> list(UTXO): A function
Expand All @@ -876,11 +879,18 @@ def sendToAddress(self, value, address, keysource, utxosource, feeRate=None):
amount. If the filtering function is provided, UTXOs for which
the function return a falsey value will not be included in the
returned UTXO list.
MsgTx: The newly created transaction on success, `False` on failure.
relayFee (int): Transaction fees in atoms per kb.
allowHighFees (bool): Optional. Default is False. Whether to allow
fees higher than txscript.HighFeeRate.

Returns:
MsgTx: The newly created transaction. Raises an exception on error.
"""
if not allowHighFees:
self.checkFeeRate(relayFee)
self.updateTip()
outputs = makeOutputs([(address, value)], self.netParams)
return self.sendOutputs(outputs, keysource, utxosource, feeRate)
return self.sendOutputs(outputs, keysource, utxosource, relayFee, allowHighFees)

def broadcast(self, txHex):
"""
Expand Down Expand Up @@ -962,7 +972,9 @@ def confirmUTXO(self, utxo, block=None, tx=None):
pass
return False

def sendOutputs(self, outputs, keysource, utxosource, feeRate=None):
def sendOutputs(
self, outputs, keysource, utxosource, relayFee, allowHighFees=False
):
"""
Send the `TxOut`s to the address.

Expand All @@ -982,12 +994,17 @@ def sendOutputs(self, outputs, keysource, utxosource, feeRate=None):
sufficient to complete the transaction. If the filtering
function is provided, UTXOs for which the function return a
falsey value will not be included in the returned UTXO list.
relayFee (int): Transaction fees in atoms per kb.
allowHighFees (bool): Optional. Default is False. Whether to allow
fees higher than txscript.HighFeeRate.

Returns:
newTx MsgTx: The sent transaction.
utxos list(UTXO): The spent UTXOs.
newUTXOs list(UTXO): Length 1 array containing the new change UTXO.
"""
if not allowHighFees:
self.checkFeeRate(relayFee)
total = 0
inputs = []
scripts = []
Expand All @@ -998,14 +1015,13 @@ def sendOutputs(self, outputs, keysource, utxosource, feeRate=None):
changeScriptVersion = txscript.DefaultScriptVersion
changeScriptSize = txscript.P2PKHPkScriptSize

relayFeePerKb = feeRate * 1e3 if feeRate else self.relayFee()
for (i, txout) in enumerate(outputs):
checkOutput(txout, relayFeePerKb)
checkOutput(txout, relayFee)

Comment on lines 1017 to 1020
Copy link
Member Author

Choose a reason for hiding this comment

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

It was converted here. So I guess it did take atoms/b. Should I revert? Or try to make everything atoms/b? Either is fine with me, but some accepting atoms/byte and other atoms/kb is confusing.

signedSize = txscript.estimateSerializeSize(
[txscript.RedeemP2PKHSigScriptSize], outputs, changeScriptSize
)
targetFee = txscript.calcMinRequiredTxRelayFee(relayFeePerKb, signedSize)
targetFee = txscript.calcMinRequiredTxRelayFee(relayFee, signedSize)
targetAmount = sum(txo.value for txo in outputs)

while True:
Expand Down Expand Up @@ -1034,7 +1050,7 @@ def sendOutputs(self, outputs, keysource, utxosource, feeRate=None):
signedSize = txscript.estimateSerializeSize(
scriptSizes, outputs, changeScriptSize
)
requiredFee = txscript.calcMinRequiredTxRelayFee(relayFeePerKb, signedSize)
requiredFee = txscript.calcMinRequiredTxRelayFee(relayFee, signedSize)
remainingAmount = total - targetAmount
if remainingAmount < requiredFee:
targetFee = requiredFee
Expand All @@ -1055,7 +1071,7 @@ def sendOutputs(self, outputs, keysource, utxosource, feeRate=None):
changeVout = -1
changeAmount = round(total - targetAmount - requiredFee)
if changeAmount != 0 and not txscript.isDustAmount(
changeAmount, changeScriptSize, relayFeePerKb
changeAmount, changeScriptSize, relayFee
):
if len(changeScript) > txscript.MaxScriptElementSize:
raise DecredError(
Expand Down Expand Up @@ -1110,7 +1126,7 @@ def sendOutputs(self, outputs, keysource, utxosource, feeRate=None):

return newTx, utxos, newUTXOs

def purchaseTickets(self, keysource, utxosource, req):
def purchaseTickets(self, keysource, utxosource, req, allowHighFees=False):
"""
Based on dcrwallet (*Wallet).purchaseTickets.
purchaseTickets indicates to the wallet that a ticket should be
Expand All @@ -1120,11 +1136,13 @@ def purchaseTickets(self, keysource, utxosource, req):
available.

Args:
keysource account.KeySource: a source for private keys.
keysource (account.KeySource): a source for private keys.
utxosource func(int, filterFunc) -> (list(UTXO), bool): a source for
UTXOs. The filterFunc is an optional function to filter UTXOs,
and is of the form func(UTXO) -> bool.
req account.TicketRequest: the ticket data.
req (account.TicketRequest): the ticket data.
allowHighFees (bool): Optional. Default is False. Whether to allow
fees higher than txscript.HighFeeRate.

Returns:
(splitTx, tickets) tuple: first element is the split transaction.
Expand All @@ -1135,6 +1153,9 @@ def purchaseTickets(self, keysource, utxosource, req):
addresses.

"""
if not allowHighFees:
self.checkFeeRate(req.txFee)
self.checkFeeRate(req.ticketFee)
self.updateTip()
# account minConf is zero for regular outputs for now. Need to make that
# adjustable.
Expand Down Expand Up @@ -1205,10 +1226,6 @@ def purchaseTickets(self, keysource, utxosource, req):
"unsupported voting address type %s" % votingAddress.__class__.__name__
)

ticketFeeIncrement = req.ticketFee
if ticketFeeIncrement == 0:
ticketFeeIncrement = self.relayFee()

# Make sure that we have enough funds. Calculate different
# ticket required amounts depending on whether or not a
# pool output is needed. If the ticket fee increment is
Expand All @@ -1232,7 +1249,7 @@ def purchaseTickets(self, keysource, utxosource, req):
]
estSize = txscript.estimateSerializeSizeFromScriptSizes(inSizes, outSizes, 0)

ticketFee = txscript.calcMinRequiredTxRelayFee(ticketFeeIncrement, estSize)
ticketFee = txscript.calcMinRequiredTxRelayFee(req.ticketFee, estSize)
neededPerTicket = ticketFee + ticketPrice

# If we need to calculate the amount for a pool fee percentage,
Expand Down Expand Up @@ -1270,14 +1287,9 @@ def purchaseTickets(self, keysource, utxosource, req):
# User amount.
splitOuts.append(msgtx.TxOut(value=userAmt, pkScript=splitPkScript,))

txFeeIncrement = req.txFee
if txFeeIncrement == 0:
txFeeIncrement = self.relayFee()

# Send the split transaction.
# sendOutputs takes the fee rate in atoms/byte
splitTx, splitSpent, internalOutputs = self.sendOutputs(
splitOuts, keysource, utxosource, int(txFeeIncrement / 1000)
splitOuts, keysource, utxosource, req.txFee, allowHighFees
)

# Generate the tickets individually.
Expand Down Expand Up @@ -1373,7 +1385,7 @@ def purchaseTickets(self, keysource, utxosource, req):
)
return (splitTx, tickets), splitSpent, internalOutputs

def revokeTicket(self, tx, keysource, redeemScript):
def revokeTicket(self, tx, keysource, redeemScript, relayFee, allowHighFees=False):
"""
Revoke a ticket by signing the supplied redeem script and broadcasting
the raw transaction.
Expand All @@ -1384,12 +1396,17 @@ def revokeTicket(self, tx, keysource, redeemScript):
the private key used for signing.
redeemScript (byte-like): the 1-of-2 multisig script that delegates
voting rights for the ticket.
relayFee (int): Transaction fees in atoms per kb.
allowHighFees (bool): Optional. Default is False. Whether to allow
fees higher than txscript.HighFeeRate.

Returns:
MsgTx: the signed revocation.
"""
if not allowHighFees:
self.checkFeeRate(relayFee)

revocation = txscript.makeRevocation(tx, self.relayFee())
revocation = txscript.makeRevocation(tx, relayFee)

signedScript = txscript.signTxOutput(
self.netParams,
Expand Down
Loading