diff --git a/btcjson/chainsvrwscmds.go b/btcjson/chainsvrwscmds.go
index cc1b458c..ba8a5391 100644
--- a/btcjson/chainsvrwscmds.go
+++ b/btcjson/chainsvrwscmds.go
@@ -31,6 +31,15 @@ func NewNotifyBlocksCmd() *NotifyBlocksCmd {
return &NotifyBlocksCmd{}
}
+// StopNotifyBlocksCmd defines the stopnotifyblocks JSON-RPC command.
+type StopNotifyBlocksCmd struct{}
+
+// NewStopNotifyBlocksCmd returns a new instance which can be used to issue a
+// stopnotifyblocks JSON-RPC command.
+func NewStopNotifyBlocksCmd() *StopNotifyBlocksCmd {
+ return &StopNotifyBlocksCmd{}
+}
+
// NotifyNewTransactionsCmd defines the notifynewtransactions JSON-RPC command.
type NotifyNewTransactionsCmd struct {
Verbose *bool `jsonrpcdefault:"false"`
@@ -47,6 +56,18 @@ func NewNotifyNewTransactionsCmd(verbose *bool) *NotifyNewTransactionsCmd {
}
}
+// StopNotifyNewTransactionsCmd defines the stopnotifynewtransactions JSON-RPC command.
+type StopNotifyNewTransactionsCmd struct{}
+
+// NewStopNotifyNewTransactionsCmd returns a new instance which can be used to issue
+// a stopnotifynewtransactions JSON-RPC command.
+//
+// The parameters which are pointers indicate they are optional. Passing nil
+// for optional parameters will use the default value.
+func NewStopNotifyNewTransactionsCmd() *StopNotifyNewTransactionsCmd {
+ return &StopNotifyNewTransactionsCmd{}
+}
+
// NotifyReceivedCmd defines the notifyreceived JSON-RPC command.
type NotifyReceivedCmd struct {
Addresses []string
@@ -80,6 +101,32 @@ func NewNotifySpentCmd(outPoints []OutPoint) *NotifySpentCmd {
}
}
+// StopNotifyReceivedCmd defines the stopnotifyreceived JSON-RPC command.
+type StopNotifyReceivedCmd struct {
+ Addresses []string
+}
+
+// NewStopNotifyReceivedCmd returns a new instance which can be used to issue a
+// stopnotifyreceived JSON-RPC command.
+func NewStopNotifyReceivedCmd(addresses []string) *StopNotifyReceivedCmd {
+ return &StopNotifyReceivedCmd{
+ Addresses: addresses,
+ }
+}
+
+// StopNotifySpentCmd defines the stopnotifyspent JSON-RPC command.
+type StopNotifySpentCmd struct {
+ OutPoints []OutPoint
+}
+
+// NewStopNotifySpentCmd returns a new instance which can be used to issue a
+// stopnotifyspent JSON-RPC command.
+func NewStopNotifySpentCmd(outPoints []OutPoint) *StopNotifySpentCmd {
+ return &StopNotifySpentCmd{
+ OutPoints: outPoints,
+ }
+}
+
// RescanCmd defines the rescan JSON-RPC command.
type RescanCmd struct {
BeginBlock string
@@ -111,5 +158,9 @@ func init() {
MustRegisterCmd("notifynewtransactions", (*NotifyNewTransactionsCmd)(nil), flags)
MustRegisterCmd("notifyreceived", (*NotifyReceivedCmd)(nil), flags)
MustRegisterCmd("notifyspent", (*NotifySpentCmd)(nil), flags)
+ MustRegisterCmd("stopnotifyblocks", (*StopNotifyBlocksCmd)(nil), flags)
+ MustRegisterCmd("stopnotifynewtransactions", (*StopNotifyNewTransactionsCmd)(nil), flags)
+ MustRegisterCmd("stopnotifyspent", (*StopNotifySpentCmd)(nil), flags)
+ MustRegisterCmd("stopnotifyreceived", (*StopNotifyReceivedCmd)(nil), flags)
MustRegisterCmd("rescan", (*RescanCmd)(nil), flags)
}
diff --git a/btcjson/chainsvrwscmds_test.go b/btcjson/chainsvrwscmds_test.go
index 0c862ba9..27d73c2b 100644
--- a/btcjson/chainsvrwscmds_test.go
+++ b/btcjson/chainsvrwscmds_test.go
@@ -51,6 +51,17 @@ func TestChainSvrWsCmds(t *testing.T) {
marshalled: `{"jsonrpc":"1.0","method":"notifyblocks","params":[],"id":1}`,
unmarshalled: &btcjson.NotifyBlocksCmd{},
},
+ {
+ name: "stopnotifyblocks",
+ newCmd: func() (interface{}, error) {
+ return btcjson.NewCmd("stopnotifyblocks")
+ },
+ staticCmd: func() interface{} {
+ return btcjson.NewStopNotifyBlocksCmd()
+ },
+ marshalled: `{"jsonrpc":"1.0","method":"stopnotifyblocks","params":[],"id":1}`,
+ unmarshalled: &btcjson.StopNotifyBlocksCmd{},
+ },
{
name: "notifynewtransactions",
newCmd: func() (interface{}, error) {
@@ -77,6 +88,17 @@ func TestChainSvrWsCmds(t *testing.T) {
Verbose: btcjson.Bool(true),
},
},
+ {
+ name: "stopnotifynewtransactions",
+ newCmd: func() (interface{}, error) {
+ return btcjson.NewCmd("stopnotifynewtransactions")
+ },
+ staticCmd: func() interface{} {
+ return btcjson.NewStopNotifyNewTransactionsCmd()
+ },
+ marshalled: `{"jsonrpc":"1.0","method":"stopnotifynewtransactions","params":[],"id":1}`,
+ unmarshalled: &btcjson.StopNotifyNewTransactionsCmd{},
+ },
{
name: "notifyreceived",
newCmd: func() (interface{}, error) {
@@ -90,6 +112,19 @@ func TestChainSvrWsCmds(t *testing.T) {
Addresses: []string{"1Address"},
},
},
+ {
+ name: "stopnotifyreceived",
+ newCmd: func() (interface{}, error) {
+ return btcjson.NewCmd("stopnotifyreceived", []string{"1Address"})
+ },
+ staticCmd: func() interface{} {
+ return btcjson.NewStopNotifyReceivedCmd([]string{"1Address"})
+ },
+ marshalled: `{"jsonrpc":"1.0","method":"stopnotifyreceived","params":[["1Address"]],"id":1}`,
+ unmarshalled: &btcjson.StopNotifyReceivedCmd{
+ Addresses: []string{"1Address"},
+ },
+ },
{
name: "notifyspent",
newCmd: func() (interface{}, error) {
@@ -104,6 +139,20 @@ func TestChainSvrWsCmds(t *testing.T) {
OutPoints: []btcjson.OutPoint{{Hash: "123", Index: 0}},
},
},
+ {
+ name: "stopnotifyspent",
+ newCmd: func() (interface{}, error) {
+ return btcjson.NewCmd("stopnotifyspent", `[{"hash":"123","index":0}]`)
+ },
+ staticCmd: func() interface{} {
+ ops := []btcjson.OutPoint{{Hash: "123", Index: 0}}
+ return btcjson.NewStopNotifySpentCmd(ops)
+ },
+ marshalled: `{"jsonrpc":"1.0","method":"stopnotifyspent","params":[[{"hash":"123","index":0}]],"id":1}`,
+ unmarshalled: &btcjson.StopNotifySpentCmd{
+ OutPoints: []btcjson.OutPoint{{Hash: "123", Index: 0}},
+ },
+ },
{
name: "rescan",
newCmd: func() (interface{}, error) {
diff --git a/docs/json_rpc_api.md b/docs/json_rpc_api.md
index eeae5a14..a46fd6d3 100644
--- a/docs/json_rpc_api.md
+++ b/docs/json_rpc_api.md
@@ -649,10 +649,14 @@ user. Click the method name for further details such as parameter and return in
|---|------|-----------|-------------|
|1|[authenticate](#authenticate)|Authenticate the connection against the username and passphrase configured for the RPC server.
NOTE: This is only required if an HTTP Authorization header is not being used.|None|
|2|[notifyblocks](#notifyblocks)|Send notifications when a block is connected or disconnected from the best chain.|[blockconnected](#blockconnected) and [blockdisconnected](#blockdisconnected)|
-|3|[notifyreceived](#notifyreceived)|Send notifications when a txout spends to an address.|[recvtx](#recvtx) and [redeemingtx](#redeemingtx)|
-|4|[notifyspent](#notifyspent)|Send notification when a txout is spent.|[redeemingtx](#redeemingtx)|
-|5|[rescan](#rescan)|Rescan block chain for transactions to addresses and spent transaction outpoints.|[recvtx](#recvtx), [redeemingtx](#redeemingtx), [rescanprogress](#rescanprogress), and [rescanfinished](#rescanfinished) |
-|6|[notifynewtransactions](#notifynewtransactions)|Send notifications for all new transactions as they are accepted into the mempool.|[txaccepted](#txaccepted) or [txacceptedverbose](#txacceptedverbose)|
+|3|[stopnotifyblocks](#stopnotifyblocks)|Cancel registered notifications for whenever a block is connected or disconnected from the main (best) chain. |None|
+|4|[notifyreceived](#notifyreceived)|Send notifications when a txout spends to an address.|[recvtx](#recvtx) and [redeemingtx](#redeemingtx)|
+|5|[stopnotifyreceived](#stopnotifyreceived)|Cancel registered notifications for when a txout spends to any of the passed addresses.|None|
+|6|[notifyspent](#notifyspent)|Send notification when a txout is spent.|[redeemingtx](#redeemingtx)|
+|7|[stopnotifyspent](#stopnotifyspent)|Cancel registered spending notifications for each passed outpoint.|None|
+|8|[rescan](#rescan)|Rescan block chain for transactions to addresses and spent transaction outpoints.|[recvtx](#recvtx), [redeemingtx](#redeemingtx), [rescanprogress](#rescanprogress), and [rescanfinished](#rescanfinished) |
+|9|[notifynewtransactions](#notifynewtransactions)|Send notifications for all new transactions as they are accepted into the mempool.|[txaccepted](#txaccepted) or [txacceptedverbose](#txacceptedverbose)|
+|10|[stopnotifynewtransactions](#stopnotifynewtransactions)|Stop sending either a txaccepted or a txacceptedverbose notification when a new transaction is accepted into the mempool.|None|
**7.2 Method Details**
@@ -667,6 +671,8 @@ user. Click the method name for further details such as parameter and return in
|Returns|Success: Nothing
Failure: Nothing (websocket disconnected)|
[Return to Overview](#ExtensionRequestOverview)
+***
+
| | |
@@ -678,6 +684,18 @@ user. Click the method name for further details such as parameter and return in
|Returns|Nothing|
[Return to Overview](#ExtensionRequestOverview)
+***
+
+
+| | |
+|---|---|
+|Method|stopnotifyblocks|
+|Notifications|None|
+|Parameters|None|
+|Description|Cancel sending notifications for whenever a block is connected or disconnected from the main (best) chain.|
+|Returns|Nothing|
+[Return to Overview](#ExtensionRequestOverview)
+
***
@@ -693,6 +711,19 @@ user. Click the method name for further details such as parameter and return in
***
+
+
+| | |
+|---|---|
+|Method|stopnotifyreceived|
+|Notifications|None|
+|Parameters|1. Addresses (JSON array, required)
`[ (json array of strings)`
`"bitcoinaddress", (string) the bitcoin address`
`...`
`]`|
+|Description|Cancel registered receive notifications for each passed address.|
+|Returns|Nothing|
+[Return to Overview](#ExtensionRequestOverview)
+
+***
+
| | |
@@ -706,6 +737,19 @@ user. Click the method name for further details such as parameter and return in
***
+
+
+| | |
+|---|---|
+|Method|stopnotifyspent|
+|Notifications|None|
+|Parameters|1. Outpoints (JSON array, required)
`[ (JSON array)`
`{ (JSON object)`
`"hash":"data", (string) the hex-encoded bytes of the outpoint hash`
`"index":n (numeric) the txout index of the outpoint`
`},`
`...`
`]`|
+|Description|Cancel registered spending notifications for each passed outpoint.|
+|Returns|Nothing|
+[Return to Overview](#ExtensionRequestOverview)
+
+***
+
| | |
@@ -730,6 +774,19 @@ user. Click the method name for further details such as parameter and return in
|Returns|Nothing|
[Return to Overview](#ExtensionRequestOverview)
+***
+
+
+
+| | |
+|---|---|
+|Method|stopnotifynewtransactions|
+|Notifications|None|
+|Parameters|None|
+|Description|Stop sending either a [txaccepted](#txaccepted) or a [txacceptedverbose](#txacceptedverbose) notification when a new transaction is accepted into the mempool.|
+|Returns|Nothing|
+[Return to Overview](#ExtensionRequestOverview)
+
### 8. Notifications (Websocket-specific)
diff --git a/rpcserverhelp.go b/rpcserverhelp.go
index 4ffbc326..f14250e0 100644
--- a/rpcserverhelp.go
+++ b/rpcserverhelp.go
@@ -475,15 +475,25 @@ var helpDescsEnUS = map[string]string{
// NotifyBlocksCmd help.
"notifyblocks--synopsis": "Request notifications for whenever a block is connected or disconnected from the main (best) chain.",
+ // StopNotifyBlocksCmd help.
+ "stopnotifyblocks--synopsis": "Cancel registered notifications for whenever a block is connected or disconnected from the main (best) chain.",
+
// NotifyNewTransactionsCmd help.
"notifynewtransactions--synopsis": "Send either a txaccepted or a txacceptedverbose notification when a new transaction is accepted into the mempool.",
"notifynewtransactions-verbose": "Specifies which type of notification to receive. If verbose is true, then the caller receives txacceptedverbose, otherwise the caller receives txaccepted",
+ // StopNotifyNewTransactionsCmd help.
+ "stopnotifynewtransactions--synopsis": "Stop sending either a txaccepted or a txacceptedverbose notification when a new transaction is accepted into the mempool.",
+
// NotifyReceivedCmd help.
"notifyreceived--synopsis": "Send a recvtx notification when a transaction added to mempool or appears in a newly-attached block contains a txout pkScript sending to any of the passed addresses.\n" +
"Matching outpoints are automatically registered for redeemingtx notifications.",
"notifyreceived-addresses": "List of address to receive notifications about",
+ // StopNotifyReceivedCmd help.
+ "stopnotifyreceived--synopsis": "Cancel registered receive notifications for each passed address.",
+ "stopnotifyreceived-addresses": "List of address to cancel receive notifications for",
+
// OutPoint help.
"outpoint-hash": "The hex-encoded bytes of the outpoint hash",
"outpoint-index": "The index of the outpoint",
@@ -492,6 +502,10 @@ var helpDescsEnUS = map[string]string{
"notifyspent--synopsis": "Send a redeemingtx notification when a transaction spending an outpoint appears in mempool (if relayed to this btcd instance) and when such a transaction first appears in a newly-attached block.",
"notifyspent-outpoints": "List of transaction outpoints to monitor.",
+ // StopNotifySpentCmd help.
+ "stopnotifyspent--synopsis": "Cancel registered spending notifications for each passed outpoint.",
+ "stopnotifyspent-outpoints": "List of transaction outpoints to stop monitoring.",
+
// Rescan help.
"rescan--synopsis": "Rescan block chain for transactions to addresses.\n" +
"When the endblock parameter is omitted, the rescan continues through the best block in the main chain.\n" +
@@ -547,11 +561,15 @@ var rpcResultTypes = map[string][]interface{}{
"verifymessage": []interface{}{(*bool)(nil)},
// Websocket commands.
- "notifyblocks": nil,
- "notifynewtransactions": nil,
- "notifyreceived": nil,
- "notifyspent": nil,
- "rescan": nil,
+ "notifyblocks": nil,
+ "stopnotifyblocks": nil,
+ "notifynewtransactions": nil,
+ "stopnotifynewtransactions": nil,
+ "notifyreceived": nil,
+ "stopnotifyreceived": nil,
+ "notifyspent": nil,
+ "stopnotifyspent": nil,
+ "rescan": nil,
}
// helpCacher provides a concurrent safe type that provides help and usage for
diff --git a/rpcwebsocket.go b/rpcwebsocket.go
index 976c09e7..665fda72 100644
--- a/rpcwebsocket.go
+++ b/rpcwebsocket.go
@@ -49,12 +49,16 @@ type wsCommandHandler func(*wsClient, interface{}) (interface{}, error)
// causes a dependency loop.
var wsHandlers map[string]wsCommandHandler
var wsHandlersBeforeInit = map[string]wsCommandHandler{
- "help": handleWebsocketHelp,
- "notifyblocks": handleNotifyBlocks,
- "notifynewtransactions": handleNotifyNewTransactions,
- "notifyreceived": handleNotifyReceived,
- "notifyspent": handleNotifySpent,
- "rescan": handleRescan,
+ "help": handleWebsocketHelp,
+ "notifyblocks": handleNotifyBlocks,
+ "notifynewtransactions": handleNotifyNewTransactions,
+ "notifyreceived": handleNotifyReceived,
+ "notifyspent": handleNotifySpent,
+ "stopnotifyblocks": handleStopNotifyBlocks,
+ "stopnotifynewtransactions": handleStopNotifyNewTransactions,
+ "stopnotifyspent": handleStopNotifySpent,
+ "stopnotifyreceived": handleStopNotifyReceived,
+ "rescan": handleRescan,
}
// wsAsyncHandlers holds the websocket commands which should be run
@@ -1451,6 +1455,13 @@ func handleNotifyBlocks(wsc *wsClient, icmd interface{}) (interface{}, error) {
return nil, nil
}
+// handleStopNotifyBlocks implements the stopnotifyblocks command extension for
+// websocket connections.
+func handleStopNotifyBlocks(wsc *wsClient, icmd interface{}) (interface{}, error) {
+ wsc.server.ntfnMgr.UnregisterBlockUpdates(wsc)
+ return nil, nil
+}
+
// handleNotifySpent implements the notifyspent command extension for
// websocket connections.
func handleNotifySpent(wsc *wsClient, icmd interface{}) (interface{}, error) {
@@ -1459,15 +1470,11 @@ func handleNotifySpent(wsc *wsClient, icmd interface{}) (interface{}, error) {
return nil, btcjson.ErrRPCInternal
}
- outpoints := make([]*wire.OutPoint, 0, len(cmd.OutPoints))
- for i := range cmd.OutPoints {
- blockHash, err := wire.NewShaHashFromStr(cmd.OutPoints[i].Hash)
- if err != nil {
- return nil, rpcDecodeHexError(cmd.OutPoints[i].Hash)
- }
- index := cmd.OutPoints[i].Index
- outpoints = append(outpoints, wire.NewOutPoint(blockHash, index))
+ outpoints, err := deserializeOutpoints(cmd.OutPoints)
+ if err != nil {
+ return nil, err
}
+
wsc.server.ntfnMgr.RegisterSpentRequests(wsc, outpoints)
return nil, nil
}
@@ -1485,6 +1492,13 @@ func handleNotifyNewTransactions(wsc *wsClient, icmd interface{}) (interface{},
return nil, nil
}
+// handleStopNotifyNewTransations implements the stopnotifynewtransactions
+// command extension for websocket connections.
+func handleStopNotifyNewTransactions(wsc *wsClient, icmd interface{}) (interface{}, error) {
+ wsc.server.ntfnMgr.UnregisterNewMempoolTxsUpdates(wsc)
+ return nil, nil
+}
+
// handleNotifyReceived implements the notifyreceived command extension for
// websocket connections.
func handleNotifyReceived(wsc *wsClient, icmd interface{}) (interface{}, error) {
@@ -1495,18 +1509,88 @@ func handleNotifyReceived(wsc *wsClient, icmd interface{}) (interface{}, error)
// Decode addresses to validate input, but the strings slice is used
// directly if these are all ok.
+ err := checkAddressValidity(cmd.Addresses)
+ if err != nil {
+ return nil, err
+ }
+
+ wsc.server.ntfnMgr.RegisterTxOutAddressRequests(wsc, cmd.Addresses)
+ return nil, nil
+}
+
+// handleStopNotifySpent implements the stopnotifyspent command extension for
+// websocket connections.
+func handleStopNotifySpent(wsc *wsClient, icmd interface{}) (interface{}, error) {
+ cmd, ok := icmd.(*btcjson.StopNotifySpentCmd)
+ if !ok {
+ return nil, btcjson.ErrRPCInternal
+ }
+
+ outpoints, err := deserializeOutpoints(cmd.OutPoints)
+ if err != nil {
+ return nil, err
+ }
+
+ for _, outpoint := range outpoints {
+ wsc.server.ntfnMgr.UnregisterSpentRequest(wsc, outpoint)
+ }
+
+ return nil, nil
+}
+
+// handleStopNotifyReceived implements the stopnotifyreceived command extension
+// for websocket connections.
+func handleStopNotifyReceived(wsc *wsClient, icmd interface{}) (interface{}, error) {
+ cmd, ok := icmd.(*btcjson.StopNotifyReceivedCmd)
+ if !ok {
+ return nil, btcjson.ErrRPCInternal
+ }
+
+ // Decode addresses to validate input, but the strings slice is used
+ // directly if these are all ok.
+ err := checkAddressValidity(cmd.Addresses)
+ if err != nil {
+ return nil, err
+ }
+
for _, addr := range cmd.Addresses {
+ wsc.server.ntfnMgr.UnregisterTxOutAddressRequest(wsc, addr)
+ }
+
+ return nil, nil
+}
+
+// checkAddressValidity checks the validity of each address in the passed
+// string slice. It does this by attempting to decode each address using the
+// current active network parameters. If any single address fails to decode
+// properly, the function returns an error. Otherwise, nil is returned.
+func checkAddressValidity(addrs []string) error {
+ for _, addr := range addrs {
_, err := btcutil.DecodeAddress(addr, activeNetParams.Params)
if err != nil {
- return nil, &btcjson.RPCError{
+ return &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidAddressOrKey,
Message: fmt.Sprintf("Invalid address or key: %v",
addr),
}
}
}
- wsc.server.ntfnMgr.RegisterTxOutAddressRequests(wsc, cmd.Addresses)
- return nil, nil
+ return nil
+}
+
+// deserializeOutpoints deserializes each serialized outpoint.
+func deserializeOutpoints(serializedOuts []btcjson.OutPoint) ([]*wire.OutPoint, error) {
+ outpoints := make([]*wire.OutPoint, 0, len(serializedOuts))
+ for i := range serializedOuts {
+ blockHash, err := wire.NewShaHashFromStr(serializedOuts[i].Hash)
+ if err != nil {
+ return nil, rpcDecodeHexError(serializedOuts[i].Hash)
+ }
+ index := serializedOuts[i].Index
+ outpoints = append(outpoints, wire.NewOutPoint(blockHash, index))
+ }
+
+ return outpoints, nil
}
type rescanKeys struct {