Skip to content
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

btcjson,rpcclient: add support for PSBT commands to rpcclient #1596

Merged
merged 1 commit into from
Sep 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion btcjson/chainsvrcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,11 +80,24 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]fl
}
}

// ChangeType defines the different output types to use for the change address
// of a transaction built by the node.
type ChangeType string

var (
// ChangeTypeLegacy indicates a P2PKH change address type.
ChangeTypeLegacy ChangeType = "legacy"
// ChangeTypeP2SHSegWit indicates a P2WPKH-in-P2SH change address type.
ChangeTypeP2SHSegWit ChangeType = "p2sh-segwit"
// ChangeTypeBech32 indicates a P2WPKH change address type.
ChangeTypeBech32 ChangeType = "bech32"
)

// FundRawTransactionOpts are the different options that can be passed to rawtransaction
type FundRawTransactionOpts struct {
ChangeAddress *string `json:"changeAddress,omitempty"`
ChangePosition *int `json:"changePosition,omitempty"`
ChangeType *string `json:"change_type,omitempty"`
ChangeType *ChangeType `json:"change_type,omitempty"`
IncludeWatching *bool `json:"includeWatching,omitempty"`
LockUnspents *bool `json:"lockUnspents,omitempty"`
FeeRate *float64 `json:"feeRate,omitempty"` // BTC/kB
Expand Down
4 changes: 2 additions & 2 deletions btcjson/chainsvrcmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ func TestChainSvrCmds(t *testing.T) {
}
changeAddress := "bcrt1qeeuctq9wutlcl5zatge7rjgx0k45228cxez655"
change := 1
changeType := "legacy"
changeType := btcjson.ChangeTypeLegacy
watching := true
lockUnspents := true
feeRate := 0.7
Expand All @@ -151,7 +151,7 @@ func TestChainSvrCmds(t *testing.T) {
unmarshalled: func() interface{} {
changeAddress := "bcrt1qeeuctq9wutlcl5zatge7rjgx0k45228cxez655"
change := 1
changeType := "legacy"
changeType := btcjson.ChangeTypeLegacy
watching := true
lockUnspents := true
feeRate := 0.7
Expand Down
87 changes: 87 additions & 0 deletions btcjson/walletsvrcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,11 @@
package btcjson

import (
"encoding/hex"
"encoding/json"
"fmt"

"github.com/btcsuite/btcutil"
)

// AddMultisigAddressCmd defines the addmutisigaddress JSON-RPC command.
Expand Down Expand Up @@ -893,6 +896,88 @@ func NewImportMultiCmd(requests []ImportMultiRequest, options *ImportMultiOption
}
}

// 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]interface{}

// NewPsbtOutput returns a new instance of a PSBT output to use with the
// WalletCreateFundedPsbtCmd command.
func NewPsbtOutput(address string, amount btcutil.Amount) PsbtOutput {
return PsbtOutput{address: amount.ToBTC()}
}

// NewPsbtDataOutput returns a new instance of a PSBT data output to use with
// the WalletCreateFundedPsbtCmd command.
func NewPsbtDataOutput(data []byte) PsbtOutput {
return PsbtOutput{"data": hex.EncodeToString(data)}
}

// 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 *ChangeType `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 `jsonrpcdefault:"\"ALL\""`
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 Down Expand Up @@ -939,4 +1024,6 @@ func init() {
MustRegisterCmd("walletlock", (*WalletLockCmd)(nil), flags)
MustRegisterCmd("walletpassphrase", (*WalletPassphraseCmd)(nil), flags)
MustRegisterCmd("walletpassphrasechange", (*WalletPassphraseChangeCmd)(nil), flags)
MustRegisterCmd("walletcreatefundedpsbt", (*WalletCreateFundedPsbtCmd)(nil), flags)
MustRegisterCmd("walletprocesspsbt", (*WalletProcessPsbtCmd)(nil), flags)
}
76 changes: 76 additions & 0 deletions btcjson/walletsvrcmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"testing"

"github.com/btcsuite/btcd/btcjson"
"github.com/btcsuite/btcutil"
)

// TestWalletSvrCmds tests all of the wallet server commands marshal and
Expand Down Expand Up @@ -1495,6 +1496,81 @@ func TestWalletSvrCmds(t *testing.T) {
},
},
},
{
name: "walletcreatefundedpsbt",
newCmd: func() (interface{}, error) {
return btcjson.NewCmd(
"walletcreatefundedpsbt",
[]btcjson.PsbtInput{
{
Txid: "1234",
Vout: 0,
Sequence: 0,
},
},
[]btcjson.PsbtOutput{
btcjson.NewPsbtOutput("1234", btcutil.Amount(1234)),
btcjson.NewPsbtDataOutput([]byte{1, 2, 3, 4}),
},
btcjson.Uint32(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", btcutil.Amount(1234)),
btcjson.NewPsbtDataOutput([]byte{1, 2, 3, 4}),
},
btcjson.Uint32(1),
&btcjson.WalletCreateFundedPsbtOpts{},
btcjson.Bool(true),
)
},
marshalled: `{"jsonrpc":"1.0","method":"walletcreatefundedpsbt","params":[[{"txid":"1234","vout":0,"sequence":0}],[{"1234":0.00001234},{"data":"01020304"}],1,{},true],"id":1}`,
unmarshalled: &btcjson.WalletCreateFundedPsbtCmd{
Inputs: []btcjson.PsbtInput{
{
Txid: "1234",
Vout: 0,
Sequence: 0,
},
},
Outputs: []btcjson.PsbtOutput{
btcjson.NewPsbtOutput("1234", btcutil.Amount(1234)),
btcjson.NewPsbtDataOutput([]byte{1, 2, 3, 4}),
},
Locktime: btcjson.Uint32(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 @@ -184,3 +184,18 @@ type ImportMultiResults []struct {
Error *RPCError `json:"error,omitempty"`
Warnings *[]string `json:"warnings,omitempty"`
}

// WalletCreateFundedPsbtResult models the data returned from the
// walletcreatefundedpsbtresult command.
type WalletCreateFundedPsbtResult struct {
Psbt string `json:"psbt"`
federicobond marked this conversation as resolved.
Show resolved Hide resolved
Fee float64 `json:"fee"`
federicobond marked this conversation as resolved.
Show resolved Hide resolved
ChangePos int64 `json:"changepos"`
}

// WalletProcessPsbtResult models the data returned from the
// walletprocesspsbtresult command.
type WalletProcessPsbtResult struct {
Psbt string `json:"psbt"`
federicobond marked this conversation as resolved.
Show resolved Hide resolved
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 @@ -2405,6 +2405,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
federicobond marked this conversation as resolved.
Show resolved Hide resolved

// 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 *uint32,
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 *uint32,
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 SigHashType, bip32Derivs *bool,
) FutureWalletProcessPsbtResult {
cmd := btcjson.NewWalletProcessPsbtCmd(psbt, sign, btcjson.String(sighashType.String()), 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 SigHashType, bip32Derivs *bool,
) (*btcjson.WalletProcessPsbtResult, error) {
return c.WalletProcessPsbtAsync(psbt, sign, sighashType, bip32Derivs).Receive()
}

// TODO(davec): Implement
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

might make sense to remove these in favor of issues, but not germane to this review

// backupwallet (NYI in btcwallet)
// encryptwallet (Won't be supported by btcwallet since it's always encrypted)
Expand Down