Skip to content

Commit

Permalink
btcjson,rpcclient: add support for PSBT commands to rpcclient
Browse files Browse the repository at this point in the history
  • Loading branch information
federicobond committed Aug 27, 2020
1 parent 1db1b6f commit 83e5cef
Show file tree
Hide file tree
Showing 4 changed files with 256 additions and 0 deletions.
80 changes: 80 additions & 0 deletions btcjson/btcwalletextcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 uint32 `json:"vout"`
Sequence uint32 `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 *uint32
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 *uint32,
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
Expand All @@ -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)
}
72 changes: 72 additions & 0 deletions btcjson/btcwalletextcmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,78 @@ func TestBtcWalletExtCmds(t *testing.T) {
NewAccount: "newacct",
},
},
{
name: "walletcreatefundedpsbt",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd(
"walletcreatefundedpsbt",
[]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{
{
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{
{
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))
Expand Down
15 changes: 15 additions & 0 deletions btcjson/walletsvrresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,18 @@ type GetBalancesResult struct {
Mine BalanceDetailsResult `json:"mine"`
WatchOnly *BalanceDetailsResult `json:"watchonly"`
}

// 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"`
}
89 changes: 89 additions & 0 deletions rpcclient/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2367,6 +2367,95 @@ func (c *Client) GetInfo() (*btcjson.InfoWalletResult, error) {
return c.GetInfoAsync().Receive()
}

// FutureImportPubKeyResult is a future promise to deliver the result of an
// WalletCreateFundedPsbt RPC invocation (or an applicable error).
type FutureWalletCreateFundedPsbtResult chan *response

// Receive waits for the response promised by the future and returns the
// partially signed transaction in PSBT format along with the resulting fee
// and change output index.
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 an updated
// PSBT with signed inputs from the wallet and a boolen indicating if the the
// transaction has a complete set of signatures.
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)
Expand Down

0 comments on commit 83e5cef

Please sign in to comment.