Skip to content

Commit c5193e7

Browse files
committed
rpc: support hex data output for createrawtransaction
1 parent 8a80f06 commit c5193e7

File tree

5 files changed

+116
-50
lines changed

5 files changed

+116
-50
lines changed

btcjson/chainsvrcmds.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ type TransactionInput struct {
6767
// CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command.
6868
type CreateRawTransactionCmd struct {
6969
Inputs []TransactionInput
70-
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
70+
Outputs map[string]interface{} `jsonrpcusage:"{\"address\":amount, \"data\":\"hex\", ...}"`
7171
LockTime *int64
7272
}
7373

@@ -76,7 +76,7 @@ type CreateRawTransactionCmd struct {
7676
//
7777
// Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent,
7878
// both gets interpreted as the empty slice.
79-
func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]float64,
79+
func NewCreateRawTransactionCmd(inputs []TransactionInput, outputs map[string]interface{},
8080
lockTime *int64) *CreateRawTransactionCmd {
8181
// to make sure we're serializing this to the empty list and not null, we
8282
// explicitly initialize the list
@@ -85,7 +85,7 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]fl
8585
}
8686
return &CreateRawTransactionCmd{
8787
Inputs: inputs,
88-
Amounts: amounts,
88+
Outputs: outputs,
8989
LockTime: lockTime,
9090
}
9191
}

btcjson/chainsvrcmds_test.go

Lines changed: 28 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,13 @@ func TestChainSvrCmds(t *testing.T) {
5252
txInputs := []btcjson.TransactionInput{
5353
{Txid: "123", Vout: 1},
5454
}
55-
amounts := map[string]float64{"456": .0123}
56-
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, nil)
55+
txOutputs := map[string]interface{}{"456": .0123}
56+
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, nil)
5757
},
5858
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123}],"id":1}`,
5959
unmarshalled: &btcjson.CreateRawTransactionCmd{
6060
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
61-
Amounts: map[string]float64{"456": .0123},
61+
Outputs: map[string]interface{}{"456": .0123},
6262
},
6363
},
6464
{
@@ -67,13 +67,13 @@ func TestChainSvrCmds(t *testing.T) {
6767
return btcjson.NewCmd("createrawtransaction", `[]`, `{"456":0.0123}`)
6868
},
6969
staticCmd: func() interface{} {
70-
amounts := map[string]float64{"456": .0123}
71-
return btcjson.NewCreateRawTransactionCmd(nil, amounts, nil)
70+
txOutputs := map[string]interface{}{"456": .0123}
71+
return btcjson.NewCreateRawTransactionCmd(nil, txOutputs, nil)
7272
},
7373
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[],{"456":0.0123}],"id":1}`,
7474
unmarshalled: &btcjson.CreateRawTransactionCmd{
7575
Inputs: []btcjson.TransactionInput{},
76-
Amounts: map[string]float64{"456": .0123},
76+
Outputs: map[string]interface{}{"456": .0123},
7777
},
7878
},
7979
{
@@ -86,16 +86,35 @@ func TestChainSvrCmds(t *testing.T) {
8686
txInputs := []btcjson.TransactionInput{
8787
{Txid: "123", Vout: 1},
8888
}
89-
amounts := map[string]float64{"456": .0123}
90-
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, btcjson.Int64(12312333333))
89+
txOutputs := map[string]interface{}{"456": .0123}
90+
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, btcjson.Int64(12312333333))
9191
},
9292
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123},12312333333],"id":1}`,
9393
unmarshalled: &btcjson.CreateRawTransactionCmd{
9494
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
95-
Amounts: map[string]float64{"456": .0123},
95+
Outputs: map[string]interface{}{"456": .0123},
9696
LockTime: btcjson.Int64(12312333333),
9797
},
9898
},
99+
{
100+
name: "createrawtransaction with data",
101+
newCmd: func() (interface{}, error) {
102+
return btcjson.NewCmd("createrawtransaction", `[{"txid":"123","vout":1}]`,
103+
`{"data":"6a134920616d204672616374616c456e6372797074"}`)
104+
},
105+
staticCmd: func() interface{} {
106+
txInputs := []btcjson.TransactionInput{
107+
{Txid: "123", Vout: 1},
108+
}
109+
txOutputs := map[string]interface{}{"data": "6a134920616d204672616374616c456e6372797074"}
110+
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, nil)
111+
},
112+
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"data":"6a134920616d204672616374616c456e6372797074"}],"id":1}`,
113+
unmarshalled: &btcjson.CreateRawTransactionCmd{
114+
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
115+
Outputs: map[string]interface{}{"data": "6a134920616d204672616374616c456e6372797074"},
116+
},
117+
},
99118
{
100119
name: "fundrawtransaction - empty opts",
101120
newCmd: func() (i interface{}, e error) {

rpcclient/rawtransactions.go

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -291,23 +291,28 @@ func (r FutureCreateRawTransactionResult) Receive() (*wire.MsgTx, error) {
291291
//
292292
// See CreateRawTransaction for the blocking version and more details.
293293
func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
294-
amounts map[btcutil.Address]btcutil.Amount, lockTime *int64) FutureCreateRawTransactionResult {
295-
296-
convertedAmts := make(map[string]float64, len(amounts))
297-
for addr, amount := range amounts {
298-
convertedAmts[addr.String()] = amount.ToBTC()
294+
outputs map[btcutil.Address]interface{}, lockTime *int64) FutureCreateRawTransactionResult {
295+
296+
convertedData := make(map[string]interface{}, len(outputs))
297+
for key, value := range outputs {
298+
switch val := value.(type) {
299+
case btcutil.Amount:
300+
convertedData[key.String()] = val.ToBTC()
301+
case string:
302+
convertedData[key.String()] = val
303+
}
299304
}
300-
cmd := btcjson.NewCreateRawTransactionCmd(inputs, convertedAmts, lockTime)
305+
cmd := btcjson.NewCreateRawTransactionCmd(inputs, convertedData, lockTime)
301306
return c.SendCmd(cmd)
302307
}
303308

304309
// CreateRawTransaction returns a new transaction spending the provided inputs
305310
// and sending to the provided addresses. If the inputs are either nil or an
306311
// empty slice, it is interpreted as an empty slice.
307312
func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput,
308-
amounts map[btcutil.Address]btcutil.Amount, lockTime *int64) (*wire.MsgTx, error) {
313+
outputs map[btcutil.Address]interface{}, lockTime *int64) (*wire.MsgTx, error) {
309314

310-
return c.CreateRawTransactionAsync(inputs, amounts, lockTime).Receive()
315+
return c.CreateRawTransactionAsync(inputs, outputs, lockTime).Receive()
311316
}
312317

313318
// FutureSendRawTransactionResult is a future promise to deliver the result

rpcserver.go

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,15 @@ func rpcDecodeHexError(gotHex string) *btcjson.RPCError {
327327
gotHex))
328328
}
329329

