rpc: support hex data output for createrawtransaction
This commit is contained in:
parent
8a80f0683a
commit
c5193e74ac
5 changed files with 115 additions and 49 deletions
|
@ -67,7 +67,7 @@ type TransactionInput struct {
|
||||||
// CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command.
|
// CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command.
|
||||||
type CreateRawTransactionCmd struct {
|
type CreateRawTransactionCmd struct {
|
||||||
Inputs []TransactionInput
|
Inputs []TransactionInput
|
||||||
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
Outputs map[string]interface{} `jsonrpcusage:"{\"address\":amount, \"data\":\"hex\", ...}"`
|
||||||
LockTime *int64
|
LockTime *int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ type CreateRawTransactionCmd struct {
|
||||||
//
|
//
|
||||||
// Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent,
|
// Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent,
|
||||||
// both gets interpreted as the empty slice.
|
// both gets interpreted as the empty slice.
|
||||||
func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]float64,
|
func NewCreateRawTransactionCmd(inputs []TransactionInput, outputs map[string]interface{},
|
||||||
lockTime *int64) *CreateRawTransactionCmd {
|
lockTime *int64) *CreateRawTransactionCmd {
|
||||||
// to make sure we're serializing this to the empty list and not null, we
|
// to make sure we're serializing this to the empty list and not null, we
|
||||||
// explicitly initialize the list
|
// explicitly initialize the list
|
||||||
|
@ -85,7 +85,7 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]fl
|
||||||
}
|
}
|
||||||
return &CreateRawTransactionCmd{
|
return &CreateRawTransactionCmd{
|
||||||
Inputs: inputs,
|
Inputs: inputs,
|
||||||
Amounts: amounts,
|
Outputs: outputs,
|
||||||
LockTime: lockTime,
|
LockTime: lockTime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,13 +52,13 @@ func TestChainSvrCmds(t *testing.T) {
|
||||||
txInputs := []btcjson.TransactionInput{
|
txInputs := []btcjson.TransactionInput{
|
||||||
{Txid: "123", Vout: 1},
|
{Txid: "123", Vout: 1},
|
||||||
}
|
}
|
||||||
amounts := map[string]float64{"456": .0123}
|
txOutputs := map[string]interface{}{"456": .0123}
|
||||||
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, nil)
|
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123}],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123}],"id":1}`,
|
||||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||||
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
||||||
Amounts: map[string]float64{"456": .0123},
|
Outputs: map[string]interface{}{"456": .0123},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -67,13 +67,13 @@ func TestChainSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewCmd("createrawtransaction", `[]`, `{"456":0.0123}`)
|
return btcjson.NewCmd("createrawtransaction", `[]`, `{"456":0.0123}`)
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
amounts := map[string]float64{"456": .0123}
|
txOutputs := map[string]interface{}{"456": .0123}
|
||||||
return btcjson.NewCreateRawTransactionCmd(nil, amounts, nil)
|
return btcjson.NewCreateRawTransactionCmd(nil, txOutputs, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[],{"456":0.0123}],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[],{"456":0.0123}],"id":1}`,
|
||||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||||
Inputs: []btcjson.TransactionInput{},
|
Inputs: []btcjson.TransactionInput{},
|
||||||
Amounts: map[string]float64{"456": .0123},
|
Outputs: map[string]interface{}{"456": .0123},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -86,16 +86,35 @@ func TestChainSvrCmds(t *testing.T) {
|
||||||
txInputs := []btcjson.TransactionInput{
|
txInputs := []btcjson.TransactionInput{
|
||||||
{Txid: "123", Vout: 1},
|
{Txid: "123", Vout: 1},
|
||||||
}
|
}
|
||||||
amounts := map[string]float64{"456": .0123}
|
txOutputs := map[string]interface{}{"456": .0123}
|
||||||
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, btcjson.Int64(12312333333))
|
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, btcjson.Int64(12312333333))
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123},12312333333],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123},12312333333],"id":1}`,
|
||||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||||
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
||||||
Amounts: map[string]float64{"456": .0123},
|
Outputs: map[string]interface{}{"456": .0123},
|
||||||
LockTime: btcjson.Int64(12312333333),
|
LockTime: btcjson.Int64(12312333333),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "createrawtransaction with data",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("createrawtransaction", `[{"txid":"123","vout":1}]`,
|
||||||
|
`{"data":"6a134920616d204672616374616c456e6372797074"}`)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
txInputs := []btcjson.TransactionInput{
|
||||||
|
{Txid: "123", Vout: 1},
|
||||||
|
}
|
||||||
|
txOutputs := map[string]interface{}{"data": "6a134920616d204672616374616c456e6372797074"}
|
||||||
|
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"data":"6a134920616d204672616374616c456e6372797074"}],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||||
|
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
||||||
|
Outputs: map[string]interface{}{"data": "6a134920616d204672616374616c456e6372797074"},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "fundrawtransaction - empty opts",
|
name: "fundrawtransaction - empty opts",
|
||||||
newCmd: func() (i interface{}, e error) {
|
newCmd: func() (i interface{}, e error) {
|
||||||
|
|
|
@ -291,13 +291,18 @@ func (r FutureCreateRawTransactionResult) Receive() (*wire.MsgTx, error) {
|
||||||
//
|
//
|
||||||
// See CreateRawTransaction for the blocking version and more details.
|
// See CreateRawTransaction for the blocking version and more details.
|
||||||
func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
|
func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
|
||||||
amounts map[btcutil.Address]btcutil.Amount, lockTime *int64) FutureCreateRawTransactionResult {
|
outputs map[btcutil.Address]interface{}, lockTime *int64) FutureCreateRawTransactionResult {
|
||||||
|
|
||||||
convertedAmts := make(map[string]float64, len(amounts))
|
convertedData := make(map[string]interface{}, len(outputs))
|
||||||
for addr, amount := range amounts {
|
for key, value := range outputs {
|
||||||
convertedAmts[addr.String()] = amount.ToBTC()
|
switch val := value.(type) {
|
||||||
|
case btcutil.Amount:
|
||||||
|
convertedData[key.String()] = val.ToBTC()
|
||||||
|
case string:
|
||||||
|
convertedData[key.String()] = val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cmd := btcjson.NewCreateRawTransactionCmd(inputs, convertedAmts, lockTime)
|
cmd := btcjson.NewCreateRawTransactionCmd(inputs, convertedData, lockTime)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,9 +310,9 @@ func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
|
||||||
// and sending to the provided addresses. If the inputs are either nil or an
|
// and sending to the provided addresses. If the inputs are either nil or an
|
||||||
// empty slice, it is interpreted as an empty slice.
|
// empty slice, it is interpreted as an empty slice.
|
||||||
func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput,
|
func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput,
|
||||||
amounts map[btcutil.Address]btcutil.Amount, lockTime *int64) (*wire.MsgTx, error) {
|
outputs map[btcutil.Address]interface{}, lockTime *int64) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
return c.CreateRawTransactionAsync(inputs, amounts, lockTime).Receive()
|
return c.CreateRawTransactionAsync(inputs, outputs, lockTime).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FutureSendRawTransactionResult is a future promise to deliver the result
|
// FutureSendRawTransactionResult is a future promise to deliver the result
|
||||||
|
|
94
rpcserver.go
94
rpcserver.go
|
@ -327,6 +327,15 @@ func rpcDecodeHexError(gotHex string) *btcjson.RPCError {
|
||||||
gotHex))
|
gotHex))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rpcInvalidAddressOrKey is a convenience function for returning a nicely
|
||||||
|
// formatted RPC error which indicates the address or key is invalid.
|
||||||
|
func rpcInvalidAddressOrKeyError(addr string, msg string) *btcjson.RPCError {
|
||||||
|
return &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// rpcNoTxInfoError is a convenience function for returning a nicely formatted
|
// rpcNoTxInfoError is a convenience function for returning a nicely formatted
|
||||||
// RPC error which indicates there is no information available for the provided
|
// RPC error which indicates there is no information available for the provided
|
||||||
// transaction hash.
|
// transaction hash.
|
||||||
|
@ -568,59 +577,92 @@ func handleCreateRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan
|
||||||
// Add all transaction outputs to the transaction after performing
|
// Add all transaction outputs to the transaction after performing
|
||||||
// some validity checks.
|
// some validity checks.
|
||||||
params := s.cfg.ChainParams
|
params := s.cfg.ChainParams
|
||||||
for encodedAddr, amount := range c.Amounts {
|
|
||||||
// Ensure amount is in the valid range for monetary amounts.
|
// Ensure amount is in the valid range for monetary amounts.
|
||||||
if amount <= 0 || amount*btcutil.SatoshiPerBitcoin > btcutil.MaxSatoshi {
|
// Decode the provided address.
|
||||||
|
// Ensure the address is one of the supported types and that
|
||||||
|
// the network encoded with the address matches the network the
|
||||||
|
// server is currently on.
|
||||||
|
// Create a new script which pays to the provided address.
|
||||||
|
// Convert the amount to satoshi.
|
||||||
|
handleAmountFn := func(amount float64, encodedAddr string) (*wire.TxOut,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
if amount <= 0 ||
|
||||||
|
amount*btcutil.SatoshiPerBitcoin > btcutil.MaxSatoshi {
|
||||||
return nil, &btcjson.RPCError{
|
return nil, &btcjson.RPCError{
|
||||||
Code: btcjson.ErrRPCType,
|
Code: btcjson.ErrRPCType,
|
||||||
Message: "Invalid amount",
|
Message: "invalid amount",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the provided address.
|
|
||||||
addr, err := btcutil.DecodeAddress(encodedAddr, params)
|
addr, err := btcutil.DecodeAddress(encodedAddr, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &btcjson.RPCError{
|
return nil, rpcInvalidAddressOrKeyError(encodedAddr,
|
||||||
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
"invalid address or key")
|
||||||
Message: "Invalid address or key: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the address is one of the supported types and that
|
|
||||||
// the network encoded with the address matches the network the
|
|
||||||
// server is currently on.
|
|
||||||
switch addr.(type) {
|
switch addr.(type) {
|
||||||
case *btcutil.AddressPubKeyHash:
|
case *btcutil.AddressPubKeyHash:
|
||||||
case *btcutil.AddressScriptHash:
|
case *btcutil.AddressScriptHash:
|
||||||
default:
|
default:
|
||||||
return nil, &btcjson.RPCError{
|
return nil, rpcInvalidAddressOrKeyError(addr.String(),
|
||||||
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
"invalid address or key")
|
||||||
Message: "Invalid address or key: " + addr.String(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !addr.IsForNet(params) {
|
if !addr.IsForNet(params) {
|
||||||
return nil, &btcjson.RPCError{
|
return nil, rpcInvalidAddressOrKeyError(addr.String(),
|
||||||
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
"wrong network")
|
||||||
Message: "Invalid address: " + encodedAddr +
|
|
||||||
" is for the wrong network",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new script which pays to the provided address.
|
|
||||||
pkScript, err := txscript.PayToAddrScript(addr)
|
pkScript, err := txscript.PayToAddrScript(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context := "Failed to generate pay-to-address script"
|
context := "failed to generate pay-to-address script"
|
||||||
return nil, internalRPCError(err.Error(), context)
|
return nil, internalRPCError(err.Error(), context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the amount to satoshi.
|
|
||||||
satoshi, err := btcutil.NewAmount(amount)
|
satoshi, err := btcutil.NewAmount(amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context := "Failed to convert amount"
|
context := "failed to convert amount"
|
||||||
return nil, internalRPCError(err.Error(), context)
|
return nil, internalRPCError(err.Error(), context)
|
||||||
}
|
}
|
||||||
|
|
||||||
txOut := wire.NewTxOut(int64(satoshi), pkScript)
|
return wire.NewTxOut(int64(satoshi), pkScript), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDataFn := func(key string, value string) (*wire.TxOut, error) {
|
||||||
|
if key != "data" {
|
||||||
|
context := "output key must be an address or \"data\""
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidParameter,
|
||||||
|
Message: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
data, err := hex.DecodeString(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, rpcDecodeHexError(value)
|
||||||
|
}
|
||||||
|
return wire.NewTxOut(0, data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range c.Outputs {
|
||||||
|
var err error
|
||||||
|
var txOut *wire.TxOut
|
||||||
|
switch value := value.(type) {
|
||||||
|
case float64:
|
||||||
|
txOut, err = handleAmountFn(value, key)
|
||||||
|
case string:
|
||||||
|
txOut, err = handleDataFn(key, value)
|
||||||
|
default:
|
||||||
|
context := "output value must be a string or float"
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCType,
|
||||||
|
Message: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
mtx.AddTxOut(txOut)
|
mtx.AddTxOut(txOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -49,10 +49,10 @@ var helpDescsEnUS = map[string]string{
|
||||||
"The transaction inputs are not signed in the created transaction.\n" +
|
"The transaction inputs are not signed in the created transaction.\n" +
|
||||||
"The signrawtransaction RPC command provided by wallet must be used to sign the resulting transaction.",
|
"The signrawtransaction RPC command provided by wallet must be used to sign the resulting transaction.",
|
||||||
"createrawtransaction-inputs": "The inputs to the transaction",
|
"createrawtransaction-inputs": "The inputs to the transaction",
|
||||||
"createrawtransaction-amounts": "JSON object with the destination addresses as keys and amounts as values",
|
"createrawtransaction-outputs": "JSON object with the destination addresses as keys and amounts as values",
|
||||||
"createrawtransaction-amounts--key": "address",
|
"createrawtransaction-outputs--key": "address or \"data\"",
|
||||||
"createrawtransaction-amounts--value": "n.nnn",
|
"createrawtransaction-outputs--value": "value in BTC as floating point number or hex-encoded data for \"data\"",
|
||||||
"createrawtransaction-amounts--desc": "The destination address as the key and the amount in LBC as the value",
|
"createrawtransaction-outputs--desc": "The destination address as the key and the amount in LBC as the value",
|
||||||
"createrawtransaction-locktime": "Locktime value; a non-zero value will also locktime-activate the inputs",
|
"createrawtransaction-locktime": "Locktime value; a non-zero value will also locktime-activate the inputs",
|
||||||
"createrawtransaction--result0": "Hex-encoded bytes of the serialized transaction",
|
"createrawtransaction--result0": "Hex-encoded bytes of the serialized transaction",
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue