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 {
|
||||
// Marshal the command as created by the new static command
|
||||
// creation function.
|
||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
@ -235,7 +235,7 @@ func TestBtcdExtCmds(t *testing.T) {
|
|||
|
||||
// Marshal the command as created by the generic new command
|
||||
// creation function.
|
||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
|
|
@ -145,7 +145,7 @@ func TestBtcWalletExtCmds(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
// Marshal the command as created by the new static command
|
||||
// creation function.
|
||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
@ -169,7 +169,7 @@ func TestBtcWalletExtCmds(t *testing.T) {
|
|||
|
||||
// Marshal the command as created by the generic new command
|
||||
// creation function.
|
||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
|
|
@ -1466,7 +1466,7 @@ func TestChainSvrCmds(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
// Marshal the command as created by the new static command
|
||||
// creation function.
|
||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
@ -1491,7 +1491,7 @@ func TestChainSvrCmds(t *testing.T) {
|
|||
|
||||
// Marshal the command as created by the generic new command
|
||||
// creation function.
|
||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
|
|
@ -233,7 +233,7 @@ func TestChainSvrWsCmds(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
// Marshal the command as created by the new static command
|
||||
// creation function.
|
||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
@ -257,7 +257,7 @@ func TestChainSvrWsCmds(t *testing.T) {
|
|||
|
||||
// Marshal the command as created by the generic new command
|
||||
// creation function.
|
||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
|
|
@ -231,7 +231,7 @@ func TestChainSvrWsNtfns(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
// Marshal the notification as created by the new static
|
||||
// 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 {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
@ -256,7 +256,7 @@ func TestChainSvrWsNtfns(t *testing.T) {
|
|||
// Marshal the notification as created by the generic new
|
||||
// notification creation function. The ID is nil for
|
||||
// notifications.
|
||||
marshalled, err = btcjson.MarshalCmd(nil, cmd)
|
||||
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, nil, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
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
|
||||
// must be a registered type. All commands provided by this package are
|
||||
// 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.
|
||||
rt := reflect.TypeOf(cmd)
|
||||
registerLock.RLock()
|
||||
|
@ -60,7 +60,7 @@ func MarshalCmd(id interface{}, cmd interface{}) ([]byte, error) {
|
|||
params := makeParams(rt.Elem(), rv.Elem())
|
||||
|
||||
// Generate and marshal the final JSON-RPC request.
|
||||
rawCmd, err := NewRequest(id, method, params)
|
||||
rawCmd, err := NewRequest(rpcVersion, id, method, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -460,7 +460,7 @@ func TestMarshalCmd(t *testing.T) {
|
|||
|
||||
t.Logf("Running %d tests", len(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 {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%v)",
|
||||
i, test.name, err, err)
|
||||
|
@ -507,7 +507,7 @@ func TestMarshalCmdErrors(t *testing.T) {
|
|||
|
||||
t.Logf("Running %d tests", len(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) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
|
||||
"want %T", i, test.name, err, err, test.err)
|
||||
|
@ -535,7 +535,7 @@ func TestUnmarshalCmdErrors(t *testing.T) {
|
|||
{
|
||||
name: "unregistered type",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: "1.0",
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "bogusmethod",
|
||||
Params: nil,
|
||||
ID: nil,
|
||||
|
@ -545,7 +545,7 @@ func TestUnmarshalCmdErrors(t *testing.T) {
|
|||
{
|
||||
name: "incorrect number of params",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: "1.0",
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getblockcount",
|
||||
Params: []json.RawMessage{[]byte(`"bogusparam"`)},
|
||||
ID: nil,
|
||||
|
@ -555,7 +555,7 @@ func TestUnmarshalCmdErrors(t *testing.T) {
|
|||
{
|
||||
name: "invalid type for a parameter",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: "1.0",
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getblock",
|
||||
Params: []json.RawMessage{[]byte("1")},
|
||||
ID: nil,
|
||||
|
@ -565,7 +565,7 @@ func TestUnmarshalCmdErrors(t *testing.T) {
|
|||
{
|
||||
name: "invalid JSON for a parameter",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: "1.0",
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getblock",
|
||||
Params: []json.RawMessage{[]byte(`"1`)},
|
||||
ID: nil,
|
||||
|
|
|
@ -27,7 +27,7 @@ func ExampleMarshalCmd() {
|
|||
// server. Typically the client would increment the id here which is
|
||||
// request so the response can be identified.
|
||||
id := 1
|
||||
marshalledBytes, err := btcjson.MarshalCmd(id, gbCmd)
|
||||
marshalledBytes, err := btcjson.MarshalCmd(btcjson.RpcVersion1, id, gbCmd)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
|
@ -95,7 +95,7 @@ func ExampleUnmarshalCmd() {
|
|||
func ExampleMarshalResponse() {
|
||||
// Marshal a new JSON-RPC response. For example, this is a response
|
||||
// to a getblockheight request.
|
||||
marshalledBytes, err := btcjson.MarshalResponse(1, 350001, nil)
|
||||
marshalledBytes, err := btcjson.MarshalResponse(btcjson.RpcVersion1, 1, 350001, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
return
|
||||
|
@ -107,7 +107,7 @@ func ExampleMarshalResponse() {
|
|||
fmt.Printf("%s\n", marshalledBytes)
|
||||
|
||||
// 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
|
||||
|
@ -116,7 +116,7 @@ func Example_unmarshalResponse() {
|
|||
// 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
|
||||
// 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.
|
||||
var response btcjson.Response
|
||||
|
|
|
@ -9,6 +9,33 @@ import (
|
|||
"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
|
||||
// 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
|
||||
// construct raw requests for some reason.
|
||||
type Request struct {
|
||||
Jsonrpc string `json:"jsonrpc"`
|
||||
Jsonrpc RPCVersion `json:"jsonrpc"`
|
||||
Method string `json:"method"`
|
||||
Params []json.RawMessage `json:"params"`
|
||||
ID interface{} `json:"id"`
|
||||
}
|
||||
|
||||
// NewRequest returns a new JSON-RPC 1.0 request object given the provided 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(id interface{}, method string, params []interface{}) (*Request, error) {
|
||||
// UnmarshalJSON is a custom unmarshal func for the Request struct. The param
|
||||
// field defaults to an empty json.RawMessage array it is omitted by the request
|
||||
// or nil if the supplied value is invalid.
|
||||
func (request *Request) UnmarshalJSON(b []byte) error {
|
||||
// Step 1: Create a type alias of the original struct.
|
||||
type Alias Request
|
||||
|
||||
// Step 2: Create an anonymous struct with raw replacements for the special
|
||||
// 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) {
|
||||
str := fmt.Sprintf("the id of type '%T' is invalid", id)
|
||||
return nil, makeError(ErrInvalidType, str)
|
||||
|
@ -98,30 +178,35 @@ func NewRequest(id interface{}, method string, params []interface{}) (*Request,
|
|||
}
|
||||
|
||||
return &Request{
|
||||
Jsonrpc: "1.0",
|
||||
Jsonrpc: rpcVersion,
|
||||
ID: id,
|
||||
Method: method,
|
||||
Params: rawParams,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Response is the general form of a JSON-RPC response. The type of the 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
|
||||
// Response is the general form of a JSON-RPC response. The type of the
|
||||
// Result field varies from one command to the next, so it is implemented as an
|
||||
// interface. The ID field has to be a pointer to allow for a nil value when
|
||||
// empty.
|
||||
type Response struct {
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error *RPCError `json:"error"`
|
||||
ID *interface{} `json:"id"`
|
||||
Jsonrpc RPCVersion `json:"jsonrpc"`
|
||||
Result json.RawMessage `json:"result"`
|
||||
Error *RPCError `json:"error"`
|
||||
ID *interface{} `json:"id"`
|
||||
}
|
||||
|
||||
// NewResponse returns a new JSON-RPC response object given the provided id,
|
||||
// marshalled result, and RPC error. This function is only provided in case the
|
||||
// caller wants to construct raw responses for some reason.
|
||||
//
|
||||
// NewResponse returns a new JSON-RPC response object given the provided rpc
|
||||
// version, id, marshalled result, and RPC error. This function is only
|
||||
// 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
|
||||
// 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) {
|
||||
str := fmt.Sprintf("the id of type '%T' is invalid", id)
|
||||
return nil, makeError(ErrInvalidType, str)
|
||||
|
@ -129,20 +214,27 @@ func NewResponse(id interface{}, marshalledResult []byte, rpcErr *RPCError) (*Re
|
|||
|
||||
pid := &id
|
||||
return &Response{
|
||||
Result: marshalledResult,
|
||||
Error: rpcErr,
|
||||
ID: pid,
|
||||
Jsonrpc: rpcVersion,
|
||||
Result: marshalledResult,
|
||||
Error: rpcErr,
|
||||
ID: pid,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// MarshalResponse marshals the passed id, result, and RPCError to a JSON-RPC
|
||||
// response byte slice that is suitable for transmission to a JSON-RPC client.
|
||||
func MarshalResponse(id interface{}, result interface{}, rpcErr *RPCError) ([]byte, error) {
|
||||
// MarshalResponse marshals the passed rpc version, id, result, and RPCError to
|
||||
// a JSON-RPC response byte slice that is suitable for transmission to a
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := NewResponse(id, marshalledResult, rpcErr)
|
||||
response, err := NewResponse(rpcVersion, id, marshalledResult, rpcErr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
|
@ -68,7 +68,7 @@ func TestMarshalResponse(t *testing.T) {
|
|||
name: "ordinary bool result with no error",
|
||||
result: true,
|
||||
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",
|
||||
|
@ -76,14 +76,14 @@ func TestMarshalResponse(t *testing.T) {
|
|||
jsonErr: func() *btcjson.RPCError {
|
||||
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))
|
||||
for i, test := range tests {
|
||||
_, _ = 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 {
|
||||
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||
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
|
||||
// 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 {
|
||||
t.Error("NewRequest: did not receive error")
|
||||
return
|
||||
|
@ -113,7 +113,7 @@ func TestMiscErrors(t *testing.T) {
|
|||
// Force an error in MarshalResponse by giving it an id type that is not
|
||||
// supported.
|
||||
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 {
|
||||
t.Errorf("MarshalResult: did not receive expected error - got "+
|
||||
"%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
|
||||
// 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 {
|
||||
wantErr := &json.UnsupportedTypeError{}
|
||||
t.Errorf("MarshalResult: did not receive expected error - got "+
|
||||
|
|
|
@ -1800,7 +1800,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
// Marshal the command as created by the new static command
|
||||
// creation function.
|
||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
@ -1824,7 +1824,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
|
||||
// Marshal the command as created by the generic new command
|
||||
// creation function.
|
||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
|
|
@ -195,7 +195,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
// Marshal the command as created by the new static command
|
||||
// creation function.
|
||||
marshalled, err := btcjson.MarshalCmd(testID, test.staticCmd())
|
||||
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, testID, test.staticCmd())
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
@ -219,7 +219,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
|||
|
||||
// Marshal the command as created by the generic new command
|
||||
// creation function.
|
||||
marshalled, err = btcjson.MarshalCmd(testID, cmd)
|
||||
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, testID, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
|
|
@ -122,7 +122,7 @@ func TestWalletSvrWsNtfns(t *testing.T) {
|
|||
for i, test := range tests {
|
||||
// Marshal the notification as created by the new static
|
||||
// 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 {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
@ -147,7 +147,7 @@ func TestWalletSvrWsNtfns(t *testing.T) {
|
|||
// Marshal the notification as created by the generic new
|
||||
// notification creation function. The ID is nil for
|
||||
// notifications.
|
||||
marshalled, err = btcjson.MarshalCmd(nil, cmd)
|
||||
marshalled, err = btcjson.MarshalCmd(btcjson.RpcVersion1, nil, cmd)
|
||||
if err != nil {
|
||||
t.Errorf("MarshalCmd #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
|
|
|
@ -127,7 +127,7 @@ func main() {
|
|||
|
||||
// Marshal the command into a JSON-RPC byte slice in preparation for
|
||||
// sending it to the RPC server.
|
||||
marshalledJSON, err := btcjson.MarshalCmd(1, cmd)
|
||||
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, 1, cmd)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
|
|
|
@ -33,7 +33,7 @@ const (
|
|||
// ensures its version either has the provided bit set or unset per the set
|
||||
// flag.
|
||||
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 {
|
||||
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
|
||||
// harness and ensures it matches the provided expected height.
|
||||
func assertChainHeight(r *rpctest.Harness, t *testing.T, expectedHeight uint32) {
|
||||
height, err := r.Node.GetBlockCount()
|
||||
height, err := r.Client.GetBlockCount()
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
info, err := r.Node.GetBlockChainInfo()
|
||||
info, err := r.Client.GetBlockChainInfo()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to retrieve chain info: %v", err)
|
||||
}
|
||||
|
@ -339,7 +339,7 @@ func TestBIP0009Mining(t *testing.T) {
|
|||
// in the defined threshold state.
|
||||
deployment := &r.ActiveNet.Deployments[chaincfg.DeploymentTestDummy]
|
||||
testDummyBitNum := deployment.BitNumber
|
||||
hashes, err := r.Node.Generate(1)
|
||||
hashes, err := r.Client.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to generate blocks: %v", err)
|
||||
}
|
||||
|
@ -358,7 +358,7 @@ func TestBIP0009Mining(t *testing.T) {
|
|||
// dummy deployment as started.
|
||||
confirmationWindow := r.ActiveNet.MinerConfirmationWindow
|
||||
numNeeded := confirmationWindow - 1
|
||||
hashes, err = r.Node.Generate(numNeeded)
|
||||
hashes, err = r.Client.Generate(numNeeded)
|
||||
if err != nil {
|
||||
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
|
||||
// version since the btcd mining code will have recognized the test
|
||||
// dummy deployment as locked in.
|
||||
hashes, err = r.Node.Generate(confirmationWindow)
|
||||
hashes, err = r.Client.Generate(confirmationWindow)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generated %d blocks: %v", confirmationWindow,
|
||||
err)
|
||||
|
@ -392,7 +392,7 @@ func TestBIP0009Mining(t *testing.T) {
|
|||
// version since the btcd mining code will have recognized the test
|
||||
// dummy deployment as activated and thus there is no longer any need
|
||||
// to set the bit.
|
||||
hashes, err = r.Node.Generate(confirmationWindow)
|
||||
hashes, err = r.Client.Generate(confirmationWindow)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generated %d blocks: %v", confirmationWindow,
|
||||
err)
|
||||
|
|
|
@ -57,14 +57,14 @@ func makeTestOutput(r *rpctest.Harness, t *testing.T,
|
|||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
txHash, err := r.Node.SendRawTransaction(fundTx, true)
|
||||
txHash, err := r.Client.SendRawTransaction(fundTx, true)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
// The transaction created above should be included within the next
|
||||
// generated block.
|
||||
blockHash, err := r.Node.Generate(1)
|
||||
blockHash, err := r.Client.Generate(1)
|
||||
if err != nil {
|
||||
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
|
||||
// current MTP of the chain.
|
||||
chainInfo, err := r.Node.GetBlockChainInfo()
|
||||
chainInfo, err := r.Client.GetBlockChainInfo()
|
||||
if err != nil {
|
||||
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
|
||||
// for transactions finality is now a policy rule. Additionally, the
|
||||
// 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 {
|
||||
t.Fatalf("transaction accepted, but should be non-final")
|
||||
} 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
|
||||
// block AFTER the current height.
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -220,7 +220,7 @@ func TestBIP0113Activation(t *testing.T) {
|
|||
// rejected.
|
||||
timeLockDeltas := []int64{-1, 0, 1}
|
||||
for _, timeLockDelta := range timeLockDeltas {
|
||||
chainInfo, err = r.Node.GetBlockChainInfo()
|
||||
chainInfo, err = r.Client.GetBlockChainInfo()
|
||||
if err != nil {
|
||||
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
|
||||
// second _before_ the current MTP.
|
||||
|
||||
_, err = r.Node.SendRawTransaction(tx, true)
|
||||
_, err = r.Client.SendRawTransaction(tx, true)
|
||||
if err == nil && timeLockDelta >= 0 {
|
||||
t.Fatal("transaction was accepted into the mempool " +
|
||||
"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,
|
||||
txid *chainhash.Hash) {
|
||||
|
||||
block, err := r.Node.GetBlock(blockHash)
|
||||
block, err := r.Client.GetBlock(blockHash)
|
||||
if err != nil {
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
blocks, err := r.Node.Generate(1)
|
||||
blocks, err := r.Client.Generate(1)
|
||||
if err != nil {
|
||||
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
|
||||
// CSV validation is already mempool policy pre-fork.
|
||||
_, err = r.Node.SendRawTransaction(spendingTx, true)
|
||||
_, err = r.Client.SendRawTransaction(spendingTx, true)
|
||||
if err == nil {
|
||||
t.Fatalf("transaction should have been rejected, but was " +
|
||||
"instead accepted")
|
||||
|
@ -496,7 +496,7 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
|||
// height 299. The getblockchaininfo call checks the state for the
|
||||
// block AFTER the current height.
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -530,7 +530,7 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
|||
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)
|
||||
}
|
||||
|
||||
|
@ -542,17 +542,17 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
|||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// Now mine 10 additional blocks giving the inputs generated above a
|
||||
// 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 {
|
||||
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 {
|
||||
t.Fatalf("unable to get block: %v", err)
|
||||
}
|
||||
|
@ -652,7 +652,7 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
|||
}
|
||||
|
||||
for i, test := range tests {
|
||||
txid, err := r.Node.SendRawTransaction(test.tx, true)
|
||||
txid, err := r.Client.SendRawTransaction(test.tx, true)
|
||||
switch {
|
||||
// Test case passes, nothing further to report.
|
||||
case test.accept && err == nil:
|
||||
|
@ -686,7 +686,7 @@ func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
|||
|
||||
// Generate a block, the transaction should be included within
|
||||
// the newly mined block.
|
||||
blockHashes, err := r.Node.Generate(1)
|
||||
blockHashes, err := r.Client.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("unable to mine block: %v", err)
|
||||
}
|
||||
|
|
|
@ -15,22 +15,24 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/btcsuite/btcd/chaincfg"
|
||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||
"github.com/btcsuite/btcd/integration/rpctest"
|
||||
"github.com/btcsuite/btcd/rpcclient"
|
||||
)
|
||||
|
||||
func testGetBestBlock(r *rpctest.Harness, t *testing.T) {
|
||||
_, prevbestHeight, err := r.Node.GetBestBlock()
|
||||
_, prevbestHeight, err := r.Client.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatalf("Call to `getbestblock` failed: %v", err)
|
||||
}
|
||||
|
||||
// Create a new block connecting to the current tip.
|
||||
generatedBlockHashes, err := r.Node.Generate(1)
|
||||
generatedBlockHashes, err := r.Client.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to generate block: %v", err)
|
||||
}
|
||||
|
||||
bestHash, bestHeight, err := r.Node.GetBestBlock()
|
||||
bestHash, bestHeight, err := r.Client.GetBestBlock()
|
||||
if err != nil {
|
||||
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) {
|
||||
// Save the current count.
|
||||
currentCount, err := r.Node.GetBlockCount()
|
||||
currentCount, err := r.Client.GetBlockCount()
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
// Count should have increased by one.
|
||||
newCount, err := r.Node.GetBlockCount()
|
||||
newCount, err := r.Client.GetBlockCount()
|
||||
if err != nil {
|
||||
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) {
|
||||
// Create a new block connecting to the current tip.
|
||||
generatedBlockHashes, err := r.Node.Generate(1)
|
||||
generatedBlockHashes, err := r.Client.Generate(1)
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to generate block: %v", err)
|
||||
}
|
||||
|
||||
info, err := r.Node.GetInfo()
|
||||
info, err := r.Client.GetInfo()
|
||||
if err != nil {
|
||||
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 {
|
||||
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{
|
||||
testGetBestBlock,
|
||||
testGetBlockCount,
|
||||
testGetBlockHash,
|
||||
testBulkClient,
|
||||
}
|
||||
|
||||
var primaryHarness *rpctest.Harness
|
||||
|
|
|
@ -92,9 +92,10 @@ type Harness struct {
|
|||
// attempts.
|
||||
ConnectionRetryTimeout time.Duration
|
||||
|
||||
Node *rpcclient.Client
|
||||
node *node
|
||||
handlers *rpcclient.NotificationHandlers
|
||||
Client *rpcclient.Client
|
||||
BatchClient *rpcclient.Client
|
||||
node *node
|
||||
handlers *rpcclient.NotificationHandlers
|
||||
|
||||
wallet *memWallet
|
||||
|
||||
|
@ -245,13 +246,13 @@ func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error {
|
|||
// Filter transactions that pay to the coinbase associated with the
|
||||
// wallet.
|
||||
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
|
||||
}
|
||||
|
||||
// Ensure btcd properly dispatches our registered call-back for each new
|
||||
// block. Otherwise, the memWallet won't function properly.
|
||||
if err := h.Node.NotifyBlocks(); err != nil {
|
||||
if err := h.Client.NotifyBlocks(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -260,7 +261,7 @@ func (h *Harness) SetUp(createTestChain bool, numMatureOutputs uint32) error {
|
|||
if createTestChain && numMatureOutputs != 0 {
|
||||
numToGenerate := (uint32(h.ActiveNet.CoinbaseMaturity) +
|
||||
numMatureOutputs)
|
||||
_, err := h.Node.Generate(numToGenerate)
|
||||
_, err := h.Client.Generate(numToGenerate)
|
||||
if err != nil {
|
||||
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
|
||||
// chain.
|
||||
_, height, err := h.Node.GetBestBlock()
|
||||
_, height, err := h.Client.GetBestBlock()
|
||||
if err != nil {
|
||||
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).
|
||||
func (h *Harness) tearDown() error {
|
||||
if h.Node != nil {
|
||||
h.Node.Shutdown()
|
||||
if h.Client != nil {
|
||||
h.Client.Shutdown()
|
||||
}
|
||||
|
||||
if h.BatchClient != nil {
|
||||
h.BatchClient.Shutdown()
|
||||
}
|
||||
|
||||
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
|
||||
// error.
|
||||
func (h *Harness) connectRPCClient() error {
|
||||
var client *rpcclient.Client
|
||||
var client, batchClient *rpcclient.Client
|
||||
var err error
|
||||
|
||||
rpcConf := h.node.config.rpcConnConfig()
|
||||
batchConf := h.node.config.rpcConnConfig()
|
||||
batchConf.HTTPPostMode = true
|
||||
for i := 0; i < h.MaxConnRetries; i++ {
|
||||
if client, err = rpcclient.New(&rpcConf, h.handlers); err != nil {
|
||||
time.Sleep(time.Duration(i) * h.ConnectionRetryTimeout)
|
||||
continue
|
||||
fail := false
|
||||
if client == nil {
|
||||
if client, err = rpcclient.New(&rpcConf, h.handlers); err != nil {
|
||||
time.Sleep(time.Duration(i) * h.ConnectionRetryTimeout)
|
||||
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")
|
||||
}
|
||||
|
||||
h.Node = client
|
||||
h.Client = client
|
||||
h.wallet.SetRPCClient(client)
|
||||
h.BatchClient = batchClient
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -464,11 +483,11 @@ func (h *Harness) GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
|
|||
blockVersion = BlockVersion
|
||||
}
|
||||
|
||||
prevBlockHash, prevBlockHeight, err := h.Node.GetBestBlock()
|
||||
prevBlockHash, prevBlockHeight, err := h.Client.GetBestBlock()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mBlock, err := h.Node.GetBlock(prevBlockHash)
|
||||
mBlock, err := h.Client.GetBlock(prevBlockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -483,7 +502,7 @@ func (h *Harness) GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
|
|||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
|
|
|
@ -43,7 +43,7 @@ func testSendOutputs(r *Harness, t *testing.T) {
|
|||
}
|
||||
|
||||
assertTxMined := func(txid *chainhash.Hash, blockHash *chainhash.Hash) {
|
||||
block, err := r.Node.GetBlock(blockHash)
|
||||
block, err := r.Client.GetBlock(blockHash)
|
||||
if err != nil {
|
||||
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
|
||||
// be found in this block.
|
||||
blockHashes, err := r.Node.Generate(1)
|
||||
blockHashes, err := r.Client.Generate(1)
|
||||
if err != nil {
|
||||
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
|
||||
// transaction should also have been mined properly.
|
||||
txid = genSpend(btcutil.Amount(500 * btcutil.SatoshiPerBitcoin))
|
||||
blockHashes, err = r.Node.Generate(1)
|
||||
blockHashes, err = r.Client.Generate(1)
|
||||
if err != nil {
|
||||
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) {
|
||||
nodeAPeers, err := nodeA.Node.GetPeerInfo()
|
||||
nodeAPeers, err := nodeA.Client.GetPeerInfo()
|
||||
if err != nil {
|
||||
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) {
|
||||
// Assert main test harness has no transactions in its mempool.
|
||||
pooledHashes, err := r.Node.GetRawMempool()
|
||||
pooledHashes, err := r.Client.GetRawMempool()
|
||||
if err != nil {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -219,7 +219,7 @@ func testJoinMempools(r *Harness, t *testing.T) {
|
|||
harnessSynced := make(chan struct{})
|
||||
go func() {
|
||||
for {
|
||||
poolHashes, err := r.Node.GetRawMempool()
|
||||
poolHashes, err := r.Client.GetRawMempool()
|
||||
if err != nil {
|
||||
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
|
||||
// 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)
|
||||
}
|
||||
|
||||
|
@ -612,7 +612,7 @@ func TestHarness(t *testing.T) {
|
|||
|
||||
// Current tip should be at a height of numMatureOutputs plus the
|
||||
// required number of blocks for coinbase maturity.
|
||||
nodeInfo, err := mainHarness.Node.GetInfo()
|
||||
nodeInfo, err := mainHarness.Client.GetInfo()
|
||||
if err != nil {
|
||||
t.Fatalf("unable to execute getinfo on node: %v", err)
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ func syncMempools(nodes []*Harness) error {
|
|||
|
||||
retry:
|
||||
for !poolsMatch {
|
||||
firstPool, err := nodes[0].Node.GetRawMempool()
|
||||
firstPool, err := nodes[0].Client.GetRawMempool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ retry:
|
|||
// first node, then we're done. Otherwise, drop back to the top
|
||||
// of the loop and retry after a short wait period.
|
||||
for _, node := range nodes[1:] {
|
||||
nodePool, err := node.Node.GetRawMempool()
|
||||
nodePool, err := node.Client.GetRawMempool()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ retry:
|
|||
var prevHash *chainhash.Hash
|
||||
var prevHeight int32
|
||||
for _, node := range nodes {
|
||||
blockHash, blockHeight, err := node.Node.GetBestBlock()
|
||||
blockHash, blockHeight, err := node.Client.GetBestBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -108,24 +108,24 @@ retry:
|
|||
// therefore in the case of disconnects, "from" will attempt to reestablish a
|
||||
// connection to the "to" harness.
|
||||
func ConnectNode(from *Harness, to *Harness) error {
|
||||
peerInfo, err := from.Node.GetPeerInfo()
|
||||
peerInfo, err := from.Client.GetPeerInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
numPeers := len(peerInfo)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// Block until a new connection has been established.
|
||||
peerInfo, err = from.Node.GetPeerInfo()
|
||||
peerInfo, err = from.Client.GetPeerInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for len(peerInfo) <= numPeers {
|
||||
peerInfo, err = from.Node.GetPeerInfo()
|
||||
peerInfo, err = from.Client.GetPeerInfo()
|
||||
if err != nil {
|
||||
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 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
|
||||
// reconnect to the RPC server.
|
||||
retryCount int64
|
||||
|
@ -220,8 +224,13 @@ func (c *Client) addRequest(jReq *jsonRequest) error {
|
|||
default:
|
||||
}
|
||||
|
||||
element := c.requestList.PushBack(jReq)
|
||||
c.requestMap[jReq.id] = element
|
||||
if !c.batch {
|
||||
element := c.requestList.PushBack(jReq)
|
||||
c.requestMap[jReq.id] = element
|
||||
} else {
|
||||
element := c.batchList.PushBack(jReq)
|
||||
c.requestMap[jReq.id] = element
|
||||
}
|
||||
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
|
||||
// into. It supports both requests (for notification support) and
|
||||
// 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.
|
||||
var resp rawResponse
|
||||
err = json.Unmarshal(respBytes, &resp)
|
||||
var batchResponse json.RawMessage
|
||||
if c.batch {
|
||||
err = json.Unmarshal(respBytes, &batchResponse)
|
||||
} else {
|
||||
err = json.Unmarshal(respBytes, &resp)
|
||||
}
|
||||
if err != nil {
|
||||
// When the response itself isn't a valid JSON-RPC response
|
||||
// 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}
|
||||
return
|
||||
}
|
||||
|
||||
res, err := resp.result()
|
||||
var res []byte
|
||||
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}
|
||||
}
|
||||
|
||||
|
@ -875,7 +930,13 @@ func (c *Client) sendRequest(jReq *jsonRequest) {
|
|||
// POST mode, the command is issued via an HTTP client. Otherwise,
|
||||
// the command is issued via the asynchronous websocket channels.
|
||||
if c.config.HTTPPostMode {
|
||||
c.sendPost(jReq)
|
||||
if c.batch {
|
||||
if err := c.addRequest(jReq); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
} else {
|
||||
c.sendPost(jReq)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -905,6 +966,10 @@ func (c *Client) sendRequest(jReq *jsonRequest) {
|
|||
// future. It handles both websocket and HTTP POST mode depending on the
|
||||
// configuration of the client.
|
||||
func (c *Client) sendCmd(cmd interface{}) chan *response {
|
||||
rpcVersion := btcjson.RpcVersion1
|
||||
if c.batch {
|
||||
rpcVersion = btcjson.RpcVersion2
|
||||
}
|
||||
// Get the method associated with the command.
|
||||
method, err := btcjson.CmdMethod(cmd)
|
||||
if err != nil {
|
||||
|
@ -913,7 +978,7 @@ func (c *Client) sendCmd(cmd interface{}) chan *response {
|
|||
|
||||
// Marshal the command.
|
||||
id := c.NextID()
|
||||
marshalledJSON, err := btcjson.MarshalCmd(id, cmd)
|
||||
marshalledJSON, err := btcjson.MarshalCmd(rpcVersion, id, cmd)
|
||||
if err != nil {
|
||||
return newFutureError(err)
|
||||
}
|
||||
|
@ -927,6 +992,7 @@ func (c *Client) sendCmd(cmd interface{}) chan *response {
|
|||
marshalledJSON: marshalledJSON,
|
||||
responseChan: responseChan,
|
||||
}
|
||||
|
||||
c.sendRequest(jReq)
|
||||
|
||||
return responseChan
|
||||
|
@ -1357,6 +1423,8 @@ func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error
|
|||
httpClient: httpClient,
|
||||
requestMap: make(map[uint64]*list.Element),
|
||||
requestList: list.New(),
|
||||
batch: false,
|
||||
batchList: list.New(),
|
||||
ntfnHandlers: ntfnHandlers,
|
||||
ntfnState: newNotificationState(),
|
||||
sendChan: make(chan []byte, sendBufferSize),
|
||||
|
@ -1397,6 +1465,24 @@ func New(config *ConnConfig, ntfnHandlers *NotificationHandlers) (*Client, error
|
|||
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
|
||||
// a client was created after setting the DisableConnectOnNew field of the
|
||||
// Config struct.
|
||||
|
@ -1534,3 +1620,69 @@ func (c *Client) BackendVersion() (BackendVersion, error) {
|
|||
|
||||
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.
|
||||
id := c.NextID()
|
||||
rawRequest := &btcjson.Request{
|
||||
Jsonrpc: "1.0",
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
ID: id,
|
||||
Method: method,
|
||||
Params: params,
|
||||
|
|
308
rpcserver.go
308
rpcserver.go
|
@ -101,6 +101,9 @@ var (
|
|||
// declared here to avoid the overhead of creating the slice on every
|
||||
// invocation for constant data.
|
||||
gbtCapabilities = []string{"proposal"}
|
||||
|
||||
// JSON 2.0 batched request prefix
|
||||
batchedRequestPrefix = []byte("[")
|
||||
)
|
||||
|
||||
// Errors
|
||||
|
@ -3939,10 +3942,11 @@ 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
|
||||
// parsing it.
|
||||
type parsedRPCCmd struct {
|
||||
id interface{}
|
||||
method string
|
||||
cmd interface{}
|
||||
err *btcjson.RPCError
|
||||
jsonrpc btcjson.RPCVersion
|
||||
id interface{}
|
||||
method string
|
||||
cmd interface{}
|
||||
err *btcjson.RPCError
|
||||
}
|
||||
|
||||
// standardCmdResult checks that a parsed command is a standard Bitcoin JSON-RPC
|
||||
|
@ -3975,9 +3979,11 @@ handled:
|
|||
// is suitable for use in replies if the command is invalid in some way such as
|
||||
// an unregistered command or invalid parameters.
|
||||
func parseCmd(request *btcjson.Request) *parsedRPCCmd {
|
||||
var parsedCmd parsedRPCCmd
|
||||
parsedCmd.id = request.ID
|
||||
parsedCmd.method = request.Method
|
||||
parsedCmd := parsedRPCCmd{
|
||||
jsonrpc: request.Jsonrpc,
|
||||
id: request.ID,
|
||||
method: request.Method,
|
||||
}
|
||||
|
||||
cmd, err := btcjson.UnmarshalCmd(request)
|
||||
if err != nil {
|
||||
|
@ -4004,7 +4010,7 @@ func parseCmd(request *btcjson.Request) *parsedRPCCmd {
|
|||
// createMarshalledReply returns a new marshalled JSON-RPC response given the
|
||||
// passed parameters. It will automatically convert errors that are not of
|
||||
// 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
|
||||
if replyErr != nil {
|
||||
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.
|
||||
|
@ -4059,80 +4125,186 @@ func (s *rpcServer) jsonRPCRead(w http.ResponseWriter, r *http.Request, isAdmin
|
|||
conn.SetReadDeadline(timeZeroVal)
|
||||
|
||||
// Attempt to parse the raw body into a JSON-RPC request.
|
||||
var responseID interface{}
|
||||
var jsonErr error
|
||||
var result interface{}
|
||||
var request btcjson.Request
|
||||
if err := json.Unmarshal(body, &request); err != nil {
|
||||
jsonErr = &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCParse.Code,
|
||||
Message: "Failed to parse request: " + err.Error(),
|
||||
// Setup a close notifier. Since the connection is hijacked,
|
||||
// the CloseNotifer on the ResponseWriter is not available.
|
||||
closeChan := make(chan struct{}, 1)
|
||||
go func() {
|
||||
_, err = conn.Read(make([]byte, 1))
|
||||
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,
|
||||
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 err == nil {
|
||||
// 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.
|
||||
//
|
||||
// A JSON-RPC 2.0 notification is a request with "json-rpc":"2.0", and
|
||||
// without an "id" member. The specification states that notifications
|
||||
// must not be responded to. JSON-RPC 2.0 permits the null value as a
|
||||
// valid request id, therefore such requests are not notifications.
|
||||
//
|
||||
// Bitcoin Core serves requests with "id":null or even an absent "id",
|
||||
// and responds to such requests with "id":null in the response.
|
||||
//
|
||||
// Btcd does not respond to any request without and "id" or "id":null,
|
||||
// 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.
|
||||
//
|
||||
// RPC quirks can be enabled by the user to avoid compatibility issues
|
||||
// with software relying on Core's behavior.
|
||||
if req.ID == nil && !(cfg.RPCQuirks && req.Jsonrpc == "") {
|
||||
return
|
||||
}
|
||||
resp = s.processRequest(&req, isAdmin, closeChan)
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
results = append(results, resp)
|
||||
}
|
||||
}
|
||||
if jsonErr == nil {
|
||||
// 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.
|
||||
//
|
||||
// A JSON-RPC 2.0 notification is a request with "json-rpc":"2.0", and
|
||||
// without an "id" member. The specification states that notifications
|
||||
// must not be responded to. JSON-RPC 2.0 permits the null value as a
|
||||
// valid request id, therefore such requests are not notifications.
|
||||
//
|
||||
// Bitcoin Core serves requests with "id":null or even an absent "id",
|
||||
// and responds to such requests with "id":null in the response.
|
||||
//
|
||||
// Btcd does not respond to any request without and "id" or "id":null,
|
||||
// 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.
|
||||
//
|
||||
// RPC quirks can be enabled by the user to avoid compatibility issues
|
||||
// with software relying on Core's behavior.
|
||||
if request.ID == nil && !(cfg.RPCQuirks && request.Jsonrpc == "") {
|
||||
return
|
||||
|
||||
// Process a batched request
|
||||
if batchedRequest {
|
||||
var batchedRequests []interface{}
|
||||
var resp json.RawMessage
|
||||
err = json.Unmarshal(body, &batchedRequests)
|
||||
if err != nil {
|
||||
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)
|
||||
}
|
||||
|
||||
if resp != nil {
|
||||
results = append(results, resp)
|
||||
}
|
||||
}
|
||||
|
||||
// The parse was at least successful enough to have an ID so
|
||||
// set it for the response.
|
||||
responseID = request.ID
|
||||
if err == nil {
|
||||
// Response with an empty batch error if the batch size is zero
|
||||
if len(batchedRequests) == 0 {
|
||||
jsonErr := &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCInvalidRequest.Code,
|
||||
Message: "Invalid request: empty batch",
|
||||
}
|
||||
resp, err = btcjson.MarshalResponse(btcjson.RpcVersion2, nil, nil, jsonErr)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||
}
|
||||
|
||||
// Setup a close notifier. Since the connection is hijacked,
|
||||
// the CloseNotifer on the ResponseWriter is not available.
|
||||
closeChan := make(chan struct{}, 1)
|
||||
go func() {
|
||||
_, err := conn.Read(make([]byte, 1))
|
||||
if err != nil {
|
||||
close(closeChan)
|
||||
if resp != nil {
|
||||
results = append(results, resp)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// Check if the user is limited and set error if method unauthorized
|
||||
if !isAdmin {
|
||||
if _, ok := rpcLimited[request.Method]; !ok {
|
||||
jsonErr = &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCInvalidParams.Code,
|
||||
Message: "limited user not authorized for this method",
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if jsonErr == 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, jsonErr = s.standardCmdResult(parsedCmd, closeChan)
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
// Marshal the response.
|
||||
msg, err := createMarshalledReply(responseID, result, jsonErr)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||
return
|
||||
if !batchedRequest || batchSize == 0 {
|
||||
// Respond with the first results entry for single requests
|
||||
if len(results) > 0 {
|
||||
msg = results[0]
|
||||
}
|
||||
}
|
||||
|
||||
// Write the response.
|
||||
|
|
578
rpcwebsocket.go
578
rpcwebsocket.go
|
@ -695,7 +695,7 @@ func (*wsNotificationManager) notifyBlockConnected(clients map[chan struct{}]*ws
|
|||
// Notify interested websocket clients about the connected block.
|
||||
ntfn := btcjson.NewBlockConnectedNtfn(block.Hash().String(), block.Height(),
|
||||
block.MsgBlock().Header.Timestamp.Unix())
|
||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
||||
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal block connected notification: "+
|
||||
"%v", err)
|
||||
|
@ -719,7 +719,7 @@ func (*wsNotificationManager) notifyBlockDisconnected(clients map[chan struct{}]
|
|||
// Notify interested websocket clients about the disconnected block.
|
||||
ntfn := btcjson.NewBlockDisconnectedNtfn(block.Hash().String(),
|
||||
block.Height(), block.MsgBlock().Header.Timestamp.Unix())
|
||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
||||
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal block disconnected "+
|
||||
"notification: %v", err)
|
||||
|
@ -765,7 +765,7 @@ func (m *wsNotificationManager) notifyFilteredBlockConnected(clients map[chan st
|
|||
ntfn.SubscribedTxs = subscribedTxs[quitChan]
|
||||
|
||||
// Marshal and queue notification.
|
||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
||||
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal filtered block "+
|
||||
"connected notification: %v", err)
|
||||
|
@ -796,7 +796,7 @@ func (*wsNotificationManager) notifyFilteredBlockDisconnected(clients map[chan s
|
|||
}
|
||||
ntfn := btcjson.NewFilteredBlockDisconnectedNtfn(block.Height(),
|
||||
hex.EncodeToString(w.Bytes()))
|
||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
||||
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal filtered block disconnected "+
|
||||
"notification: %v", err)
|
||||
|
@ -831,7 +831,7 @@ func (m *wsNotificationManager) notifyForNewTx(clients map[chan struct{}]*wsClie
|
|||
}
|
||||
|
||||
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 {
|
||||
rpcsLog.Errorf("Failed to marshal tx notification: %s", err.Error())
|
||||
return
|
||||
|
@ -854,7 +854,7 @@ func (m *wsNotificationManager) notifyForNewTx(clients map[chan struct{}]*wsClie
|
|||
}
|
||||
|
||||
verboseNtfn = btcjson.NewTxAcceptedVerboseNtfn(*rawTx)
|
||||
marshalledJSONVerbose, err = btcjson.MarshalCmd(nil,
|
||||
marshalledJSONVerbose, err = btcjson.MarshalCmd(btcjson.RpcVersion1, nil,
|
||||
verboseNtfn)
|
||||
if err != nil {
|
||||
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) {
|
||||
// Create and marshal the notification.
|
||||
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
|
||||
|
@ -1016,7 +1016,7 @@ func (m *wsNotificationManager) notifyForTxOuts(ops map[wire.OutPoint]map[chan s
|
|||
ntfn := btcjson.NewRecvTxNtfn(txHex, blockDetails(block,
|
||||
tx.Index()))
|
||||
|
||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
||||
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal processedtx notification: %v", err)
|
||||
continue
|
||||
|
@ -1047,7 +1047,7 @@ func (m *wsNotificationManager) notifyRelevantTxAccepted(tx *btcutil.Tx,
|
|||
|
||||
if len(clientsToNotify) != 0 {
|
||||
n := btcjson.NewRelevantTxAcceptedNtfn(txHexString(tx.MsgTx()))
|
||||
marshalled, err := btcjson.MarshalCmd(nil, n)
|
||||
marshalled, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, n)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal notification: %v", err)
|
||||
return
|
||||
|
@ -1323,153 +1323,435 @@ out:
|
|||
break out
|
||||
}
|
||||
|
||||
var request btcjson.Request
|
||||
err = json.Unmarshal(msg, &request)
|
||||
if err != nil {
|
||||
if !c.authenticated {
|
||||
break out
|
||||
}
|
||||
var batchedRequest bool
|
||||
|
||||
jsonErr := &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCParse.Code,
|
||||
Message: "Failed to parse request: " + err.Error(),
|
||||
}
|
||||
reply, err := createMarshalledReply(nil, nil, jsonErr)
|
||||
// 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 {
|
||||
rpcsLog.Errorf("Failed to marshal parse failure "+
|
||||
"reply: %v", err)
|
||||
continue
|
||||
}
|
||||
c.SendMessage(reply, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// A JSON-RPC 2.0 notification is a request with "json-rpc":"2.0", and
|
||||
// without an "id" member. The specification states that notifications
|
||||
// must not be responded to. JSON-RPC 2.0 permits the null value as a
|
||||
// valid request id, therefore such requests are not notifications.
|
||||
//
|
||||
// Bitcoin Core serves requests with "id":null or even an absent "id",
|
||||
// and responds to such requests with "id":null in the response.
|
||||
//
|
||||
// Btcd does not respond to any request without and "id" or "id":null,
|
||||
// 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.
|
||||
//
|
||||
// RPC quirks can be enabled by the user to avoid compatibility issues
|
||||
// with software relying on Core's behavior.
|
||||
if request.ID == nil && !(cfg.RPCQuirks && request.Jsonrpc == "") {
|
||||
if !c.authenticated {
|
||||
break out
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
cmd := parseCmd(&request)
|
||||
if cmd.err != nil {
|
||||
if !c.authenticated {
|
||||
break out
|
||||
}
|
||||
|
||||
reply, err := createMarshalledReply(cmd.id, nil, cmd.err)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal parse failure "+
|
||||
"reply: %v", err)
|
||||
continue
|
||||
}
|
||||
c.SendMessage(reply, nil)
|
||||
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.id, nil, nil)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal authenticate reply: "+
|
||||
"%v", err.Error())
|
||||
continue
|
||||
}
|
||||
c.SendMessage(reply, nil)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check if the client is using limited RPC credentials and
|
||||
// error when not authorized to call this RPC.
|
||||
if !c.isAdmin {
|
||||
if _, ok := rpcLimited[request.Method]; !ok {
|
||||
jsonErr := &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCInvalidParams.Code,
|
||||
Message: "limited user not authorized for this method",
|
||||
// only process requests from authenticated clients
|
||||
if !c.authenticated {
|
||||
break out
|
||||
}
|
||||
// Marshal and send response.
|
||||
reply, err := createMarshalledReply(request.ID, nil, jsonErr)
|
||||
|
||||
jsonErr := &btcjson.RPCError{
|
||||
Code: btcjson.ErrRPCParse.Code,
|
||||
Message: "Failed to parse request: " + err.Error(),
|
||||
}
|
||||
reply, err = createMarshalledReply(btcjson.RpcVersion1, nil, nil, jsonErr)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal parse failure "+
|
||||
"reply: %v", err)
|
||||
rpcsLog.Errorf("Failed to marshal reply: %v", err)
|
||||
continue
|
||||
}
|
||||
c.SendMessage(reply, nil)
|
||||
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
|
||||
}
|
||||
c.SendMessage(reply, nil)
|
||||
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
|
||||
}
|
||||
c.SendMessage(reply, nil)
|
||||
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
|
||||
}
|
||||
c.SendMessage(reply, nil)
|
||||
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.ID, nil, jsonErr)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal parse failure "+
|
||||
"reply: %v", err)
|
||||
continue
|
||||
}
|
||||
c.SendMessage(reply, nil)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Asynchronously handle the request. A semaphore is used to
|
||||
// limit the number of concurrent requests currently being
|
||||
// serviced. If the semaphore can not be acquired, simply wait
|
||||
// until a request finished before reading the next RPC request
|
||||
// from the websocket client.
|
||||
//
|
||||
// This could be a little fancier by timing out and erroring
|
||||
// when it takes too long to service the request, but if that is
|
||||
// done, the read of the next request should not be blocked by
|
||||
// this semaphore, otherwise the next request will be read and
|
||||
// will probably sit here for another few seconds before timing
|
||||
// out as well. This will cause the total timeout duration for
|
||||
// later requests to be much longer than the check here would
|
||||
// imply.
|
||||
//
|
||||
// If a timeout is added, the semaphore acquiring should be
|
||||
// moved inside of the new goroutine with a select statement
|
||||
// that also reads a time.After channel. This will unblock the
|
||||
// read of the next request from the websocket client and allow
|
||||
// many requests to be waited on concurrently.
|
||||
c.serviceRequestSem.acquire()
|
||||
go func() {
|
||||
c.serviceRequest(cmd)
|
||||
c.serviceRequestSem.release()
|
||||
}()
|
||||
}
|
||||
|
||||
// Asynchronously handle the request. A semaphore is used to
|
||||
// limit the number of concurrent requests currently being
|
||||
// serviced. If the semaphore can not be acquired, simply wait
|
||||
// until a request finished before reading the next RPC request
|
||||
// from the websocket client.
|
||||
//
|
||||
// This could be a little fancier by timing out and erroring
|
||||
// when it takes too long to service the request, but if that is
|
||||
// done, the read of the next request should not be blocked by
|
||||
// this semaphore, otherwise the next request will be read and
|
||||
// will probably sit here for another few seconds before timing
|
||||
// out as well. This will cause the total timeout duration for
|
||||
// later requests to be much longer than the check here would
|
||||
// imply.
|
||||
//
|
||||
// If a timeout is added, the semaphore acquiring should be
|
||||
// moved inside of the new goroutine with a select statement
|
||||
// that also reads a time.After channel. This will unblock the
|
||||
// read of the next request from the websocket client and allow
|
||||
// many requests to be waited on concurrently.
|
||||
c.serviceRequestSem.acquire()
|
||||
go func() {
|
||||
c.serviceRequest(cmd)
|
||||
// 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.
|
||||
|
@ -1495,7 +1777,7 @@ func (c *wsClient) serviceRequest(r *parsedRPCCmd) {
|
|||
} else {
|
||||
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 {
|
||||
rpcsLog.Errorf("Failed to marshal reply for <%s> "+
|
||||
"command: %v", r.method, err)
|
||||
|
@ -2125,7 +2407,7 @@ func rescanBlock(wsc *wsClient, lookups *rescanKeys, blk *btcutil.Block) {
|
|||
ntfn := btcjson.NewRecvTxNtfn(txHex,
|
||||
blockDetails(blk, tx.Index()))
|
||||
|
||||
marshalledJSON, err := btcjson.MarshalCmd(nil, ntfn)
|
||||
marshalledJSON, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, ntfn)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal recvtx notification: %v", err)
|
||||
return
|
||||
|
@ -2492,7 +2774,7 @@ fetchRange:
|
|||
hashList[i].String(), blk.Height(),
|
||||
blk.MsgBlock().Header.Timestamp.Unix(),
|
||||
)
|
||||
mn, err := btcjson.MarshalCmd(nil, n)
|
||||
mn, err := btcjson.MarshalCmd(btcjson.RpcVersion1, nil, n)
|
||||
if err != nil {
|
||||
rpcsLog.Errorf("Failed to marshal rescan "+
|
||||
"progress notification: %v", err)
|
||||
|
@ -2637,7 +2919,7 @@ func handleRescan(wsc *wsClient, icmd interface{}) (interface{}, error) {
|
|||
lastBlockHash.String(), lastBlock.Height(),
|
||||
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 "+
|
||||
"notification: %v", err)
|
||||
} else {
|
||||
|
|
Loading…
Reference in a new issue