Skip to content

Commit

Permalink
Add searchrawtransaction
Browse files Browse the repository at this point in the history
  • Loading branch information
Roasbeef committed Jan 8, 2015
1 parent effe99b commit b9c8ec9
Show file tree
Hide file tree
Showing 8 changed files with 391 additions and 128 deletions.
45 changes: 45 additions & 0 deletions cmdhelp.go
Expand Up @@ -651,6 +651,51 @@ getpeerinfo.`,
"reconsiderblock": `reconsiderblock "hash"
Remove invalid mark from block specified by "hash" so it is considered again.`,

"searchrawtransaction": `searchrawtransaction "address" (verbose=1 skip=0 count=100)
Returns raw tx data related to credits or debits to "address". Skip indicates
the number of leading transactions to leave out of the final result. Count
represents the max number of transactions to return. If verbose is false, a
string containing hex-encoded serialized data for txid. If verbose is true a
JSON object with the following information about txid is returned:
{
"hex":"data", # String of serialized, hex encoded data for txid.
"txid":"id", # String containing the transaction id (same as "txid" parameter)
"version":n # Numeric tx version number.
"locktime":t, # Transaction locktime.
"vin":[ # Array of objects representing transaction inputs.
{
"txid":"id", # Spent transaction id as a string.
"vout""n, # Spent transaction output no.
"scriptSig":{ # Signature script as an object.
"asm":"asm", # Disassembled script string.
"hex":"hex", # Hex serialized string.
},
"sequence":n, # Script sequence number.
},
...
],
vout:[ # Array of objects representing transaction outputs.
{
"value":n, # Numeric value of output in btc.
"n", n, # Numeric output index.
"scriptPubKey":{ # Object representing pubkey script.
"asm":"asm" # Disassembled string of script.
"hex":"hex" # Hex serialized string.
"reqSigs":n, # Number of required signatures.
"type":"pubkey", # Type of scirpt. e.g. pubkeyhash" or "pubkey".
"addresses":[ # Array of address strings.
"address", # Bitcoin address.
...
],
}
}
],
"blockhash":"hash" # Hash of the block the transaction is part of.
"confirmations":n, # Number of numeric confirmations of block.
"time":t, # Transaction time in seconds since the epoch.
"blocktime":t, # Block time in seconds since the epoch.
}`,

"sendfrom": `sendfrom "fromaccount" "tobitcoinaddress" amount ( minconf=1 "comment" "comment-to" )
Sends "amount" (rounded to the nearest 0.00000001) to
"tobitcoindaddress" from "fromaccount". Only funds with at least
Expand Down
24 changes: 24 additions & 0 deletions jsonapi.go
Expand Up @@ -618,6 +618,30 @@ func CreateMessageWithId(message string, id interface{}, args ...interface{}) ([
}
*/
finalMessage, err = jsonWithArgs(message, id, []interface{}{args[0].(string), txList})
// one required string (address), one optional bool, two optional ints.
case "searchrawtransaction":
if len(args) > 4 || len(args) == 0 {
err = fmt.Errorf("wrong number of arguments for %s", message)
return finalMessage, err
}
_, ok1 := args[0].(string)
ok2 := true
ok3 := true
ok4 := true
if len(args) >= 2 {
_, ok2 = args[1].(bool)
}
if len(args) >= 3 {
_, ok3 = args[2].(int)
}
if len(args) == 4 {
_, ok4 = args[3].(int)
}
if !ok1 || !ok2 || !ok3 || !ok4 {
err = fmt.Errorf("arguments must be string, one optional "+
"bool, and two optional ints for %s", message)
return finalMessage, err
}
// Any other message
default:
err = fmt.Errorf("not a valid command: %s", message)
Expand Down
6 changes: 6 additions & 0 deletions jsonapi_test.go
Expand Up @@ -181,6 +181,12 @@ var cmdtests = []struct {
{"signrawtransaction", []interface{}{1.2, "test", "test2", "test3", "test4"}, false},
{"signrawtransaction", []interface{}{"hexstring", 1, "test2", "test3", "test4"}, false},
{"signrawtransaction", []interface{}{"hexstring", "test", "test2", 3, "test4"}, false},
{"searchrawtransaction", []interface{}{"someaddr"}, true},
{"searchrawtransaction", []interface{}{"someaddr", true}, true},
{"searchrawtransaction", []interface{}{"someaddr", false, 1}, true},
{"searchrawtransaction", []interface{}{"someaddr", true, 5, 500}, true},
{"searchrawtransaction", []interface{}{"someaddr", true, 5, "test"}, false},
{"searchrawtransaction", []interface{}{}, false},
{"listsinceblock", []interface{}{"test", "test"}, true},
{"listsinceblock", []interface{}{"test", "test", "test"}, false},
{"listsinceblock", []interface{}{"test"}, true},
Expand Down
143 changes: 143 additions & 0 deletions jsoncmd.go
Expand Up @@ -286,6 +286,9 @@ func ParseMarshaledCmd(b []byte) (Cmd, error) {
case "reconsiderblock":
cmd = new(ReconsiderBlockCmd)

case "searchrawtransaction":
cmd = new(SearchRawTransactionCmd)

case "sendfrom":
cmd = new(SendFromCmd)

Expand Down Expand Up @@ -5319,6 +5322,146 @@ func (cmd *ReconsiderBlockCmd) UnmarshalJSON(b []byte) error {
return nil
}

// SearchRawTransactionCmd is a type handling custom marshaling and
// unmarshaling of sendrawtransaction JSON RPC commands.
type SearchRawTransactionCmd struct {
id interface{}
Address string
Verbose bool
Skip int
Count int
}

// NewSearchRawTransactionCmd creates a new SearchRawTransactionCmd.
func NewSearchRawTransactionCmd(id interface{}, address string,
optArgs ...interface{}) (*SearchRawTransactionCmd, error) {
verbose := true
var skip int
count := 100

if len(optArgs) > 3 {
return nil, ErrTooManyOptArgs
}

if len(optArgs) > 0 {
v, ok := optArgs[0].(bool)
if !ok {
return nil, errors.New("first optional argument verbose is not a bool")
}

verbose = v
}
if len(optArgs) > 1 {
s, ok := optArgs[1].(int)
if !ok {
return nil, errors.New("second optional argument skip is not an int")
}
skip = s
}
if len(optArgs) > 2 {
c, ok := optArgs[2].(int)
if !ok {
return nil, errors.New("third optional argument count is not an int")
}

count = c
}

return &SearchRawTransactionCmd{
id: id,
Address: address,
Verbose: verbose,
Skip: skip,
Count: count,
}, nil
}

// Id satisfies the Cmd interface by returning the id of the command.
func (cmd *SearchRawTransactionCmd) Id() interface{} {
return cmd.id
}

// Method satisfies the Cmd interface by returning the json method.
func (cmd *SearchRawTransactionCmd) Method() string {
return "searchrawtransaction"
}

// MarshalJSON returns the JSON encoding of cmd. Part of the Cmd interface.
func (cmd *SearchRawTransactionCmd) MarshalJSON() ([]byte, error) {
params := make([]interface{}, 1, 4)
params[0] = cmd.Address
if !cmd.Verbose || cmd.Skip != 0 || cmd.Count != 100 {
params = append(params, cmd.Verbose)
}
if cmd.Skip != 0 || cmd.Count != 100 {
params = append(params, cmd.Skip)
}
if cmd.Count != 100 {
params = append(params, cmd.Count)
}

// Fill and marshal a RawCmd.
raw, err := NewRawCmd(cmd.id, cmd.Method(), params)
if err != nil {
return nil, err

}
return json.Marshal(raw)
}

// UnmarshalJSON unmarshals the JSON encoding of cmd into cmd. Part of
// the Cmd interface.
func (cmd *SearchRawTransactionCmd) UnmarshalJSON(b []byte) error {
// Unmarshal into a RawCmd
var r RawCmd
if err := json.Unmarshal(b, &r); err != nil {
return err
}

if len(r.Params) == 0 || len(r.Params) > 4 {
return ErrWrongNumberOfParams
}

var address string
if err := json.Unmarshal(r.Params[0], &address); err != nil {
return fmt.Errorf("first parameter 'address' must be a string: %v", err)
}

optArgs := make([]interface{}, 0, 3)
if len(r.Params) > 1 {
var verbose bool
if err := json.Unmarshal(r.Params[1], &verbose); err != nil {
return fmt.Errorf("second optional parameter 'verbose' must be an bool: %v", err)
}
optArgs = append(optArgs, verbose)
}
if len(r.Params) > 2 {
var skip int
if err := json.Unmarshal(r.Params[2], &skip); err != nil {
return fmt.Errorf("third optional parameter 'skip' must be an int: %v", err)
}
optArgs = append(optArgs, skip)
}
if len(r.Params) > 3 {
var count int
if err := json.Unmarshal(r.Params[3], &count); err != nil {
return fmt.Errorf("fourth optional parameter 'count' must be an int: %v", err)
}
optArgs = append(optArgs, count)
}

newCmd, err := NewSearchRawTransactionCmd(r.Id, address, optArgs...)
if err != nil {
return err
}

*cmd = *newCmd
return nil
}

// Enforce that SearchRawTransactionCmd satisifies the Cmd interface.
var _ Cmd = &SearchRawTransactionCmd{}

// SendFromCmd is a type handling custom marshaling and
// unmarshaling of sendfrom JSON RPC commands.
type SendFromCmd struct {
Expand Down
16 changes: 16 additions & 0 deletions jsoncmd_test.go
Expand Up @@ -1204,6 +1204,21 @@ var jsoncmdtests = []struct {
BlockHash: "lotsofhex",
},
},
{
name: "basic + optionals",
cmd: "searchrawtransaction",
f: func() (Cmd, error) {
return NewSearchRawTransactionCmd(testID,
"someaddr", true, 5, 200)
},
result: &SearchRawTransactionCmd{
id: testID,
Address: "someaddr",
Verbose: true,
Skip: 5,
Count: 200,
},
},
{
name: "basic",
cmd: "sendfrom",
Expand Down Expand Up @@ -1706,6 +1721,7 @@ func TestHelps(t *testing.T) {
"move",
"ping",
"reconsiderblock",
"searchrawtransaction",
"sendfrom",
"sendmany",
"sendrawtransaction",
Expand Down
27 changes: 24 additions & 3 deletions jsonresults.go
Expand Up @@ -217,7 +217,8 @@ type ScriptSig struct {
}

// Vin models parts of the tx data. It is defined seperately since both
// getrawtransaction and decoderawtransaction use the same structure.
// getrawtransaction, sendrawtransaction, and decoderawtransaction use the
// same structure.
type Vin struct {
Coinbase string `json:"coinbase"`
Txid string `json:"txid"`
Expand Down Expand Up @@ -269,7 +270,8 @@ type ScriptPubKeyResult struct {
}

// Vout models parts of the tx data. It is defined seperately since both
// getrawtransaction and decoderawtransaction use the same structure.
// getrawtransaction, sendrawtransaction, and decoderawtransaction use the same
// structure.
type Vout struct {
Value float64 `json:"value"`
N uint32 `json:"n"`
Expand Down Expand Up @@ -398,7 +400,8 @@ type SignRawTransactionResult struct {
Complete bool `json:"complete"`
}

// TxRawResult models the data from the getrawtransaction command.
// TxRawResult models the data from the getrawtransaction and sendrawtransaction
// commands
type TxRawResult struct {
Hex string `json:"hex"`
Txid string `json:"txid"`
Expand Down Expand Up @@ -686,6 +689,24 @@ func ReadResultCmd(cmd string, message []byte) (Reply, error) {
if err == nil {
result.Result = res
}

case "searchrawtransaction":
// searchrawtransaction can either return a list of JSON objects
// or a list of hex-encoded strings depending on the verbose flag.
// Choose the right form accordingly.
if bytes.IndexByte(objmap["result"], '{') > -1 {
var res []*TxRawResult
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
} else {
var res []string
err = json.Unmarshal(objmap["result"], &res)
if err == nil {
result.Result = res
}
}
// For commands that return a single item (or no items), we get it with
// the correct concrete type for free (but treat them separately
// for clarity).
Expand Down
3 changes: 3 additions & 0 deletions jsonresults_test.go
Expand Up @@ -61,6 +61,9 @@ var resulttests = []struct {
{"signrawtransaction", []byte(`{"error":null,"id":1,"result":{false}}`), false, false},
{"listunspent", []byte(`{"error":null,"id":1,"result":[{"txid":"something"}]}`), false, true},
{"listunspent", []byte(`{"error":null,"id":1,"result":[{"txid"}]}`), false, false},
{"searchrawtransaction", []byte(`{"error":null,"id":1,"result":{"a":"b"}}`), false, false},
{"searchrawtransaction", []byte(`{"error":null,"id":1,"result":["sometxhex"]}`), false, true},
{"searchrawtransaction", []byte(`{"error":null,"id":1,"result":[{"hex":"somejunk","version":1}]}`), false, true},
}

// TestReadResultCmd tests that readResultCmd can properly unmarshall the
Expand Down

0 comments on commit b9c8ec9

Please sign in to comment.