-
Notifications
You must be signed in to change notification settings - Fork 155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
legacyrpc: add fundrawtransaction command #1195
Conversation
bd95579
to
7401a62
Compare
6855f06
to
84751bc
Compare
rpc/legacyrpc/methods.go
Outdated
@@ -31,6 +31,8 @@ import ( | |||
"github.com/decred/dcrwallet/wallet" | |||
"github.com/decred/dcrwallet/wallet/txrules" | |||
"github.com/decred/dcrwallet/wallet/udb" | |||
"google.golang.org/grpc/codes" | |||
"google.golang.org/grpc/status" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there should be no grpc imports in the json-rpc implementation (and likewise, no json-rpc code in the grpc server)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
66220b2
to
2d5208e
Compare
This PR is ready for another review |
e34ff63
to
5f82c26
Compare
rpc/legacyrpc/methods.go
Outdated
if err != nil { | ||
return nil, rpcError(dcrjson.ErrRPCDeserialization, err) | ||
} | ||
err = mtx.Deserialize(bytes.NewReader(decodedTx)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This and the hex decoding may be done in a single step by using mtx.Deserialize(hex.NewDecoder(strings.NewReader(cmd.HexString)))
rpc/legacyrpc/methods.go
Outdated
} | ||
resp := &dcrjson.FundRawTransactionResult{ | ||
Hex: hex.EncodeToString(buf.Bytes()), | ||
Fee: float64(fundedTx.EstimatedSignedSerializeSize) / 1e8, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not the correct fee calculation. The fee is totalInputValue - totalOutputValue
rpc/legacyrpc/methods.go
Outdated
} | ||
feePerKb := w.RelayFee() | ||
requiredConfs := int32(1) | ||
addr := "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be of type dcrutil.Address
. Address decoding should occur in this RPC handler and not in the wallet method.
wallet/createtx.go
Outdated
@@ -168,6 +168,70 @@ func (w *Wallet) NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcr | |||
return authoredTx, nil | |||
} | |||
|
|||
func (w *Wallet) FundRawTransaction(inputs []*wire.TxIn, outputs []*wire.TxOut, relayFeePerKb dcrutil.Amount, account uint32, minConf int32, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are other fields besides the inputs and outputs that must be preserved by fundrawtransaction (e.g. sertype, version, locktime, and expiry), so we can not just pass in the inputs and outputs of the decoded input and then return an entirely new transaction.
Use a dcrutil.Address
for the change address.
wallet/txauthor/author.go
Outdated
@@ -154,6 +154,82 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcrutil.Amount, | |||
} | |||
} | |||
|
|||
func FundRawTransaction(inputs []*wire.TxIn, outputs []*wire.TxOut, relayFeePerKb dcrutil.Amount, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Again, we must pass a MsgTx directly for all tx fields, not just inputs and outputs.
|
||
totalInTransaction := h.SumInputValues(inputs) | ||
targetAmount := h.SumOutputValues(outputs) - totalInTransaction | ||
scriptSizes := []int{txsizes.RedeemP2PKHSigScriptSize} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks wrong. The initial scriptSizes slice should contain all of the sizes of transaction input scripts that were passed to the function. We can not remove any of these inputs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I changed the initial scriptSizes to this plus each input the transaction might be, is that right now?
scriptSizes := []int{txsizes.RedeemP2PKHSigScriptSize}
for range mtx.TxIn {
scriptSizes = append(scriptSizes, txsizes.RedeemP2PKHInputSize)
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, if the inputs already contain scripts, then we should use those sizes. If they are missing, we're stuck since we don't really know what kinds of inputs are being redeemed and how large the input scripts will be once signed. We could guess it to be P2PKH, but that's still a guess and could result in fees being either too high or too low once the transaction is completely signed.
wallet/txauthor/author.go
Outdated
maxSignedSize = txsizes.EstimateSerializeSize(scriptSizes, | ||
unsignedTransaction.TxOut, 0) | ||
} | ||
return &AuthoredTx{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AuthoredTx is the wrong type to return from this function. This is because PrevScripts
is not possible to determine if the funded transaction already contained inputs that the wallet did not know about.
wallet/txauthor/author.go
Outdated
return nil, errors.E(op, errors.InsufficientBalance) | ||
} | ||
|
||
scriptSizes = []int{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what?
01b1872
to
fbe0f48
Compare
Thanks for reviewing my PR again Jrick. I fixed your comments and this PR is ready for another review. |
internal/rpchelp/helpdescs_en_US.go
Outdated
"fundrawtransaction-options": "The optional inputs to fund a raw transaction", | ||
|
||
// FundRawTransactionOptions | ||
"fundrawtransactionoptions-changeaddress": "Address the change will be send to (default=\"default\")", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default is the empty string
rpc/legacyrpc/methods.go
Outdated
var changeAddr dcrutil.Address | ||
|
||
if cmd.Options != nil { | ||
// use provided fee per Kb if specified |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's kilobyte, kB. You will sometimes see KB used in variable names due to camel case
rpc/legacyrpc/methods.go
Outdated
requiredConfs = *cmd.Options.ConfTarget | ||
} | ||
// use provided change account if specified | ||
// use default account otherwise |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
change address, not account
wallet/addresses.go
Outdated
type p2PKHAddrChangeSource struct { | ||
persist persistReturnedChildFunc | ||
changeAddress dcrutil.Address | ||
wallet *Wallet |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
since this represents a user-specified address, the persist and wallet fields aren't needed (there's no need to persist this address to the wallet's db).
@@ -160,6 +160,62 @@ func (w *Wallet) NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcr | |||
return authoredTx, nil | |||
} | |||
|
|||
func (w *Wallet) FundRawTransaction(tx wire.MsgTx, relayFeePerKb dcrutil.Amount, account uint32, minConf int32, | |||
changeAddress dcrutil.Address) (*wire.MsgTx, error) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this is an exported public API and needs a documentation comment describing what it does. Be sure to document that changeAddress may be nil, and how the behavior changes depending on if it is nil or not.
tx
should be passed by pointer.
wallet/createtx.go
Outdated
changeAddress: changeAddress, | ||
wallet: w, | ||
} | ||
fundedTx, err = txauthor.FundRawTransaction(tx, relayFeePerKb, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this call is identical in both branches of the if-else, so it can be moved out. Declare a change source interface variable above the branch and use the branches to set it to either the provided address or a wallet-derived one.
wallet/txauthor/author.go
Outdated
@@ -154,6 +154,75 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, relayFeePerKb dcrutil.Amount, | |||
} | |||
} | |||
|
|||
func FundRawTransaction(mtx wire.MsgTx, relayFeePerKb dcrutil.Amount, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needs doc comment
mtx
should be passed by pointer.
wallet/txauthor/author.go
Outdated
|
||
inputs := mtx.TxIn | ||
outputs := mtx.TxOut | ||
totalInTransaction := h.SumInputValues(inputs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
have to be careful here. If the input amount is not set in any of the inputs (using the sentinel value wire.NullValueIn
) this won't be accurate. This has to be checked, and we have to error out if it's not set (it will eventually become required by consensus).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what this wire.NullValuIn
is supposed to do. I added a verification checking if the TxIn
array is not empty, is that right? if len(inputs) > 0 {
|
||
totalInTransaction := h.SumInputValues(inputs) | ||
targetAmount := h.SumOutputValues(outputs) - totalInTransaction | ||
scriptSizes := []int{txsizes.RedeemP2PKHSigScriptSize} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, if the inputs already contain scripts, then we should use those sizes. If they are missing, we're stuck since we don't really know what kinds of inputs are being redeemed and how large the input scripts will be once signed. We could guess it to be P2PKH, but that's still a guess and could result in fees being either too high or too low once the transaction is completely signed.
wallet/txauthor/author.go
Outdated
return nil, errors.E(op, errors.InsufficientBalance) | ||
} | ||
|
||
scriptSizes = append(scriptSizes, inputDetail.RedeemScriptSizes...) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this appends the discovered inputs to the script sizes, but the code below may continue the loop without actually adding any of the chosen inputs to the transaction
e64357d
to
2f5d70a
Compare
I added the fee estimation as we discussed and this PR is ready for another review. |
8eb4f7b
to
bad05ae
Compare
bad05ae
to
b9d5954
Compare
rebased and moved |
Implemented in #1706 |
closes #1181