330+
// rpcInvalidAddressOrKey is a convenience function for returning a nicely
331+
// formatted RPC error which indicates the address or key is invalid.
332+
func rpcInvalidAddressOrKeyError(addr string, msg string) *btcjson.RPCError {
333+
return &btcjson.RPCError{
334+
Code: btcjson.ErrRPCInvalidAddressOrKey,
335+
Message: msg,
336+
}
337+
}
338+
330339
// rpcNoTxInfoError is a convenience function for returning a nicely formatted
331340
// RPC error which indicates there is no information available for the provided
332341
// transaction hash.
@@ -568,59 +577,92 @@ func handleCreateRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan
568577
// Add all transaction outputs to the transaction after performing
569578
// some validity checks.
570579
params := s.cfg.ChainParams
571-
for encodedAddr, amount := range c.Amounts {
572-
// Ensure amount is in the valid range for monetary amounts.
573-
if amount <= 0 || amount*btcutil.SatoshiPerBitcoin > btcutil.MaxSatoshi {
580+
581+
// Ensure amount is in the valid range for monetary amounts.
582+
// Decode the provided address.
583+
// Ensure the address is one of the supported types and that
584+
// the network encoded with the address matches the network the
585+
// server is currently on.
586+
// Create a new script which pays to the provided address.
587+
// Convert the amount to satoshi.
588+
handleAmountFn := func(amount float64, encodedAddr string) (*wire.TxOut,
589+
error) {
590+
591+
if amount <= 0 ||
592+
amount*btcutil.SatoshiPerBitcoin > btcutil.MaxSatoshi {
574593
return nil, &btcjson.RPCError{
575594
Code: btcjson.ErrRPCType,
576-
Message: "Invalid amount",
595+
Message: "invalid amount",
577596
}
578597
}
579598

580-
// Decode the provided address.
581599
addr, err := btcutil.DecodeAddress(encodedAddr, params)
582600
if err != nil {
583-
return nil, &btcjson.RPCError{
584-
Code: btcjson.ErrRPCInvalidAddressOrKey,
585-
Message: "Invalid address or key: " + err.Error(),
586-
}
601+
return nil, rpcInvalidAddressOrKeyError(encodedAddr,
602+
"invalid address or key")
587603
}
588604

589-
// Ensure the address is one of the supported types and that
590-
// the network encoded with the address matches the network the
591-
// server is currently on.
592605
switch addr.(type) {
593606
case *btcutil.AddressPubKeyHash:
594607
case *btcutil.AddressScriptHash:
595608
default:
596-
return nil, &btcjson.RPCError{
597-
Code: btcjson.ErrRPCInvalidAddressOrKey,
598-
Message: "Invalid address or key: " + addr.String(),
599-
}
609+
return nil, rpcInvalidAddressOrKeyError(addr.String(),
610+
"invalid address or key")
600611
}
601612
if !addr.IsForNet(params) {
602-
return nil, &btcjson.RPCError{
603-
Code: btcjson.ErrRPCInvalidAddressOrKey,
604-
Message: "Invalid address: " + encodedAddr +
605-
" is for the wrong network",
606-
}
613+
return nil, rpcInvalidAddressOrKeyError(addr.String(),
614+
"wrong network")
607615
}
608616

609-
// Create a new script which pays to the provided address.
610617
pkScript, err := txscript.PayToAddrScript(addr)
611618
if err != nil {
612-
context := "Failed to generate pay-to-address script"
619+
context := "failed to generate pay-to-address script"
613620
return nil, internalRPCError(err.Error(), context)
614621
}
615622

616-
// Convert the amount to satoshi.
617623
satoshi, err := btcutil.NewAmount(amount)
618624
if err != nil {
619-
context := "Failed to convert amount"
625+
context := "failed to convert amount"
620626
return nil, internalRPCError(err.Error(), context)
621627
}
622628

623-
txOut := wire.NewTxOut(int64(satoshi), pkScript)
629+
return wire.NewTxOut(int64(satoshi), pkScript), nil
630+
}
631+
632+
handleDataFn := func(key string, value string) (*wire.TxOut, error) {
633+
if key != "data" {
634+
context := "output key must be an address or \"data\""
635+
return nil, &btcjson.RPCError{
636+
Code: btcjson.ErrRPCInvalidParameter,
637+
Message: context,
638+
}
639+
}
640+
var data []byte
641+
data, err := hex.DecodeString(value)
642+
if err != nil {
643+
return nil, rpcDecodeHexError(value)
644+
}
645+
return wire.NewTxOut(0, data), nil
646+
}
647+
648+
for key, value := range c.Outputs {
649+
var err error
650+
var txOut *wire.TxOut
651+
switch value := value.(type) {
652+
case float64:
653+
txOut, err = handleAmountFn(value, key)
654+
case string:
655+
txOut, err = handleDataFn(key, value)
656+
default:
657+
context := "output value must be a string or float"
658+
return nil, &btcjson.RPCError{
659+
Code: btcjson.ErrRPCType,
660+
Message: context,
661+
}
662+
}
663+
if err != nil {
664+
return nil, err
665+
}
624666
mtx.AddTxOut(txOut)
625667
}
626668

rpcserverhelp.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,10 +49,10 @@ var helpDescsEnUS = map[string]string{
4949
"The transaction inputs are not signed in the created transaction.\n" +
5050
"The signrawtransaction RPC command provided by wallet must be used to sign the resulting transaction.",
5151
"createrawtransaction-inputs": "The inputs to the transaction",
52-
"createrawtransaction-amounts": "JSON object with the destination addresses as keys and amounts as values",
53-
"createrawtransaction-amounts--key": "address",
54-
"createrawtransaction-amounts--value": "n.nnn",
55-
"createrawtransaction-amounts--desc": "The destination address as the key and the amount in LBC as the value",
52+
"createrawtransaction-outputs": "JSON object with the destination addresses as keys and amounts as values",
53+
"createrawtransaction-outputs--key": "address or \"data\"",
54+
"createrawtransaction-outputs--value": "value in BTC as floating point number or hex-encoded data for \"data\"",
55+
"createrawtransaction-outputs--desc": "The destination address as the key and the amount in LBC as the value",
5656
"createrawtransaction-locktime": "Locktime value; a non-zero value will also locktime-activate the inputs",
5757
"createrawtransaction--result0": "Hex-encoded bytes of the serialized transaction",
5858

0 commit comments

Comments
 (0)