From 5eef94b91b79dbb1bc6a4127b97214e30c98daba Mon Sep 17 00:00:00 2001 From: Federico Bond Date: Thu, 30 Apr 2020 12:55:57 -0300 Subject: [PATCH] btcjson,rpcclient: add support for PSBT commands to rpcclient --- btcjson/btcwalletextcmds.go | 80 ++++++++++++++++++++++++++++++ btcjson/btcwalletextcmds_test.go | 72 +++++++++++++++++++++++++++ btcjson/walletsvrresults.go | 15 ++++++ rpcclient/wallet.go | 85 ++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+) diff --git a/btcjson/btcwalletextcmds.go b/btcjson/btcwalletextcmds.go index 3982a13551..bfc63a8374 100644 --- a/btcjson/btcwalletextcmds.go +++ b/btcjson/btcwalletextcmds.go @@ -93,6 +93,84 @@ func NewRenameAccountCmd(oldAccount, newAccount string) *RenameAccountCmd { } } +// PsbtInput represents an input to include in the PSBT created by the +// WalletCreateFundedPsbtCmd command. +type PsbtInput struct { + Txid string `json:"txid"` + Vout int64 `json:"vout"` + Sequence int64 `json:"sequence"` +} + +// PsbtOutput represents an output to include in the PSBT created by the +// WalletCreateFundedPsbtCmd command. +type PsbtOutput map[string]float64 + +// NewPsbtOutput returns a new instance of a PSBT output to use with the +// WalletCreateFundedPsbtCmd command. +func NewPsbtOutput(address string, amount float64) PsbtOutput { + out := make(map[string]float64) + out[address] = amount + return out +} + +// WalletCreateFundedPsbtOpts represents the optional options struct provided +// with a WalletCreateFundedPsbtCmd command. +type WalletCreateFundedPsbtOpts struct { + ChangeAddress *string `json:"changeAddress,omitempty"` + ChangePosition *int64 `json:"changePosition,omitempty"` + ChangeType *string `json:"change_type,omitempty"` + IncludeWatching *bool `json:"includeWatching,omitempty"` + LockUnspents *bool `json:"lockUnspents,omitempty"` + FeeRate *int64 `json:"feeRate,omitempty"` + SubtractFeeFromOutputs *[]int64 `json:"subtractFeeFromOutputs,omitempty"` + Replaceable *bool `json:"replaceable,omitempty"` + ConfTarget *int64 `json:"conf_target,omitempty"` + EstimateMode *string `json:"estimate_mode,omitempty"` +} + +// WalletCreateFundedPsbtCmd defines the walletcreatefundedpsbt JSON-RPC command. +type WalletCreateFundedPsbtCmd struct { + Inputs []PsbtInput + Outputs []PsbtOutput + Locktime *int64 + Options *WalletCreateFundedPsbtOpts + Bip32Derivs *bool +} + +// NewWalletCreateFundedPsbtCmd returns a new instance which can be used to issue a +// walletcreatefundedpsbt JSON-RPC command. +func NewWalletCreateFundedPsbtCmd( + inputs []PsbtInput, outputs []PsbtOutput, locktime *int64, + options *WalletCreateFundedPsbtOpts, bip32Derivs *bool, +) *WalletCreateFundedPsbtCmd { + return &WalletCreateFundedPsbtCmd{ + Inputs: inputs, + Outputs: outputs, + Locktime: locktime, + Options: options, + Bip32Derivs: bip32Derivs, + } +} + +// WalletProcessPsbtCmd defines the walletprocesspsbt JSON-RPC command. +type WalletProcessPsbtCmd struct { + Psbt string + Sign *bool `jsonrpcdefault:"true"` + SighashType *string + Bip32Derivs *bool +} + +// NewWalletProcessPsbtCmd returns a new instance which can be used to issue a +// walletprocesspsbt JSON-RPC command. +func NewWalletProcessPsbtCmd(psbt string, sign *bool, sighashType *string, bip32Derivs *bool) *WalletProcessPsbtCmd { + return &WalletProcessPsbtCmd{ + Psbt: psbt, + Sign: sign, + SighashType: sighashType, + Bip32Derivs: bip32Derivs, + } +} + func init() { // The commands in this file are only usable with a wallet server. flags := UFWalletOnly @@ -103,4 +181,6 @@ func init() { MustRegisterCmd("importpubkey", (*ImportPubKeyCmd)(nil), flags) MustRegisterCmd("importwallet", (*ImportWalletCmd)(nil), flags) MustRegisterCmd("renameaccount", (*RenameAccountCmd)(nil), flags) + MustRegisterCmd("walletcreatefundedpsbt", (*WalletCreateFundedPsbtCmd)(nil), flags) + MustRegisterCmd("walletprocesspsbt", (*WalletProcessPsbtCmd)(nil), flags) } diff --git a/btcjson/btcwalletextcmds_test.go b/btcjson/btcwalletextcmds_test.go index 58de1c81d0..fdfb8318d5 100644 --- a/btcjson/btcwalletextcmds_test.go +++ b/btcjson/btcwalletextcmds_test.go @@ -139,6 +139,78 @@ func TestBtcWalletExtCmds(t *testing.T) { NewAccount: "newacct", }, }, + { + name: "walletcreatefundedpsbt", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd( + "walletcreatefundedpsbt", + []btcjson.PsbtInput{ + btcjson.PsbtInput{ + Txid: "1234", + Vout: 0, + Sequence: 0, + }, + }, + []btcjson.PsbtOutput{ + btcjson.NewPsbtOutput("1234", 1234), + }, + btcjson.Int64(1), + btcjson.WalletCreateFundedPsbtOpts{}, + btcjson.Bool(true), + ) + }, + staticCmd: func() interface{} { + return btcjson.NewWalletCreateFundedPsbtCmd( + []btcjson.PsbtInput{ + btcjson.PsbtInput{ + Txid: "1234", + Vout: 0, + Sequence: 0, + }, + }, + []btcjson.PsbtOutput{ + btcjson.NewPsbtOutput("1234", 1234), + }, + btcjson.Int64(1), + &btcjson.WalletCreateFundedPsbtOpts{}, + btcjson.Bool(true), + ) + }, + marshalled: `{"jsonrpc":"1.0","method":"walletcreatefundedpsbt","params":[[{"txid":"1234","vout":0,"sequence":0}],[{"1234":1234}],1,{},true],"id":1}`, + unmarshalled: &btcjson.WalletCreateFundedPsbtCmd{ + Inputs: []btcjson.PsbtInput{ + btcjson.PsbtInput{ + Txid: "1234", + Vout: 0, + Sequence: 0, + }, + }, + Outputs: []btcjson.PsbtOutput{ + btcjson.NewPsbtOutput("1234", 1234), + }, + Locktime: btcjson.Int64(1), + Options: &btcjson.WalletCreateFundedPsbtOpts{}, + Bip32Derivs: btcjson.Bool(true), + }, + }, + { + name: "walletprocesspsbt", + newCmd: func() (interface{}, error) { + return btcjson.NewCmd( + "walletprocesspsbt", "1234", btcjson.Bool(true), btcjson.String("ALL"), btcjson.Bool(true)) + }, + staticCmd: func() interface{} { + return btcjson.NewWalletProcessPsbtCmd( + "1234", btcjson.Bool(true), btcjson.String("ALL"), btcjson.Bool(true)) + }, + marshalled: `{"jsonrpc":"1.0","method":"walletprocesspsbt","params":["1234",true,"ALL",true],"id":1}`, + unmarshalled: &btcjson.WalletProcessPsbtCmd{ + Psbt: "1234", + Sign: btcjson.Bool(true), + SighashType: btcjson.String("ALL"), + Bip32Derivs: btcjson.Bool(true), + }, + }, } t.Logf("Running %d tests", len(tests)) diff --git a/btcjson/walletsvrresults.go b/btcjson/walletsvrresults.go index 9246d131ae..0cb2b896dc 100644 --- a/btcjson/walletsvrresults.go +++ b/btcjson/walletsvrresults.go @@ -159,3 +159,18 @@ type GetBestBlockResult struct { Hash string `json:"hash"` Height int32 `json:"height"` } + +// WalletCreateFundedPsbtResult models the data returned from the +// walletcreatefundedpsbtresult command. +type WalletCreateFundedPsbtResult struct { + Psbt string `json:"psbt"` + Fee float64 `json:"fee"` + ChangePos int64 `json:"changepos"` +} + +// WalletProcessPsbtResult models the data returned from the +// walletprocesspsbtresult command. +type WalletProcessPsbtResult struct { + Psbt string `json:"psbt"` + Complete bool `json:"complete"` +} diff --git a/rpcclient/wallet.go b/rpcclient/wallet.go index d43be26181..f4f7877194 100644 --- a/rpcclient/wallet.go +++ b/rpcclient/wallet.go @@ -2307,6 +2307,91 @@ func (c *Client) GetInfo() (*btcjson.InfoWalletResult, error) { return c.GetInfoAsync().Receive() } +type FutureWalletCreateFundedPsbtResult chan *response + +// Receive waits for the response promised by the future and returns the info +// provided by the server. +func (r FutureWalletCreateFundedPsbtResult) Receive() (*btcjson.WalletCreateFundedPsbtResult, error) { + res, err := receiveFuture(r) + if err != nil { + return nil, err + } + + // Unmarshal result as a getinfo result object. + var psbtRes btcjson.WalletCreateFundedPsbtResult + err = json.Unmarshal(res, &psbtRes) + if err != nil { + return nil, err + } + + return &psbtRes, nil +} + +// WalletCreateFundedPsbtAsync returns an instance of a type that can be used +// to get the result of the RPC at some future time by invoking the Receive +// function on the returned instance. +// +// See WalletCreateFundedPsbt for the blocking version and more details. +func (c *Client) WalletCreateFundedPsbtAsync( + inputs []btcjson.PsbtInput, outputs []btcjson.PsbtOutput, locktime *int64, + options *btcjson.WalletCreateFundedPsbtOpts, bip32Derivs *bool, +) FutureWalletCreateFundedPsbtResult { + cmd := btcjson.NewWalletCreateFundedPsbtCmd(inputs, outputs, locktime, options, bip32Derivs) + return c.sendCmd(cmd) +} + +// WalletCreateFundedPsbt creates and funds a transaction in the Partially +// Signed Transaction format. Inputs will be added if supplied inputs are not +// enough. +func (c *Client) WalletCreateFundedPsbt( + inputs []btcjson.PsbtInput, outputs []btcjson.PsbtOutput, locktime *int64, + options *btcjson.WalletCreateFundedPsbtOpts, bip32Derivs *bool, +) (*btcjson.WalletCreateFundedPsbtResult, error) { + return c.WalletCreateFundedPsbtAsync(inputs, outputs, locktime, options, bip32Derivs).Receive() +} + +// FutureWalletProcessPsbtResult is a future promise to deliver the result of a +// WalletCreateFundedPsb RPC invocation (or an applicable error). +type FutureWalletProcessPsbtResult chan *response + +// Receive waits for the response promised by the future and returns the info +// provided by the server. +func (r FutureWalletProcessPsbtResult) Receive() (*btcjson.WalletProcessPsbtResult, error) { + res, err := receiveFuture(r) + if err != nil { + return nil, err + } + + // Unmarshal result as a getinfo result object. + var psbtRes btcjson.WalletProcessPsbtResult + err = json.Unmarshal(res, &psbtRes) + if err != nil { + return nil, err + } + + return &psbtRes, nil +} + +// WalletProcessPsbtAsync returns an instance of a type that can be used +// to get the result of the RPC at some future time by invoking the Receive +// function on the returned instance. +// +// See WalletProcessPsbt for the blocking version and more details. +func (c *Client) WalletProcessPsbtAsync( + psbt string, sign *bool, sighashType *string, bip32Derivs *bool, +) FutureWalletProcessPsbtResult { + cmd := btcjson.NewWalletProcessPsbtCmd(psbt, sign, sighashType, bip32Derivs) + return c.sendCmd(cmd) +} + +// WalletProcessPsbt updates a PSBT with input information from our wallet and +// then signs inputs. +func (c *Client) WalletProcessPsbt( + psbt string, sign *bool, sighashType *string, bip32Derivs *bool, +) (*btcjson.WalletProcessPsbtResult, error) { + return c.WalletProcessPsbtAsync(psbt, sign, sighashType, bip32Derivs).Receive() +} + // TODO(davec): Implement // backupwallet (NYI in btcwallet) // encryptwallet (Won't be supported by btcwallet since it's always encrypted)