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

Add fundrawtransaction RPC call #1553

Merged
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
33 changes: 33 additions & 0 deletions btcjson/chainsvrcmds.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package btcjson

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

Expand Down Expand Up @@ -79,6 +80,37 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]fl
}
}

// 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"`
IncludeWatching *bool `json:"includeWatching,omitempty"`
LockUnspents *bool `json:"lockUnspents,omitempty"`
FeeRate *float64 `json:"feeRate,omitempty"` // BTC/kB
SubtractFeeFromOutputs []int `json:"subtractFeeFromOutputs,omitempty"`
Replaceable *bool `json:"replaceable,omitempty"`
ConfTarget *int `json:"conf_target,omitempty"`
EstimateMode *EstimateSmartFeeMode `json:"estimate_mode,omitempty"`
}

// FundRawTransactionCmd defines the fundrawtransaction JSON-RPC command
type FundRawTransactionCmd struct {
HexTx string
Options FundRawTransactionOpts
IsWitness *bool
}

// NewFundRawTransactionCmd returns a new instance which can be used to issue
// a fundrawtransaction JSON-RPC command
func NewFundRawTransactionCmd(serializedTx []byte, opts FundRawTransactionOpts, isWitness *bool) *FundRawTransactionCmd {
return &FundRawTransactionCmd{
HexTx: hex.EncodeToString(serializedTx),
Options: opts,
IsWitness: isWitness,
}
}

