Add Batch JSON-RPC support (rpc client & server)
This commit is contained in:
parent
31b66488b4
commit
2a1aa5129e
26 changed files with 1193 additions and 357 deletions
|
@ -211,7 +211,7 @@ func TestBtcdExtCmds(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
// Marshal the command as created by the new static command
|
// Marshal the command as created by the new static command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
@ -235,7 +235,7 @@ func TestBtcdExtCmds(t *testing.T) {
|
||||||
|
|
||||||
// Marshal the command as created by the generic new command
|
// Marshal the command as created by the generic new command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
|
|
@ -145,7 +145,7 @@ func TestBtcWalletExtCmds(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
// Marshal the command as created by the new static command
|
// Marshal the command as created by the new static command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
@ -169,7 +169,7 @@ func TestBtcWalletExtCmds(t *testing.T) {
|
||||||
|
|
||||||
// Marshal the command as created by the generic new command
|
// Marshal the command as created by the generic new command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
|
|
@ -1466,7 +1466,7 @@ func TestChainSvrCmds(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
// Marshal the command as created by the new static command
|
// Marshal the command as created by the new static command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
@ -1491,7 +1491,7 @@ func TestChainSvrCmds(t *testing.T) {
|
||||||
|
|
||||||
// Marshal the command as created by the generic new command
|
// Marshal the command as created by the generic new command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
|
|
@ -233,7 +233,7 @@ func TestChainSvrWsCmds(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
// Marshal the command as created by the new static command
|
// Marshal the command as created by the new static command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
@ -257,7 +257,7 @@ func TestChainSvrWsCmds(t *testing.T) {
|
||||||
|
|
||||||
// Marshal the command as created by the generic new command
|
// Marshal the command as created by the generic new command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
|
|
@ -231,7 +231,7 @@ func TestChainSvrWsNtfns(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
// Marshal the notification as created by the new static
|
// Marshal the notification as created by the new static
|
||||||
// creation function. The ID is nil for notifications.
|
// creation function. The ID is nil for notifications.
|
||||||
marshalled, err := btcjson.MarshalCmd(nil, test.staticNtfn())
|
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, test.staticNtfn())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
@ -256,7 +256,7 @@ func TestChainSvrWsNtfns(t *testing.T) {
|
||||||
// Marshal the notification as created by the generic new
|
// Marshal the notification as created by the generic new
|
||||||
// notification creation function. The ID is nil for
|
// notification creation function. The ID is nil for
|
||||||
// notifications.
|
// notifications.
|
||||||
marshalled, err = btcjson.MarshalCmd(nil, cmd)
|
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, nil, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
|
|
@ -36,7 +36,7 @@ func makeParams(rt reflect.Type, rv reflect.Value) []interface{} {
|
||||||
// is suitable for transmission to an RPC server. The provided command type
|
// is suitable for transmission to an RPC server. The provided command type
|
||||||
// must be a registered type. All commands provided by this package are
|
// must be a registered type. All commands provided by this package are
|
||||||
// registered by default.
|
// registered by default.
|
||||||
func MarshalCmd(id interface{}, cmd interface{}) ([]byte, error) {
|
func MarshalCmd(rpcVersion RPCVersion, id interface{}, cmd interface{}) ([]byte, error) {
|
||||||
// Look up the cmd type and error out if not registered.
|
// Look up the cmd type and error out if not registered.
|
||||||
rt := reflect.TypeOf(cmd)
|
rt := reflect.TypeOf(cmd)
|
||||||
registerLock.RLock()
|
registerLock.RLock()
|
||||||
|
@ -60,7 +60,7 @@ func MarshalCmd(id interface{}, cmd interface{}) ([]byte, error) {
|
||||||
params := makeParams(rt.Elem(), rv.Elem())
|
params := makeParams(rt.Elem(), rv.Elem())
|
||||||
|
|
||||||
// Generate and marshal the final JSON-RPC request.
|
// Generate and marshal the final JSON-RPC request.
|
||||||
rawCmd, err := NewRequest(id, method, params)
|
rawCmd, err := NewRequest(rpcVersion, id, method, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -460,7 +460,7 @@ func TestMarshalCmd(t *testing.T) {
|
||||||
|
|
||||||
t.Logf("Running %d tests", len(tests))
|
t.Logf("Running %d tests", len(tests))
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
bytes, err := btcjson.MarshalCmd(test.id, test.cmd)
|
bytes, err := btcjson.MarshalCmd(btcjson.RpcVersion1, test.id, test.cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Test #%d (%s) wrong error - got %T (%v)",
|
t.Errorf("Test #%d (%s) wrong error - got %T (%v)",
|
||||||
i, test.name, err, err)
|
i, test.name, err, err)
|
||||||
|
@ -507,7 +507,7 @@ func TestMarshalCmdErrors(t *testing.T) {
|
||||||
|
|
||||||
t.Logf("Running %d tests", len(tests))
|
t.Logf("Running %d tests", len(tests))
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
_, err := btcjson.MarshalCmd(test.id, test.cmd)
|
_, err := btcjson.MarshalCmd(btcjson.RpcVersion1, test.id, test.cmd)
|
||||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||||
t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
|
t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
|
||||||
"want %T", i, test.name, err, err, test.err)
|
"want %T", i, test.name, err, err, test.err)
|
||||||
|
@ -535,7 +535,7 @@ func TestUnmarshalCmdErrors(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "unregistered type",
|
name: "unregistered type",
|
||||||
request: btcjson.Request{
|
request: btcjson.Request{
|
||||||
Jsonrpc: "1.0",
|
Jsonrpc: btcjson.RpcVersion1,
|
||||||
Method: "bogusmethod",
|
Method: "bogusmethod",
|
||||||
Params: nil,
|
Params: nil,
|
||||||
ID: nil,
|
ID: nil,
|
||||||
|
@ -545,7 +545,7 @@ func TestUnmarshalCmdErrors(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "incorrect number of params",
|
name: "incorrect number of params",
|
||||||
request: btcjson.Request{
|
request: btcjson.Request{
|
||||||
Jsonrpc: "1.0",
|
Jsonrpc: btcjson.RpcVersion1,
|
||||||
Method: "getblockcount",
|
Method: "getblockcount",
|
||||||
Params: []json.RawMessage{[]byte(`"bogusparam"`)},
|
Params: []json.RawMessage{[]byte(`"bogusparam"`)},
|
||||||
ID: nil,
|
ID: nil,
|
||||||
|
@ -555,7 +555,7 @@ func TestUnmarshalCmdErrors(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "invalid type for a parameter",
|
name: "invalid type for a parameter",
|
||||||
request: btcjson.Request{
|
request: btcjson.Request{
|
||||||
Jsonrpc: "1.0",
|
Jsonrpc: btcjson.RpcVersion1,
|
||||||
Method: "getblock",
|
Method: "getblock",
|
||||||
Params: []json.RawMessage{[]byte("1")},
|
Params: []json.RawMessage{[]byte("1")},
|
||||||
ID: nil,
|
ID: nil,
|
||||||
|
@ -565,7 +565,7 @@ func TestUnmarshalCmdErrors(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "invalid JSON for a parameter",
|
name: "invalid JSON for a parameter",
|
||||||
request: btcjson.Request{
|
request: btcjson.Request{
|
||||||
Jsonrpc: "1.0",
|
Jsonrpc: btcjson.RpcVersion1,
|
||||||
Method: "getblock",
|
Method: "getblock",
|
||||||
Params: []json.RawMessage{[]byte(`"1`)},
|
Params: []json.RawMessage{[]byte(`"1`)},
|
||||||
ID: nil,
|
ID: nil,
|
||||||
|
|
|
@ -27,7 +27,7 @@ func ExampleMarshalCmd() {
|
||||||
// server. Typically the client would increment the id here which is
|
// server. Typically the client would increment the id here which is
|
||||||
// request so the response can be identified.
|
// request so the response can be identified.
|
||||||
id := 1
|
id := 1
|
||||||
marshalledBytes, err := btcjson.MarshalCmd(id, gbCmd)
|
marshalledBytes, err := btcjson.MarshalCmd(btcjson.RpcVersion1, id, gbCmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
|
@ -95,7 +95,7 @@ func ExampleUnmarshalCmd() {
|
||||||
func ExampleMarshalResponse() {
|
func ExampleMarshalResponse() {
|
||||||
// Marshal a new JSON-RPC response. For example, this is a response
|
// Marshal a new JSON-RPC response. For example, this is a response
|
||||||
// to a getblockheight request.
|
// to a getblockheight request.
|
||||||
marshalledBytes, err := btcjson.MarshalResponse(1, 350001, nil)
|
marshalledBytes, err := btcjson.MarshalResponse(btcjson.RpcVersion1, 1, 350001, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
return
|
return
|
||||||
|
@ -107,7 +107,7 @@ func ExampleMarshalResponse() {
|
||||||
fmt.Printf("%s\n", marshalledBytes)
|
fmt.Printf("%s\n", marshalledBytes)
|
||||||
|
|
||||||
// Output:
|
// Output:
|
||||||
// {"result":350001,"error":null,"id":1}
|
// {"jsonrpc":"1.0","result":350001,"error":null,"id":1}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This example demonstrates how to unmarshal a JSON-RPC response and then
|
// This example demonstrates how to unmarshal a JSON-RPC response and then
|
||||||
|
@ -116,7 +116,7 @@ func Example_unmarshalResponse() {
|
||||||
// Ordinarily this would be read from the wire, but for this example,
|
// Ordinarily this would be read from the wire, but for this example,
|
||||||
// it is hard coded here for clarity. This is an example response to a
|
// it is hard coded here for clarity. This is an example response to a
|
||||||
// getblockheight request.
|
// getblockheight request.
|
||||||
data := []byte(`{"result":350001,"error":null,"id":1}`)
|
data := []byte(`{"jsonrpc":"1.0","result":350001,"error":null,"id":1}`)
|
||||||
|
|
||||||
// Unmarshal the raw bytes from the wire into a JSON-RPC response.
|
// Unmarshal the raw bytes from the wire into a JSON-RPC response.
|
||||||
var response btcjson.Response
|
var response btcjson.Response
|
||||||
|
|
|
@ -9,6 +9,33 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// RPCVersion is a type to indicate RPC versions.
|
||||||
|
type RPCVersion string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// version 1 of rpc
|
||||||
|
RpcVersion1 RPCVersion = RPCVersion("1.0")
|
||||||
|
// version 2 of rpc
|
||||||
|
RpcVersion2 RPCVersion = RPCVersion("2.0")
|
||||||
|
)
|
||||||
|
|
||||||
|
var validRpcVersions = []RPCVersion{RpcVersion1, RpcVersion2}
|
||||||
|
|
||||||
|
// check if the rpc version is a valid version
|
||||||
|
func (r RPCVersion) IsValid() bool {
|
||||||
|
for _, version := range validRpcVersions {
|
||||||
|
if version == r {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// cast rpc version to a string
|
||||||
|
func (r RPCVersion) String() string {
|
||||||
|
return string(r)
|
||||||
|
}
|
||||||
|
|
||||||
// RPCErrorCode represents an error code to be used as a part of an RPCError
|
// RPCErrorCode represents an error code to be used as a part of an RPCError
|
||||||
// which is in turn used in a JSON-RPC Response object.
|
// which is in turn used in a JSON-RPC Response object.
|
||||||
//
|
//
|
||||||
|
@ -67,21 +94,74 @@ func IsValidIDType(id interface{}) bool {
|
||||||
// requests, however this struct it being exported in case the caller wants to
|
// requests, however this struct it being exported in case the caller wants to
|
||||||
// construct raw requests for some reason.
|
// construct raw requests for some reason.
|
||||||
type Request struct {
|
type Request struct {
|
||||||
Jsonrpc string `json:"jsonrpc"`
|
Jsonrpc RPCVersion `json:"jsonrpc"`
|
||||||
Method string `json:"method"`
|
Method string `json:"method"`
|
||||||
Params []json.RawMessage `json:"params"`
|
Params []json.RawMessage `json:"params"`
|
||||||
ID interface{} `json:"id"`
|
ID interface{} `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequest returns a new JSON-RPC 1.0 request object given the provided id,
|
// UnmarshalJSON is a custom unmarshal func for the Request struct. The param
|
||||||
// method, and parameters. The parameters are marshalled into a json.RawMessage
|
// field defaults to an empty json.RawMessage array it is omitted by the request
|
||||||
// for the Params field of the returned request object. This function is only
|
// or nil if the supplied value is invalid.
|
||||||
// provided in case the caller wants to construct raw requests for some reason.
|
func (request *Request) UnmarshalJSON(b []byte) error {
|
||||||
//
|
// Step 1: Create a type alias of the original struct.
|
||||||
// Typically callers will instead want to create a registered concrete command
|
type Alias Request
|
||||||
// type with the NewCmd or New<Foo>Cmd functions and call the MarshalCmd
|
|
||||||
// function with that command to generate the marshalled JSON-RPC request.
|
// Step 2: Create an anonymous struct with raw replacements for the special
|
||||||
func NewRequest(id interface{}, method string, params []interface{}) (*Request, error) {
|
// fields.
|
||||||
|
aux := &struct {
|
||||||
|
Jsonrpc string `json:"jsonrpc"`
|
||||||
|
Params []interface{} `json:"params"`
|
||||||
|
*Alias
|
||||||
|
}{
|
||||||
|
Alias: (*Alias)(request),
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Unmarshal the data into the anonymous struct.
|
||||||
|
err := json.Unmarshal(b, &aux)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 4: Convert the raw fields to the desired types
|
||||||
|
|
||||||
|
version := RPCVersion(aux.Jsonrpc)
|
||||||
|
if version.IsValid() {
|
||||||
|
request.Jsonrpc = version
|
||||||
|
}
|
||||||
|
|
||||||
|
rawParams := make([]json.RawMessage, 0)
|
||||||
|
|
||||||
|
for _, param := range aux.Params {
|
||||||
|
marshalledParam, err := json.Marshal(param)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
rawMessage := json.RawMessage(marshalledParam)
|
||||||
|
rawParams = append(rawParams, rawMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
request.Params = rawParams
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequest returns a new JSON-RPC request object given the provided rpc
|
||||||
|
// version, id, method, and parameters. The parameters are marshalled into a
|
||||||
|
// json.RawMessage for the Params field of the returned request object. This
|
||||||
|
// function is only provided in case the caller wants to construct raw requests
|
||||||
|
// for some reason. Typically callers will instead want to create a registered
|
||||||
|
// concrete command type with the NewCmd or New<Foo>Cmd functions and call the
|
||||||
|
// MarshalCmd function with that command to generate the marshalled JSON-RPC
|
||||||
|
// request.
|
||||||
|
func NewRequest(rpcVersion RPCVersion, id interface{}, method string, params []interface{}) (*Request, error) {
|
||||||
|
// default to JSON-RPC 1.0 if RPC type is not specified
|
||||||
|
if !rpcVersion.IsValid() {
|
||||||
|
str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion)
|
||||||
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
if !IsValidIDType(id) {
|
if !IsValidIDType(id) {
|
||||||
str := fmt.Sprintf("the id of type '%T' is invalid", id)
|
str := fmt.Sprintf("the id of type '%T' is invalid", id)
|
||||||
return nil, makeError(ErrInvalidType, str)
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
@ -98,30 +178,35 @@ func NewRequest(id interface{}, method string, params []interface{}) (*Request,
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Request{
|
return &Request{
|
||||||
Jsonrpc: "1.0",
|
Jsonrpc: rpcVersion,
|
||||||
ID: id,
|
ID: id,
|
||||||
Method: method,
|
Method: method,
|
||||||
Params: rawParams,
|
Params: rawParams,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Response is the general form of a JSON-RPC response. The type of the Result
|
// Response is the general form of a JSON-RPC response. The type of the
|
||||||
// field varies from one command to the next, so it is implemented as an
|
// Result field varies from one command to the next, so it is implemented as an
|
||||||
// interface. The ID field has to be a pointer for Go to put a null in it when
|
// interface. The ID field has to be a pointer to allow for a nil value when
|
||||||
// empty.
|
// empty.
|
||||||
type Response struct {
|
type Response struct {
|
||||||
|
Jsonrpc RPCVersion `json:"jsonrpc"`
|
||||||
Result json.RawMessage `json:"result"`
|
Result json.RawMessage `json:"result"`
|
||||||
Error *RPCError `json:"error"`
|
Error *RPCError `json:"error"`
|
||||||
ID *interface{} `json:"id"`
|
ID *interface{} `json:"id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewResponse returns a new JSON-RPC response object given the provided id,
|
// NewResponse returns a new JSON-RPC response object given the provided rpc
|
||||||
// marshalled result, and RPC error. This function is only provided in case the
|
// version, id, marshalled result, and RPC error. This function is only
|
||||||
// caller wants to construct raw responses for some reason.
|
// provided in case the caller wants to construct raw responses for some reason.
|
||||||
//
|
|
||||||
// Typically callers will instead want to create the fully marshalled JSON-RPC
|
// Typically callers will instead want to create the fully marshalled JSON-RPC
|
||||||
// response to send over the wire with the MarshalResponse function.
|
// response to send over the wire with the MarshalResponse function.
|
||||||
func NewResponse(id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) {
|
func NewResponse(rpcVersion RPCVersion, id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Response, error) {
|
||||||
|
if !rpcVersion.IsValid() {
|
||||||
|
str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion)
|
||||||
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
if !IsValidIDType(id) {
|
if !IsValidIDType(id) {
|
||||||
str := fmt.Sprintf("the id of type '%T' is invalid", id)
|
str := fmt.Sprintf("the id of type '%T' is invalid", id)
|
||||||
return nil, makeError(ErrInvalidType, str)
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
@ -129,20 +214,27 @@ func NewResponse(id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Re
|
||||||
|
|
||||||
pid := &id
|
pid := &id
|
||||||
return &Response{
|
return &Response{
|
||||||
|
Jsonrpc: rpcVersion,
|
||||||
Result: marshalledResult,
|
Result: marshalledResult,
|
||||||
Error: rpcErr,
|
Error: rpcErr,
|
||||||
ID: pid,
|
ID: pid,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalResponse marshals the passed id, result, and RPCError to a JSON-RPC
|
// MarshalResponse marshals the passed rpc version, id, result, and RPCError to
|
||||||
// response byte slice that is suitable for transmission to a JSON-RPC client.
|
// a JSON-RPC response byte slice that is suitable for transmission to a
|
||||||
func MarshalResponse(id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) {
|
// JSON-RPC client.
|
||||||
|
func MarshalResponse(rpcVersion RPCVersion, id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) {
|
||||||
|
if !rpcVersion.IsValid() {
|
||||||
|
str := fmt.Sprintf("rpcversion '%s' is invalid", rpcVersion)
|
||||||
|
return nil, makeError(ErrInvalidType, str)
|
||||||
|
}
|
||||||
|
|
||||||
marshalledResult, err := json.Marshal(result)
|
marshalledResult, err := json.Marshal(result)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
response, err := NewResponse(id, marshalledResult, rpcErr)
|
response, err := NewResponse(rpcVersion, id, marshalledResult, rpcErr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ func TestMarshalResponse(t *testing.T) {
|
||||||
name: "ordinary bool result with no error",
|
name: "ordinary bool result with no error",
|
||||||
result: true,
|
result: true,
|
||||||
jsonErr: nil,
|
jsonErr: nil,
|
||||||
expected: []byte(`{"result":true,"error":null,"id":1}`),
|
expected: []byte(`{"jsonrpc":"1.0","result":true,"error":null,"id":1}`),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "result with error",
|
name: "result with error",
|
||||||
|
@ -76,14 +76,14 @@ func TestMarshalResponse(t *testing.T) {
|
||||||
jsonErr: func() *btcjson.RPCError {
|
jsonErr: func() *btcjson.RPCError {
|
||||||
return btcjson.NewRPCError(btcjson.ErrRPCBlockNotFound, "123 not found")
|
return btcjson.NewRPCError(btcjson.ErrRPCBlockNotFound, "123 not found")
|
||||||
}(),
|
}(),
|
||||||
expected: []byte(`{"result":null,"error":{"code":-5,"message":"123 not found"},"id":1}`),
|
expected: []byte(`{"jsonrpc":"1.0","result":null,"error":{"code":-5,"message":"123 not found"},"id":1}`),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Logf("Running %d tests", len(tests))
|
t.Logf("Running %d tests", len(tests))
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
_, _ = i, test
|
_, _ = i, test
|
||||||
marshalled, err := btcjson.MarshalResponse(testID, test.result, test.jsonErr)
|
marshalled, err := btcjson.MarshalResponse(btcjson.RpcVersion1, testID, test.result, test.jsonErr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
@ -104,7 +104,7 @@ func TestMiscErrors(t *testing.T) {
|
||||||
|
|
||||||
// Force an error in NewRequest by giving it a parameter type that is
|
// Force an error in NewRequest by giving it a parameter type that is
|
||||||
// not supported.
|
// not supported.
|
||||||
_, err := btcjson.NewRequest(nil, "test", []interface{}{make(chan int)})
|
_, err := btcjson.NewRequest(btcjson.RpcVersion1, nil, "test", []interface{}{make(chan int)})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("NewRequest: did not receive error")
|
t.Error("NewRequest: did not receive error")
|
||||||
return
|
return
|
||||||
|
@ -113,7 +113,7 @@ func TestMiscErrors(t *testing.T) {
|
||||||
// Force an error in MarshalResponse by giving it an id type that is not
|
// Force an error in MarshalResponse by giving it an id type that is not
|
||||||
// supported.
|
// supported.
|
||||||
wantErr := btcjson.Error{ErrorCode: btcjson.ErrInvalidType}
|
wantErr := btcjson.Error{ErrorCode: btcjson.ErrInvalidType}
|
||||||
_, err = btcjson.MarshalResponse(make(chan int), nil, nil)
|
_, err = btcjson.MarshalResponse(btcjson.RpcVersion1, make(chan int), nil, nil)
|
||||||
if jerr, ok := err.(btcjson.Error); !ok || jerr.ErrorCode != wantErr.ErrorCode {
|
if jerr, ok := err.(btcjson.Error); !ok || jerr.ErrorCode != wantErr.ErrorCode {
|
||||||
t.Errorf("MarshalResult: did not receive expected error - got "+
|
t.Errorf("MarshalResult: did not receive expected error - got "+
|
||||||
"%v (%[1]T), want %v (%[2]T)", err, wantErr)
|
"%v (%[1]T), want %v (%[2]T)", err, wantErr)
|
||||||
|
@ -122,7 +122,7 @@ func TestMiscErrors(t *testing.T) {
|
||||||
|
|
||||||
// Force an error in MarshalResponse by giving it a result type that
|
// Force an error in MarshalResponse by giving it a result type that
|
||||||
// can't be marshalled.
|
// can't be marshalled.
|
||||||
_, err = btcjson.MarshalResponse(1, make(chan int), nil)
|
_, err = btcjson.MarshalResponse(btcjson.RpcVersion1, 1, make(chan int), nil)
|
||||||
if _, ok := err.(*json.UnsupportedTypeError); !ok {
|
if _, ok := err.(*json.UnsupportedTypeError); !ok {
|
||||||
wantErr := &json.UnsupportedTypeError{}
|
wantErr := &json.UnsupportedTypeError{}
|
||||||
t.Errorf("MarshalResult: did not receive expected error - got "+
|
t.Errorf("MarshalResult: did not receive expected error - got "+
|
||||||
|
|
|
@ -1800,7 +1800,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
// Marshal the command as created by the new static command
|
// Marshal the command as created by the new static command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
@ -1824,7 +1824,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
|
|
||||||
// Marshal the command as created by the generic new command
|
// Marshal the command as created by the generic new command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
|
|
@ -195,7 +195,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
// Marshal the command as created by the new static command
|
// Marshal the command as created by the new static command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
@ -219,7 +219,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
||||||
|
|
||||||
// Marshal the command as created by the generic new command
|
// Marshal the command as created by the generic new command
|
||||||
// creation function.
|
// creation function.
|
||||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
|
|
@ -122,7 +122,7 @@ func TestWalletSvrWsNtfns(t *testing.T) {
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
// Marshal the notification as created by the new static
|
// Marshal the notification as created by the new static
|
||||||
// creation function. The ID is nil for notifications.
|
// creation function. The ID is nil for notifications.
|
||||||
marshalled, err := btcjson.MarshalCmd(nil, test.staticNtfn())
|
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, test.staticNtfn())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
@ -147,7 +147,7 @@ func TestWalletSvrWsNtfns(t *testing.T) {
|
||||||
// Marshal the notification as created by the generic new
|
// Marshal the notification as created by the generic new
|
||||||
// notification creation function. The ID is nil for
|
// notification creation function. The ID is nil for
|
||||||
// notifications.
|
// notifications.
|
||||||
marshalled, err = btcjson.MarshalCmd(nil, cmd)
|
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, nil, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||||
test.name, err)
|
test.name, err)
|
||||||
|
|
|
@ -127,7 +127,7 @@ func main() {
|
||||||
|
|
||||||
// Marshal the command into a JSON-RPC byte slice in preparation for
|
// Marshal the command into a JSON-RPC byte slice in preparation for
|
||||||
// sending it to the RPC server.
|
// sending it to the RPC server.
|
||||||
marshalledJSON, err := btcjson.MarshalCmd(1, cmd)
|
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, 1, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
@ -33,7 +33,7 @@ const (
|
||||||
// ensures its version either has the provided bit set or unset per the set
|
// ensures its version either has the provided bit set or unset per the set
|
||||||
// flag.
|
// flag.
|
||||||
func assertVersionBit(r *rpctest.Harness, t *testing.T, hash *chainhash.Hash, bit uint8, set bool) {
|
func assertVersionBit(r *rpctest.Harness, t *testing.T, hash *chainhash.Hash, bit uint8, set bool) {
|
||||||
block, err := r.Node.GetBlock(hash)
|
block, err := r.Client.GetBlock(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to retrieve block %v: %v", hash, err)
|
t.Fatalf("failed to retrieve block %v: %v", hash, err)
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,7 @@ func assertVersionBit(r *rpctest.Harness, t *testing.T, hash *chainhash.Hash, bi
|
||||||
// assertChainHeight retrieves the current chain height from the given test
|
// assertChainHeight retrieves the current chain height from the given test
|
||||||
// harness and ensures it matches the provided expected height.
|
// harness and ensures it matches the provided expected height.
|
||||||
func assertChainHeight(r *rpctest.Harness, t *testing.T, expectedHeight uint32) {
|
func assertChainHeight(r *rpctest.Harness, t *testing.T, expectedHeight uint32) {
|
||||||
height, err := r.Node.GetBlockCount()
|
height, err := r.Client.GetBlockCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to retrieve block height: %v", err)
|
t.Fatalf("failed to retrieve block height: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -96,7 +96,7 @@ func assertSoftForkStatus(r *rpctest.Harness, t *testing.T, forkKey string, stat
|
||||||
"threshold state %v to string", line, state)
|
"threshold state %v to string", line, state)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := r.Node.GetBlockChainInfo()
|
info, err := r.Client.GetBlockChainInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to retrieve chain info: %v", err)
|
t.Fatalf("failed to retrieve chain info: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -339,7 +339,7 @@ func TestBIP0009Mining(t *testing.T) {
|
||||||
// in the defined threshold state.
|
// in the defined threshold state.
|
||||||
deployment := &r.ActiveNet.Deployments[chaincfg.DeploymentTestDummy]
|
deployment := &r.ActiveNet.Deployments[chaincfg.DeploymentTestDummy]
|
||||||
testDummyBitNum := deployment.BitNumber
|
testDummyBitNum := deployment.BitNumber
|
||||||
hashes, err := r.Node.Generate(1)
|
hashes, err := r.Client.Generate(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to generate blocks: %v", err)
|
t.Fatalf("unable to generate blocks: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -358,7 +358,7 @@ func TestBIP0009Mining(t *testing.T) {
|
||||||
// dummy deployment as started.
|
// dummy deployment as started.
|
||||||
confirmationWindow := r.ActiveNet.MinerConfirmationWindow
|
confirmationWindow := r.ActiveNet.MinerConfirmationWindow
|
||||||
numNeeded := confirmationWindow - 1
|
numNeeded := confirmationWindow - 1
|
||||||
hashes, err = r.Node.Generate(numNeeded)
|
hashes, err = r.Client.Generate(numNeeded)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to generated %d blocks: %v", numNeeded, err)
|
t.Fatalf("failed to generated %d blocks: %v", numNeeded, err)
|
||||||
}
|
}
|
||||||
|
@ -373,7 +373,7 @@ func TestBIP0009Mining(t *testing.T) {
|
||||||
// The last generated block should still have the test bit set in the
|
// The last generated block should still have the test bit set in the
|
||||||
// version since the btcd mining code will have recognized the test
|
// version since the btcd mining code will have recognized the test
|
||||||
// dummy deployment as locked in.
|
// dummy deployment as locked in.
|
||||||
hashes, err = r.Node.Generate(confirmationWindow)
|
hashes, err = r.Client.Generate(confirmationWindow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to generated %d blocks: %v", confirmationWindow,
|
t.Fatalf("failed to generated %d blocks: %v", confirmationWindow,
|
||||||
err)
|
err)
|
||||||
|
@ -392,7 +392,7 @@ func TestBIP0009Mining(t *testing.T) {
|
||||||
// version since the btcd mining code will have recognized the test
|
// version since the btcd mining code will have recognized the test
|
||||||
// dummy deployment as activated and thus there is no longer any need
|
// dummy deployment as activated and thus there is no longer any need
|
||||||
// to set the bit.
|
// to set the bit.
|
||||||
hashes, err = r.Node.Generate(confirmationWindow)
|
hashes, err = r.Client.Generate(confirmationWindow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to generated %d blocks: %v", confirmationWindow,
|
t.Fatalf("failed to generated %d blocks: %v", confirmationWindow,
|
||||||
err)
|
err)
|
||||||
|
|
|
@ -57,14 +57,14 @@ func makeTestOutput(r *rpctest.Harness, t *testing.T,
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
txHash, err := r.Node.SendRawTransaction(fundTx, true)
|
txHash, err := r.Client.SendRawTransaction(fundTx, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// The transaction created above should be included within the next
|
// The transaction created above should be included within the next
|
||||||
// generated block.
|
// generated block.
|
||||||
blockHash, err := r.Node.Generate(1)
|
blockHash, err := r.Client.Generate(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ func TestBIP0113Activation(t *testing.T) {
|
||||||
|
|
||||||
// We set the lock-time of the transaction to just one minute after the
|
// We set the lock-time of the transaction to just one minute after the
|
||||||
// current MTP of the chain.
|
// current MTP of the chain.
|
||||||
chainInfo, err := r.Node.GetBlockChainInfo()
|
chainInfo, err := r.Client.GetBlockChainInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to query for chain info: %v", err)
|
t.Fatalf("unable to query for chain info: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -167,7 +167,7 @@ func TestBIP0113Activation(t *testing.T) {
|
||||||
// This transaction should be rejected from the mempool as using MTP
|
// This transaction should be rejected from the mempool as using MTP
|
||||||
// for transactions finality is now a policy rule. Additionally, the
|
// for transactions finality is now a policy rule. Additionally, the
|
||||||
// exact error should be the rejection of a non-final transaction.
|
// exact error should be the rejection of a non-final transaction.
|
||||||
_, err = r.Node.SendRawTransaction(tx, true)
|
_, err = r.Client.SendRawTransaction(tx, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("transaction accepted, but should be non-final")
|
t.Fatalf("transaction accepted, but should be non-final")
|
||||||
} else if !strings.Contains(err.Error(), "not finalized") {
|
} else if !strings.Contains(err.Error(), "not finalized") {
|
||||||
|
@ -201,7 +201,7 @@ func TestBIP0113Activation(t *testing.T) {
|
||||||
// height 299. The getblockchaininfo call checks the state for the
|
// height 299. The getblockchaininfo call checks the state for the
|
||||||
// block AFTER the current height.
|
// block AFTER the current height.
|
||||||
numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 4
|
numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 4
|
||||||
if _, err := r.Node.Generate(numBlocks); err != nil {
|
if _, err := r.Client.Generate(numBlocks); err != nil {
|
||||||
t.Fatalf("unable to generate blocks: %v", err)
|
t.Fatalf("unable to generate blocks: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +220,7 @@ func TestBIP0113Activation(t *testing.T) {
|
||||||
// rejected.
|
// rejected.
|
||||||
timeLockDeltas := []int64{-1, 0, 1}
|
timeLockDeltas := []int64{-1, 0, 1}
|
||||||
for _, timeLockDelta := range timeLockDeltas {
|
for _, timeLockDelta := range timeLockDeltas {
|
||||||
chainInfo, err = r.Node.GetBlockChainInfo()
|
chainInfo, err = r.Client.GetBlockChainInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to query for chain info: %v", err)
|
t.Fatalf("unable to query for chain info: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -257,7 +257,7 @@ func TestBIP0113Activation(t *testing.T) {
|
||||||
// accepted as it has a lock-time of one
|
// accepted as it has a lock-time of one
|
||||||
// second _before_ the current MTP.
|
// second _before_ the current MTP.
|
||||||
|
|
||||||
_, err = r.Node.SendRawTransaction(tx, true)
|
_, err = r.Client.SendRawTransaction(tx, true)
|
||||||
if err == nil && timeLockDelta >= 0 {
|
if err == nil && timeLockDelta >= 0 {
|
||||||
t.Fatal("transaction was accepted into the mempool " +
|
t.Fatal("transaction was accepted into the mempool " +
|
||||||
"but should be rejected!")
|
"but should be rejected!")
|
||||||
|
@ -366,7 +366,7 @@ func spendCSVOutput(redeemScript []byte, csvUTXO *wire.OutPoint,
|
||||||
func assertTxInBlock(r *rpctest.Harness, t *testing.T, blockHash *chainhash.Hash,
|
func assertTxInBlock(r *rpctest.Harness, t *testing.T, blockHash *chainhash.Hash,
|
||||||
txid *chainhash.Hash) {
|
txid *chainhash.Hash) {
|
||||||
|
|
||||||
block, err := r.Node.GetBlock(blockHash)
|
block, err := r.Client.GetBlock(blockHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get block: %v", err)
|
t.Fatalf("unable to get block: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -449,10 +449,10 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
||||||
|
|
||||||
// As the transaction is p2sh it should be accepted into the
|
// As the transaction is p2sh it should be accepted into the
|
||||||
// mempool and found within the next generated block.
|
// mempool and found within the next generated block.
|
||||||
if _, err := r.Node.SendRawTransaction(tx, true); err != nil {
|
if _, err := r.Client.SendRawTransaction(tx, true); err != nil {
|
||||||
t.Fatalf("unable to broadcast tx: %v", err)
|
t.Fatalf("unable to broadcast tx: %v", err)
|
||||||
}
|
}
|
||||||
blocks, err := r.Node.Generate(1)
|
blocks, err := r.Client.Generate(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to generate blocks: %v", err)
|
t.Fatalf("unable to generate blocks: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -469,7 +469,7 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
||||||
|
|
||||||
// This transaction should be rejected from the mempool since
|
// This transaction should be rejected from the mempool since
|
||||||
// CSV validation is already mempool policy pre-fork.
|
// CSV validation is already mempool policy pre-fork.
|
||||||
_, err = r.Node.SendRawTransaction(spendingTx, true)
|
_, err = r.Client.SendRawTransaction(spendingTx, true)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("transaction should have been rejected, but was " +
|
t.Fatalf("transaction should have been rejected, but was " +
|
||||||
"instead accepted")
|
"instead accepted")
|
||||||
|
@ -496,7 +496,7 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
||||||
// height 299. The getblockchaininfo call checks the state for the
|
// height 299. The getblockchaininfo call checks the state for the
|
||||||
// block AFTER the current height.
|
// block AFTER the current height.
|
||||||
numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 8
|
numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 8
|
||||||
if _, err := r.Node.Generate(numBlocks); err != nil {
|
if _, err := r.Client.Generate(numBlocks); err != nil {
|
||||||
t.Fatalf("unable to generate blocks: %v", err)
|
t.Fatalf("unable to generate blocks: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -530,7 +530,7 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
||||||
t.Fatalf("unable to create CSV output: %v", err)
|
t.Fatalf("unable to create CSV output: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := r.Node.SendRawTransaction(tx, true); err != nil {
|
if _, err := r.Client.SendRawTransaction(tx, true); err != nil {
|
||||||
t.Fatalf("unable to broadcast transaction: %v", err)
|
t.Fatalf("unable to broadcast transaction: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -542,17 +542,17 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mine a single block including all the transactions generated above.
|
// Mine a single block including all the transactions generated above.
|
||||||
if _, err := r.Node.Generate(1); err != nil {
|
if _, err := r.Client.Generate(1); err != nil {
|
||||||
t.Fatalf("unable to generate block: %v", err)
|
t.Fatalf("unable to generate block: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now mine 10 additional blocks giving the inputs generated above a
|
// Now mine 10 additional blocks giving the inputs generated above a
|
||||||
// age of 11. Space out each block 10 minutes after the previous block.
|
// age of 11. Space out each block 10 minutes after the previous block.
|
||||||
prevBlockHash, err := r.Node.GetBestBlockHash()
|
prevBlockHash, err := r.Client.GetBestBlockHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get prior block hash: %v", err)
|
t.Fatalf("unable to get prior block hash: %v", err)
|
||||||
}
|
}
|
||||||
prevBlock, err := r.Node.GetBlock(prevBlockHash)
|
prevBlock, err := r.Client.GetBlock(prevBlockHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get block: %v", err)
|
t.Fatalf("unable to get block: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -652,7 +652,7 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
txid, err := r.Node.SendRawTransaction(test.tx, true)
|
txid, err := r.Client.SendRawTransaction(test.tx, true)
|
||||||
switch {
|
switch {
|
||||||
// Test case passes, nothing further to report.
|
// Test case passes, nothing further to report.
|
||||||
case test.accept && err == nil:
|
case test.accept && err == nil:
|
||||||
|
@ -686,7 +686,7 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
||||||
|
|
||||||
// Generate a block, the transaction should be included within
|
// Generate a block, the transaction should be included within
|
||||||
// the newly mined block.
|
// the newly mined block.
|
||||||
blockHashes, err := r.Node.Generate(1)
|
blockHashes, err := r.Client.Generate(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to mine block: %v", err)
|
t.Fatalf("unable to mine block: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,22 +15,24 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/integration/rpctest"
|
"github.com/btcsuite/btcd/integration/rpctest"
|
||||||
|
"github.com/btcsuite/btcd/rpcclient"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testGetBestBlock(r *rpctest.Harness, t *testing.T) {
|
func testGetBestBlock(r *rpctest.Harness, t *testing.T) {
|
||||||
_, prevbestHeight, err := r.Node.GetBestBlock()
|
_, prevbestHeight, err := r.Client.GetBestBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Call to `getbestblock` failed: %v", err)
|
t.Fatalf("Call to `getbestblock` failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new block connecting to the current tip.
|
// Create a new block connecting to the current tip.
|
||||||
generatedBlockHashes, err := r.Node.Generate(1)
|
generatedBlockHashes, err := r.Client.Generate(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to generate block: %v", err)
|
t.Fatalf("Unable to generate block: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
bestHash, bestHeight, err := r.Node.GetBestBlock()
|
bestHash, bestHeight, err := r.Client.GetBestBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Call to `getbestblock` failed: %v", err)
|
t.Fatalf("Call to `getbestblock` failed: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -50,17 +52,17 @@ func testGetBestBlock(r *rpctest.Harness, t *testing.T) {
|
||||||
|
|
||||||
func testGetBlockCount(r *rpctest.Harness, t *testing.T) {
|
func testGetBlockCount(r *rpctest.Harness, t *testing.T) {
|
||||||
// Save the current count.
|
// Save the current count.
|
||||||
currentCount, err := r.Node.GetBlockCount()
|
currentCount, err := r.Client.GetBlockCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to get block count: %v", err)
|
t.Fatalf("Unable to get block count: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := r.Node.Generate(1); err != nil {
|
if _, err := r.Client.Generate(1); err != nil {
|
||||||
t.Fatalf("Unable to generate block: %v", err)
|
t.Fatalf("Unable to generate block: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count should have increased by one.
|
// Count should have increased by one.
|
||||||
newCount, err := r.Node.GetBlockCount()
|
newCount, err := r.Client.GetBlockCount()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to get block count: %v", err)
|
t.Fatalf("Unable to get block count: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -72,17 +74,17 @@ func testGetBlockCount(r *rpctest.Harness, t *testing.T) {
|
||||||
|
|
||||||
func testGetBlockHash(r *rpctest.Harness, t *testing.T) {
|
func testGetBlockHash(r *rpctest.Harness, t *testing.T) {
|
||||||
// Create a new block connecting to the current tip.
|
// Create a new block connecting to the current tip.
|
||||||
generatedBlockHashes, err := r.Node.Generate(1)
|
generatedBlockHashes, err := r.Client.Generate(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Unable to generate block: %v", err)
|
t.Fatalf("Unable to generate block: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := r.Node.GetInfo()
|
info, err := r.Client.GetInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("call to getinfo cailed: %v", err)
|
t.Fatalf("call to getinfo cailed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
blockHash, err := r.Node.GetBlockHash(int64(info.Blocks))
|
blockHash, err := r.Client.GetBlockHash(int64(info.Blocks))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Call to `getblockhash` failed: %v", err)
|
t.Fatalf("Call to `getblockhash` failed: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -94,10 +96,50 @@ func testGetBlockHash(r *rpctest.Harness, t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testBulkClient(r *rpctest.Harness, t *testing.T) {
|
||||||
|
// Create a new block connecting to the current tip.
|
||||||
|
generatedBlockHashes, err := r.Client.Generate(20)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to generate block: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var futureBlockResults []rpcclient.FutureGetBlockResult
|
||||||
|
for _, hash := range generatedBlockHashes {
|
||||||
|
futureBlockResults = append(futureBlockResults, r.BatchClient.GetBlockAsync(hash))
|
||||||
|
}
|
||||||
|
|
||||||
|
err = r.BatchClient.Send()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
isKnownBlockHash := func(blockHash chainhash.Hash) bool {
|
||||||
|
for _, hash := range generatedBlockHashes {
|
||||||
|
if blockHash.IsEqual(hash) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, block := range futureBlockResults {
|
||||||
|
msgBlock, err := block.Receive()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
blockHash := msgBlock.Header.BlockHash()
|
||||||
|
if !isKnownBlockHash(blockHash) {
|
||||||
|
t.Fatalf("expected hash %s to be in generated hash list", blockHash)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
var rpcTestCases = []rpctest.HarnessTestCase{
|
var rpcTestCases = []rpctest.HarnessTestCase{
|
||||||
testGetBestBlock,
|
testGetBestBlock,
|
||||||
testGetBlockCount,
|
testGetBlockCount,
|
||||||
testGetBlockHash,
|
testGetBlockHash,
|
||||||
|
testBulkClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
var primaryHarness *rpctest.Harness
|
var primaryHarness *rpctest.Harness
|
||||||
|
|
|
@ -92,7 +92,8 @@ type Harness struct {
|
||||||
// attempts.
|
// attempts.
|
||||||
ConnectionRetryTimeout time.Duration
|
ConnectionRetryTimeout time.Duration
|
||||||
|
|
||||||
Node *rpcclient.Client
|
Client *rpcclient.Client
|
||||||
|
BatchClient *rpcclient.Client
|
||||||
node *node
|
node *node
|
||||||
handlers *rpcclient.NotificationHandlers
|
handlers *rpcclient.NotificationHandlers
|
||||||
|
|
||||||
|
@ -245,13 +246,13 @@ func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error {
|
||||||
// Filter transactions that pay to the coinbase associated with the
|
// Filter transactions that pay to the coinbase associated with the
|
||||||
// wallet.
|
// wallet.
|
||||||
filterAddrs := []btcutil.Address{h.wallet.coinbaseAddr}
|
filterAddrs := []btcutil.Address{h.wallet.coinbaseAddr}
|
||||||
if err := h.Node.LoadTxFilter(true, filterAddrs, nil); err != nil {
|
if err := h.Client.LoadTxFilter(true, filterAddrs, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure btcd properly dispatches our registered call-back for each new
|
// Ensure btcd properly dispatches our registered call-back for each new
|
||||||
// block. Otherwise, the memWallet won't function properly.
|
// block. Otherwise, the memWallet won't function properly.
|
||||||
if err := h.Node.NotifyBlocks(); err != nil {
|
if err := h.Client.NotifyBlocks(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -260,7 +261,7 @@ func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error {
|
||||||
if createTestChain && numMatureOutputs != 0 {
|
if createTestChain && numMatureOutputs != 0 {
|
||||||
numToGenerate := (uint32(h.ActiveNet.CoinbaseMaturity) +
|
numToGenerate := (uint32(h.ActiveNet.CoinbaseMaturity) +
|
||||||
numMatureOutputs)
|
numMatureOutputs)
|
||||||
_, err := h.Node.Generate(numToGenerate)
|
_, err := h.Client.Generate(numToGenerate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -268,7 +269,7 @@ func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error {
|
||||||
|
|
||||||
// Block until the wallet has fully synced up to the tip of the main
|
// Block until the wallet has fully synced up to the tip of the main
|
||||||
// chain.
|
// chain.
|
||||||
_, height, err := h.Node.GetBestBlock()
|
_, height, err := h.Client.GetBestBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -289,8 +290,12 @@ func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error {
|
||||||
//
|
//
|
||||||
// This function MUST be called with the harness state mutex held (for writes).
|
// This function MUST be called with the harness state mutex held (for writes).
|
||||||
func (h *Harness) tearDown() error {
|
func (h *Harness) tearDown() error {
|
||||||
if h.Node != nil {
|
if h.Client != nil {
|
||||||
h.Node.Shutdown()
|
h.Client.Shutdown()
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.BatchClient != nil {
|
||||||
|
h.BatchClient.Shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := h.node.shutdown(); err != nil {
|
if err := h.node.shutdown(); err != nil {
|
||||||
|
@ -325,24 +330,38 @@ func (h *Harness) TearDown() error {
|
||||||
// we're not able to establish a connection, this function returns with an
|
// we're not able to establish a connection, this function returns with an
|
||||||
// error.
|
// error.
|
||||||
func (h *Harness) connectRPCClient() error {
|
func (h *Harness) connectRPCClient() error {
|
||||||
var client *rpcclient.Client
|
var client, batchClient *rpcclient.Client
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
rpcConf := h.node.config.rpcConnConfig()
|
rpcConf := h.node.config.rpcConnConfig()
|
||||||
|
batchConf := h.node.config.rpcConnConfig()
|
||||||
|
batchConf.HTTPPostMode = true
|
||||||
for i := 0; i < h.MaxConnRetries; i++ {
|
for i := 0; i < h.MaxConnRetries; i++ {
|
||||||
|
fail := false
|
||||||
|
if client == nil {
|
||||||
if client, err = rpcclient.New(&rpcConf, h.handlers); err != nil {
|
if client, err = rpcclient.New(&rpcConf, h.handlers); err != nil {
|
||||||
time.Sleep(time.Duration(i) * h.ConnectionRetryTimeout)
|
time.Sleep(time.Duration(i) * h.ConnectionRetryTimeout)
|
||||||
continue
|
fail = true
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
if batchClient == nil {
|
||||||
|
if batchClient, err = rpcclient.NewBatch(&batchConf); err != nil {
|
||||||
|
time.Sleep(time.Duration(i) * h.ConnectionRetryTimeout)
|
||||||
|
fail = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !fail {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if client == nil {
|
if client == nil || batchClient == nil {
|
||||||
return fmt.Errorf("connection timeout")
|
return fmt.Errorf("connection timeout")
|
||||||
}
|
}
|
||||||
|
|
||||||
h.Node = client
|
h.Client = client
|
||||||
h.wallet.SetRPCClient(client)
|
h.wallet.SetRPCClient(client)
|
||||||
|
h.BatchClient = batchClient
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,11 +483,11 @@ func (h *Harness) GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
|
||||||
blockVersion = BlockVersion
|
blockVersion = BlockVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
prevBlockHash, prevBlockHeight, err := h.Node.GetBestBlock()
|
prevBlockHash, prevBlockHeight, err := h.Client.GetBestBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
mBlock, err := h.Node.GetBlock(prevBlockHash)
|
mBlock, err := h.Client.GetBlock(prevBlockHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -483,7 +502,7 @@ func (h *Harness) GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Submit the block to the simnet node.
|
// Submit the block to the simnet node.
|
||||||
if err := h.Node.SubmitBlock(newBlock, nil); err != nil {
|
if err := h.Client.SubmitBlock(newBlock, nil); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ func testSendOutputs(r *Harness, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
assertTxMined := func(txid *chainhash.Hash, blockHash *chainhash.Hash) {
|
assertTxMined := func(txid *chainhash.Hash, blockHash *chainhash.Hash) {
|
||||||
block, err := r.Node.GetBlock(blockHash)
|
block, err := r.Client.GetBlock(blockHash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get block: %v", err)
|
t.Fatalf("unable to get block: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -67,7 +67,7 @@ func testSendOutputs(r *Harness, t *testing.T) {
|
||||||
|
|
||||||
// Generate a single block, the transaction the wallet created should
|
// Generate a single block, the transaction the wallet created should
|
||||||
// be found in this block.
|
// be found in this block.
|
||||||
blockHashes, err := r.Node.Generate(1)
|
blockHashes, err := r.Client.Generate(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to generate single block: %v", err)
|
t.Fatalf("unable to generate single block: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ func testSendOutputs(r *Harness, t *testing.T) {
|
||||||
// Next, generate a spend much greater than the block reward. This
|
// Next, generate a spend much greater than the block reward. This
|
||||||
// transaction should also have been mined properly.
|
// transaction should also have been mined properly.
|
||||||
txid = genSpend(btcutil.Amount(500 * btcutil.SatoshiPerBitcoin))
|
txid = genSpend(btcutil.Amount(500 * btcutil.SatoshiPerBitcoin))
|
||||||
blockHashes, err = r.Node.Generate(1)
|
blockHashes, err = r.Client.Generate(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to generate single block: %v", err)
|
t.Fatalf("unable to generate single block: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ func testSendOutputs(r *Harness, t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertConnectedTo(t *testing.T, nodeA *Harness, nodeB *Harness) {
|
func assertConnectedTo(t *testing.T, nodeA *Harness, nodeB *Harness) {
|
||||||
nodeAPeers, err := nodeA.Node.GetPeerInfo()
|
nodeAPeers, err := nodeA.Client.GetPeerInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get nodeA's peer info")
|
t.Fatalf("unable to get nodeA's peer info")
|
||||||
}
|
}
|
||||||
|
@ -170,7 +170,7 @@ func testActiveHarnesses(r *Harness, t *testing.T) {
|
||||||
|
|
||||||
func testJoinMempools(r *Harness, t *testing.T) {
|
func testJoinMempools(r *Harness, t *testing.T) {
|
||||||
// Assert main test harness has no transactions in its mempool.
|
// Assert main test harness has no transactions in its mempool.
|
||||||
pooledHashes, err := r.Node.GetRawMempool()
|
pooledHashes, err := r.Client.GetRawMempool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to get mempool for main test harness: %v", err)
|
t.Fatalf("unable to get mempool for main test harness: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -210,7 +210,7 @@ func testJoinMempools(r *Harness, t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("coinbase spend failed: %v", err)
|
t.Fatalf("coinbase spend failed: %v", err)
|
||||||
}
|
}
|
||||||
if _, err := r.Node.SendRawTransaction(testTx, true); err != nil {
|
if _, err := r.Client.SendRawTransaction(testTx, true); err != nil {
|
||||||
t.Fatalf("send transaction failed: %v", err)
|
t.Fatalf("send transaction failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,7 +219,7 @@ func testJoinMempools(r *Harness, t *testing.T) {
|
||||||
harnessSynced := make(chan struct{})
|
harnessSynced := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
for {
|
for {
|
||||||
poolHashes, err := r.Node.GetRawMempool()
|
poolHashes, err := r.Client.GetRawMempool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("failed to retrieve harness mempool: %v", err)
|
t.Fatalf("failed to retrieve harness mempool: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -262,7 +262,7 @@ func testJoinMempools(r *Harness, t *testing.T) {
|
||||||
|
|
||||||
// Send the transaction to the local harness which will result in synced
|
// Send the transaction to the local harness which will result in synced
|
||||||
// mempools.
|
// mempools.
|
||||||
if _, err := harness.Node.SendRawTransaction(testTx, true); err != nil {
|
if _, err := harness.Client.SendRawTransaction(testTx, true); err != nil {
|
||||||
t.Fatalf("send transaction failed: %v", err)
|
t.Fatalf("send transaction failed: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -612,7 +612,7 @@ func TestHarness(t *testing.T) {
|
||||||
|
|
||||||
// Current tip should be at a height of numMatureOutputs plus the
|
// Current tip should be at a height of numMatureOutputs plus the
|
||||||
// required number of blocks for coinbase maturity.
|
// required number of blocks for coinbase maturity.
|
||||||
nodeInfo, err := mainHarness.Node.GetInfo()
|
nodeInfo, err := mainHarness.Client.GetInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unable to execute getinfo on node: %v", err)
|
t.Fatalf("unable to execute getinfo on node: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ func syncMempools(nodes []*Harness) error {
|
||||||
|
|
||||||
retry:
|
retry:
|
||||||
for !poolsMatch {
|
for !poolsMatch {
|
||||||
firstPool, err := nodes[0].Node.GetRawMempool()
|
firstPool, err := nodes[0].Client.GetRawMempool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,7 @@ retry:
|
||||||
// first node, then we're done. Otherwise, drop back to the top
|
// first node, then we're done. Otherwise, drop back to the top
|
||||||
// of the loop and retry after a short wait period.
|
// of the loop and retry after a short wait period.
|
||||||
for _, node := range nodes[1:] {
|
for _, node := range nodes[1:] {
|
||||||
nodePool, err := node.Node.GetRawMempool()
|
nodePool, err := node.Client.GetRawMempool()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -84,7 +84,7 @@ retry:
|
||||||
var prevHash *chainhash.Hash
|
var prevHash *chainhash.Hash
|
||||||
var prevHeight int32
|
var prevHeight int32
|
||||||
for _, node := range nodes {
|
for _, node := range nodes {
|
||||||
blockHash, blockHeight, err := node.Node.GetBestBlock()
|
blockHash, blockHeight, err := node.Client.GetBestBlock()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -108,24 +108,24 @@ retry:
|
||||||
// therefore in the case of disconnects, "from" will attempt to reestablish a
|
// therefore in the case of disconnects, "from" will attempt to reestablish a
|
||||||
// connection to the "to" harness.
|
// connection to the "to" harness.
|
||||||
func ConnectNode(from *Harness, to *Harness) error {
|
func ConnectNode(from *Harness, to *Harness) error {
|
||||||
peerInfo, err := from.Node.GetPeerInfo()
|
peerInfo, err := from.Client.GetPeerInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
numPeers := len(peerInfo)
|
numPeers := len(peerInfo)
|
||||||
|
|
||||||
targetAddr := to.node.config.listen
|
targetAddr := to.node.config.listen
|
||||||
if err := from.Node.AddNode(targetAddr, rpcclient.ANAdd); err != nil {
|
if err := from.Client.AddNode(targetAddr, rpcclient.ANAdd); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block until a new connection has been established.
|
// Block until a new connection has been established.
|
||||||
peerInfo, err = from.Node.GetPeerInfo()
|
peerInfo, err = from.Client.GetPeerInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for len(peerInfo) <= numPeers {
|
for len(peerInfo) <= numPeers {
|
||||||
peerInfo, err = from.Node.GetPeerInfo()
|
peerInfo, err = from.Client.GetPeerInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
31
rpcclient/examples/bitcoincorehttpbulk/README.md
Normal file
31
rpcclient/examples/bitcoincorehttpbulk/README.md
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
Bitcoin Core Batch HTTP POST Example
|
||||||
|
==============================
|
||||||
|
|
||||||
|
This example shows how to use the rpclient package to connect to a Bitcoin Core RPC server using HTTP POST and batch JSON-RPC mode with TLS disabled.
|
||||||
|
|
||||||
|
## Running the Example
|
||||||
|
|
||||||
|
The first step is to use `go get` to download and install the rpcclient package:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get github.com/btcsuite/btcd/rpcclient
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, modify the `main.go` source to specify the correct RPC username and
|
||||||
|
password for the RPC server:
|
||||||
|
|
||||||
|
```Go
|
||||||
|
User: "yourrpcuser",
|
||||||
|
Pass: "yourrpcpass",
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, navigate to the example's directory and run it with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ cd $GOPATH/src/github.com/btcsuite/btcd/rpcclient/examples/bitcoincorehttp
|
||||||
|
$ go run *.go
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This example is licensed under the [copyfree](http://copyfree.org) ISC License.
|
46
rpcclient/examples/bitcoincorehttpbulk/main.go
Normal file
46
rpcclient/examples/bitcoincorehttpbulk/main.go
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// Copyright (c) 2014-2020 The btcsuite developers
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/rpcclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Connect to local bitcoin core RPC server using HTTP POST mode.
|
||||||
|
connCfg := &rpcclient.ConnConfig{
|
||||||
|
Host: "localhost:8332",
|
||||||
|
User: "yourrpcuser",
|
||||||
|
Pass: "yourrpcpass",
|
||||||
|
DisableConnectOnNew: true,
|
||||||
|
HTTPPostMode: true, // Bitcoin core only supports HTTP POST mode
|
||||||
|
DisableTLS: true, // Bitcoin core does not provide TLS by default
|
||||||
|
}
|
||||||
|
batchClient, err := rpcclient.NewBatch(connCfg)
|
||||||
|
defer batchClient.Shutdown()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// batch mode requires async requests
|
||||||
|
blockCount := batchClient.GetBlockCountAsync()
|
||||||
|
block1 := batchClient.GetBlockHashAsync(1)
|
||||||
|
batchClient.GetBlockHashAsync(2)
|
||||||
|
batchClient.GetBlockHashAsync(3)
|
||||||
|
block4 := batchClient.GetBlockHashAsync(4)
|
||||||
|
difficulty := batchClient.GetDifficultyAsync()
|
||||||
|
|
||||||
|
// sends all queued batch requests
|
||||||
|
batchClient.Send()
|
||||||
|
|
||||||
|
fmt.Println(blockCount.Receive())
|
||||||
|
fmt.Println(block1.Receive())
|
||||||
|
fmt.Println(block4.Receive())
|
||||||
|
fmt.Println(difficulty.Receive())
|
||||||
|
}
|
|
@ -163,6 +163,10 @@ type Client struct {
|
||||||
// disconnected indicated whether or not the server is disconnected.
|
// disconnected indicated whether or not the server is disconnected.
|
||||||
disconnected bool
|
disconnected bool
|
||||||
|
|
||||||
|
// whether or not to batch requests, false unless changed by Batch()
|
||||||
|
batch bool
|
||||||
|
batchList *list.List
|
||||||
|
|
||||||
// retryCount holds the number of times the client has tried to
|
// retryCount holds the number of times the client has tried to
|
||||||
// reconnect to the RPC server.
|
// reconnect to the RPC server.
|
||||||
retryCount int64
|
retryCount int64
|
||||||
|
@ -220,8 +224,13 @@ func (c *Client) addRequest(jReq *jsonRequest) error {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !c.batch {
|
||||||
element := c.requestList.PushBack(jReq)
|
element := c.requestList.PushBack(jReq)
|
||||||
c.requestMap[jReq.id] = element
|
c.requestMap[jReq.id] = element
|
||||||
|
} else {
|
||||||
|
element := c.batchList.PushBack(jReq)
|
||||||
|
c.requestMap[jReq.id] = element
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,6 +298,41 @@ func (c *Client) trackRegisteredNtfns(cmd interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FutureGetBulkResult waits for the responses promised by the future
|
||||||
|
// and returns them in a channel
|
||||||
|
type FutureGetBulkResult chan *response
|
||||||
|
|
||||||
|
// Receive waits for the response promised by the future and returns an map
|
||||||
|
// of results by request id
|
||||||
|
func (r FutureGetBulkResult) Receive() (BulkResult, error) {
|
||||||
|
m := make(BulkResult)
|
||||||
|
res, err := receiveFuture(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var arr []IndividualBulkResult
|
||||||
|
err = json.Unmarshal(res, &arr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, results := range arr {
|
||||||
|
m[results.Id] = results
|
||||||
|
}
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IndividualBulkResult represents one result
|
||||||
|
// from a bulk json rpc api
|
||||||
|
type IndividualBulkResult struct {
|
||||||
|
Result interface{} `json:"result"`
|
||||||
|
Error *btcjson.RPCError `json:"error"`
|
||||||
|
Id uint64 `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type BulkResult = map[uint64]IndividualBulkResult
|
||||||
|
|
||||||
// inMessage is the first type that an incoming message is unmarshaled
|
// inMessage is the first type that an incoming message is unmarshaled
|
||||||
// into. It supports both requests (for notification support) and
|
// into. It supports both requests (for notification support) and
|
||||||
// responses. The partially-unmarshaled message is a notification if
|
// responses. The partially-unmarshaled message is a notification if
|
||||||
|
@ -741,7 +785,12 @@ func (c *Client) handleSendPostMessage(details *sendPostDetails) {
|
||||||
|
|
||||||
// Try to unmarshal the response as a regular JSON-RPC response.
|
// Try to unmarshal the response as a regular JSON-RPC response.
|
||||||
var resp rawResponse
|
var resp rawResponse
|
||||||
|
var batchResponse json.RawMessage
|
||||||
|
if c.batch {
|
||||||
|
err = json.Unmarshal(respBytes, &batchResponse)
|
||||||
|
} else {
|
||||||
err = json.Unmarshal(respBytes, &resp)
|
err = json.Unmarshal(respBytes, &resp)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// When the response itself isn't a valid JSON-RPC response
|
// When the response itself isn't a valid JSON-RPC response
|
||||||
// return an error which includes the HTTP status code and raw
|
// return an error which includes the HTTP status code and raw
|
||||||
|
@ -751,8 +800,14 @@ func (c *Client) handleSendPostMessage(details *sendPostDetails) {
|
||||||
jReq.responseChan <- &response{err: err}
|
jReq.responseChan <- &response{err: err}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
var res []byte
|
||||||
res, err := resp.result()
|
if c.batch {
|
||||||
|
// errors must be dealt with downstream since a whole request cannot
|
||||||
|
// "error out" other than through the status code error handled above
|
||||||
|
res, err = batchResponse, nil
|
||||||
|
} else {
|
||||||
|
res, err = resp.result()
|
||||||
|
}
|
||||||
jReq.responseChan <- &response{result: res, err: err}
|
jReq.responseChan <- &response{result: res, err: err}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -875,7 +930,13 @@ func (c *Client) sendRequest(jReq *jsonRequest) {
|
||||||
// POST mode, the command is issued via an HTTP client. Otherwise,
|
// POST mode, the command is issued via an HTTP client. Otherwise,
|
||||||
// the command is issued via the asynchronous websocket channels.
|
// the command is issued via the asynchronous websocket channels.
|
||||||
if c.config.HTTPPostMode {
|
if c.config.HTTPPostMode {
|
||||||
|
if c.batch {
|
||||||
|
if err := c.addRequest(jReq); err != nil {
|
||||||
|
log.Warn(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
c.sendPost(jReq)
|
c.sendPost(jReq)
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -905,6 +966,10 @@ func (c *Client) sendRequest(jReq *jsonRequest) {
|
||||||
// future. It handles both websocket and HTTP POST mode depending on the
|
// future. It handles both websocket and HTTP POST mode depending on the
|
||||||
// configuration of the client.
|
// configuration of the client.
|
||||||
func (c *Client) sendCmd(cmd interface{}) chan *response {
|
func (c *Client) sendCmd(cmd interface{}) chan *response {
|
||||||
|
rpcVersion := btcjson.RpcVersion1
|
||||||
|
if c.batch {
|
||||||
|
rpcVersion = btcjson.RpcVersion2
|
||||||
|
}
|
||||||
// Get the method associated with the command.
|
// Get the method associated with the command.
|
||||||
method, err := btcjson.CmdMethod(cmd)
|
method, err := btcjson.CmdMethod(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -913,7 +978,7 @@ func (c *Client) sendCmd(cmd interface{}) chan *response {
|
||||||
|
|
||||||
// Marshal the command.
|
// Marshal the command.
|
||||||
id := c.NextID()
|
id := c.NextID()
|
||||||
marshalledJSON, err := btcjson.MarshalCmd(id, cmd)
|
marshalledJSON, err := btcjson.MarshalCmd(rpcVersion, id, cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return newFutureError(err)
|
return newFutureError(err)
|
||||||
}
|
}
|
||||||
|
@ -927,6 +992,7 @@ func (c *Client) sendCmd(cmd interface{}) chan *response {
|
||||||
marshalledJSON: marshalledJSON,
|
marshalledJSON: marshalledJSON,
|
||||||
responseChan: responseChan,
|
responseChan: responseChan,
|
||||||
}
|
}
|
||||||
|
|
||||||
c.sendRequest(jReq)
|
c.sendRequest(jReq)
|
||||||
|
|
||||||
return responseChan
|
return responseChan
|
||||||
|
@ -1357,6 +1423,8 @@ func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error
|
||||||
httpClient: httpClient,
|
httpClient: httpClient,
|
||||||
requestMap: make(map[uint64]*list.Element),
|
requestMap: make(map[uint64]*list.Element),
|
||||||
requestList: list.New(),
|
requestList: list.New(),
|
||||||
|
batch: false,
|
||||||
|
batchList: list.New(),
|
||||||
ntfnHandlers: ntfnHandlers,
|
ntfnHandlers: ntfnHandlers,
|
||||||
ntfnState: newNotificationState(),
|
ntfnState: newNotificationState(),
|
||||||
sendChan: make(chan []byte, sendBufferSize),
|
sendChan: make(chan []byte, sendBufferSize),
|
||||||
|
@ -1397,6 +1465,24 @@ func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error
|
||||||
return client, nil
|
return client, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Batch is a factory that creates a client able to interact with the server using
|
||||||
|
// JSON-RPC 2.0. The client is capable of accepting an arbitrary number of requests
|
||||||
|
// and having the server process the all at the same time. It's compatible with both
|
||||||
|
// btcd and bitcoind
|
||||||
|
func NewBatch(config *ConnConfig) (*Client, error) {
|
||||||
|
if !config.HTTPPostMode {
|
||||||
|
return nil, errors.New("http post mode is required to use batch client")
|
||||||
|
}
|
||||||
|
// notification parameter is nil since notifications are not supported in POST mode.
|
||||||
|
client, err := New(config, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client.batch = true //copy the client with changed batch setting
|
||||||
|
client.start()
|
||||||
|
return client, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Connect establishes the initial websocket connection. This is necessary when
|
// Connect establishes the initial websocket connection. This is necessary when
|
||||||
// a client was created after setting the DisableConnectOnNew field of the
|
// a client was created after setting the DisableConnectOnNew field of the
|
||||||
// Config struct.
|
// Config struct.
|
||||||
|
@ -1534,3 +1620,69 @@ func (c *Client) BackendVersion() (BackendVersion, error) {
|
||||||
|
|
||||||
return *c.backendVersion, nil
|
return *c.backendVersion, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) sendAsync() FutureGetBulkResult {
|
||||||
|
// convert the array of marshalled json requests to a single request we can send
|
||||||
|
responseChan := make(chan *response, 1)
|
||||||
|
marshalledRequest := []byte("[")
|
||||||
|
for iter := c.batchList.Front(); iter != nil; iter = iter.Next() {
|
||||||
|
request := iter.Value.(*jsonRequest)
|
||||||
|
marshalledRequest = append(marshalledRequest, request.marshalledJSON...)
|
||||||
|
marshalledRequest = append(marshalledRequest, []byte(",")...)
|
||||||
|
}
|
||||||
|
if len(marshalledRequest) > 0 {
|
||||||
|
// removes the trailing comma to process the request individually
|
||||||
|
marshalledRequest = marshalledRequest[:len(marshalledRequest)-1]
|
||||||
|
}
|
||||||
|
marshalledRequest = append(marshalledRequest, []byte("]")...)
|
||||||
|
request := jsonRequest{
|
||||||
|
id: c.NextID(),
|
||||||
|
method: "",
|
||||||
|
cmd: nil,
|
||||||
|
marshalledJSON: marshalledRequest,
|
||||||
|
responseChan: responseChan,
|
||||||
|
}
|
||||||
|
c.sendPost(&request)
|
||||||
|
return responseChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshall's bulk requests and sends to the server
|
||||||
|
// creates a response channel to receive the response
|
||||||
|
func (c *Client) Send() error {
|
||||||
|
// if batchlist is empty, there's nothing to send
|
||||||
|
if c.batchList.Len() == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// clear batchlist in case of an error
|
||||||
|
defer func() {
|
||||||
|
c.batchList = list.New()
|
||||||
|
}()
|
||||||
|
|
||||||
|
result, err := c.sendAsync().Receive()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for iter := c.batchList.Front(); iter != nil; iter = iter.Next() {
|
||||||
|
var requestError error
|
||||||
|
request := iter.Value.(*jsonRequest)
|
||||||
|
individualResult := result[request.id]
|
||||||
|
fullResult, err := json.Marshal(individualResult.Result)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if individualResult.Error != nil {
|
||||||
|
requestError = individualResult.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
result := response{
|
||||||
|
result: fullResult,
|
||||||
|
err: requestError,
|
||||||
|
}
|
||||||
|
request.responseChan <- &result
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -44,7 +44,7 @@ func (c *Client) RawRequestAsync(method string, params []json.RawMessage) Future
|
||||||
// than custom commands.
|
// than custom commands.
|
||||||
id := c.NextID()
|
id := c.NextID()
|
||||||
rawRequest := &btcjson.Request{
|
rawRequest := &btcjson.Request{
|
||||||
Jsonrpc: "1.0",
|
Jsonrpc: btcjson.RpcVersion1,
|
||||||
ID: id,
|
ID: id,
|
||||||
Method: method,
|
Method: method,
|
||||||
Params: params,
|
Params: params,
|
||||||
|
|
262
rpcserver.go
262
rpcserver.go
|
@ -101,6 +101,9 @@ var (
|
||||||
// declared here to avoid the overhead of creating the slice on every
|
// declared here to avoid the overhead of creating the slice on every
|
||||||
// invocation for constant data.
|
// invocation for constant data.
|
||||||
gbtCapabilities = []string{"proposal"}
|
gbtCapabilities = []string{"proposal"}
|
||||||
|
|
||||||
|
// JSON 2.0 batched request prefix
|
||||||
|
batchedRequestPrefix = []byte("[")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Errors
|
// Errors
|
||||||
|
@ -3939,6 +3942,7 @@ func (s *rpcServer) checkAuth(r *http.Request, require bool) (bool, bool, error)
|
||||||
// a known concrete command along with any error that might have happened while
|
// a known concrete command along with any error that might have happened while
|
||||||
// parsing it.
|
// parsing it.
|
||||||
type parsedRPCCmd struct {
|
type parsedRPCCmd struct {
|
||||||
|
jsonrpc btcjson.RPCVersion
|
||||||
id interface{}
|
id interface{}
|
||||||
method string
|
method string
|
||||||
cmd interface{}
|
cmd interface{}
|
||||||
|
@ -3975,9 +3979,11 @@ handled:
|
||||||
// is suitable for use in replies if the command is invalid in some way such as
|
// is suitable for use in replies if the command is invalid in some way such as
|
||||||
// an unregistered command or invalid parameters.
|
// an unregistered command or invalid parameters.
|
||||||
func parseCmd(request *btcjson.Request) *parsedRPCCmd {
|
func parseCmd(request *btcjson.Request) *parsedRPCCmd {
|
||||||
var parsedCmd parsedRPCCmd
|
parsedCmd := parsedRPCCmd{
|
||||||
parsedCmd.id = request.ID
|
jsonrpc: request.Jsonrpc,
|
||||||
parsedCmd.method = request.Method
|
id: request.ID,
|
||||||
|
method: request.Method,
|
||||||
|
}
|
||||||
|
|
||||||
cmd, err := btcjson.UnmarshalCmd(request)
|
cmd, err := btcjson.UnmarshalCmd(request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -4004,7 +4010,7 @@ func parseCmd(request *btcjson.Request) *parsedRPCCmd {
|
||||||
// createMarshalledReply returns a new marshalled JSON-RPC response given the
|
// createMarshalledReply returns a new marshalled JSON-RPC response given the
|
||||||
// passed parameters. It will automatically convert errors that are not of
|
// passed parameters. It will automatically convert errors that are not of
|
||||||
// the type *btcjson.RPCError to the appropriate type as needed.
|
// the type *btcjson.RPCError to the appropriate type as needed.
|
||||||
func createMarshalledReply(id, result interface{}, replyErr error) ([]byte, error) {
|
func createMarshalledReply(rpcVersion btcjson.RPCVersion, id interface{}, result interface{}, replyErr error) ([]byte, error) {
|
||||||
var jsonErr *btcjson.RPCError
|
var jsonErr *btcjson.RPCError
|
||||||
if replyErr != nil {
|
if replyErr != nil {
|
||||||
if jErr, ok := replyErr.(*btcjson.RPCError); ok {
|
if jErr, ok := replyErr.(*btcjson.RPCError); ok {
|
||||||
|
@ -4014,7 +4020,67 @@ func createMarshalledReply(id, result interface{}, replyErr error) ([]byte, erro
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return btcjson.MarshalResponse(id, result, jsonErr)
|
return btcjson.MarshalResponse(rpcVersion, id, result, jsonErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// processRequest determines the incoming request type (single or batched),
|
||||||
|
// parses it and returns a marshalled response.
|
||||||
|
func (s *rpcServer) processRequest(request *btcjson.Request, isAdmin bool, closeChan <-chan struct{}) []byte {
|
||||||
|
var result interface{}
|
||||||
|
var err error
|
||||||
|
var jsonErr *btcjson.RPCError
|
||||||
|
|
||||||
|
if !isAdmin {
|
||||||
|
if _, ok := rpcLimited[request.Method]; !ok {
|
||||||
|
jsonErr = internalRPCError("limited user not "+
|
||||||
|
"authorized for this method", "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonErr == nil {
|
||||||
|
if request.Method == "" || request.Params == nil {
|
||||||
|
jsonErr = &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||||
|
Message: "Invalid request: malformed",
|
||||||
|
}
|
||||||
|
msg, err := createMarshalledReply(request.Jsonrpc, request.ID, result, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return msg
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid requests with no ID (notifications) must not have a response
|
||||||
|
// per the JSON-RPC spec.
|
||||||
|
if request.ID == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to parse the JSON-RPC request into a known
|
||||||
|
// concrete command.
|
||||||
|
parsedCmd := parseCmd(request)
|
||||||
|
if parsedCmd.err != nil {
|
||||||
|
jsonErr = parsedCmd.err
|
||||||
|
} else {
|
||||||
|
result, err = s.standardCmdResult(parsedCmd,
|
||||||
|
closeChan)
|
||||||
|
if err != nil {
|
||||||
|
jsonErr = &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||||
|
Message: "Invalid request: malformed",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal the response.
|
||||||
|
msg, err := createMarshalledReply(request.Jsonrpc, request.ID, result, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return msg
|
||||||
}
|
}
|
||||||
|
|
||||||
// jsonRPCRead handles reading and responding to RPC messages.
|
// jsonRPCRead handles reading and responding to RPC messages.
|
||||||
|
@ -4059,17 +4125,43 @@ func (s *rpcServer) jsonRPCRead(w http.ResponseWriter, r *http.Request, isAdmin
|
||||||
conn.SetReadDeadline(timeZeroVal)
|
conn.SetReadDeadline(timeZeroVal)
|
||||||
|
|
||||||
// Attempt to parse the raw body into a JSON-RPC request.
|
// Attempt to parse the raw body into a JSON-RPC request.
|
||||||
var responseID interface{}
|
// Setup a close notifier. Since the connection is hijacked,
|
||||||
var jsonErr error
|
// the CloseNotifer on the ResponseWriter is not available.
|
||||||
var result interface{}
|
closeChan := make(chan struct{}, 1)
|
||||||
var request btcjson.Request
|
go func() {
|
||||||
if err := json.Unmarshal(body, &request); err != nil {
|
_, err = conn.Read(make([]byte, 1))
|
||||||
jsonErr = &btcjson.RPCError{
|
if err != nil {
|
||||||
|
close(closeChan)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
var results []json.RawMessage
|
||||||
|
var batchSize int
|
||||||
|
var batchedRequest bool
|
||||||
|
|
||||||
|
// Determine request type
|
||||||
|
if bytes.HasPrefix(body, batchedRequestPrefix) {
|
||||||
|
batchedRequest = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process a single request
|
||||||
|
if !batchedRequest {
|
||||||
|
var req btcjson.Request
|
||||||
|
var resp json.RawMessage
|
||||||
|
err = json.Unmarshal(body, &req)
|
||||||
|
if err != nil {
|
||||||
|
jsonErr := &btcjson.RPCError{
|
||||||
Code: btcjson.ErrRPCParse.Code,
|
Code: btcjson.ErrRPCParse.Code,
|
||||||
Message: "Failed to parse request: " + err.Error(),
|
Message: fmt.Sprintf("Failed to parse request: %v",
|
||||||
|
err),
|
||||||
|
}
|
||||||
|
resp, err = btcjson.MarshalResponse(btcjson.RpcVersion1, nil, nil, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to create reply: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if jsonErr == nil {
|
|
||||||
|
if err == nil {
|
||||||
// The JSON-RPC 1.0 spec defines that notifications must have their "id"
|
// The JSON-RPC 1.0 spec defines that notifications must have their "id"
|
||||||
// set to null and states that notifications do not have a response.
|
// set to null and states that notifications do not have a response.
|
||||||
//
|
//
|
||||||
|
@ -4088,51 +4180,131 @@ func (s *rpcServer) jsonRPCRead(w http.ResponseWriter, r *http.Request, isAdmin
|
||||||
//
|
//
|
||||||
// RPC quirks can be enabled by the user to avoid compatibility issues
|
// RPC quirks can be enabled by the user to avoid compatibility issues
|
||||||
// with software relying on Core's behavior.
|
// with software relying on Core's behavior.
|
||||||
if request.ID == nil && !(cfg.RPCQuirks && request.Jsonrpc == "") {
|
if req.ID == nil && !(cfg.RPCQuirks && req.Jsonrpc == "") {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
resp = s.processRequest(&req, isAdmin, closeChan)
|
||||||
|
}
|
||||||
|
|
||||||
// The parse was at least successful enough to have an ID so
|
if resp != nil {
|
||||||
// set it for the response.
|
results = append(results, resp)
|
||||||
responseID = request.ID
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Setup a close notifier. Since the connection is hijacked,
|
// Process a batched request
|
||||||
// the CloseNotifer on the ResponseWriter is not available.
|
if batchedRequest {
|
||||||
closeChan := make(chan struct{}, 1)
|
var batchedRequests []interface{}
|
||||||
go func() {
|
var resp json.RawMessage
|
||||||
_, err := conn.Read(make([]byte, 1))
|
err = json.Unmarshal(body, &batchedRequests)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
close(closeChan)
|
jsonErr := &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCParse.Code,
|
||||||
|
Message: fmt.Sprintf("Failed to parse request: %v",
|
||||||
|
err),
|
||||||
|
}
|
||||||
|
resp, err = btcjson.MarshalResponse(btcjson.RpcVersion2, nil, nil, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to create reply: %v", err)
|
||||||
}
|
}
|
||||||
}()
|
|
||||||
|
|
||||||
// Check if the user is limited and set error if method unauthorized
|
if resp != nil {
|
||||||
if !isAdmin {
|
results = append(results, resp)
|
||||||
if _, ok := rpcLimited[request.Method]; !ok {
|
|
||||||
jsonErr = &btcjson.RPCError{
|
|
||||||
Code: btcjson.ErrRPCInvalidParams.Code,
|
|
||||||
Message: "limited user not authorized for this method",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if jsonErr == nil {
|
if err == nil {
|
||||||
// Attempt to parse the JSON-RPC request into a known concrete
|
// Response with an empty batch error if the batch size is zero
|
||||||
// command.
|
if len(batchedRequests) == 0 {
|
||||||
parsedCmd := parseCmd(&request)
|
jsonErr := &btcjson.RPCError{
|
||||||
if parsedCmd.err != nil {
|
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||||
jsonErr = parsedCmd.err
|
Message: "Invalid request: empty batch",
|
||||||
} else {
|
|
||||||
result, jsonErr = s.standardCmdResult(parsedCmd, closeChan)
|
|
||||||
}
|
}
|
||||||
}
|
resp, err = btcjson.MarshalResponse(btcjson.RpcVersion2, nil, nil, jsonErr)
|
||||||
}
|
|
||||||
|
|
||||||
// Marshal the response.
|
|
||||||
msg, err := createMarshalledReply(responseID, result, jsonErr)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||||
return
|
}
|
||||||
|
|
||||||
|
if resp != nil {
|
||||||
|
results = append(results, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each batch entry individually
|
||||||
|
if len(batchedRequests) > 0 {
|
||||||
|
batchSize = len(batchedRequests)
|
||||||
|
|
||||||
|
for _, entry := range batchedRequests {
|
||||||
|
var reqBytes []byte
|
||||||
|
reqBytes, err = json.Marshal(entry)
|
||||||
|
if err != nil {
|
||||||
|
jsonErr := &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||||
|
Message: fmt.Sprintf("Invalid request: %v",
|
||||||
|
err),
|
||||||
|
}
|
||||||
|
resp, err = btcjson.MarshalResponse(btcjson.RpcVersion2, nil, nil, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to create reply: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != nil {
|
||||||
|
results = append(results, resp)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var req btcjson.Request
|
||||||
|
err := json.Unmarshal(reqBytes, &req)
|
||||||
|
if err != nil {
|
||||||
|
jsonErr := &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||||
|
Message: fmt.Sprintf("Invalid request: %v",
|
||||||
|
err),
|
||||||
|
}
|
||||||
|
resp, err = btcjson.MarshalResponse("", nil, nil, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to create reply: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp != nil {
|
||||||
|
results = append(results, resp)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
resp = s.processRequest(&req, isAdmin, closeChan)
|
||||||
|
if resp != nil {
|
||||||
|
results = append(results, resp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var msg = []byte{}
|
||||||
|
if batchedRequest && batchSize > 0 {
|
||||||
|
if len(results) > 0 {
|
||||||
|
// Form the batched response json
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteByte('[')
|
||||||
|
for idx, reply := range results {
|
||||||
|
if idx == len(results)-1 {
|
||||||
|
buffer.Write(reply)
|
||||||
|
buffer.WriteByte(']')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buffer.Write(reply)
|
||||||
|
buffer.WriteByte(',')
|
||||||
|
}
|
||||||
|
msg = buffer.Bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !batchedRequest || batchSize == 0 {
|
||||||
|
// Respond with the first results entry for single requests
|
||||||
|
if len(results) > 0 {
|
||||||
|
msg = results[0]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write the response.
|
// Write the response.
|
||||||
|
|
372
rpcwebsocket.go
372
rpcwebsocket.go
|
@ -695,7 +695,7 @@ func (*wsNotificationManager) notifyBlockConnected(clients map[chan struct{}]*ws
|
||||||
// Notify interested websocket clients about the connected block.
|
// Notify interested websocket clients about the connected block.
|
||||||
ntfn := btcjson.NewBlockConnectedNtfn(block.Hash().String(), block.Height(),
|
ntfn := btcjson.NewBlockConnectedNtfn(block.Hash().String(), block.Height(),
|
||||||
block.MsgBlock().Header.Timestamp.Unix())
|
block.MsgBlock().Header.Timestamp.Unix())
|
||||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal block connected notification: "+
|
rpcsLog.Errorf("Failed to marshal block connected notification: "+
|
||||||
"%v", err)
|
"%v", err)
|
||||||
|
@ -719,7 +719,7 @@ func (*wsNotificationManager) notifyBlockDisconnected(clients map[chan struct{}]
|
||||||
// Notify interested websocket clients about the disconnected block.
|
// Notify interested websocket clients about the disconnected block.
|
||||||
ntfn := btcjson.NewBlockDisconnectedNtfn(block.Hash().String(),
|
ntfn := btcjson.NewBlockDisconnectedNtfn(block.Hash().String(),
|
||||||
block.Height(), block.MsgBlock().Header.Timestamp.Unix())
|
block.Height(), block.MsgBlock().Header.Timestamp.Unix())
|
||||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal block disconnected "+
|
rpcsLog.Errorf("Failed to marshal block disconnected "+
|
||||||
"notification: %v", err)
|
"notification: %v", err)
|
||||||
|
@ -765,7 +765,7 @@ func (m *wsNotificationManager) notifyFilteredBlockConnected(clients map[chan st
|
||||||
ntfn.SubscribedTxs = subscribedTxs[quitChan]
|
ntfn.SubscribedTxs = subscribedTxs[quitChan]
|
||||||
|
|
||||||
// Marshal and queue notification.
|
// Marshal and queue notification.
|
||||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal filtered block "+
|
rpcsLog.Errorf("Failed to marshal filtered block "+
|
||||||
"connected notification: %v", err)
|
"connected notification: %v", err)
|
||||||
|
@ -796,7 +796,7 @@ func (*wsNotificationManager) notifyFilteredBlockDisconnected(clients map[chan s
|
||||||
}
|
}
|
||||||
ntfn := btcjson.NewFilteredBlockDisconnectedNtfn(block.Height(),
|
ntfn := btcjson.NewFilteredBlockDisconnectedNtfn(block.Height(),
|
||||||
hex.EncodeToString(w.Bytes()))
|
hex.EncodeToString(w.Bytes()))
|
||||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal filtered block disconnected "+
|
rpcsLog.Errorf("Failed to marshal filtered block disconnected "+
|
||||||
"notification: %v", err)
|
"notification: %v", err)
|
||||||
|
@ -831,7 +831,7 @@ func (m *wsNotificationManager) notifyForNewTx(clients map[chan struct{}]*wsClie
|
||||||
}
|
}
|
||||||
|
|
||||||
ntfn := btcjson.NewTxAcceptedNtfn(txHashStr, btcutil.Amount(amount).ToBTC())
|
ntfn := btcjson.NewTxAcceptedNtfn(txHashStr, btcutil.Amount(amount).ToBTC())
|
||||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal tx notification: %s", err.Error())
|
rpcsLog.Errorf("Failed to marshal tx notification: %s", err.Error())
|
||||||
return
|
return
|
||||||
|
@ -854,7 +854,7 @@ func (m *wsNotificationManager) notifyForNewTx(clients map[chan struct{}]*wsClie
|
||||||
}
|
}
|
||||||
|
|
||||||
verboseNtfn = btcjson.NewTxAcceptedVerboseNtfn(*rawTx)
|
verboseNtfn = btcjson.NewTxAcceptedVerboseNtfn(*rawTx)
|
||||||
marshalledJSONVerbose, err = btcjson.MarshalCmd(nil,
|
marshalledJSONVerbose, err = btcjson.MarshalCmd(btcjson.RpcVersion1, nil,
|
||||||
verboseNtfn)
|
verboseNtfn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal verbose tx "+
|
rpcsLog.Errorf("Failed to marshal verbose tx "+
|
||||||
|
@ -980,7 +980,7 @@ func blockDetails(block *btcutil.Block, txIndex int) *btcjson.BlockDetails {
|
||||||
func newRedeemingTxNotification(txHex string, index int, block *btcutil.Block) ([]byte, error) {
|
func newRedeemingTxNotification(txHex string, index int, block *btcutil.Block) ([]byte, error) {
|
||||||
// Create and marshal the notification.
|
// Create and marshal the notification.
|
||||||
ntfn := btcjson.NewRedeemingTxNtfn(txHex, blockDetails(block, index))
|
ntfn := btcjson.NewRedeemingTxNtfn(txHex, blockDetails(block, index))
|
||||||
return btcjson.MarshalCmd(nil, ntfn)
|
return btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// notifyForTxOuts examines each transaction output, notifying interested
|
// notifyForTxOuts examines each transaction output, notifying interested
|
||||||
|
@ -1016,7 +1016,7 @@ func (m *wsNotificationManager) notifyForTxOuts(ops map[wire.OutPoint]map[chan s
|
||||||
ntfn := btcjson.NewRecvTxNtfn(txHex, blockDetails(block,
|
ntfn := btcjson.NewRecvTxNtfn(txHex, blockDetails(block,
|
||||||
tx.Index()))
|
tx.Index()))
|
||||||
|
|
||||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal processedtx notification: %v", err)
|
rpcsLog.Errorf("Failed to marshal processedtx notification: %v", err)
|
||||||
continue
|
continue
|
||||||
|
@ -1047,7 +1047,7 @@ func (m *wsNotificationManager) notifyRelevantTxAccepted(tx *btcutil.Tx,
|
||||||
|
|
||||||
if len(clientsToNotify) != 0 {
|
if len(clientsToNotify) != 0 {
|
||||||
n := btcjson.NewRelevantTxAcceptedNtfn(txHexString(tx.MsgTx()))
|
n := btcjson.NewRelevantTxAcceptedNtfn(txHexString(tx.MsgTx()))
|
||||||
marshalled, err := btcjson.MarshalCmd(nil, n)
|
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal notification: %v", err)
|
rpcsLog.Errorf("Failed to marshal notification: %v", err)
|
||||||
return
|
return
|
||||||
|
@ -1323,9 +1323,19 @@ out:
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
|
|
||||||
var request btcjson.Request
|
var batchedRequest bool
|
||||||
err = json.Unmarshal(msg, &request)
|
|
||||||
|
// Determine request type
|
||||||
|
if bytes.HasPrefix(msg, batchedRequestPrefix) {
|
||||||
|
batchedRequest = true
|
||||||
|
}
|
||||||
|
|
||||||
|
if !batchedRequest {
|
||||||
|
var req btcjson.Request
|
||||||
|
var reply json.RawMessage
|
||||||
|
err = json.Unmarshal(msg, &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// only process requests from authenticated clients
|
||||||
if !c.authenticated {
|
if !c.authenticated {
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
|
@ -1334,56 +1344,54 @@ out:
|
||||||
Code: btcjson.ErrRPCParse.Code,
|
Code: btcjson.ErrRPCParse.Code,
|
||||||
Message: "Failed to parse request: " + err.Error(),
|
Message: "Failed to parse request: " + err.Error(),
|
||||||
}
|
}
|
||||||
reply, err := createMarshalledReply(nil, nil, jsonErr)
|
reply, err = createMarshalledReply(btcjson.RpcVersion1, nil, nil, jsonErr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal parse failure "+
|
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||||
"reply: %v", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.SendMessage(reply, nil)
|
c.SendMessage(reply, nil)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// The JSON-RPC 1.0 spec defines that notifications must have their "id"
|
if req.Method == "" || req.Params == nil {
|
||||||
// set to null and states that notifications do not have a response.
|
jsonErr := &btcjson.RPCError{
|
||||||
//
|
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||||
// A JSON-RPC 2.0 notification is a request with "json-rpc":"2.0", and
|
Message: "Invalid request: malformed",
|
||||||
// without an "id" member. The specification states that notifications
|
}
|
||||||
// must not be responded to. JSON-RPC 2.0 permits the null value as a
|
reply, err := createMarshalledReply(req.Jsonrpc, req.ID, nil, jsonErr)
|
||||||
// valid request id, therefore such requests are not notifications.
|
if err != nil {
|
||||||
//
|
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||||
// Bitcoin Core serves requests with "id":null or even an absent "id",
|
continue
|
||||||
// and responds to such requests with "id":null in the response.
|
}
|
||||||
//
|
c.SendMessage(reply, nil)
|
||||||
// Btcd does not respond to any request without and "id" or "id":null,
|
continue
|
||||||
// regardless the indicated JSON-RPC protocol version unless RPC quirks
|
}
|
||||||
// are enabled. With RPC quirks enabled, such requests will be responded
|
|
||||||
// to if the reqeust does not indicate JSON-RPC version.
|
// Valid requests with no ID (notifications) must not have a response
|
||||||
//
|
// per the JSON-RPC spec.
|
||||||
// RPC quirks can be enabled by the user to avoid compatibility issues
|
if req.ID == nil {
|
||||||
// with software relying on Core's behavior.
|
|
||||||
if request.ID == nil && !(cfg.RPCQuirks && request.Jsonrpc == "") {
|
|
||||||
if !c.authenticated {
|
if !c.authenticated {
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := parseCmd(&request)
|
cmd := parseCmd(&req)
|
||||||
if cmd.err != nil {
|
if cmd.err != nil {
|
||||||
|
// Only process requests from authenticated clients
|
||||||
if !c.authenticated {
|
if !c.authenticated {
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
|
|
||||||
reply, err := createMarshalledReply(cmd.id, nil, cmd.err)
|
reply, err = createMarshalledReply(cmd.jsonrpc, cmd.id, nil, cmd.err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal parse failure "+
|
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||||
"reply: %v", err)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
c.SendMessage(reply, nil)
|
c.SendMessage(reply, nil)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
rpcsLog.Debugf("Received command <%s> from %s", cmd.method, c.addr)
|
rpcsLog.Debugf("Received command <%s> from %s", cmd.method, c.addr)
|
||||||
|
|
||||||
// Check auth. The client is immediately disconnected if the
|
// Check auth. The client is immediately disconnected if the
|
||||||
|
@ -1415,7 +1423,7 @@ out:
|
||||||
c.isAdmin = cmp == 1
|
c.isAdmin = cmp == 1
|
||||||
|
|
||||||
// Marshal and send response.
|
// Marshal and send response.
|
||||||
reply, err := createMarshalledReply(cmd.id, nil, nil)
|
reply, err = createMarshalledReply(cmd.jsonrpc, cmd.id, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal authenticate reply: "+
|
rpcsLog.Errorf("Failed to marshal authenticate reply: "+
|
||||||
"%v", err.Error())
|
"%v", err.Error())
|
||||||
|
@ -1426,15 +1434,15 @@ out:
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the client is using limited RPC credentials and
|
// Check if the client is using limited RPC credentials and
|
||||||
// error when not authorized to call this RPC.
|
// error when not authorized to call the supplied RPC.
|
||||||
if !c.isAdmin {
|
if !c.isAdmin {
|
||||||
if _, ok := rpcLimited[request.Method]; !ok {
|
if _, ok := rpcLimited[req.Method]; !ok {
|
||||||
jsonErr := &btcjson.RPCError{
|
jsonErr := &btcjson.RPCError{
|
||||||
Code: btcjson.ErrRPCInvalidParams.Code,
|
Code: btcjson.ErrRPCInvalidParams.Code,
|
||||||
Message: "limited user not authorized for this method",
|
Message: "limited user not authorized for this method",
|
||||||
}
|
}
|
||||||
// Marshal and send response.
|
// Marshal and send response.
|
||||||
reply, err := createMarshalledReply(request.ID, nil, jsonErr)
|
reply, err = createMarshalledReply("", req.ID, nil, jsonErr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal parse failure "+
|
rpcsLog.Errorf("Failed to marshal parse failure "+
|
||||||
"reply: %v", err)
|
"reply: %v", err)
|
||||||
|
@ -1472,6 +1480,280 @@ out:
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process a batched request
|
||||||
|
if batchedRequest {
|
||||||
|
var batchedRequests []interface{}
|
||||||
|
var results []json.RawMessage
|
||||||
|
var batchSize int
|
||||||
|
var reply json.RawMessage
|
||||||
|
c.serviceRequestSem.acquire()
|
||||||
|
err = json.Unmarshal(msg, &batchedRequests)
|
||||||
|
if err != nil {
|
||||||
|
// Only process requests from authenticated clients
|
||||||
|
if !c.authenticated {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonErr := &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCParse.Code,
|
||||||
|
Message: fmt.Sprintf("Failed to parse request: %v",
|
||||||
|
err),
|
||||||
|
}
|
||||||
|
reply, err = btcjson.MarshalResponse(btcjson.RpcVersion2, nil, nil, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to create reply: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply != nil {
|
||||||
|
results = append(results, reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
// Response with an empty batch error if the batch size is zero
|
||||||
|
if len(batchedRequests) == 0 {
|
||||||
|
if !c.authenticated {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonErr := &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||||
|
Message: "Invalid request: empty batch",
|
||||||
|
}
|
||||||
|
reply, err = btcjson.MarshalResponse(btcjson.RpcVersion2, nil, nil, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply != nil {
|
||||||
|
results = append(results, reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process each batch entry individually
|
||||||
|
if len(batchedRequests) > 0 {
|
||||||
|
batchSize = len(batchedRequests)
|
||||||
|
for _, entry := range batchedRequests {
|
||||||
|
var reqBytes []byte
|
||||||
|
reqBytes, err = json.Marshal(entry)
|
||||||
|
if err != nil {
|
||||||
|
// Only process requests from authenticated clients
|
||||||
|
if !c.authenticated {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonErr := &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||||
|
Message: fmt.Sprintf("Invalid request: %v",
|
||||||
|
err),
|
||||||
|
}
|
||||||
|
reply, err = btcjson.MarshalResponse(btcjson.RpcVersion2, nil, nil, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to create reply: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply != nil {
|
||||||
|
results = append(results, reply)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
var req btcjson.Request
|
||||||
|
err := json.Unmarshal(reqBytes, &req)
|
||||||
|
if err != nil {
|
||||||
|
// Only process requests from authenticated clients
|
||||||
|
if !c.authenticated {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonErr := &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||||
|
Message: fmt.Sprintf("Invalid request: %v",
|
||||||
|
err),
|
||||||
|
}
|
||||||
|
reply, err = btcjson.MarshalResponse(btcjson.RpcVersion2, nil, nil, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to create reply: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply != nil {
|
||||||
|
results = append(results, reply)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.Method == "" || req.Params == nil {
|
||||||
|
jsonErr := &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||||
|
Message: "Invalid request: malformed",
|
||||||
|
}
|
||||||
|
reply, err := createMarshalledReply(req.Jsonrpc, req.ID, nil, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply != nil {
|
||||||
|
results = append(results, reply)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid requests with no ID (notifications) must not have a response
|
||||||
|
// per the JSON-RPC spec.
|
||||||
|
if req.ID == nil {
|
||||||
|
if !c.authenticated {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := parseCmd(&req)
|
||||||
|
if cmd.err != nil {
|
||||||
|
// Only process requests from authenticated clients
|
||||||
|
if !c.authenticated {
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err = createMarshalledReply(cmd.jsonrpc, cmd.id, nil, cmd.err)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply != nil {
|
||||||
|
results = append(results, reply)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
rpcsLog.Debugf("Received command <%s> from %s", cmd.method, c.addr)
|
||||||
|
|
||||||
|
// Check auth. The client is immediately disconnected if the
|
||||||
|
// first request of an unauthentiated websocket client is not
|
||||||
|
// the authenticate request, an authenticate request is received
|
||||||
|
// when the client is already authenticated, or incorrect
|
||||||
|
// authentication credentials are provided in the request.
|
||||||
|
switch authCmd, ok := cmd.cmd.(*btcjson.AuthenticateCmd); {
|
||||||
|
case c.authenticated && ok:
|
||||||
|
rpcsLog.Warnf("Websocket client %s is already authenticated",
|
||||||
|
c.addr)
|
||||||
|
break out
|
||||||
|
case !c.authenticated && !ok:
|
||||||
|
rpcsLog.Warnf("Unauthenticated websocket message " +
|
||||||
|
"received")
|
||||||
|
break out
|
||||||
|
case !c.authenticated:
|
||||||
|
// Check credentials.
|
||||||
|
login := authCmd.Username + ":" + authCmd.Passphrase
|
||||||
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
|
||||||
|
authSha := sha256.Sum256([]byte(auth))
|
||||||
|
cmp := subtle.ConstantTimeCompare(authSha[:], c.server.authsha[:])
|
||||||
|
limitcmp := subtle.ConstantTimeCompare(authSha[:], c.server.limitauthsha[:])
|
||||||
|
if cmp != 1 && limitcmp != 1 {
|
||||||
|
rpcsLog.Warnf("Auth failure.")
|
||||||
|
break out
|
||||||
|
}
|
||||||
|
|
||||||
|
c.authenticated = true
|
||||||
|
c.isAdmin = cmp == 1
|
||||||
|
|
||||||
|
// Marshal and send response.
|
||||||
|
reply, err = createMarshalledReply(cmd.jsonrpc, cmd.id, nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to marshal authenticate reply: "+
|
||||||
|
"%v", err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply != nil {
|
||||||
|
results = append(results, reply)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the client is using limited RPC credentials and
|
||||||
|
// error when not authorized to call the supplied RPC.
|
||||||
|
if !c.isAdmin {
|
||||||
|
if _, ok := rpcLimited[req.Method]; !ok {
|
||||||
|
jsonErr := &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidParams.Code,
|
||||||
|
Message: "limited user not authorized for this method",
|
||||||
|
}
|
||||||
|
// Marshal and send response.
|
||||||
|
reply, err = createMarshalledReply(req.Jsonrpc, req.ID, nil, jsonErr)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to marshal parse failure "+
|
||||||
|
"reply: %v", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply != nil {
|
||||||
|
results = append(results, reply)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the websocket extension for the command, if it doesn't
|
||||||
|
// exist fallback to handling the command as a standard command.
|
||||||
|
var resp interface{}
|
||||||
|
wsHandler, ok := wsHandlers[cmd.method]
|
||||||
|
if ok {
|
||||||
|
resp, err = wsHandler(c, cmd.cmd)
|
||||||
|
} else {
|
||||||
|
resp, err = c.server.standardCmdResult(cmd, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Marshal request output.
|
||||||
|
reply, err := createMarshalledReply(cmd.jsonrpc, cmd.id, resp, err)
|
||||||
|
if err != nil {
|
||||||
|
rpcsLog.Errorf("Failed to marshal reply for <%s> "+
|
||||||
|
"command: %v", cmd.method, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if reply != nil {
|
||||||
|
results = append(results, reply)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// generate reply
|
||||||
|
var payload = []byte{}
|
||||||
|
if batchedRequest && batchSize > 0 {
|
||||||
|
if len(results) > 0 {
|
||||||
|
// Form the batched response json
|
||||||
|
var buffer bytes.Buffer
|
||||||
|
buffer.WriteByte('[')
|
||||||
|
for idx, marshalledReply := range results {
|
||||||
|
if idx == len(results)-1 {
|
||||||
|
buffer.Write(marshalledReply)
|
||||||
|
buffer.WriteByte(']')
|
||||||
|
break
|
||||||
|
}
|
||||||
|
buffer.Write(marshalledReply)
|
||||||
|
buffer.WriteByte(',')
|
||||||
|
}
|
||||||
|
payload = buffer.Bytes()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !batchedRequest || batchSize == 0 {
|
||||||
|
// Respond with the first results entry for single requests
|
||||||
|
if len(results) > 0 {
|
||||||
|
payload = results[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.SendMessage(payload, nil)
|
||||||
|
c.serviceRequestSem.release()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure the connection is closed.
|
// Ensure the connection is closed.
|
||||||
c.Disconnect()
|
c.Disconnect()
|
||||||
c.wg.Done()
|
c.wg.Done()
|
||||||
|
@ -1495,7 +1777,7 @@ func (c *wsClient) serviceRequest(r *parsedRPCCmd) {
|
||||||
} else {
|
} else {
|
||||||
result, err = c.server.standardCmdResult(r, nil)
|
result, err = c.server.standardCmdResult(r, nil)
|
||||||
}
|
}
|
||||||
reply, err := createMarshalledReply(r.id, result, err)
|
reply, err := createMarshalledReply(r.jsonrpc, r.id, result, err)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal reply for <%s> "+
|
rpcsLog.Errorf("Failed to marshal reply for <%s> "+
|
||||||
"command: %v", r.method, err)
|
"command: %v", r.method, err)
|
||||||
|
@ -2125,7 +2407,7 @@ func rescanBlock(wsc *wsClient, lookups *rescanKeys, blk *btcutil.Block) {
|
||||||
ntfn := btcjson.NewRecvTxNtfn(txHex,
|
ntfn := btcjson.NewRecvTxNtfn(txHex,
|
||||||
blockDetails(blk, tx.Index()))
|
blockDetails(blk, tx.Index()))
|
||||||
|
|
||||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal recvtx notification: %v", err)
|
rpcsLog.Errorf("Failed to marshal recvtx notification: %v", err)
|
||||||
return
|
return
|
||||||
|
@ -2492,7 +2774,7 @@ fetchRange:
|
||||||
hashList[i].String(), blk.Height(),
|
hashList[i].String(), blk.Height(),
|
||||||
blk.MsgBlock().Header.Timestamp.Unix(),
|
blk.MsgBlock().Header.Timestamp.Unix(),
|
||||||
)
|
)
|
||||||
mn, err := btcjson.MarshalCmd(nil, n)
|
mn, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, n)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal rescan "+
|
rpcsLog.Errorf("Failed to marshal rescan "+
|
||||||
"progress notification: %v", err)
|
"progress notification: %v", err)
|
||||||
|
@ -2637,7 +2919,7 @@ func handleRescan(wsc *wsClient, icmd interface{}) (interface{}, error) {
|
||||||
lastBlockHash.String(), lastBlock.Height(),
|
lastBlockHash.String(), lastBlock.Height(),
|
||||||
lastBlock.MsgBlock().Header.Timestamp.Unix(),
|
lastBlock.MsgBlock().Header.Timestamp.Unix(),
|
||||||
)
|
)
|
||||||
if mn, err := btcjson.MarshalCmd(nil, n); err != nil {
|
if mn, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, n); err != nil {
|
||||||
rpcsLog.Errorf("Failed to marshal rescan finished "+
|
rpcsLog.Errorf("Failed to marshal rescan finished "+
|
||||||
"notification: %v", err)
|
"notification: %v", err)
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in a new issue