Skip to content

Commit

Permalink
Add get/setticketfee rpc handlers and fix fees in purchaseTicket
Browse files Browse the repository at this point in the history
Fix estimateSSTxSize calcultion

First use EstMaxTicketFeeAmount to ensure you get enough utxos.

Then when actually calculating fees for tickets, use new consts:
sstxTicketCommitmentEstimate
sstxSubsidyCommitmentEstimate
sstxChangeOutputEstimate
  • Loading branch information
alexlyp committed Mar 1, 2016
1 parent daf40c4 commit 4f51acb
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 19 deletions.
9 changes: 9 additions & 0 deletions internal/rpchelp/helpdescs_en_US.go
Original file line number Diff line number Diff line change
Expand Up @@ -571,4 +571,13 @@ var helpDescsEnUS = map[string]string{
"sendtossgen-blockhash": "Hash for the block being voted on",
"sendtossgen-tickethash": "Hash of the ticket used for vote",
"sendtossgen-fromaccount": "The account to use (default=\"default\")",

// SetTxFeeCmd help.
"setticketfee--synopsis": "Modify the increment used each time more fee is required for an authored stake transaction.",
"setticketfee-fee": "The new fee increment valued in decred",
"setticketfee--result0": "The boolean 'true'",

// SetTxFeeCmd help.
"getticketfee--synopsis": "Get the current fee increment used for an authored stake transaction.",
"getticketfee--result0": "The current fee",
}
2 changes: 2 additions & 0 deletions internal/rpchelp/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ var Methods = []struct {
{"sendtosstx", returnsString},
{"sendtossgen", returnsString},
{"getstakeinfo", []interface{}{(*dcrjson.GetStakeInfoResult)(nil)}},
{"getticketfee", returnsNumber},
{"setticketfee", returnsBool},
}