// DecodeRawTransactionCmd defines the decoderawtransaction JSON-RPC command.
type DecodeRawTransactionCmd struct {
HexTx string
Expand Down Expand Up @@ -838,6 +870,7 @@ func init() {

MustRegisterCmd("addnode", (*AddNodeCmd)(nil), flags)
MustRegisterCmd("createrawtransaction", (*CreateRawTransactionCmd)(nil), flags)
MustRegisterCmd("fundrawtransaction", (*FundRawTransactionCmd)(nil), flags)
MustRegisterCmd("decoderawtransaction", (*DecodeRawTransactionCmd)(nil), flags)
MustRegisterCmd("decodescript", (*DecodeScriptCmd)(nil), flags)
MustRegisterCmd("getaddednodeinfo", (*GetAddedNodeInfoCmd)(nil), flags)
Expand Down
102 changes: 102 additions & 0 deletions btcjson/chainsvrcmds_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package btcjson_test

import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"reflect"
Expand Down Expand Up @@ -95,7 +96,108 @@ func TestChainSvrCmds(t *testing.T) {
LockTime: btcjson.Int64(12312333333),
},
},
{
name: "fundrawtransaction - empty opts",
newCmd: func() (i interface{}, e error) {
return btcjson.NewCmd("fundrawtransaction", "deadbeef", "{}")
},
staticCmd: func() interface{} {
deadbeef, err := hex.DecodeString("deadbeef")
if err != nil {
panic(err)
}
return btcjson.NewFundRawTransactionCmd(deadbeef, btcjson.FundRawTransactionOpts{}, nil)
},
marshalled: `{"jsonrpc":"1.0","method":"fundrawtransaction","params":["deadbeef",{}],"id":1}`,
unmarshalled: &btcjson.FundRawTransactionCmd{
HexTx: "deadbeef",
Options: btcjson.FundRawTransactionOpts{},
IsWitness: nil,
},
},
{
name: "fundrawtransaction - full opts",
newCmd: func() (i interface{}, e error) {
return btcjson.NewCmd("fundrawtransaction", "deadbeef", `{"changeAddress":"bcrt1qeeuctq9wutlcl5zatge7rjgx0k45228cxez655","changePosition":1,"change_type":"legacy","includeWatching":true,"lockUnspents":true,"feeRate":0.7,"subtractFeeFromOutputs":[0],"replaceable":true,"conf_target":8,"estimate_mode":"ECONOMICAL"}`)
},
staticCmd: func() interface{} {
deadbeef, err := hex.DecodeString("deadbeef")
if err != nil {
panic(err)
}
changeAddress := "bcrt1qeeuctq9wutlcl5zatge7rjgx0k45228cxez655"
change := 1
changeType := "legacy"
watching := true
lockUnspents := true
feeRate := 0.7
replaceable := true
confTarget := 8

return btcjson.NewFundRawTransactionCmd(deadbeef, btcjson.FundRawTransactionOpts{
ChangeAddress: &changeAddress,
ChangePosition: &change,
ChangeType: &changeType,
IncludeWatching: &watching,
LockUnspents: &lockUnspents,
FeeRate: &feeRate,
SubtractFeeFromOutputs: []int{0},
Replaceable: &replaceable,
ConfTarget: &confTarget,
EstimateMode: &btcjson.EstimateModeEconomical,
}, nil)
},
marshalled: `{"jsonrpc":"1.0","method":"fundrawtransaction","params":["deadbeef",{"changeAddress":"bcrt1qeeuctq9wutlcl5zatge7rjgx0k45228cxez655","changePosition":1,"change_type":"legacy","includeWatching":true,"lockUnspents":true,"feeRate":0.7,"subtractFeeFromOutputs":[0],"replaceable":true,"conf_target":8,"estimate_mode":"ECONOMICAL"}],"id":1}`,
unmarshalled: func() interface{} {
changeAddress := "bcrt1qeeuctq9wutlcl5zatge7rjgx0k45228cxez655"
change := 1
changeType := "legacy"
watching := true
lockUnspents := true
feeRate := 0.7
replaceable := true
confTarget := 8
return &btcjson.FundRawTransactionCmd{
HexTx: "deadbeef",
Options: btcjson.FundRawTransactionOpts{
ChangeAddress: &changeAddress,
ChangePosition: &change,
ChangeType: &changeType,
IncludeWatching: &watching,
LockUnspents: &lockUnspents,
FeeRate: &feeRate,
SubtractFeeFromOutputs: []int{0},
Replaceable: &replaceable,
ConfTarget: &confTarget,
EstimateMode: &btcjson.EstimateModeEconomical,
},
IsWitness: nil,
}
}(),
},
{
name: "fundrawtransaction - iswitness",
newCmd: func() (i interface{}, e error) {
return btcjson.NewCmd("fundrawtransaction", "deadbeef", "{}", true)
},
staticCmd: func() interface{} {
deadbeef, err := hex.DecodeString("deadbeef")
if err != nil {
panic(err)
}
t := true
return btcjson.NewFundRawTransactionCmd(deadbeef, btcjson.FundRawTransactionOpts{}, &t)
},
marshalled: `{"jsonrpc":"1.0","method":"fundrawtransaction","params":["deadbeef",{},true],"id":1}`,
unmarshalled: &btcjson.FundRawTransactionCmd{
HexTx: "deadbeef",
Options: btcjson.FundRawTransactionOpts{},
IsWitness: func() *bool {
t := true
return &t
}(),
},
},
{
name: "decoderawtransaction",
newCmd: func() (interface{}, error) {
Expand Down
56 changes: 55 additions & 1 deletion btcjson/chainsvrresults.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@

package btcjson

import "encoding/json"
import (
"bytes"
"encoding/hex"
"encoding/json"

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

// GetBlockHeaderVerboseResult models the data from the getblockheader command when
// the verbose flag is set. When the verbose flag is not set, getblockheader
Expand Down Expand Up @@ -664,3 +671,50 @@ type EstimateSmartFeeResult struct {
Errors []string `json:"errors,omitempty"`
Blocks int64 `json:"blocks"`
}

var _ json.Unmarshaler = &FundRawTransactionResult{}

type rawFundRawTransactionResult struct {
Transaction string `json:"hex"`
Fee float64 `json:"fee"`
ChangePosition int `json:"changepos"`
}

// FundRawTransactionResult is the result of the fundrawtransaction JSON-RPC call
type FundRawTransactionResult struct {
Transaction *wire.MsgTx
Fee btcutil.Amount
ChangePosition int // the position of the added change output, or -1
}
torkelrogstad marked this conversation as resolved.
Show resolved Hide resolved

// UnmarshalJSON unmarshals the result of the fundrawtransaction JSON-RPC call
func (f *FundRawTransactionResult) UnmarshalJSON(data []byte) error {
var rawRes rawFundRawTransactionResult
if err := json.Unmarshal(data, &rawRes); err != nil {
return err
}

txBytes, err := hex.DecodeString(rawRes.Transaction)
if err != nil {
return err
}

var msgTx wire.MsgTx
witnessErr := msgTx.Deserialize(bytes.NewReader(txBytes))
if witnessErr != nil {
legacyErr := msgTx.DeserializeNoWitness(bytes.NewReader(txBytes))
if legacyErr != nil {
return legacyErr
}
}

fee, err := btcutil.NewAmount(rawRes.Fee)
if err != nil {
return err
}

f.Transaction = &msgTx
torkelrogstad marked this conversation as resolved.
Show resolved Hide resolved
f.Fee = fee
f.ChangePosition = rawRes.ChangePosition
return nil
}
41 changes: 41 additions & 0 deletions rpcclient/rawtransactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,47 @@ func (c *Client) DecodeRawTransaction(serializedTx []byte) (*btcjson.TxRawResult
return c.DecodeRawTransactionAsync(serializedTx).Receive()
}

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

// Receive waits for the response promised by the future and returns information
// about a funding attempt
func (r FutureFundRawTransactionResult) Receive() (*btcjson.FundRawTransactionResult, error) {
res, err := receiveFuture(r)
if err != nil {
return nil, err
}

var marshalled btcjson.FundRawTransactionResult
if err := json.Unmarshal(res, &marshalled); err != nil {
return nil, err
}

return &marshalled, nil
}

// FundRawTransactionAsync 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 FundRawTransaction for the blocking version and more details.
func (c *Client) FundRawTransactionAsync(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) FutureFundRawTransactionResult {
var txBuf bytes.Buffer
if err := tx.Serialize(&txBuf); err != nil {
return newFutureError(err)
}

cmd := btcjson.NewFundRawTransactionCmd(txBuf.Bytes(), opts, isWitness)
return c.sendCmd(cmd)
}

// FundRawTransaction returns the result of trying to fund the given transaction with
// funds from the node wallet
func (c *Client) FundRawTransaction(tx *wire.MsgTx, opts btcjson.FundRawTransactionOpts, isWitness *bool) (*btcjson.FundRawTransactionResult, error) {
return c.FundRawTransactionAsync(tx, opts, isWitness).Receive()
}

// FutureCreateRawTransactionResult is a future promise to deliver the result
// of a CreateRawTransactionAsync RPC invocation (or an applicable error).
type FutureCreateRawTransactionResult chan *response
Expand Down