Skip to content

Commit

Permalink
Add fundrawtransaction RPC call
Browse files Browse the repository at this point in the history
  • Loading branch information
torkelrogstad committed Jun 10, 2020
1 parent b11bf58 commit efc3cdb
Show file tree
Hide file tree
Showing 4 changed files with 231 additions and 1 deletion.
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
}

// 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
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

0 comments on commit efc3cdb

Please sign in to comment.