diff --git a/rpc/jsonrpc/types/walletsvrcmds_test.go b/rpc/jsonrpc/types/walletsvrcmds_test.go index 850984e95..8921bfeae 100644 --- a/rpc/jsonrpc/types/walletsvrcmds_test.go +++ b/rpc/jsonrpc/types/walletsvrcmds_test.go @@ -93,14 +93,14 @@ func TestWalletSvrCmds(t *testing.T) { { name: "dumpprivkey", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("dumpprivkey", "1Address") + return dcrjson.NewCmd("dumpprivkey", "TsAddress") }, staticCmd: func() interface{} { - return NewDumpPrivKeyCmd("1Address") + return NewDumpPrivKeyCmd("TsAddress") }, - marshalled: `{"jsonrpc":"1.0","method":"dumpprivkey","params":["1Address"],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"dumpprivkey","params":["TsAddress"],"id":1}`, unmarshalled: &DumpPrivKeyCmd{ - Address: "1Address", + Address: "TsAddress", }, }, { @@ -119,14 +119,14 @@ func TestWalletSvrCmds(t *testing.T) { { name: "getaccount", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("getaccount", "1Address") + return dcrjson.NewCmd("getaccount", "TsAddress") }, staticCmd: func() interface{} { - return NewGetAccountCmd("1Address") + return NewGetAccountCmd("TsAddress") }, - marshalled: `{"jsonrpc":"1.0","method":"getaccount","params":["1Address"],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"getaccount","params":["TsAddress"],"id":1}`, unmarshalled: &GetAccountCmd{ - Address: "1Address", + Address: "TsAddress", }, }, { @@ -282,28 +282,28 @@ func TestWalletSvrCmds(t *testing.T) { { name: "getreceivedbyaddress", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("getreceivedbyaddress", "1Address") + return dcrjson.NewCmd("getreceivedbyaddress", "TsAddress") }, staticCmd: func() interface{} { - return NewGetReceivedByAddressCmd("1Address", nil) + return NewGetReceivedByAddressCmd("TsAddress", nil) }, - marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaddress","params":["1Address"],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaddress","params":["TsAddress"],"id":1}`, unmarshalled: &GetReceivedByAddressCmd{ - Address: "1Address", + Address: "TsAddress", MinConf: dcrjson.Int(1), }, }, { name: "getreceivedbyaddress optional", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("getreceivedbyaddress", "1Address", 6) + return dcrjson.NewCmd("getreceivedbyaddress", "TsAddress", 6) }, staticCmd: func() interface{} { - return NewGetReceivedByAddressCmd("1Address", dcrjson.Int(6)) + return NewGetReceivedByAddressCmd("TsAddress", dcrjson.Int(6)) }, - marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaddress","params":["1Address",6],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaddress","params":["TsAddress",6],"id":1}`, unmarshalled: &GetReceivedByAddressCmd{ - Address: "1Address", + Address: "TsAddress", MinConf: dcrjson.Int(6), }, }, @@ -769,17 +769,17 @@ func TestWalletSvrCmds(t *testing.T) { { name: "listunspent optional3", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("listunspent", 6, 100, []string{"1Address", "1Address2"}) + return dcrjson.NewCmd("listunspent", 6, 100, []string{"TsAddress", "TsAddress2"}) }, staticCmd: func() interface{} { return NewListUnspentCmd(dcrjson.Int(6), dcrjson.Int(100), - &[]string{"1Address", "1Address2"}) + &[]string{"TsAddress", "TsAddress2"}) }, - marshalled: `{"jsonrpc":"1.0","method":"listunspent","params":[6,100,["1Address","1Address2"]],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"listunspent","params":[6,100,["TsAddress","TsAddress2"]],"id":1}`, unmarshalled: &ListUnspentCmd{ MinConf: dcrjson.Int(6), MaxConf: dcrjson.Int(100), - Addresses: &[]string{"1Address", "1Address2"}, + Addresses: &[]string{"TsAddress", "TsAddress2"}, }, }, { @@ -818,15 +818,15 @@ func TestWalletSvrCmds(t *testing.T) { { name: "sendfrom", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("sendfrom", "from", "1Address", 0.5) + return dcrjson.NewCmd("sendfrom", "from", "TsAddress", 0.5) }, staticCmd: func() interface{} { - return NewSendFromCmd("from", "1Address", 0.5, nil, nil, nil) + return NewSendFromCmd("from", "TsAddress", 0.5, nil, nil, nil) }, - marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","TsAddress",0.5],"id":1}`, unmarshalled: &SendFromCmd{ FromAccount: "from", - ToAddress: "1Address", + ToAddress: "TsAddress", Amount: 0.5, MinConf: dcrjson.Int(1), Comment: nil, @@ -836,15 +836,15 @@ func TestWalletSvrCmds(t *testing.T) { { name: "sendfrom optional1", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6) + return dcrjson.NewCmd("sendfrom", "from", "TsAddress", 0.5, 6) }, staticCmd: func() interface{} { - return NewSendFromCmd("from", "1Address", 0.5, dcrjson.Int(6), nil, nil) + return NewSendFromCmd("from", "TsAddress", 0.5, dcrjson.Int(6), nil, nil) }, - marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","TsAddress",0.5,6],"id":1}`, unmarshalled: &SendFromCmd{ FromAccount: "from", - ToAddress: "1Address", + ToAddress: "TsAddress", Amount: 0.5, MinConf: dcrjson.Int(6), Comment: nil, @@ -854,16 +854,16 @@ func TestWalletSvrCmds(t *testing.T) { { name: "sendfrom optional2", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "comment") + return dcrjson.NewCmd("sendfrom", "from", "TsAddress", 0.5, 6, "comment") }, staticCmd: func() interface{} { - return NewSendFromCmd("from", "1Address", 0.5, dcrjson.Int(6), + return NewSendFromCmd("from", "TsAddress", 0.5, dcrjson.Int(6), dcrjson.String("comment"), nil) }, - marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment"],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","TsAddress",0.5,6,"comment"],"id":1}`, unmarshalled: &SendFromCmd{ FromAccount: "from", - ToAddress: "1Address", + ToAddress: "TsAddress", Amount: 0.5, MinConf: dcrjson.Int(6), Comment: dcrjson.String("comment"), @@ -873,16 +873,16 @@ func TestWalletSvrCmds(t *testing.T) { { name: "sendfrom optional3", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "comment", "commentto") + return dcrjson.NewCmd("sendfrom", "from", "TsAddress", 0.5, 6, "comment", "commentto") }, staticCmd: func() interface{} { - return NewSendFromCmd("from", "1Address", 0.5, dcrjson.Int(6), + return NewSendFromCmd("from", "TsAddress", 0.5, dcrjson.Int(6), dcrjson.String("comment"), dcrjson.String("commentto")) }, - marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment","commentto"],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","TsAddress",0.5,6,"comment","commentto"],"id":1}`, unmarshalled: &SendFromCmd{ FromAccount: "from", - ToAddress: "1Address", + ToAddress: "TsAddress", Amount: 0.5, MinConf: dcrjson.Int(6), Comment: dcrjson.String("comment"), @@ -892,16 +892,16 @@ func TestWalletSvrCmds(t *testing.T) { { name: "sendmany", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("sendmany", "from", `{"1Address":0.5}`) + return dcrjson.NewCmd("sendmany", "from", `{"TsAddress":0.5}`) }, staticCmd: func() interface{} { - amounts := map[string]float64{"1Address": 0.5} + amounts := map[string]float64{"TsAddress": 0.5} return NewSendManyCmd("from", amounts, nil, nil) }, - marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5}],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"TsAddress":0.5}],"id":1}`, unmarshalled: &SendManyCmd{ FromAccount: "from", - Amounts: map[string]float64{"1Address": 0.5}, + Amounts: map[string]float64{"TsAddress": 0.5}, MinConf: dcrjson.Int(1), Comment: nil, }, @@ -909,16 +909,16 @@ func TestWalletSvrCmds(t *testing.T) { { name: "sendmany optional1", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6) + return dcrjson.NewCmd("sendmany", "from", `{"TsAddress":0.5}`, 6) }, staticCmd: func() interface{} { - amounts := map[string]float64{"1Address": 0.5} + amounts := map[string]float64{"TsAddress": 0.5} return NewSendManyCmd("from", amounts, dcrjson.Int(6), nil) }, - marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"TsAddress":0.5},6],"id":1}`, unmarshalled: &SendManyCmd{ FromAccount: "from", - Amounts: map[string]float64{"1Address": 0.5}, + Amounts: map[string]float64{"TsAddress": 0.5}, MinConf: dcrjson.Int(6), Comment: nil, }, @@ -926,16 +926,16 @@ func TestWalletSvrCmds(t *testing.T) { { name: "sendmany optional2", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "comment") + return dcrjson.NewCmd("sendmany", "from", `{"TsAddress":0.5}`, 6, "comment") }, staticCmd: func() interface{} { - amounts := map[string]float64{"1Address": 0.5} + amounts := map[string]float64{"TsAddress": 0.5} return NewSendManyCmd("from", amounts, dcrjson.Int(6), dcrjson.String("comment")) }, - marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"comment"],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"TsAddress":0.5},6,"comment"],"id":1}`, unmarshalled: &SendManyCmd{ FromAccount: "from", - Amounts: map[string]float64{"1Address": 0.5}, + Amounts: map[string]float64{"TsAddress": 0.5}, MinConf: dcrjson.Int(6), Comment: dcrjson.String("comment"), }, @@ -943,14 +943,14 @@ func TestWalletSvrCmds(t *testing.T) { { name: "sendtoaddress", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("sendtoaddress", "1Address", 0.5) + return dcrjson.NewCmd("sendtoaddress", "TsAddress", 0.5) }, staticCmd: func() interface{} { - return NewSendToAddressCmd("1Address", 0.5, nil, nil) + return NewSendToAddressCmd("TsAddress", 0.5, nil, nil) }, - marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["TsAddress",0.5],"id":1}`, unmarshalled: &SendToAddressCmd{ - Address: "1Address", + Address: "TsAddress", Amount: 0.5, Comment: nil, CommentTo: nil, @@ -959,15 +959,15 @@ func TestWalletSvrCmds(t *testing.T) { { name: "sendtoaddress optional1", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("sendtoaddress", "1Address", 0.5, "comment", "commentto") + return dcrjson.NewCmd("sendtoaddress", "TsAddress", 0.5, "comment", "commentto") }, staticCmd: func() interface{} { - return NewSendToAddressCmd("1Address", 0.5, dcrjson.String("comment"), + return NewSendToAddressCmd("TsAddress", 0.5, dcrjson.String("comment"), dcrjson.String("commentto")) }, - marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"comment","commentto"],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["TsAddress",0.5,"comment","commentto"],"id":1}`, unmarshalled: &SendToAddressCmd{ - Address: "1Address", + Address: "TsAddress", Amount: 0.5, Comment: dcrjson.String("comment"), CommentTo: dcrjson.String("commentto"), @@ -976,17 +976,17 @@ func TestWalletSvrCmds(t *testing.T) { { name: "sendtoaddress optional2", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("sendtoaddress", "1Address", 0.5, "comment", "commentto", dcrjson.Bool(true)) + return dcrjson.NewCmd("sendtoaddress", "TsAddress", 0.5, "comment", "commentto", dcrjson.Bool(true)) }, staticCmd: func() interface{} { - val := NewSendToAddressCmd("1Address", 0.5, dcrjson.String("comment"), + val := NewSendToAddressCmd("TsAddress", 0.5, dcrjson.String("comment"), dcrjson.String("commentto")) val.SubtractFeeFromAmount = dcrjson.Bool(true) return val }, - marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"comment","commentto",true],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["TsAddress",0.5,"comment","commentto",true],"id":1}`, unmarshalled: &SendToAddressCmd{ - Address: "1Address", + Address: "TsAddress", Amount: 0.5, Comment: dcrjson.String("comment"), CommentTo: dcrjson.String("commentto"), @@ -1009,14 +1009,14 @@ func TestWalletSvrCmds(t *testing.T) { { name: "signmessage", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("signmessage", "1Address", "message") + return dcrjson.NewCmd("signmessage", "TsAddress", "message") }, staticCmd: func() interface{} { - return NewSignMessageCmd("1Address", "message") + return NewSignMessageCmd("TsAddress", "message") }, - marshalled: `{"jsonrpc":"1.0","method":"signmessage","params":["1Address","message"],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"signmessage","params":["TsAddress","message"],"id":1}`, unmarshalled: &SignMessageCmd{ - Address: "1Address", + Address: "TsAddress", Message: "message", }, }, diff --git a/rpc/jsonrpc/types/walletsvrwscmds_test.go b/rpc/jsonrpc/types/walletsvrwscmds_test.go index db7d67122..785c64c44 100644 --- a/rpc/jsonrpc/types/walletsvrwscmds_test.go +++ b/rpc/jsonrpc/types/walletsvrwscmds_test.go @@ -113,29 +113,29 @@ func TestWalletSvrWsCmds(t *testing.T) { { name: "listaddresstransactions", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("listaddresstransactions", `["1Address"]`) + return dcrjson.NewCmd("listaddresstransactions", `["TsAddress"]`) }, staticCmd: func() interface{} { - return NewListAddressTransactionsCmd([]string{"1Address"}, nil) + return NewListAddressTransactionsCmd([]string{"TsAddress"}, nil) }, - marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"]],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["TsAddress"]],"id":1}`, unmarshalled: &ListAddressTransactionsCmd{ - Addresses: []string{"1Address"}, + Addresses: []string{"TsAddress"}, Account: nil, }, }, { name: "listaddresstransactions optional1", newCmd: func() (interface{}, error) { - return dcrjson.NewCmd("listaddresstransactions", `["1Address"]`, "acct") + return dcrjson.NewCmd("listaddresstransactions", `["TsAddress"]`, "acct") }, staticCmd: func() interface{} { - return NewListAddressTransactionsCmd([]string{"1Address"}, + return NewListAddressTransactionsCmd([]string{"TsAddress"}, dcrjson.String("acct")) }, - marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"],"acct"],"id":1}`, + marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["TsAddress"],"acct"],"id":1}`, unmarshalled: &ListAddressTransactionsCmd{ - Addresses: []string{"1Address"}, + Addresses: []string{"TsAddress"}, Account: dcrjson.String("acct"), }, }, diff --git a/rpc/jsonrpc/types/walletsvrwsntfns_test.go b/rpc/jsonrpc/types/walletsvrwsntfns_test.go index 9c651d7d6..c62e91b10 100644 --- a/rpc/jsonrpc/types/walletsvrwsntfns_test.go +++ b/rpc/jsonrpc/types/walletsvrwsntfns_test.go @@ -60,12 +60,12 @@ func TestWalletSvrWsNtfns(t *testing.T) { { name: "newtx", newNtfn: func() (interface{}, error) { - return dcrjson.NewCmd("newtx", "acct", `{"account":"acct","address":"1Address","category":"send","amount":1.5,"fee":0.0001,"confirmations":1,"txid":"456","walletconflicts":[],"time":12345678,"timereceived":12345876,"vout":789,"otheraccount":"otheracct"}`) + return dcrjson.NewCmd("newtx", "acct", `{"account":"acct","address":"TsAddress","category":"send","amount":1.5,"fee":0.0001,"confirmations":1,"txid":"456","walletconflicts":[],"time":12345678,"timereceived":12345876,"vout":789,"otheraccount":"otheracct"}`) }, staticNtfn: func() interface{} { result := ListTransactionsResult{ Account: "acct", - Address: "1Address", + Address: "TsAddress", Category: "send", Amount: 1.5, Fee: dcrjson.Float64(0.0001), @@ -79,12 +79,12 @@ func TestWalletSvrWsNtfns(t *testing.T) { } return NewNewTxNtfn("acct", result) }, - marshalled: `{"jsonrpc":"1.0","method":"newtx","params":["acct",{"account":"acct","address":"1Address","amount":1.5,"category":"send","confirmations":1,"fee":0.0001,"time":12345678,"timereceived":12345876,"txid":"456","vout":789,"walletconflicts":[],"otheraccount":"otheracct"}],"id":null}`, + marshalled: `{"jsonrpc":"1.0","method":"newtx","params":["acct",{"account":"acct","address":"TsAddress","amount":1.5,"category":"send","confirmations":1,"fee":0.0001,"time":12345678,"timereceived":12345876,"txid":"456","vout":789,"walletconflicts":[],"otheraccount":"otheracct"}],"id":null}`, unmarshalled: &NewTxNtfn{ Account: "acct", Details: ListTransactionsResult{ Account: "acct", - Address: "1Address", + Address: "TsAddress", Category: "send", Amount: 1.5, Fee: dcrjson.Float64(0.0001), diff --git a/wallet/createtx.go b/wallet/createtx.go index 91514b1c7..1e3ec6447 100644 --- a/wallet/createtx.go +++ b/wallet/createtx.go @@ -304,39 +304,6 @@ func (w *Wallet) checkHighFees(totalInput dcrutil.Amount, tx *wire.MsgTx) error return nil } -// defaulted returns a new instance of the CreateTxOptions instance -// with default values set. -func (o *CreateTxOptions) initFromWallet(w *Wallet) *CreateTxOptions { - newOpts := CreateTxOptions{} - newOpts.RelayFeePerKb = dcrjson.Int64(int64(o.relayFee(w))) - - if newOpts.RecipientPaysFee == nil { - newOpts.RecipientPaysFee = dcrjson.Bool(false) - } - - if newOpts.RandomizeChangeIndex == nil { - newOpts.RandomizeChangeIndex = dcrjson.Bool(true) - } - - if newOpts.MinimumConfirmations == nil { - newOpts.MinimumConfirmations = dcrjson.Int32(1) - } - - if newOpts.Account == nil { - newOpts.Account = dcrjson.Uint32(udb.DefaultAccountNum) - } - - return &newOpts -} - -func (o *CreateTxOptions) relayFee(w *Wallet) dcrutil.Amount { - if o == nil || o.RelayFeePerKb == nil { - return w.RelayFee() - } - - return dcrutil.Amount(*o.RelayFeePerKb) -} - // txToOutputs creates a signed transaction which includes each output // from outputs. Previous outputs to reedeem are chosen from the passed // account's UTXO set and minconf policy. An additional output may be added to @@ -351,11 +318,6 @@ func (o *CreateTxOptions) relayFee(w *Wallet) dcrutil.Amount { func (w *Wallet) txToOutputs(op errors.Op, outputs []*wire.TxOut, n NetworkBackend, options *CreateTxOptions) (*txauthor.AuthoredTx, error) { - if options == nil { - // fill unset options - options = (&CreateTxOptions{}).initFromWallet(w) - } - var unlockOutpoints []*wire.OutPoint defer func() { if len(unlockOutpoints) != 0 { @@ -385,23 +347,23 @@ func (w *Wallet) txToOutputs(op errors.Op, outputs []*wire.TxOut, // Create the unsigned transaction. _, tipHeight := w.TxStore.MainChainTip(txmgrNs) inputSource := w.TxStore.MakeIgnoredInputSource(txmgrNs, addrmgrNs, - *options.Account, *options.MinimumConfirmations, + options.account(), options.minConf(), tipHeight, ignoreInput) changeSource := &p2PKHChangeSource{ persist: w.deferPersistReturnedChild(&changeSourceUpdates), - account: *options.Account, + account: options.account(), wallet: w, } var err error - if *options.RecipientPaysFee { + if options.recipientPaysFee() { if len(outputs) != 1 { return errors.E(errors.Invalid, "expected exactly one output for "+ "transaction where recipient pays the fee") } output := outputs[0] - atx, err = txauthor.NewUnsignedTransactionMinusFee(output, + atx, err = newUnsignedTransactionMinusFee(op, output, options.relayFee(w), inputSource.SelectInputs, changeSource) } else { atx, err = txauthor.NewUnsignedTransaction(outputs, @@ -420,7 +382,7 @@ func (w *Wallet) txToOutputs(op errors.Op, outputs []*wire.TxOut, // Randomize change position, if change exists, before signing. This // doesn't affect the serialize size, so the change amount will still be // valid. - if atx.ChangeIndex >= 0 && *options.RandomizeChangeIndex { + if atx.ChangeIndex >= 0 && options.randomizeChangeIndex() { atx.RandomizeChangePosition() } @@ -444,7 +406,7 @@ func (w *Wallet) txToOutputs(op errors.Op, outputs []*wire.TxOut, // Warn when spending UTXOs controlled by imported keys created change for // the default account. - if atx.ChangeIndex >= 0 && *options.Account == udb.ImportedAddrAccount { + if atx.ChangeIndex >= 0 && options.account() == udb.ImportedAddrAccount { changeAmount := dcrutil.Amount(atx.Tx.TxOut[atx.ChangeIndex].Value) log.Warnf("Spend from imported account produced change: moving"+ " %v from imported account into default account.", changeAmount) @@ -503,6 +465,47 @@ func (w *Wallet) txToOutputs(op errors.Op, outputs []*wire.TxOut, return atx, nil } +// newUnsignedTransactionMinusFee creates an unsigned transaction paying to one +// non-change output. An appropriate transaction fee is included based on the +// transaction size, and is subtracted from the provided `output` parameter. +// +// The behavior of this method mirrors a call to `NewUnsignedTransaction` with the differences being that: +// * this method takes a single `output` argument +// * all fees are subtracted from the provided output rather than from change. +// * the output in the return value is a shallow copy of the output argument and references the original output script. +func newUnsignedTransactionMinusFee(op errors.Op, output *wire.TxOut, relayFeePerKb dcrutil.Amount, + fetchInputs txauthor.InputSource, fetchChange txauthor.ChangeSource) (*txauthor.AuthoredTx, error) { + + // Create shallow copy of `output` to include in return value since the `Value` property will be mutated + // Still retains a reference to the output script array. + outputCopy := &wire.TxOut{ + PkScript: output.PkScript, + Version: output.Version, + Value: output.Value, + } + + // Since the fee will come directly from the full output amount, + // defer fee calculation until enough inputs have been consumed. + authoredTx, err := txauthor.NewUnsignedTransaction([]*wire.TxOut{outputCopy}, + 0, fetchInputs, fetchChange) + if err != nil { + return nil, err + } + + // At this point, all inputs have been determined and the estimated size of the transaction is fixed. + // Calculate the fee and subtract it from the copied instance of the provided output. + feeAmount := txrules.FeeForSerializeSize(relayFeePerKb, + authoredTx.EstimatedSignedSerializeSize) + outputCopy.Value -= int64(feeAmount) + + // Mirror the behavior of txauthor.NewUnsignedTransaction when the fee exceeds the amount to send. + if outputCopy.Value < 0 { + return nil, errors.E(op, errors.InsufficientBalance) + } + + return authoredTx, nil +} + // txToMultisig spends funds to a multisig output, partially signs the // transaction, then returns fund func (w *Wallet) txToMultisig(op errors.Op, account uint32, amount dcrutil.Amount, pubkeys []*dcrutil.AddressSecpPubKey, diff --git a/wallet/createtx_test.go b/wallet/createtx_test.go new file mode 100644 index 000000000..5bfd7fe39 --- /dev/null +++ b/wallet/createtx_test.go @@ -0,0 +1,187 @@ +package wallet + +import ( + "testing" + + "github.com/decred/dcrd/dcrutil" + "github.com/decred/dcrd/wire" + "github.com/decred/dcrwallet/errors" + "github.com/decred/dcrwallet/wallet/v2/txauthor" + . "github.com/decred/dcrwallet/wallet/v2/txauthor" + "github.com/decred/dcrwallet/wallet/v2/txrules" + + "github.com/decred/dcrwallet/wallet/v2/internal/txsizes" +) + + +type AuthorTestChangeSource struct{} + +func (src AuthorTestChangeSource) Script() ([]byte, uint16, error) { + // Only length matters for these tests. + return make([]byte, txsizes.P2PKHPkScriptSize), 0, nil +} + +func (src AuthorTestChangeSource) ScriptSize() int { + return txsizes.P2PKHPkScriptSize +} + +func p2pkhOutputs(amounts ...dcrutil.Amount) []*wire.TxOut { + v := make([]*wire.TxOut, 0, len(amounts)) + for _, a := range amounts { + outScript := make([]byte, txsizes.P2PKHOutputSize) + v = append(v, wire.NewTxOut(int64(a), outScript)) + } + return v +} + +func makeInputSource(unspents []*wire.TxOut) InputSource { + // Return outputs in order. + currentTotal := dcrutil.Amount(0) + currentInputs := make([]*wire.TxIn, 0, len(unspents)) + redeemScriptSizes := make([]int, 0, len(unspents)) + f := func(target dcrutil.Amount) (*InputDetail, error) { + for currentTotal < target && len(unspents) != 0 { + u := unspents[0] + unspents = unspents[1:] + nextInput := wire.NewTxIn(&wire.OutPoint{}, u.Value, nil) + currentTotal += dcrutil.Amount(u.Value) + currentInputs = append(currentInputs, nextInput) + redeemScriptSizes = append(redeemScriptSizes, txsizes.RedeemP2PKHSigScriptSize) + } + + inputDetail := txauthor.InputDetail{ + Amount: currentTotal, + Inputs: currentInputs, + Scripts: make([][]byte, len(currentInputs)), + RedeemScriptSizes: redeemScriptSizes, + } + return &inputDetail, nil + } + return InputSource(f) +} + +func TestNewUnsignedTransactionMinusFee(t *testing.T) { + const op errors.Op = "createtx.TestNewUnsignedTransactionMinusFee" + + tests := []struct { + UnspentOutputs []*wire.TxOut + Output *wire.TxOut + RelayFee dcrutil.Amount + ExpectedChange dcrutil.Amount + ShouldError bool + ExpectedError errors.Kind + }{ + 0: { + // Spend exactly what we have available, but would be negative since fee is + // +1 more than available + UnspentOutputs: p2pkhOutputs(227), + Output: p2pkhOutputs(227)[0], + RelayFee: 1e3, + ShouldError: true, + ExpectedError: errors.InsufficientBalance, + }, + 1: { + // Spend all inputs, and do not fail if dust + UnspentOutputs: p2pkhOutputs(228), + Output: p2pkhOutputs(228)[0], + RelayFee: 1e3, + ShouldError: false, + }, + 2: { + // Spend exactly what we have available but enough for fee + UnspentOutputs: p2pkhOutputs(1e8), + Output: p2pkhOutputs(1e8)[0], + RelayFee: 1e3, + ShouldError: false, + }, + 3: { + // Spend more than we have available + UnspentOutputs: p2pkhOutputs(1e6), + Output: p2pkhOutputs(1e6 + 1)[0], + RelayFee: 1e3, + ShouldError: true, + ExpectedError: errors.InsufficientBalance, + }, + 4: { + // Expect change exactly to equal input - (output without fee) + // Output should have fee subtracted. + UnspentOutputs: p2pkhOutputs(2e6), + Output: p2pkhOutputs(1e6)[0], + RelayFee: 1e3, + ExpectedChange: 1e6, + ShouldError: false, + }, + 5: { + // Make sure we get expected change + UnspentOutputs: p2pkhOutputs(2), + Output: p2pkhOutputs(1)[0], + RelayFee: 0, + ExpectedChange: 1, + ShouldError: false, + }, + } + + var changeSource AuthorTestChangeSource + + for i, test := range tests { + inputSource := makeInputSource(test.UnspentOutputs) + tx, err := newUnsignedTransactionMinusFee(op, test.Output, test.RelayFee, + inputSource, changeSource) + + if test.ShouldError { + if err == nil { + t.Errorf("Test %d: Expected error but one was not returned", i) + continue + } else if !errors.Is(test.ExpectedError, err) { + t.Errorf("Test %d: Error=%v expected %v", i, err, test.ExpectedError) + continue + } else { + // pass, got expected error + continue + } + } else { + if err != nil { + t.Errorf("Test %d: Unexpected error: %v", i, err) + continue + } else { + // no unexpected errors, carry on... + } + } + + if test.ExpectedChange > 0 && tx.ChangeIndex < 0 { + t.Errorf("Test %d: Expected change value (%v) but no change returned", i, test.ExpectedChange) + continue + } + + outputIndex := 0 + if tx.ChangeIndex >= 0 { + outputIndex = tx.ChangeIndex ^ 1 + } + + outputValue := tx.Tx.TxOut[outputIndex].Value + expectedFee := int64(txrules.FeeForSerializeSize(test.RelayFee, tx.EstimatedSignedSerializeSize)) + + // Make sure the resulting TxOut is not the same reference as the `output` argument + if test.Output == tx.Tx.TxOut[outputIndex] { + t.Errorf("Test %d: Expected returned TxOut reference to not equal test output reference", i) + continue + } + + // Adding the fee back to the output should give original value + if outputValue+expectedFee != test.Output.Value { + t.Errorf("Test %d: Expected output value (%v) plus fee (%v) to equal original output value (%v)", + i, outputValue, expectedFee, test.Output.Value) + continue + } + + // If there is change, make sure it's the expected value. + if tx.ChangeIndex >= 0 { + changeValue := tx.Tx.TxOut[tx.ChangeIndex].Value + if changeValue != int64(test.ExpectedChange) { + t.Errorf("Test %d: Got change amount %v, Expected %v", + i, changeValue, test.ExpectedChange) + continue + } + } + } +} diff --git a/wallet/txauthor/author.go b/wallet/txauthor/author.go index f577352f1..46234ef5c 100644 --- a/wallet/txauthor/author.go +++ b/wallet/txauthor/author.go @@ -154,46 +154,6 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcrutil.Amount, } } -// NewUnsignedTransactionMinusFee creates an unsigned transaction paying to one -// non-change output. An appropriate transaction fee is included based on the -// transaction size, and is subtracted from the provided `output` parameter. -// -// The behavior of this method mirrors a call to `NewUnsignedTransaction` with the differences being that: -// * this method takes a single `output` argument -// * all fees are subtracted from the provided output rather than from change. -// * the output in the return value is a shallow copy of the output argument and references the original output script. -func NewUnsignedTransactionMinusFee(output *wire.TxOut, relayFeePerKb dcrutil.Amount, - fetchInputs InputSource, fetchChange ChangeSource) (*AuthoredTx, error) { - const op errors.Op = "txauthor.NewUnsignedTransactionMinusFee" - - // Create shallow copy of `output` to include in return value since the `Value` property will be mutated - // Still retains a reference to the output script array. - outputCopy := &wire.TxOut{ - PkScript: output.PkScript, - Version: output.Version, - Value: output.Value, - } - - // Since the fee will come directly from the full output amount, - // defer fee calculation until enough inputs have been consumed. - authoredTx, err := NewUnsignedTransaction([]*wire.TxOut{outputCopy}, 0, fetchInputs, fetchChange) - if err != nil { - return nil, err - } - - // At this point, all inputs have been determined and the estimated size of the transaction is fixed. - // Calculate the fee and subtract it from the copied instance of the provided output. - feeAmount := txrules.FeeForSerializeSize(relayFeePerKb, authoredTx.EstimatedSignedSerializeSize) - outputCopy.Value -= int64(feeAmount) - - // Mirror the behavior of NewUnsignedTransaction when the fee exceeds the amount to send. - if outputCopy.Value < 0 { - return nil, errors.E(op, errors.InsufficientBalance) - } - - return authoredTx, nil -} - // RandomizeOutputPosition randomizes the position of a transaction's output by // swapping it with a random output. The new index is returned. This should be // done before signing. diff --git a/wallet/txauthor/author_test.go b/wallet/txauthor/author_test.go index 9b63ca837..f72fa1777 100644 --- a/wallet/txauthor/author_test.go +++ b/wallet/txauthor/author_test.go @@ -238,127 +238,3 @@ func TestNewUnsignedTransaction(t *testing.T) { } } } - -func TestNewUnsignedTransactionMinusFee(t *testing.T) { - tests := []struct { - UnspentOutputs []*wire.TxOut - Output *wire.TxOut - RelayFee dcrutil.Amount - ExpectedChange dcrutil.Amount - ShouldError bool - ExpectedError errors.Kind - }{ - 0: { - // Spend exactly what we have available, but would be negative since fee is - // +1 more than available - UnspentOutputs: p2pkhOutputs(227), - Output: p2pkhOutputs(227)[0], - RelayFee: 1e3, - ShouldError: true, - ExpectedError: errors.InsufficientBalance, - }, - 1: { - // Spend all inputs, and do not fail if dust - UnspentOutputs: p2pkhOutputs(228), - Output: p2pkhOutputs(228)[0], - RelayFee: 1e3, - ShouldError: false, - }, - 2: { - // Spend exactly what we have available but enough for fee - UnspentOutputs: p2pkhOutputs(1e8), - Output: p2pkhOutputs(1e8)[0], - RelayFee: 1e3, - ShouldError: false, - }, - 3: { - // Spend more than we have available - UnspentOutputs: p2pkhOutputs(1e6), - Output: p2pkhOutputs(1e6 + 1)[0], - RelayFee: 1e3, - ShouldError: true, - ExpectedError: errors.InsufficientBalance, - }, - 4: { - // Expect change exactly to equal input - (output without fee) - // Output should have fee subtracted. - UnspentOutputs: p2pkhOutputs(2e6), - Output: p2pkhOutputs(1e6)[0], - RelayFee: 1e3, - ExpectedChange: 1e6, - ShouldError: false, - }, - 5: { - // Make sure we get expected change - UnspentOutputs: p2pkhOutputs(2), - Output: p2pkhOutputs(1)[0], - RelayFee: 0, - ExpectedChange: 1, - ShouldError: false, - }, - } - - var changeSource AuthorTestChangeSource - - for i, test := range tests { - inputSource := makeInputSource(test.UnspentOutputs) - tx, err := NewUnsignedTransactionMinusFee(test.Output, test.RelayFee, - inputSource, changeSource) - - if test.ShouldError { - if err == nil { - t.Errorf("Test %d: Expected error but one was not returned", i) - continue - } else if !errors.Is(test.ExpectedError, err) { - t.Errorf("Test %d: Error=%v expected %v", i, err, test.ExpectedError) - continue - } else { - // pass, got expected error - continue - } - } else { - if err != nil { - t.Errorf("Test %d: Unexpected error: %v", i, err) - continue - } else { - // no unexpected errors, carry on... - } - } - - if test.ExpectedChange > 0 && tx.ChangeIndex < 0 { - t.Errorf("Test %d: Expected change value (%v) but no change returned", i, test.ExpectedChange) - continue - } - - outputIndex := 0 - if tx.ChangeIndex >= 0 { - outputIndex = tx.ChangeIndex ^ 1 - } - - outputValue := tx.Tx.TxOut[outputIndex].Value - expectedFee := int64(txrules.FeeForSerializeSize(test.RelayFee, tx.EstimatedSignedSerializeSize)) - - // Make sure the resulting TxOut is not the same reference as the `output` argument - if test.Output == tx.Tx.TxOut[outputIndex] { - t.Errorf("Test %d: Expected returned TxOut reference to not equal test output reference", i) - continue - } - - // Adding the fee back to the output should give original value - if outputValue+expectedFee != test.Output.Value { - t.Errorf("Test %d: Expected output value (%v) plus fee (%v) to equal original output value (%v)", - i, outputValue, expectedFee, test.Output.Value) - continue - } - - // If there is change, make sure it's the expected value. - if tx.ChangeIndex >= 0 { - changeValue := tx.Tx.TxOut[tx.ChangeIndex].Value - if changeValue != int64(test.ExpectedChange) { - t.Errorf("Test %d: Got change amount %v, Expected %v", - i, changeValue, test.ExpectedChange) - continue - } - } - } -} diff --git a/wallet/wallet.go b/wallet/wallet.go index efcdb36f1..a4dd85eb9 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -3797,6 +3797,46 @@ type CreateTxOptions struct { Account *uint32 } +func (o *CreateTxOptions) recipientPaysFee() bool { + if o == nil || o.RecipientPaysFee == nil { + return false + } + + return *o.RecipientPaysFee +} + +func (o *CreateTxOptions) randomizeChangeIndex() bool { + if o == nil || o.RandomizeChangeIndex == nil { + return true + } + + return *o.RandomizeChangeIndex +} + +func (o *CreateTxOptions) relayFee(w *Wallet) dcrutil.Amount { + if o == nil || o.RelayFeePerKb == nil { + return w.RelayFee() + } + + return dcrutil.Amount(*o.RelayFeePerKb) +} + +func (o *CreateTxOptions) minConf() int32 { + if o == nil || o.MinimumConfirmations == nil { + return 1 + } + + return *o.MinimumConfirmations +} + +func (o *CreateTxOptions) account() uint32 { + if o == nil || o.Account == nil { + return 1 + } + + return *o.Account +} + // SendOutputs creates and sends payment transactions. // It returns the transaction hash upon success. //