var HelpDescs = []struct {
Expand Down
26 changes: 26 additions & 0 deletions rpc/legacyrpc/methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ var rpcHandlers = map[string]struct {
"getreceivedbyaddress": {handler: GetReceivedByAddress},
"getseed": {handler: GetSeed, requireUnsafeOnMainNet: true},
"getstakeinfo": {handlerWithChain: GetStakeInfo},
"getticketfee": {handler: GetTicketFee},
"getticketmaxprice": {handler: GetTicketMaxPrice},
"gettickets": {handlerWithChain: GetTickets},
"getticketvotebits": {handler: GetTicketVoteBits},
Expand Down Expand Up @@ -146,6 +147,7 @@ var rpcHandlers = map[string]struct {
"sendtossgen": {handler: SendToSSGen},
"sendtossrtx": {handlerWithChain: SendToSSRtx},
"setgenerate": {handler: SetGenerate},
"setticketfee": {handler: SetTicketFee},
"setticketmaxprice": {handler: SetTicketMaxPrice},
"setticketvotebits": {handler: SetTicketVoteBits},
"settxfee": {handler: SetTxFee},
Expand Down Expand Up @@ -1285,6 +1287,11 @@ func GetStakeInfo(icmd interface{}, w *wallet.Wallet, chainClient *chain.RPCClie
return resp, nil
}

// GetTicketFee gets the currently set price per kb for tickets
func GetTicketFee(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
return w.TicketFeeIncrement().ToCoin(), nil
}

// GetTicketMaxPrice gets the maximum price the user is willing to pay for a
// ticket.
func GetTicketMaxPrice(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
Expand Down Expand Up @@ -2742,6 +2749,25 @@ func SetTicketVoteBits(icmd interface{}, w *wallet.Wallet) (interface{}, error)
return nil, nil
}

// SetTicketFee sets the transaction fee per kilobyte added to tickets.
func SetTicketFee(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*dcrjson.SetTicketFeeCmd)

// Check that amount is not negative.
if cmd.Fee < 0 {
return nil, ErrNeedPositiveAmount
}

incr, err := dcrutil.NewAmount(cmd.Fee)
if err != nil {
return nil, err
}
w.SetTicketFeeIncrement(incr)

// A boolean true result is returned upon success.
return true, nil
}

// SetTxFee sets the transaction fee per kilobyte added to transactions.
func SetTxFee(icmd interface{}, w *wallet.Wallet) (interface{}, error) {
cmd := icmd.(*dcrjson.SetTxFeeCmd)
Expand Down
4 changes: 3 additions & 1 deletion rpc/legacyrpc/rpcserverhelp.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,11 +65,13 @@ func helpDescsEnUS() map[string]string {
"sendtosstx": "sendtosstx \"fromaccount\" amounts [{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"amt\":n},...] [{\"addr\":\"value\",\"commitamt\":n,\"changeaddr\":\"value\",\"changeamt\":n},...] (minconf=1 \"comment\")\n\nSend to SStx\n\nArguments:\n1. fromaccount (string, required) The account sent from\n2. amounts (object, required) Amounts to send\n{\n \"Key\": Value, (object) Unused\n ...\n}\n3. inputs (array of object, required) Inputs for the tx\n[{\n \"txid\": \"value\", (string) Txid to use\n \"vout\": n, (numeric) Vout for the input tx\n \"tree\": n, (numeric) Input tree\n \"amt\": n, (numeric) Amount\n},...]\n4. couts (array of object, required) Couts for the tx\n[{\n \"addr\": \"value\", (string) Address to use\n \"commitamt\": n, (numeric) Amount to commit\n \"changeaddr\": \"value\", (string) Change address to use\n \"changeamt\": n, (numeric) Change amount\n},...]\n5. minconf (numeric, optional, default=1) Minimum number of block confirmations required\n6. comment (string, optional) Unused\n\nResult:\n\"value\" (string) txid of the resulting transaction\n",
"sendtossgen": "sendtossgen \"fromaccount\" \"tickethash\" \"blockhash\" height votebits (\"comment\")\n\nGenerate a vote tx\n\nArguments:\n1. fromaccount (string, required) The account to use (default=\"default\")\n2. tickethash (string, required) Hash of the ticket used for vote\n3. blockhash (string, required) Hash for the block being voted on\n4. height (numeric, required) Blockheight for vote\n5. votebits (numeric, required) Votebits to set\n6. comment (string, optional) Unused\n\nResult:\n\"value\" (string) txid of the resulting transaction\n",
"getstakeinfo": "getstakeinfo\n\nReturns statistics about staking from the wallet.\n\nArguments:\nNone\n\nResult:\n{\n \"poolsize\": n, (numeric) Number of live tickets in the ticket pool.\n \"difficulty\": n.nnn, (numeric) Current stake difficulty.\n \"allmempooltix\": n, (numeric) Number of tickets currently in the mempool\n \"ownmempooltix\": n, (numeric) Number of tickets submitted by this wallet currently in mempool\n \"immature\": n, (numeric) Number of tickets from this wallet that are in the blockchain but which are not yet mature\n \"live\": n, (numeric) Number of mature, active tickets owned by this wallet\n \"proportionlive\": n.nnn, (numeric) (Live / PoolSize)\n \"voted\": n, (numeric) Number of votes cast by this wallet\n \"totalsubsidy\": n.nnn, (numeric) Total amount of coins earned by stake mining\n \"missed\": n, (numeric) Number of missed tickets (failing to vote or expired)\n \"proportionmissed\": n.nnn, (numeric) (Missed / (Missed + Voted))\n \"revoked\": n, (numeric) Number of missed tickets that were missed and then revoked\n} \n",
"getticketfee": "getticketfee\n\nGet the current fee increment used for an authored stake transaction.\n\nArguments:\nNone\n\nResult:\nn.nnn (numeric) The current fee\n",
"setticketfee": "setticketfee fee\n\nModify the increment used each time more fee is required for an authored stake transaction.\n\nArguments:\n1. fee (numeric, required) The new fee increment valued in decred\n\nResult:\ntrue|false (boolean) The boolean 'true'\n",
}
}

var localeHelpDescs = map[string]func() map[string]string{
"en_US": helpDescsEnUS,
}

var requestUsages = "addmultisigaddress nrequired [\"key\",...] (\"account\")\ncreatemultisig nrequired [\"key\",...]\ndumpprivkey \"address\"\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1 \"balancetype\")\ngetbestblockhash\ngetblockcount\ngetinfo\ngetmasterpubkey\ngetmultisigoutinfo \"hash\" index\ngetseed\ngetnewaddress (\"account\" verbose=false)\ngetrawchangeaddress (\"account\" verbose=false)\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettickets includeimmature\ngetticketmaxprice\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true)\nimportscript \"hex\"\nkeypoolrefill (newsize=100)\nlistaccounts (minconf=1)\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsetticketmaxprice max\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\ncreatenewaccount \"account\"\nexportwatchingwallet (\"account\" download=false)\ngetbestblock\ngetunconfirmedbalance (\"account\")\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nrenameaccount \"oldaccount\" \"newaccount\"\nwalletislocked\npurchaseticket \"fromaccount\" spendlimit (minconf=1 \"ticketaddress\" \"comment\")\nsendtossrtx \"fromaccount\" \"tickethash\" (\"comment\")\nsendtosstx \"fromaccount\" amounts [{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"amt\":n},...] [{\"addr\":\"value\",\"commitamt\":n,\"changeaddr\":\"value\",\"changeamt\":n},...] (minconf=1 \"comment\")\nsendtossgen \"fromaccount\" \"tickethash\" \"blockhash\" height votebits (\"comment\")\ngetstakeinfo"
var requestUsages = "addmultisigaddress nrequired [\"key\",...] (\"account\")\ncreatemultisig nrequired [\"key\",...]\ndumpprivkey \"address\"\ngetaccount \"address\"\ngetaccountaddress \"account\"\ngetaddressesbyaccount \"account\"\ngetbalance (\"account\" minconf=1 \"balancetype\")\ngetbestblockhash\ngetblockcount\ngetinfo\ngetmasterpubkey\ngetmultisigoutinfo \"hash\" index\ngetseed\ngetnewaddress (\"account\" verbose=false)\ngetrawchangeaddress (\"account\" verbose=false)\ngetreceivedbyaccount \"account\" (minconf=1)\ngetreceivedbyaddress \"address\" (minconf=1)\ngettickets includeimmature\ngetticketmaxprice\ngettransaction \"txid\" (includewatchonly=false)\nhelp (\"command\")\nimportprivkey \"privkey\" (\"label\" rescan=true)\nimportscript \"hex\"\nkeypoolrefill (newsize=100)\nlistaccounts (minconf=1)\nlistlockunspent\nlistreceivedbyaccount (minconf=1 includeempty=false includewatchonly=false)\nlistreceivedbyaddress (minconf=1 includeempty=false includewatchonly=false)\nlistsinceblock (\"blockhash\" targetconfirmations=1 includewatchonly=false)\nlisttransactions (\"account\" count=10 from=0 includewatchonly=false)\nlistunspent (minconf=1 maxconf=9999999 [\"address\",...])\nlockunspent unlock [{\"txid\":\"value\",\"vout\":n,\"tree\":n},...]\nredeemmultisigout \"hash\" index tree (\"address\")\nredeemmultisigouts \"fromscraddress\" (\"toaddress\" number)\nsendfrom \"fromaccount\" \"toaddress\" amount (minconf=1 \"comment\" \"commentto\")\nsendmany \"fromaccount\" {\"address\":amount,...} (minconf=1 \"comment\")\nsendtoaddress \"address\" amount (\"comment\" \"commentto\")\nsendtomultisig \"fromaccount\" amount [\"pubkey\",...] (nrequired=1 minconf=1 \"comment\")\nsetticketmaxprice max\nsettxfee amount\nsignmessage \"address\" \"message\"\nsignrawtransaction \"rawtx\" ([{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"scriptpubkey\":\"value\",\"redeemscript\":\"value\"},...] [\"privkey\",...] flags=\"ALL\")\nsignrawtransactions [\"rawtx\",...] (send=true)\nvalidateaddress \"address\"\nverifymessage \"address\" \"signature\" \"message\"\nwalletlock\nwalletpassphrase \"passphrase\" timeout\nwalletpassphrasechange \"oldpassphrase\" \"newpassphrase\"\ncreatenewaccount \"account\"\nexportwatchingwallet (\"account\" download=false)\ngetbestblock\ngetunconfirmedbalance (\"account\")\nlistaddresstransactions [\"address\",...] (\"account\")\nlistalltransactions (\"account\")\nrenameaccount \"oldaccount\" \"newaccount\"\nwalletislocked\npurchaseticket \"fromaccount\" spendlimit (minconf=1 \"ticketaddress\" \"comment\")\nsendtossrtx \"fromaccount\" \"tickethash\" (\"comment\")\nsendtosstx \"fromaccount\" amounts [{\"txid\":\"value\",\"vout\":n,\"tree\":n,\"amt\":n},...] [{\"addr\":\"value\",\"commitamt\":n,\"changeaddr\":\"value\",\"changeamt\":n},...] (minconf=1 \"comment\")\nsendtossgen \"fromaccount\" \"tickethash\" \"blockhash\" height votebits (\"comment\")\ngetstakeinfo\ngetticketfee\nsetticketfee fee"
52 changes: 38 additions & 14 deletions wallet/createtx.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ const (
// fraud proof, and the estimated signature script size.
txInEstimate = 32 + 4 + 1 + 12 + 4 + sigScriptEstimate

// sstxTicketCommitmentEstimate =
// - version + amount +
// OP_SSTX OP_DUP OP_HASH160 OP_DATA_20 OP_EQUALVERIFY OP_CHECKSIG
sstxTicketCommitmentEstimate = 2 + 8 + 1 + 1 + 1 + 1 + 20 + 1 + 1

// sstxSubsidyCommitmentEstimate =
// version + amount + OP_RETURN OP_DATA_30
sstxSubsidyCommitmentEstimate = 2 + 8 + 2 + 30

// sstxChangeOutputEstimate =
// version + amount + OP_SSTXCHANGE OP_DUP OP_HASH160 OP_DATA_20
// OP_EQUALVERIFY OP_CHECKSIG
sstxChangeOutputEstimate = 2 + 8 + 1 + 1 + 1 + 1 + 20 + 1 + 1

// A P2PKH pkScript contains the following bytes:
// - OP_DUP
// - OP_HASH160
Expand Down Expand Up @@ -92,8 +106,11 @@ func EstimateTxSize(numInputs, numOutputs int) int {
return estimateTxSize(numInputs, numOutputs)
}

func estimateSSTxSize(numInputs, numOutputs int) int {
return txOverheadEstimate + txInEstimate*numInputs + ssTxOutEsimate*numOutputs
func estimateSSTxSize(numInputs int) int {
return txOverheadEstimate + txInEstimate*numInputs +
sstxTicketCommitmentEstimate +
(sstxSubsidyCommitmentEstimate+
sstxChangeOutputEstimate)*numInputs
}

func feeForSize(incr dcrutil.Amount, sz int) dcrutil.Amount {
Expand All @@ -112,6 +129,14 @@ const FeeIncrementMainnet = 5e6
// measured in atoms) added to transactions requiring a fee for TestNet.
const FeeIncrementTestnet = 1e3

// TicketFeeIncrement is the default minimum stake transation fee (0.05 coin,
// measured in atoms).
const TicketFeeIncrement = 5e6

// EstMaxTicketFeeAmount is the estimated max ticket fee to be used for size
// calculation for eligible utxos for ticket purchasing
const EstMaxTicketFeeAmount = 0.1 * 1e8

// --------------------------------------------------------------------------------
// Error Handling

Expand Down Expand Up @@ -1301,9 +1326,15 @@ func (w *Wallet) purchaseTicket(req purchaseTicketRequest) (interface{},
pair := make(map[string]dcrutil.Amount, 1)
pair[ticketAddr.String()] = ticketPrice

// TODO Currently we are using an estimated max ticket size
// to get estimate fees to make sure we have enough eligible
// utxos
var estFee dcrutil.Amount
estFee = EstMaxTicketFeeAmount

// Instead of taking reward addresses by arg, just create them now and
// automatically find all eligible outputs from all current utxos.
amountNeeded := req.minBalance + ticketPrice
amountNeeded := req.minBalance + ticketPrice + estFee
eligible, err := w.findEligibleOutputsAmount(account, req.minConf,
amountNeeded, bs)
if err != nil {
Expand Down Expand Up @@ -1361,18 +1392,11 @@ func (w *Wallet) purchaseTicket(req purchaseTicketRequest) (interface{},
// so we'll have to change to pop in the
// last output.

// Calculate the amount of fees needed.
s := estimateSSTxSize(i, i)
estSize := estimateSSTxSize(i)
var feeIncrement dcrutil.Amount
switch {
case w.chainParams == &chaincfg.MainNetParams:
feeIncrement = FeeIncrementMainnet
case w.chainParams == &chaincfg.TestNetParams:
feeIncrement = FeeIncrementTestnet
default:
feeIncrement = FeeIncrementTestnet
}
fee := feeForSize(feeIncrement, s)
feeIncrement = w.TicketFeeIncrement()

fee := feeForSize(feeIncrement, estSize)

// Not enough funds after taking fee into account.
// Should retry instead of failing, Decred TODO
Expand Down
29 changes: 25 additions & 4 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,11 @@ type Wallet struct {

lockedOutpoints map[wire.OutPoint]struct{}

feeIncrementLock sync.Mutex
feeIncrement dcrutil.Amount
DisallowFree bool
feeIncrementLock sync.Mutex
feeIncrement dcrutil.Amount
ticketFeeIncrementLock sync.Mutex
ticketFeeIncrement dcrutil.Amount
DisallowFree bool

// Channels for rescan processing. Requests are added and merged with
// any waiting requests, before being sent to another goroutine to
Expand Down Expand Up @@ -212,6 +214,9 @@ func newWallet(vb uint16, esm bool, btm dcrutil.Amount, addressReuse bool,
feeIncrement = FeeIncrementTestnet
}

var ticketFeeIncrement dcrutil.Amount
ticketFeeIncrement = TicketFeeIncrement

internalPool := NewAddressPool()
externalPool := NewAddressPool()

Expand All @@ -226,6 +231,7 @@ func newWallet(vb uint16, esm bool, btm dcrutil.Amount, addressReuse bool,
CurrentStakeDiff: &StakeDifficultyInfo{nil, -1, -1},
lockedOutpoints: map[wire.OutPoint]struct{}{},
feeIncrement: feeIncrement,
ticketFeeIncrement: ticketFeeIncrement,
rescanAddJob: make(chan *RescanJob),
rescanBatch: make(chan *rescanBatch),
rescanNotifications: make(chan interface{}),
Expand Down Expand Up @@ -296,13 +302,28 @@ func (w *Wallet) FeeIncrement() dcrutil.Amount {
}

// SetFeeIncrement is used to set the current w.FeeIncrement for the wallet.
// Uses non-exported mutex safe setFeeIncrement func
func (w *Wallet) SetFeeIncrement(fee dcrutil.Amount) {
w.feeIncrementLock.Lock()
w.feeIncrement = fee
w.feeIncrementLock.Unlock()
}

// TicketFeeIncrement is used to get the current feeIncrement for the wallet.
func (w *Wallet) TicketFeeIncrement() dcrutil.Amount {
w.ticketFeeIncrementLock.Lock()
fee := w.ticketFeeIncrement
w.ticketFeeIncrementLock.Unlock()

return fee
}

// SetTicketFeeIncrement is used to set the current w.ticketFeeIncrement for the wallet.
func (w *Wallet) SetTicketFeeIncrement(fee dcrutil.Amount) {
w.ticketFeeIncrementLock.Lock()
w.ticketFeeIncrement = fee
w.ticketFeeIncrementLock.Unlock()
}

// SetGenerate is used to enable or disable stake mining in the
// wallet.
func (w *Wallet) SetGenerate(flag bool) error {
Expand Down

0 comments on commit 4f51acb

Please sign in to comment.