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.
|
||||
type CreateRawTransactionCmd struct {
|
||||
Inputs []TransactionInput
|
||||
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
||||
Outputs map[string]interface{} `jsonrpcusage:"{\"address\":amount, \"data\":\"hex\", ...}"`
|
||||
LockTime *int64
|
||||
}
|
||||
|
||||
|
@ -76,7 +76,7 @@ type CreateRawTransactionCmd struct {
|
|||
//
|
||||
// Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent,
|
||||
// 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 {
|
||||
// to make sure we're serializing this to the empty list and not null, we
|
||||
// explicitly initialize the list
|
||||
|
@ -85,7 +85,7 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]fl
|
|||
}
|
||||
return &CreateRawTransactionCmd{
|
||||
Inputs: inputs,
|
||||
Amounts: amounts,
|
||||
Outputs: outputs,
|
||||
LockTime: lockTime,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,13 +52,13 @@ func TestChainSvrCmds(t *testing.T) {
|
|||
txInputs := []btcjson.TransactionInput{
|
||||
{Txid: "123", Vout: 1},
|
||||
}
|
||||
amounts := map[string]float64{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, nil)
|
||||
txOutputs := map[string]interface{}{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123}],"id":1}`,
|
||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||
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}`)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
amounts := map[string]float64{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(nil, amounts, nil)
|
||||
txOutputs := map[string]interface{}{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(nil, txOutputs, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[],{"456":0.0123}],"id":1}`,
|
||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||
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{
|
||||
{Txid: "123", Vout: 1},
|
||||
}
|
||||
amounts := map[string]float64{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, btcjson.Int64(12312333333))
|
||||
txOutputs := map[string]interface{}{"456": .0123}
|
||||
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}`,
|
||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
||||
Amounts: map[string]float64{"456": .0123},
|
||||
Outputs: map[string]interface{}{"456": .0123},
|
||||
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",
|
||||
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.
|
||||
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))
|
||||
for addr, amount := range amounts {
|
||||
convertedAmts[addr.String()] = amount.ToBTC()
|
||||
convertedData := make(map[string]interface{}, len(outputs))
|
||||
for key, value := range outputs {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
// empty slice, it is interpreted as an empty slice.
|
||||
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
|
||||
|
|
102
rpcserver.go
102
rpcserver.go
|
@ -327,6 +327,15 @@ func rpcDecodeHexError(gotHex string) *btcjson.RPCError {
|
|||
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
|
||||
// RPC error which indicates there is no information available for the provided
|
||||
// transaction hash.
|
||||
|
@ -568,59 +577,92 @@ func handleCreateRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan
|
|||
// Add all transaction outputs to the transaction after performing
|
||||
// some validity checks.
|
||||
params := s.cfg.ChainParams
|
||||
for encodedAddr, amount := range c.Amounts {
|
||||
|
||||
// Ensure amount is in the valid range for monetary amounts.
|
||||
if amount <= 0 || amount*btcutil.SatoshiPerBitcoin > btcutil.MaxSatoshi {
|
||||
return nil, &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCType,
|
||||
Message: "Invalid amount",
|
||||
}
|
||||
}
|
||||
|
||||
// Decode the provided address.
|
||||
addr, err := btcutil.DecodeAddress(encodedAddr, params)
|
||||
if err != nil {
|
||||
return nil, &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
||||
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.
|
||||
// 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{
|
||||
Code: btcjson.ErrRPCType,
|
||||
Message: "invalid amount",
|
||||
}
|
||||
}
|
||||
|
||||
addr, err := btcutil.DecodeAddress(encodedAddr, params)
|
||||
if err != nil {
|
||||
return nil, rpcInvalidAddressOrKeyError(encodedAddr,
|
||||
"invalid address or key")
|
||||
}
|
||||
|
||||
switch addr.(type) {
|
||||
case *btcutil.AddressPubKeyHash:
|
||||
case *btcutil.AddressScriptHash:
|
||||
default:
|
||||
return nil, &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
||||
Message: "Invalid address or key: " + addr.String(),
|
||||
}
|
||||
return nil, rpcInvalidAddressOrKeyError(addr.String(),
|
||||
"invalid address or key")
|
||||
}
|
||||
if !addr.IsForNet(params) {
|
||||
return nil, &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
||||
Message: "Invalid address: " + encodedAddr +
|
||||
" is for the wrong network",
|
||||
}
|
||||
return nil, rpcInvalidAddressOrKeyError(addr.String(),
|
||||
"wrong network")
|
||||
}
|
||||
|
||||
// Create a new script which pays to the provided address.
|
||||
pkScript, err := txscript.PayToAddrScript(addr)
|
||||
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)
|
||||
}
|
||||
|
||||
// Convert the amount to satoshi.
|
||||
satoshi, err := btcutil.NewAmount(amount)
|
||||
if err != nil {
|
||||
context := "Failed to convert amount"
|
||||
context := "failed to convert amount"
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -49,10 +49,10 @@ var helpDescsEnUS = map[string]string{
|
|||
"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.",
|
||||
"createrawtransaction-inputs": "The inputs to the transaction",
|
||||
"createrawtransaction-amounts": "JSON object with the destination addresses as keys and amounts as values",
|
||||
"createrawtransaction-amounts--key": "address",
|
||||
"createrawtransaction-amounts--value": "n.nnn",
|
||||
"createrawtransaction-amounts--desc": "The destination address as the key and the amount in LBC as the value",
|
||||
"createrawtransaction-outputs": "JSON object with the destination addresses as keys and amounts as values",
|
||||
"createrawtransaction-outputs--key": "address or \"data\"",
|
||||
"createrawtransaction-outputs--value": "value in BTC as floating point number or hex-encoded data for \"data\"",
|
||||
"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--result0": "Hex-encoded bytes of the serialized transaction",
|
||||
|
||||
|
|
Loading…
Reference in a new issue