2013-08-06 23:55:22 +02:00
|
|
|
// Copyright (c) 2013 Conformal Systems LLC.
|
|
|
|
// Use of this source code is governed by an ISC
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2013-08-14 22:55:31 +02:00
|
|
|
"bytes"
|
|
|
|
"code.google.com/p/go.net/websocket"
|
2013-11-04 19:31:56 +01:00
|
|
|
"container/list"
|
2013-11-07 17:25:11 +01:00
|
|
|
"crypto/ecdsa"
|
|
|
|
"crypto/elliptic"
|
|
|
|
"crypto/rand"
|
|
|
|
_ "crypto/sha512" // for cert generation
|
|
|
|
"crypto/tls"
|
|
|
|
"crypto/x509"
|
|
|
|
"crypto/x509/pkix"
|
2013-10-01 22:43:45 +02:00
|
|
|
"encoding/base64"
|
2013-08-14 22:55:31 +02:00
|
|
|
"encoding/hex"
|
2013-08-06 23:55:22 +02:00
|
|
|
"encoding/json"
|
2013-11-07 17:25:11 +01:00
|
|
|
"encoding/pem"
|
2013-08-14 22:55:31 +02:00
|
|
|
"errors"
|
2013-10-01 22:43:45 +02:00
|
|
|
"fmt"
|
2013-08-06 23:55:22 +02:00
|
|
|
"github.com/conformal/btcchain"
|
2013-08-14 22:55:31 +02:00
|
|
|
"github.com/conformal/btcdb"
|
2013-08-06 23:55:22 +02:00
|
|
|
"github.com/conformal/btcjson"
|
|
|
|
"github.com/conformal/btcscript"
|
2013-10-08 18:37:06 +02:00
|
|
|
"github.com/conformal/btcutil"
|
2013-08-06 23:55:22 +02:00
|
|
|
"github.com/conformal/btcwire"
|
2013-11-06 17:20:36 +01:00
|
|
|
"github.com/conformal/btcws"
|
2013-08-06 23:55:22 +02:00
|
|
|
"math/big"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
2013-11-07 17:25:11 +01:00
|
|
|
"os"
|
2013-08-06 23:55:22 +02:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
2013-10-03 01:33:42 +02:00
|
|
|
"sync/atomic"
|
2013-11-07 17:25:11 +01:00
|
|
|
"time"
|
2013-08-06 23:55:22 +02:00
|
|
|
)
|
|
|
|
|
2013-08-14 22:55:31 +02:00
|
|
|
// Errors
|
|
|
|
var (
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
// ErrBadParamsField describes an error where the parameters JSON
|
|
|
|
// field cannot be properly parsed.
|
2013-08-14 22:55:31 +02:00
|
|
|
ErrBadParamsField = errors.New("bad params field")
|
|
|
|
)
|
|
|
|
|
2013-08-06 23:55:22 +02:00
|
|
|
// rpcServer holds the items the rpc server may need to access (config,
|
|
|
|
// shutdown, main server, etc.)
|
|
|
|
type rpcServer struct {
|
2013-10-03 01:33:42 +02:00
|
|
|
started int32
|
|
|
|
shutdown int32
|
2013-08-07 17:38:39 +02:00
|
|
|
server *server
|
2013-08-14 22:55:31 +02:00
|
|
|
ws wsContext
|
2013-08-07 17:38:39 +02:00
|
|
|
wg sync.WaitGroup
|
|
|
|
username string
|
|
|
|
password string
|
|
|
|
listeners []net.Listener
|
2013-08-14 22:55:31 +02:00
|
|
|
quit chan int
|
|
|
|
}
|
|
|
|
|
|
|
|
// wsContext holds the items the RPC server needs to handle websocket
|
|
|
|
// connections for wallets.
|
|
|
|
type wsContext struct {
|
2013-11-04 19:31:56 +01:00
|
|
|
sync.RWMutex
|
2013-10-23 17:07:00 +02:00
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
// connections holds a map of each currently connected wallet
|
|
|
|
// listener as the key.
|
|
|
|
connections map[chan []byte]*requestContexts
|
2013-08-14 22:55:31 +02:00
|
|
|
|
|
|
|
// Any chain notifications meant to be received by every connected
|
|
|
|
// wallet are sent across this channel.
|
|
|
|
walletNotificationMaster chan []byte
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
// Map of address hash to list of notificationCtx. This is the global
|
|
|
|
// list we actually use for notifications, we also keep a list in the
|
|
|
|
// requestContexts to make removal from this list on connection close
|
|
|
|
// less horrendously expensive.
|
|
|
|
txNotifications map[string]*list.List
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
// Map of outpoint to list of notificationCtx.
|
|
|
|
spentNotifications map[btcwire.OutPoint]*list.List
|
2013-11-04 17:59:48 +01:00
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
// Map of shas to list of notificationCtx.
|
|
|
|
minedTxNotifications map[btcwire.ShaHash]*list.List
|
|
|
|
}
|
|
|
|
|
|
|
|
type notificationCtx struct {
|
|
|
|
id interface{}
|
|
|
|
connection chan []byte
|
|
|
|
rc *requestContexts
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddTxRequest adds the request context for new transaction notifications.
|
2013-11-04 19:31:56 +01:00
|
|
|
func (r *wsContext) AddTxRequest(walletNotification chan []byte, rc *requestContexts, addrhash string, id interface{}) {
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
nc := ¬ificationCtx{
|
|
|
|
id: id,
|
|
|
|
connection: walletNotification,
|
|
|
|
rc: rc,
|
|
|
|
}
|
|
|
|
|
|
|
|
clist, ok := r.txNotifications[addrhash]
|
|
|
|
if !ok {
|
|
|
|
clist = list.New()
|
|
|
|
r.txNotifications[addrhash] = clist
|
|
|
|
}
|
|
|
|
|
|
|
|
clist.PushBack(nc)
|
|
|
|
|
2013-11-04 17:59:48 +01:00
|
|
|
rc.txRequests[addrhash] = id
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
}
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
func (r *wsContext) removeGlobalTxRequest(walletNotification chan []byte, addrhash string) {
|
|
|
|
clist := r.txNotifications[addrhash]
|
2013-11-12 22:24:32 +01:00
|
|
|
var enext *list.Element
|
|
|
|
for e := clist.Front(); e != nil; e = enext {
|
|
|
|
enext = e.Next()
|
2013-11-04 19:31:56 +01:00
|
|
|
ctx := e.Value.(*notificationCtx)
|
|
|
|
if ctx.connection == walletNotification {
|
|
|
|
clist.Remove(e)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if clist.Len() == 0 {
|
|
|
|
delete(r.txNotifications, addrhash)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
// AddSpentRequest adds a request context for notifications of a spent
|
|
|
|
// Outpoint.
|
2013-11-04 19:31:56 +01:00
|
|
|
func (r *wsContext) AddSpentRequest(walletNotification chan []byte, rc *requestContexts, op *btcwire.OutPoint, id interface{}) {
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
nc := ¬ificationCtx{
|
|
|
|
id: id,
|
|
|
|
connection: walletNotification,
|
|
|
|
rc: rc,
|
|
|
|
}
|
|
|
|
clist, ok := r.spentNotifications[*op]
|
|
|
|
if !ok {
|
|
|
|
clist = list.New()
|
|
|
|
r.spentNotifications[*op] = clist
|
|
|
|
}
|
|
|
|
clist.PushBack(nc)
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
rc.spentRequests[*op] = id
|
|
|
|
}
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
func (r *wsContext) removeGlobalSpentRequest(walletNotification chan []byte, op *btcwire.OutPoint) {
|
|
|
|
clist := r.spentNotifications[*op]
|
2013-11-12 22:24:32 +01:00
|
|
|
var enext *list.Element
|
|
|
|
for e := clist.Front(); e != nil; e = enext {
|
|
|
|
enext = e.Next()
|
2013-11-04 19:31:56 +01:00
|
|
|
ctx := e.Value.(*notificationCtx)
|
|
|
|
if ctx.connection == walletNotification {
|
|
|
|
clist.Remove(e)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if clist.Len() == 0 {
|
|
|
|
delete(r.spentNotifications, *op)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
// RemoveSpentRequest removes a request context for notifications of a
|
|
|
|
// spent Outpoint.
|
2013-11-04 19:31:56 +01:00
|
|
|
func (r *wsContext) RemoveSpentRequest(walletNotification chan []byte, rc *requestContexts, op *btcwire.OutPoint) {
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
r.removeGlobalSpentRequest(walletNotification, op)
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
delete(rc.spentRequests, *op)
|
|
|
|
}
|
|
|
|
|
2013-10-23 17:07:00 +02:00
|
|
|
// AddMinedTxRequest adds request contexts for notifications of a
|
|
|
|
// mined transaction.
|
2013-11-04 19:31:56 +01:00
|
|
|
func (r *wsContext) AddMinedTxRequest(walletNotification chan []byte, txID *btcwire.ShaHash) {
|
2013-10-23 17:07:00 +02:00
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
rc := r.connections[walletNotification]
|
|
|
|
|
|
|
|
nc := ¬ificationCtx{
|
|
|
|
connection: walletNotification,
|
|
|
|
rc: rc,
|
|
|
|
}
|
|
|
|
clist, ok := r.minedTxNotifications[*txID]
|
|
|
|
if !ok {
|
|
|
|
clist = list.New()
|
|
|
|
r.minedTxNotifications[*txID] = clist
|
|
|
|
}
|
|
|
|
clist.PushBack(nc)
|
2013-10-23 17:07:00 +02:00
|
|
|
rc.minedTxRequests[*txID] = true
|
|
|
|
}
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
func (r *wsContext) removeGlobalMinedTxRequest(walletNotification chan []byte, txID *btcwire.ShaHash) {
|
|
|
|
clist := r.minedTxNotifications[*txID]
|
2013-11-12 22:24:32 +01:00
|
|
|
var enext *list.Element
|
|
|
|
for e := clist.Front(); e != nil; e = enext {
|
|
|
|
enext = e.Next()
|
2013-11-04 19:31:56 +01:00
|
|
|
ctx := e.Value.(*notificationCtx)
|
|
|
|
if ctx.connection == walletNotification {
|
|
|
|
clist.Remove(e)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if clist.Len() == 0 {
|
|
|
|
delete(r.minedTxNotifications, *txID)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-23 17:07:00 +02:00
|
|
|
// RemoveMinedTxRequest removes request contexts for notifications of a
|
|
|
|
// mined transaction.
|
2013-11-04 19:31:56 +01:00
|
|
|
func (r *wsContext) RemoveMinedTxRequest(walletNotification chan []byte, rc *requestContexts, txID *btcwire.ShaHash) {
|
2013-10-23 17:07:00 +02:00
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
|
2013-11-11 20:19:12 +01:00
|
|
|
r.removeMinedTxRequest(walletNotification, rc, txID)
|
|
|
|
}
|
|
|
|
|
|
|
|
// removeMinedTxRequest removes request contexts for notifications of a
|
|
|
|
// mined transaction without grabbing any locks.
|
|
|
|
func (r *wsContext) removeMinedTxRequest(walletNotification chan []byte, rc *requestContexts, txID *btcwire.ShaHash) {
|
2013-11-04 19:31:56 +01:00
|
|
|
r.removeGlobalMinedTxRequest(walletNotification, txID)
|
2013-10-23 17:07:00 +02:00
|
|
|
delete(rc.minedTxRequests, *txID)
|
|
|
|
}
|
|
|
|
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
// CloseListeners removes all request contexts for notifications sent
|
|
|
|
// to a wallet notification channel and closes the channel to stop all
|
|
|
|
// goroutines currently serving that wallet.
|
2013-11-04 19:31:56 +01:00
|
|
|
func (r *wsContext) CloseListeners(walletNotification chan []byte) {
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
r.Lock()
|
|
|
|
defer r.Unlock()
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
delete(r.connections, walletNotification)
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
close(walletNotification)
|
|
|
|
}
|
|
|
|
|
|
|
|
// requestContexts holds all requests for a single wallet connection.
|
|
|
|
type requestContexts struct {
|
|
|
|
// txRequests maps between a 160-byte pubkey hash and the JSON
|
|
|
|
// id of the requester so replies can be correctly routed back
|
2013-11-04 17:59:48 +01:00
|
|
|
// to the correct btcwallet callback. The key must be a stringified
|
|
|
|
// address hash.
|
|
|
|
txRequests map[string]interface{}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
|
|
|
// spentRequests maps between an Outpoint of an unspent
|
|
|
|
// transaction output and the JSON id of the requester so
|
|
|
|
// replies can be correctly routed back to the correct
|
|
|
|
// btcwallet callback.
|
|
|
|
spentRequests map[btcwire.OutPoint]interface{}
|
2013-10-23 17:07:00 +02:00
|
|
|
|
|
|
|
// minedTxRequests holds a set of transaction IDs (tx hashes) of
|
|
|
|
// transactions created by a wallet. A wallet may request
|
|
|
|
// notifications of when a tx it created is mined into a block and
|
|
|
|
// removed from the mempool. Once a tx has been mined into a
|
|
|
|
// block, wallet may remove the raw transaction from its unmined tx
|
|
|
|
// pool.
|
|
|
|
minedTxRequests map[btcwire.ShaHash]bool
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Start is used by server.go to start the rpc listener.
|
|
|
|
func (s *rpcServer) Start() {
|
2013-10-03 01:33:42 +02:00
|
|
|
if atomic.AddInt32(&s.started, 1) != 1 {
|
2013-08-06 23:55:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Trace("Starting RPC server")
|
2013-10-23 17:45:43 +02:00
|
|
|
rpcServeMux := http.NewServeMux()
|
|
|
|
httpServer := &http.Server{Handler: rpcServeMux}
|
|
|
|
rpcServeMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
2013-10-01 22:43:45 +02:00
|
|
|
login := s.username + ":" + s.password
|
|
|
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
|
2013-10-03 14:12:18 +02:00
|
|
|
authhdr := r.Header["Authorization"]
|
|
|
|
if len(authhdr) > 0 && authhdr[0] == auth {
|
2013-10-01 22:43:45 +02:00
|
|
|
jsonRPCRead(w, r, s)
|
|
|
|
} else {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Warnf("Auth failure.")
|
2013-10-01 22:43:45 +02:00
|
|
|
jsonAuthFail(w, r, s)
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
})
|
2013-08-14 22:55:31 +02:00
|
|
|
go s.walletListenerDuplicator()
|
2013-11-20 00:26:33 +01:00
|
|
|
wsServer := websocket.Server{
|
|
|
|
Handler: websocket.Handler(func(ws *websocket.Conn) {
|
|
|
|
s.walletReqsNotifications(ws)
|
|
|
|
}),
|
|
|
|
Handshake: func(_ *websocket.Config, r *http.Request) error {
|
|
|
|
login := s.username + ":" + s.password
|
|
|
|
auth := "Basic " + base64.StdEncoding.EncodeToString([]byte(login))
|
|
|
|
authhdr := r.Header["Authorization"]
|
|
|
|
if len(authhdr) <= 0 || authhdr[0] != auth {
|
|
|
|
return errors.New("auth failure")
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
},
|
|
|
|
}
|
|
|
|
rpcServeMux.Handle("/wallet", wsServer)
|
2013-08-07 17:38:39 +02:00
|
|
|
for _, listener := range s.listeners {
|
2013-09-13 00:24:37 +02:00
|
|
|
s.wg.Add(1)
|
2013-08-07 17:38:39 +02:00
|
|
|
go func(listener net.Listener) {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("RPC server listening on %s", listener.Addr())
|
2013-08-07 17:38:39 +02:00
|
|
|
httpServer.Serve(listener)
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Tracef("RPC listener done for %s", listener.Addr())
|
2013-08-07 17:38:39 +02:00
|
|
|
s.wg.Done()
|
|
|
|
}(listener)
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Stop is used by server.go to stop the rpc listener.
|
|
|
|
func (s *rpcServer) Stop() error {
|
2013-10-03 01:33:42 +02:00
|
|
|
if atomic.AddInt32(&s.shutdown, 1) != 1 {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("RPC server is already in the process of shutting down")
|
2013-08-06 23:55:22 +02:00
|
|
|
return nil
|
|
|
|
}
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Warnf("RPC server shutting down")
|
2013-08-07 17:38:39 +02:00
|
|
|
for _, listener := range s.listeners {
|
|
|
|
err := listener.Close()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Problem shutting down rpc: %v", err)
|
2013-08-07 17:38:39 +02:00
|
|
|
return err
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("RPC server shutdown complete")
|
2013-08-06 23:55:22 +02:00
|
|
|
s.wg.Wait()
|
2013-08-14 22:55:31 +02:00
|
|
|
close(s.quit)
|
2013-08-06 23:55:22 +02:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-11-07 17:25:11 +01:00
|
|
|
// genkey generates a key/cert pair to the paths provided.
|
|
|
|
// TODO(oga) wrap errors with fmt.Errorf for more context?
|
|
|
|
func genKey(key, cert string) error {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("Generating TLS certificates...")
|
2013-11-07 17:25:11 +01:00
|
|
|
priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
notBefore := time.Now()
|
|
|
|
notAfter := notBefore.Add(10 * 365 * 24 * time.Hour)
|
|
|
|
|
|
|
|
// end of ASN.1 time
|
|
|
|
endOfTime := time.Date(2049, 12, 31, 23, 59, 59, 0, time.UTC)
|
|
|
|
if notAfter.After(endOfTime) {
|
|
|
|
notAfter = endOfTime
|
|
|
|
}
|
|
|
|
|
|
|
|
template := x509.Certificate{
|
|
|
|
SerialNumber: new(big.Int).SetInt64(0),
|
|
|
|
Subject: pkix.Name{
|
|
|
|
Organization: []string{"btcd autogenerated cert"},
|
|
|
|
},
|
|
|
|
NotBefore: notBefore,
|
|
|
|
NotAfter: notAfter,
|
|
|
|
|
|
|
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageCertSign,
|
|
|
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
|
|
|
IsCA: true, // so can sign self.
|
|
|
|
BasicConstraintsValid: true,
|
|
|
|
}
|
|
|
|
|
|
|
|
host, err := os.Hostname()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-11-19 18:02:40 +01:00
|
|
|
template.DNSNames = append(template.DNSNames, host, "localhost")
|
2013-11-07 17:25:11 +01:00
|
|
|
|
2013-11-19 23:35:00 +01:00
|
|
|
needLocalhost := true
|
2013-11-07 17:25:11 +01:00
|
|
|
addrs, err := net.InterfaceAddrs()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, a := range addrs {
|
|
|
|
ip, _, err := net.ParseCIDR(a.String())
|
|
|
|
if err == nil {
|
2013-11-19 23:35:00 +01:00
|
|
|
if ip.String() == "127.0.0.1" {
|
|
|
|
needLocalhost = false
|
|
|
|
}
|
2013-11-07 17:25:11 +01:00
|
|
|
template.IPAddresses = append(template.IPAddresses, ip)
|
|
|
|
}
|
|
|
|
}
|
2013-11-19 23:35:00 +01:00
|
|
|
if needLocalhost {
|
|
|
|
localHost := net.ParseIP("127.0.0.1")
|
|
|
|
template.IPAddresses = append(template.IPAddresses, localHost)
|
|
|
|
}
|
2013-11-07 17:25:11 +01:00
|
|
|
|
|
|
|
derBytes, err := x509.CreateCertificate(rand.Reader, &template,
|
|
|
|
&template, &priv.PublicKey, priv)
|
|
|
|
if err != nil {
|
|
|
|
fmt.Fprintf(os.Stderr, "Failed to create certificate: %v\n", err)
|
|
|
|
os.Exit(-1)
|
|
|
|
}
|
|
|
|
|
|
|
|
certOut, err := os.Create(cert)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
|
|
|
|
certOut.Close()
|
|
|
|
|
|
|
|
keyOut, err := os.OpenFile(key, os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
|
|
|
|
0600)
|
|
|
|
if err != nil {
|
|
|
|
os.Remove(cert)
|
|
|
|
return err
|
|
|
|
}
|
2013-11-20 21:34:37 +01:00
|
|
|
keybytes, err := x509.MarshalECPrivateKey(priv)
|
2013-11-07 17:25:11 +01:00
|
|
|
if err != nil {
|
|
|
|
os.Remove(key)
|
|
|
|
os.Remove(cert)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: keybytes})
|
|
|
|
keyOut.Close()
|
|
|
|
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("Done generating TLS certificates")
|
2013-11-07 17:25:11 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-09-18 07:36:40 +02:00
|
|
|
// newRPCServer returns a new instance of the rpcServer struct.
|
2013-11-14 02:51:37 +01:00
|
|
|
func newRPCServer(listenAddrs []string, s *server) (*rpcServer, error) {
|
2013-08-06 23:55:22 +02:00
|
|
|
rpc := rpcServer{
|
|
|
|
server: s,
|
2013-10-20 18:05:35 +02:00
|
|
|
quit: make(chan int),
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
|
|
|
// Get values from config
|
2013-09-18 07:36:40 +02:00
|
|
|
rpc.username = cfg.RPCUser
|
|
|
|
rpc.password = cfg.RPCPass
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-08-14 22:55:31 +02:00
|
|
|
// initialize memory for websocket connections
|
2013-11-04 19:31:56 +01:00
|
|
|
rpc.ws.connections = make(map[chan []byte]*requestContexts)
|
2013-08-14 22:55:31 +02:00
|
|
|
rpc.ws.walletNotificationMaster = make(chan []byte)
|
2013-11-04 19:31:56 +01:00
|
|
|
rpc.ws.txNotifications = make(map[string]*list.List)
|
|
|
|
rpc.ws.spentNotifications = make(map[btcwire.OutPoint]*list.List)
|
|
|
|
rpc.ws.minedTxNotifications = make(map[btcwire.ShaHash]*list.List)
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-11-20 22:55:36 +01:00
|
|
|
// check for existence of cert file and key file
|
2013-11-07 17:25:11 +01:00
|
|
|
if !fileExists(cfg.RPCKey) && !fileExists(cfg.RPCCert) {
|
|
|
|
// if both files do not exist, we generate them.
|
|
|
|
err := genKey(cfg.RPCKey, cfg.RPCCert)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
keypair, err := tls.LoadX509KeyPair(cfg.RPCCert, cfg.RPCKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
tlsConfig := tls.Config{
|
|
|
|
Certificates: []tls.Certificate{keypair},
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(oga) this code is similar to that in server, should be
|
2013-11-14 02:51:37 +01:00
|
|
|
// factored into something shared.
|
|
|
|
ipv4ListenAddrs, ipv6ListenAddrs, err := parseListeners(listenAddrs)
|
2013-11-07 17:25:11 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2013-11-14 02:51:37 +01:00
|
|
|
listeners := make([]net.Listener, 0,
|
|
|
|
len(ipv6ListenAddrs)+len(ipv4ListenAddrs))
|
|
|
|
for _, addr := range ipv4ListenAddrs {
|
2013-11-07 17:25:11 +01:00
|
|
|
var listener net.Listener
|
|
|
|
listener, err = tls.Listen("tcp4", addr, &tlsConfig)
|
2013-11-14 02:51:37 +01:00
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Warnf("Can't listen on %s: %v", addr,
|
2013-11-14 02:51:37 +01:00
|
|
|
err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
listeners = append(listeners, listener)
|
2013-08-07 17:38:39 +02:00
|
|
|
}
|
|
|
|
|
2013-11-14 02:51:37 +01:00
|
|
|
for _, addr := range ipv6ListenAddrs {
|
2013-11-07 17:25:11 +01:00
|
|
|
var listener net.Listener
|
|
|
|
listener, err = tls.Listen("tcp6", addr, &tlsConfig)
|
2013-11-14 02:51:37 +01:00
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Warnf("Can't listen on %s: %v", addr,
|
2013-11-14 02:51:37 +01:00
|
|
|
err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
listeners = append(listeners, listener)
|
|
|
|
}
|
|
|
|
if len(listeners) == 0 {
|
|
|
|
return nil, errors.New("RPCS: No valid listen address")
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
2013-08-07 17:38:39 +02:00
|
|
|
|
|
|
|
rpc.listeners = listeners
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-08-06 23:55:22 +02:00
|
|
|
return &rpc, err
|
|
|
|
}
|
|
|
|
|
2013-10-01 22:43:45 +02:00
|
|
|
// jsonAuthFail sends a message back to the client if the http auth is rejected.
|
|
|
|
func jsonAuthFail(w http.ResponseWriter, r *http.Request, s *rpcServer) {
|
|
|
|
fmt.Fprint(w, "401 Unauthorized.\n")
|
|
|
|
}
|
|
|
|
|
2013-08-14 22:55:31 +02:00
|
|
|
// jsonRPCRead is the RPC wrapper around the jsonRead function to handles
|
|
|
|
// reading and responding to RPC messages.
|
2013-09-18 07:36:40 +02:00
|
|
|
func jsonRPCRead(w http.ResponseWriter, r *http.Request, s *rpcServer) {
|
2013-08-06 23:55:22 +02:00
|
|
|
r.Close = true
|
2013-10-03 01:33:42 +02:00
|
|
|
if atomic.LoadInt32(&s.shutdown) != 0 {
|
2013-08-06 23:55:22 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
body, err := btcjson.GetRaw(r.Body)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting json message: %v", err)
|
2013-08-06 23:55:22 +02:00
|
|
|
return
|
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-11-07 18:47:54 +01:00
|
|
|
var reply btcjson.Reply
|
|
|
|
cmd, jsonErr := parseCmd(body)
|
|
|
|
if cmd != nil {
|
|
|
|
// Unmarshaling at least a valid JSON-RPC message succeeded.
|
|
|
|
// Use the provided id for errors.
|
|
|
|
id := cmd.Id()
|
|
|
|
reply.Id = &id
|
|
|
|
}
|
|
|
|
if jsonErr != nil {
|
|
|
|
reply.Error = jsonErr
|
|
|
|
} else {
|
|
|
|
reply = standardCmdReply(cmd, s, nil)
|
|
|
|
}
|
|
|
|
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Tracef("reply: %v", reply)
|
2013-08-14 22:55:31 +02:00
|
|
|
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
msg, err := btcjson.MarshallAndSend(reply, w)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf(msg)
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
return
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Debugf(msg)
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
// TODO(jrick): Remove the wallet notification chan.
|
2013-10-29 16:42:34 +01:00
|
|
|
type commandHandler func(*rpcServer, btcjson.Cmd, chan []byte) (interface{}, error)
|
|
|
|
|
|
|
|
var handlers = map[string]commandHandler{
|
2013-11-04 19:31:56 +01:00
|
|
|
"addmultisigaddress": handleAskWallet,
|
|
|
|
"addnode": handleAddNode,
|
|
|
|
"backupwallet": handleAskWallet,
|
|
|
|
"createmultisig": handleAskWallet,
|
|
|
|
"createrawtransaction": handleUnimplemented,
|
2013-11-22 17:46:56 +01:00
|
|
|
"debuglevel": handleDebugLevel,
|
2013-11-04 19:31:56 +01:00
|
|
|
"decoderawtransaction": handleDecodeRawTransaction,
|
|
|
|
"decodescript": handleUnimplemented,
|
|
|
|
"dumpprivkey": handleAskWallet,
|
|
|
|
"dumpwallet": handleAskWallet,
|
|
|
|
"encryptwallet": handleAskWallet,
|
|
|
|
"getaccount": handleAskWallet,
|
|
|
|
"getaccountaddress": handleAskWallet,
|
|
|
|
"getaddednodeinfo": handleUnimplemented,
|
|
|
|
"getaddressesbyaccount": handleAskWallet,
|
|
|
|
"getbalance": handleAskWallet,
|
|
|
|
"getbestblockhash": handleGetBestBlockHash,
|
|
|
|
"getblock": handleGetBlock,
|
|
|
|
"getblockcount": handleGetBlockCount,
|
|
|
|
"getblockhash": handleGetBlockHash,
|
|
|
|
"getblocktemplate": handleUnimplemented,
|
|
|
|
"getconnectioncount": handleGetConnectionCount,
|
|
|
|
"getdifficulty": handleGetDifficulty,
|
|
|
|
"getgenerate": handleGetGenerate,
|
|
|
|
"gethashespersec": handleGetHashesPerSec,
|
|
|
|
"getinfo": handleUnimplemented,
|
|
|
|
"getmininginfo": handleUnimplemented,
|
|
|
|
"getnettotals": handleUnimplemented,
|
|
|
|
"getnetworkhashps": handleUnimplemented,
|
|
|
|
"getnewaddress": handleUnimplemented,
|
|
|
|
"getpeerinfo": handleGetPeerInfo,
|
|
|
|
"getrawchangeaddress": handleAskWallet,
|
|
|
|
"getrawmempool": handleGetRawMempool,
|
|
|
|
"getrawtransaction": handleGetRawTransaction,
|
|
|
|
"getreceivedbyaccount": handleAskWallet,
|
|
|
|
"getreceivedbyaddress": handleAskWallet,
|
|
|
|
"gettransaction": handleAskWallet,
|
|
|
|
"gettxout": handleAskWallet,
|
|
|
|
"gettxoutsetinfo": handleAskWallet,
|
|
|
|
"getwork": handleUnimplemented,
|
|
|
|
"help": handleUnimplemented,
|
|
|
|
"importprivkey": handleAskWallet,
|
|
|
|
"importwallet": handleAskWallet,
|
|
|
|
"keypoolrefill": handleAskWallet,
|
|
|
|
"listaccounts": handleAskWallet,
|
|
|
|
"listaddressgroupings": handleAskWallet,
|
|
|
|
"listlockunspent": handleAskWallet,
|
|
|
|
"listreceivedbyaccount": handleAskWallet,
|
|
|
|
"listreceivedbyaddress": handleAskWallet,
|
|
|
|
"listsinceblock": handleAskWallet,
|
|
|
|
"listtransactions": handleAskWallet,
|
|
|
|
"listunspent": handleAskWallet,
|
|
|
|
"lockunspent": handleAskWallet,
|
|
|
|
"move": handleAskWallet,
|
|
|
|
"ping": handleUnimplemented,
|
|
|
|
"sendfrom": handleAskWallet,
|
|
|
|
"sendmany": handleAskWallet,
|
|
|
|
"sendrawtransaction": handleSendRawTransaction,
|
|
|
|
"sendtoaddress": handleAskWallet,
|
|
|
|
"setaccount": handleAskWallet,
|
|
|
|
"setgenerate": handleSetGenerate,
|
|
|
|
"settxfee": handleAskWallet,
|
|
|
|
"signmessage": handleAskWallet,
|
|
|
|
"signrawtransaction": handleAskWallet,
|
|
|
|
"stop": handleStop,
|
|
|
|
"submitblock": handleUnimplemented,
|
|
|
|
"validateaddress": handleAskWallet,
|
2013-11-12 17:39:10 +01:00
|
|
|
"verifychain": handleVerifyChain,
|
2013-11-04 19:31:56 +01:00
|
|
|
"verifymessage": handleAskWallet,
|
|
|
|
"walletlock": handleAskWallet,
|
|
|
|
"walletpassphrase": handleAskWallet,
|
|
|
|
"walletpassphrasechange": handleAskWallet,
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
type wsCommandHandler func(*rpcServer, btcjson.Cmd, chan []byte, *requestContexts) error
|
2013-11-06 17:20:36 +01:00
|
|
|
|
2013-11-04 19:50:24 +01:00
|
|
|
var wsHandlers = map[string]wsCommandHandler{
|
|
|
|
"getcurrentnet": handleGetCurrentNet,
|
|
|
|
"getbestblock": handleGetBestBlock,
|
|
|
|
"rescan": handleRescan,
|
2013-11-06 17:20:36 +01:00
|
|
|
"notifynewtxs": handleNotifyNewTXs,
|
2013-11-04 19:50:24 +01:00
|
|
|
"notifyspent": handleNotifySpent,
|
|
|
|
}
|
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
// handleUnimplemented is a temporary handler for commands that we should
|
|
|
|
// support but do not.
|
|
|
|
func handleUnimplemented(s *rpcServer, cmd btcjson.Cmd,
|
|
|
|
walletNotification chan []byte) (interface{}, error) {
|
|
|
|
return nil, btcjson.ErrUnimplemented
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleAskWallet is the handler for commands that we do recognise as valid
|
|
|
|
// but that we can not answer correctly since it involves wallet state.
|
|
|
|
// These commands will be implemented in btcwallet.
|
|
|
|
func handleAskWallet(s *rpcServer, cmd btcjson.Cmd,
|
|
|
|
walletNotification chan []byte) (interface{}, error) {
|
|
|
|
return nil, btcjson.ErrNoWallet
|
|
|
|
}
|
|
|
|
|
2013-10-29 18:18:53 +01:00
|
|
|
// handleDecodeRawTransaction handles decoderawtransaction commands.
|
|
|
|
func handleAddNode(s *rpcServer, cmd btcjson.Cmd,
|
|
|
|
walletNotification chan []byte) (interface{}, error) {
|
|
|
|
c := cmd.(*btcjson.AddNodeCmd)
|
|
|
|
|
2013-09-19 17:46:33 +02:00
|
|
|
addr := normalizeAddress(c.Addr, activeNetParams.peerPort)
|
2013-10-29 18:18:53 +01:00
|
|
|
var err error
|
|
|
|
switch c.SubCmd {
|
|
|
|
case "add":
|
|
|
|
err = s.server.AddAddr(addr, true)
|
|
|
|
case "remove":
|
|
|
|
err = s.server.RemoveAddr(addr)
|
|
|
|
case "onetry":
|
|
|
|
err = s.server.AddAddr(addr, false)
|
|
|
|
default:
|
|
|
|
err = errors.New("Invalid subcommand for addnode")
|
|
|
|
}
|
|
|
|
|
|
|
|
// no data returned unless an error.
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2013-11-22 17:46:56 +01:00
|
|
|
// handleDebugLevel handles debuglevel commands.
|
|
|
|
func handleDebugLevel(s *rpcServer, cmd btcjson.Cmd,
|
|
|
|
walletNotification chan []byte) (interface{}, error) {
|
|
|
|
c := cmd.(*btcjson.DebugLevelCmd)
|
|
|
|
|
|
|
|
// Special show command to list supported subsystems.
|
|
|
|
if c.LevelSpec == "show" {
|
|
|
|
return fmt.Sprintf("Supported subsystems %v",
|
|
|
|
supportedSubsystems()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
err := parseAndSetDebugLevels(c.LevelSpec)
|
|
|
|
if err != nil {
|
|
|
|
jsonErr := btcjson.Error{
|
|
|
|
Code: btcjson.ErrInvalidParams.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, jsonErr
|
|
|
|
}
|
|
|
|
|
|
|
|
return "Done.", nil
|
|
|
|
}
|
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleDecodeRawTransaction handles decoderawtransaction commands.
|
|
|
|
func handleDecodeRawTransaction(s *rpcServer, cmd btcjson.Cmd,
|
|
|
|
walletNotification chan []byte) (interface{}, error) {
|
|
|
|
// TODO: use c.HexTx and fill result with info.
|
|
|
|
return btcjson.TxRawDecodeResult{}, nil
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetBestBlockHash implements the getbestblockhash command.
|
|
|
|
func handleGetBestBlockHash(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
var sha *btcwire.ShaHash
|
|
|
|
sha, _, err := s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting newest sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBestBlockHash
|
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-11-07 19:53:22 +01:00
|
|
|
return sha.String(), nil
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// handleGetBlock implements the getblock command.
|
|
|
|
func handleGetBlock(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
c := cmd.(*btcjson.GetBlockCmd)
|
|
|
|
sha, err := btcwire.NewShaHashFromStr(c.Hash)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error generating sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockNotFound
|
|
|
|
}
|
|
|
|
blk, err := s.server.db.FetchBlockBySha(sha)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error fetching sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockNotFound
|
|
|
|
}
|
|
|
|
idx := blk.Height()
|
|
|
|
buf, err := blk.Bytes()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error fetching block: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockNotFound
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
txList, _ := blk.TxShas()
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
txNames := make([]string, len(txList))
|
|
|
|
for i, v := range txList {
|
|
|
|
txNames[i] = v.String()
|
|
|
|
}
|
2013-10-28 23:53:16 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
_, maxidx, err := s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Cannot get newest sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockNotFound
|
|
|
|
}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
blockHeader := &blk.MsgBlock().Header
|
|
|
|
blockReply := btcjson.BlockResult{
|
|
|
|
Hash: c.Hash,
|
|
|
|
Version: blockHeader.Version,
|
|
|
|
MerkleRoot: blockHeader.MerkleRoot.String(),
|
|
|
|
PreviousHash: blockHeader.PrevBlock.String(),
|
|
|
|
Nonce: blockHeader.Nonce,
|
|
|
|
Time: blockHeader.Timestamp.Unix(),
|
|
|
|
Confirmations: uint64(1 + maxidx - idx),
|
|
|
|
Height: idx,
|
|
|
|
Tx: txNames,
|
|
|
|
Size: len(buf),
|
|
|
|
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
|
|
|
|
Difficulty: getDifficultyRatio(blockHeader.Bits),
|
|
|
|
}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// Get next block unless we are already at the top.
|
|
|
|
if idx < maxidx {
|
|
|
|
var shaNext *btcwire.ShaHash
|
|
|
|
shaNext, err = s.server.db.FetchBlockShaByHeight(int64(idx + 1))
|
2013-08-06 23:55:22 +02:00
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("No next block: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockNotFound
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
2013-10-29 16:42:34 +01:00
|
|
|
blockReply.NextHash = shaNext.String()
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
return blockReply, nil
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetBlockCount implements the getblockcount command.
|
|
|
|
func handleGetBlockCount(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
_, maxidx, err := s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting newest sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrBlockCount
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
return maxidx, nil
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetBlockHash implements the getblockhash command.
|
|
|
|
func handleGetBlockHash(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
c := cmd.(*btcjson.GetBlockHashCmd)
|
|
|
|
sha, err := s.server.db.FetchBlockShaByHeight(c.Index)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting block: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrOutOfRange
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
return sha.String(), nil
|
|
|
|
}
|
2013-08-06 23:55:22 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetConnectionCount implements the getconnectioncount command.
|
|
|
|
func handleGetConnectionCount(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
2013-10-21 19:45:30 +02:00
|
|
|
return s.server.ConnectedCount(), nil
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetDifficulty implements the getdifficulty command.
|
|
|
|
func handleGetDifficulty(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
sha, _, err := s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting sha: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrDifficulty
|
|
|
|
}
|
|
|
|
blk, err := s.server.db.FetchBlockBySha(sha)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting block: %v", err)
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, btcjson.ErrDifficulty
|
|
|
|
}
|
|
|
|
blockHeader := &blk.MsgBlock().Header
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
return getDifficultyRatio(blockHeader.Bits), nil
|
|
|
|
}
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetGenerate implements the getgenerate command.
|
|
|
|
func handleGetGenerate(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
// btcd does not do mining so we can hardcode replies here.
|
|
|
|
return false, nil
|
|
|
|
}
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetHashesPerSec implements the gethashespersec command.
|
|
|
|
func handleGetHashesPerSec(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
// btcd does not do mining so we can hardcode replies here.
|
|
|
|
return 0, nil
|
|
|
|
}
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-10-21 19:45:30 +02:00
|
|
|
// handleGetPeerInfo implements the getpeerinfo command.
|
|
|
|
func handleGetPeerInfo(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
2013-10-29 18:18:53 +01:00
|
|
|
return s.server.PeerInfo(), nil
|
2013-10-21 19:45:30 +02:00
|
|
|
}
|
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// handleGetRawMempool implements the getrawmempool command.
|
|
|
|
func handleGetRawMempool(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
hashes := s.server.txMemPool.TxShas()
|
|
|
|
hashStrings := make([]string, len(hashes))
|
|
|
|
for i := 0; i < len(hashes); i++ {
|
|
|
|
hashStrings[i] = hashes[i].String()
|
|
|
|
}
|
|
|
|
return hashStrings, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleGetRawTransaction implements the getrawtransaction command.
|
|
|
|
func handleGetRawTransaction(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
c := cmd.(*btcjson.GetRawTransactionCmd)
|
|
|
|
if c.Verbose {
|
|
|
|
// TODO: check error code. tx is not checked before
|
|
|
|
// this point.
|
|
|
|
txSha, _ := btcwire.NewShaHashFromStr(c.Txid)
|
2013-11-12 20:03:23 +01:00
|
|
|
var mtx *btcwire.MsgTx
|
|
|
|
var blksha *btcwire.ShaHash
|
|
|
|
tx, err := s.server.txMemPool.FetchTransaction(txSha)
|
2013-10-29 01:43:09 +01:00
|
|
|
if err != nil {
|
2013-11-12 20:03:23 +01:00
|
|
|
txList, err := s.server.db.FetchTxBySha(txSha)
|
|
|
|
if err != nil || len(txList) == 0 {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error fetching tx: %v", err)
|
2013-11-12 20:03:23 +01:00
|
|
|
return nil, btcjson.ErrNoTxInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
lastTx := len(txList) - 1
|
|
|
|
mtx = txList[lastTx].Tx
|
|
|
|
|
|
|
|
blksha = txList[lastTx].BlkSha
|
|
|
|
} else {
|
|
|
|
mtx = tx.MsgTx()
|
2013-10-29 01:43:09 +01:00
|
|
|
}
|
|
|
|
|
2013-11-12 20:03:23 +01:00
|
|
|
txOutList := mtx.TxOut
|
2013-10-29 16:42:34 +01:00
|
|
|
voutList := make([]btcjson.Vout, len(txOutList))
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-11-12 20:03:23 +01:00
|
|
|
txInList := mtx.TxIn
|
2013-10-29 16:42:34 +01:00
|
|
|
vinList := make([]btcjson.Vin, len(txInList))
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
for i, v := range txInList {
|
|
|
|
vinList[i].Sequence = float64(v.Sequence)
|
|
|
|
disbuf, _ := btcscript.DisasmString(v.SignatureScript)
|
|
|
|
vinList[i].ScriptSig.Asm = strings.Replace(disbuf, " ", "", -1)
|
|
|
|
vinList[i].Vout = i + 1
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Debugf(disbuf)
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
for i, v := range txOutList {
|
|
|
|
voutList[i].N = i
|
|
|
|
voutList[i].Value = float64(v.Value) / 100000000
|
|
|
|
isbuf, _ := btcscript.DisasmString(v.PkScript)
|
|
|
|
voutList[i].ScriptPubKey.Asm = isbuf
|
2013-11-22 14:50:15 +01:00
|
|
|
voutList[i].ScriptPubKey.ReqSigs = strings.Count(isbuf, "OP_CHECKSIG")
|
2013-10-29 16:42:34 +01:00
|
|
|
_, addrhash, err := btcscript.ScriptToAddrHash(v.PkScript)
|
2013-08-06 23:55:22 +02:00
|
|
|
if err != nil {
|
2013-10-29 16:42:34 +01:00
|
|
|
// TODO: set and return error?
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error getting address hash for %v: %v", txSha, err)
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
2013-11-15 22:12:08 +01:00
|
|
|
if addr, err := btcutil.EncodeAddress(addrhash, s.server.btcnet); err == nil {
|
2013-10-29 16:42:34 +01:00
|
|
|
// TODO: set and return error?
|
|
|
|
addrList := make([]string, 1)
|
|
|
|
addrList[0] = addr
|
|
|
|
voutList[i].ScriptPubKey.Addresses = addrList
|
2013-08-06 23:55:22 +02:00
|
|
|
}
|
|
|
|
}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
txReply := btcjson.TxRawResult{
|
|
|
|
Txid: c.Txid,
|
|
|
|
Vout: voutList,
|
|
|
|
Vin: vinList,
|
2013-11-12 20:03:23 +01:00
|
|
|
Version: mtx.Version,
|
|
|
|
LockTime: mtx.LockTime,
|
|
|
|
}
|
|
|
|
if blksha != nil {
|
|
|
|
blk, err := s.server.db.FetchBlockBySha(blksha)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error fetching sha: %v", err)
|
2013-11-12 20:03:23 +01:00
|
|
|
return nil, btcjson.ErrBlockNotFound
|
|
|
|
}
|
|
|
|
idx := blk.Height()
|
|
|
|
|
|
|
|
_, maxidx, err := s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Cannot get newest sha: %v", err)
|
2013-11-12 20:03:23 +01:00
|
|
|
return nil, btcjson.ErrNoNewestBlockInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
blockHeader := &blk.MsgBlock().Header
|
2013-10-29 16:42:34 +01:00
|
|
|
// This is not a typo, they are identical in
|
|
|
|
// bitcoind as well.
|
2013-11-12 20:03:23 +01:00
|
|
|
txReply.Time = blockHeader.Timestamp.Unix()
|
|
|
|
txReply.Blocktime = blockHeader.Timestamp.Unix()
|
|
|
|
txReply.BlockHash = blksha.String()
|
|
|
|
txReply.Confirmations = uint64(1 + maxidx - idx)
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
|
|
|
return txReply, nil
|
|
|
|
} else {
|
|
|
|
// Don't return details
|
|
|
|
// not used yet
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleSendRawTransaction implements the sendrawtransaction command.
|
|
|
|
func handleSendRawTransaction(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
c := cmd.(*btcjson.SendRawTransactionCmd)
|
|
|
|
// Deserialize and send off to tx relay
|
|
|
|
serializedTx, err := hex.DecodeString(c.HexTx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, btcjson.ErrDecodeHexString
|
|
|
|
}
|
|
|
|
msgtx := btcwire.NewMsgTx()
|
|
|
|
err = msgtx.Deserialize(bytes.NewBuffer(serializedTx))
|
|
|
|
if err != nil {
|
|
|
|
err := btcjson.Error{
|
2013-10-30 01:41:38 +01:00
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
2013-11-07 16:34:55 +01:00
|
|
|
Message: "TX decode failed",
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-10-29 16:42:34 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
2013-10-31 06:28:37 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
tx := btcutil.NewTx(msgtx)
|
|
|
|
err = s.server.txMemPool.ProcessTransaction(tx)
|
|
|
|
if err != nil {
|
2013-10-30 20:11:11 +01:00
|
|
|
// When the error is a rule error, it means the transaction was
|
|
|
|
// simply rejected as opposed to something actually going wrong,
|
|
|
|
// so log it as such. Otherwise, something really did go wrong,
|
|
|
|
// so log it as an actual error.
|
|
|
|
if _, ok := err.(TxRuleError); ok {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Debugf("Rejected transaction %v: %v", tx.Sha(),
|
2013-10-31 00:40:55 +01:00
|
|
|
err)
|
2013-10-30 20:11:11 +01:00
|
|
|
} else {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Failed to process transaction %v: %v",
|
2013-10-31 00:40:55 +01:00
|
|
|
tx.Sha(), err)
|
2013-11-07 16:34:55 +01:00
|
|
|
err = btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: "TX rejected",
|
|
|
|
}
|
|
|
|
return nil, err
|
2013-10-30 20:11:11 +01:00
|
|
|
}
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
// If called from websocket code, add a mined tx hashes
|
|
|
|
// request.
|
|
|
|
if walletNotification != nil {
|
2013-11-04 19:31:56 +01:00
|
|
|
s.ws.AddMinedTxRequest(walletNotification, tx.Sha())
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
2013-10-23 17:07:00 +02:00
|
|
|
|
2013-10-31 06:28:37 +01:00
|
|
|
return tx.Sha().String(), nil
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// handleSetGenerate implements the setgenerate command.
|
|
|
|
func handleSetGenerate(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
// btcd does not do mining so we can hardcode replies here.
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleStop implements the stop command.
|
|
|
|
func handleStop(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
s.server.Stop()
|
|
|
|
return "btcd stopping.", nil
|
|
|
|
}
|
|
|
|
|
2013-11-12 17:39:10 +01:00
|
|
|
func verifyChain(db btcdb.Db, level, depth int32) error {
|
|
|
|
_, curheight64, err := db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Verify is unable to fetch current block "+
|
2013-11-12 17:39:10 +01:00
|
|
|
"height: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
curheight := int32(curheight64)
|
|
|
|
|
|
|
|
if depth > curheight {
|
|
|
|
depth = curheight
|
|
|
|
}
|
|
|
|
|
|
|
|
for height := curheight; height > (curheight - depth); height-- {
|
|
|
|
// Level 0 just looks up the block.
|
|
|
|
sha, err := db.FetchBlockShaByHeight(int64(height))
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Verify is unable to fetch block at "+
|
2013-11-12 17:39:10 +01:00
|
|
|
"height %d: %v", height, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
block, err := db.FetchBlockBySha(sha)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Verify is unable to fetch block at "+
|
2013-11-12 17:39:10 +01:00
|
|
|
"sha %v height %d: %v", sha, height, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Level 1 does basic chain sanity checks.
|
|
|
|
if level > 0 {
|
|
|
|
err := btcchain.CheckBlockSanity(block,
|
|
|
|
activeNetParams.powLimit)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Verify is unable to "+
|
2013-11-12 17:39:10 +01:00
|
|
|
"validate block at sha %v height "+
|
|
|
|
"%s: %v", sha, height, err)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Infof("Chain verify completed successfully")
|
2013-11-12 17:39:10 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func handleVerifyChain(s *rpcServer, cmd btcjson.Cmd, walletNotification chan []byte) (interface{}, error) {
|
|
|
|
c := cmd.(*btcjson.VerifyChainCmd)
|
|
|
|
|
|
|
|
err := verifyChain(s.server.db, c.CheckLevel, c.CheckDepth)
|
|
|
|
if err != nil {
|
|
|
|
}
|
|
|
|
return "", nil
|
|
|
|
}
|
|
|
|
|
2013-11-07 18:47:54 +01:00
|
|
|
// parseCmd parses a marshaled known command, returning any errors as a
|
|
|
|
// btcjson.Error that can be used in replies. The returned cmd may still
|
|
|
|
// be non-nil if b is at least a valid marshaled JSON-RPC message.
|
|
|
|
func parseCmd(b []byte) (btcjson.Cmd, *btcjson.Error) {
|
|
|
|
cmd, err := btcjson.ParseMarshaledCmd(b)
|
2013-10-29 16:42:34 +01:00
|
|
|
if err != nil {
|
2013-11-07 18:47:54 +01:00
|
|
|
jsonErr, ok := err.(btcjson.Error)
|
|
|
|
if !ok {
|
|
|
|
jsonErr = btcjson.Error{
|
|
|
|
Code: btcjson.ErrParse.Code,
|
|
|
|
Message: err.Error(),
|
2013-10-30 04:23:22 +01:00
|
|
|
}
|
2013-10-29 01:43:09 +01:00
|
|
|
}
|
2013-11-07 18:47:54 +01:00
|
|
|
return cmd, &jsonErr
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
2013-11-07 18:47:54 +01:00
|
|
|
return cmd, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// standardCmdReply checks that a parsed command is a standard
|
|
|
|
// Bitcoin JSON-RPC command and runs the proper handler to reply to the
|
|
|
|
// command.
|
|
|
|
func standardCmdReply(cmd btcjson.Cmd, s *rpcServer,
|
|
|
|
walletNotification chan []byte) (reply btcjson.Reply) {
|
2013-10-29 16:42:34 +01:00
|
|
|
|
|
|
|
id := cmd.Id()
|
2013-11-07 18:47:54 +01:00
|
|
|
reply.Id = &id
|
2013-10-29 01:43:09 +01:00
|
|
|
|
2013-10-29 16:42:34 +01:00
|
|
|
handler, ok := handlers[cmd.Method()]
|
|
|
|
if !ok {
|
2013-11-07 18:47:54 +01:00
|
|
|
reply.Error = &btcjson.ErrMethodNotFound
|
|
|
|
return reply
|
2013-10-29 16:42:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
result, err := handler(s, cmd, walletNotification)
|
|
|
|
if err != nil {
|
2013-11-07 18:47:54 +01:00
|
|
|
jsonErr, ok := err.(btcjson.Error)
|
|
|
|
if !ok {
|
2013-10-29 16:42:34 +01:00
|
|
|
// In the case where we did not have a btcjson
|
|
|
|
// error to begin with, make a new one to send,
|
|
|
|
// but this really should not happen.
|
2013-11-07 18:47:54 +01:00
|
|
|
jsonErr = btcjson.Error{
|
2013-10-30 01:41:38 +01:00
|
|
|
Code: btcjson.ErrInternal.Code,
|
2013-10-29 16:42:34 +01:00
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
2013-11-07 18:47:54 +01:00
|
|
|
reply.Error = &jsonErr
|
2013-10-29 16:42:34 +01:00
|
|
|
} else {
|
2013-11-07 18:47:54 +01:00
|
|
|
reply.Result = result
|
|
|
|
}
|
|
|
|
return reply
|
|
|
|
}
|
|
|
|
|
|
|
|
// respondToAnyCmd checks that a parsed command is a standard or
|
|
|
|
// extension JSON-RPC command and runs the proper handler to reply to
|
|
|
|
// the command. Any and all responses are sent to the wallet from
|
|
|
|
// this function.
|
|
|
|
func respondToAnyCmd(cmd btcjson.Cmd, s *rpcServer,
|
|
|
|
walletNotification chan []byte, rc *requestContexts) {
|
|
|
|
|
|
|
|
reply := standardCmdReply(cmd, s, walletNotification)
|
|
|
|
if reply.Error != &btcjson.ErrMethodNotFound {
|
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
wsHandler, ok := wsHandlers[cmd.Method()]
|
|
|
|
if !ok {
|
|
|
|
reply.Error = &btcjson.ErrMethodNotFound
|
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
|
|
|
return
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
}
|
|
|
|
|
2013-11-07 18:47:54 +01:00
|
|
|
if err := wsHandler(s, cmd, walletNotification, rc); err != nil {
|
|
|
|
jsonErr, ok := err.(btcjson.Error)
|
|
|
|
if ok {
|
|
|
|
reply.Error = &jsonErr
|
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// In the case where we did not have a btcjson
|
|
|
|
// error to begin with, make a new one to send,
|
|
|
|
// but this really should not happen.
|
|
|
|
jsonErr = btcjson.Error{
|
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
reply.Error = &jsonErr
|
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
|
|
|
}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
}
|
|
|
|
|
2013-11-04 19:50:24 +01:00
|
|
|
// handleGetCurrentNet implements the getcurrentnet command extension
|
|
|
|
// for websocket connections.
|
2013-11-06 17:20:36 +01:00
|
|
|
func handleGetCurrentNet(s *rpcServer, cmd btcjson.Cmd,
|
2013-11-04 19:31:56 +01:00
|
|
|
walletNotification chan []byte, rc *requestContexts) error {
|
2013-11-06 17:20:36 +01:00
|
|
|
|
|
|
|
id := cmd.Id()
|
|
|
|
reply := &btcjson.Reply{Id: &id}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-11-04 19:50:24 +01:00
|
|
|
var net btcwire.BitcoinNet
|
|
|
|
if cfg.TestNet3 {
|
|
|
|
net = btcwire.TestNet3
|
|
|
|
} else {
|
|
|
|
net = btcwire.MainNet
|
|
|
|
}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
reply.Result = float64(net)
|
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
2013-11-04 19:50:24 +01:00
|
|
|
return nil
|
|
|
|
}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-11-04 19:50:24 +01:00
|
|
|
// handleGetBestBlock implements the getbestblock command extension
|
|
|
|
// for websocket connections.
|
2013-11-06 17:20:36 +01:00
|
|
|
func handleGetBestBlock(s *rpcServer, cmd btcjson.Cmd,
|
2013-11-04 19:31:56 +01:00
|
|
|
walletNotification chan []byte, rc *requestContexts) error {
|
2013-11-06 17:20:36 +01:00
|
|
|
|
|
|
|
id := cmd.Id()
|
|
|
|
reply := &btcjson.Reply{Id: &id}
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
|
2013-11-04 19:50:24 +01:00
|
|
|
// All other "get block" commands give either the height, the
|
|
|
|
// hash, or both but require the block SHA. This gets both for
|
|
|
|
// the best block.
|
|
|
|
sha, height, err := s.server.db.NewestSha()
|
|
|
|
if err != nil {
|
2013-11-06 17:20:36 +01:00
|
|
|
return btcjson.ErrBestBlockHash
|
2013-11-04 19:50:24 +01:00
|
|
|
}
|
2013-11-06 17:20:36 +01:00
|
|
|
|
|
|
|
reply.Result = map[string]interface{}{
|
|
|
|
"hash": sha.String(),
|
|
|
|
"height": height,
|
2013-11-04 19:50:24 +01:00
|
|
|
}
|
2013-11-06 17:20:36 +01:00
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
2013-11-04 19:50:24 +01:00
|
|
|
return nil
|
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-11-04 19:50:24 +01:00
|
|
|
// handleRescan implements the rescan command extension for websocket
|
|
|
|
// connections.
|
2013-11-06 17:20:36 +01:00
|
|
|
func handleRescan(s *rpcServer, cmd btcjson.Cmd,
|
2013-11-04 19:31:56 +01:00
|
|
|
walletNotification chan []byte, rc *requestContexts) error {
|
2013-11-04 19:50:24 +01:00
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
id := cmd.Id()
|
|
|
|
reply := &btcjson.Reply{Id: &id}
|
2013-11-04 19:50:24 +01:00
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
rescanCmd, ok := cmd.(*btcws.RescanCmd)
|
|
|
|
if !ok {
|
|
|
|
return btcjson.ErrInternal
|
2013-11-04 19:50:24 +01:00
|
|
|
}
|
2013-10-31 03:56:45 +01:00
|
|
|
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Debugf("Begining rescan")
|
2013-10-31 03:56:45 +01:00
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
minblock := int64(rescanCmd.BeginBlock)
|
|
|
|
maxblock := int64(rescanCmd.EndBlock)
|
|
|
|
|
2013-11-04 19:50:24 +01:00
|
|
|
// FetchHeightRange may not return a complete list of block shas for
|
|
|
|
// the given range, so fetch range as many times as necessary.
|
|
|
|
for {
|
2013-11-06 17:20:36 +01:00
|
|
|
blkshalist, err := s.server.db.FetchHeightRange(minblock,
|
|
|
|
maxblock)
|
2013-11-04 19:50:24 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-11-04 19:50:24 +01:00
|
|
|
if len(blkshalist) == 0 {
|
|
|
|
break
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
|
2013-11-04 19:50:24 +01:00
|
|
|
for i := range blkshalist {
|
|
|
|
blk, err := s.server.db.FetchBlockBySha(&blkshalist[i])
|
2013-08-14 22:55:31 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2013-11-04 19:50:24 +01:00
|
|
|
txShaList, err := blk.TxShas()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-11-04 19:50:24 +01:00
|
|
|
txList := s.server.db.FetchTxByShaList(txShaList)
|
|
|
|
for _, txReply := range txList {
|
|
|
|
if txReply.Err != nil || txReply.Tx == nil {
|
|
|
|
continue
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-11-04 19:50:24 +01:00
|
|
|
for txOutIdx, txout := range txReply.Tx.TxOut {
|
|
|
|
st, txaddrhash, err := btcscript.ScriptToAddrHash(txout.PkScript)
|
|
|
|
if st != btcscript.ScriptAddr || err != nil {
|
2013-10-20 18:50:31 +02:00
|
|
|
continue
|
|
|
|
}
|
2013-11-04 19:50:24 +01:00
|
|
|
txaddr, err := btcutil.EncodeAddress(txaddrhash, s.server.btcnet)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Error encoding address: %v", err)
|
2013-11-04 19:50:24 +01:00
|
|
|
return err
|
|
|
|
}
|
2013-11-06 17:20:36 +01:00
|
|
|
if _, ok := rescanCmd.Addresses[txaddr]; ok {
|
|
|
|
reply.Result = struct {
|
|
|
|
Sender string `json:"sender"`
|
|
|
|
Receiver string `json:"receiver"`
|
|
|
|
BlockHash string `json:"blockhash"`
|
|
|
|
Height int64 `json:"height"`
|
|
|
|
TxHash string `json:"txhash"`
|
|
|
|
Index uint32 `json:"index"`
|
|
|
|
Amount int64 `json:"amount"`
|
|
|
|
PkScript string `json:"pkscript"`
|
|
|
|
Spent bool `json:"spent"`
|
|
|
|
}{
|
|
|
|
Sender: "Unknown", // TODO(jrick)
|
|
|
|
Receiver: txaddr,
|
|
|
|
BlockHash: blkshalist[i].String(),
|
|
|
|
Height: blk.Height(),
|
|
|
|
TxHash: txReply.Sha.String(),
|
|
|
|
Index: uint32(txOutIdx),
|
|
|
|
Amount: txout.Value,
|
|
|
|
PkScript: btcutil.Base58Encode(txout.PkScript),
|
|
|
|
Spent: txReply.TxSpent[txOutIdx],
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-11-06 17:20:36 +01:00
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-11-04 19:50:24 +01:00
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-11-04 19:50:24 +01:00
|
|
|
if maxblock-minblock > int64(len(blkshalist)) {
|
|
|
|
minblock += int64(len(blkshalist))
|
|
|
|
} else {
|
|
|
|
break
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-11-04 19:50:24 +01:00
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
2013-11-04 19:50:24 +01:00
|
|
|
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Debug("Finished rescan")
|
2013-11-04 19:50:24 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
// handleNotifyNewTXs implements the notifynewtxs command extension for
|
2013-11-04 19:50:24 +01:00
|
|
|
// websocket connections.
|
2013-11-06 17:20:36 +01:00
|
|
|
func handleNotifyNewTXs(s *rpcServer, cmd btcjson.Cmd,
|
2013-11-04 19:31:56 +01:00
|
|
|
walletNotification chan []byte, rc *requestContexts) error {
|
2013-11-04 19:50:24 +01:00
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
id := cmd.Id()
|
|
|
|
reply := &btcjson.Reply{Id: &id}
|
|
|
|
|
|
|
|
notifyCmd, ok := cmd.(*btcws.NotifyNewTXsCmd)
|
2013-11-04 19:50:24 +01:00
|
|
|
if !ok {
|
2013-11-06 17:20:36 +01:00
|
|
|
return btcjson.ErrInternal
|
2013-11-04 19:50:24 +01:00
|
|
|
}
|
2013-11-06 17:20:36 +01:00
|
|
|
|
|
|
|
for _, addr := range notifyCmd.Addresses {
|
|
|
|
hash, _, err := btcutil.DecodeAddress(addr)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot decode address: %v", err)
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-11-04 19:31:56 +01:00
|
|
|
s.ws.AddTxRequest(walletNotification, rc, string(hash),
|
|
|
|
cmd.Id())
|
2013-11-04 19:50:24 +01:00
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
2013-11-04 19:50:24 +01:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// handleNotifySpent implements the notifyspent command extension for
|
|
|
|
// websocket connections.
|
2013-11-06 17:20:36 +01:00
|
|
|
func handleNotifySpent(s *rpcServer, cmd btcjson.Cmd,
|
2013-11-04 19:31:56 +01:00
|
|
|
walletNotification chan []byte, rc *requestContexts) error {
|
2013-11-04 19:50:24 +01:00
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
id := cmd.Id()
|
|
|
|
reply := &btcjson.Reply{Id: &id}
|
2013-11-04 19:50:24 +01:00
|
|
|
|
2013-11-06 17:20:36 +01:00
|
|
|
notifyCmd, ok := cmd.(*btcws.NotifySpentCmd)
|
|
|
|
if !ok {
|
|
|
|
return btcjson.ErrInternal
|
2013-11-04 19:50:24 +01:00
|
|
|
}
|
2013-11-06 17:20:36 +01:00
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
s.ws.AddSpentRequest(walletNotification, rc, notifyCmd.OutPoint,
|
|
|
|
cmd.Id())
|
2013-11-06 17:20:36 +01:00
|
|
|
|
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
2013-11-04 19:50:24 +01:00
|
|
|
return nil
|
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-08-06 23:55:22 +02:00
|
|
|
// getDifficultyRatio returns the proof-of-work difficulty as a multiple of the
|
|
|
|
// minimum difficulty using the passed bits field from the header of a block.
|
|
|
|
func getDifficultyRatio(bits uint32) float64 {
|
|
|
|
// The minimum difficulty is the max possible proof-of-work limit bits
|
|
|
|
// converted back to a number. Note this is not the same as the the
|
|
|
|
// proof of work limit directly because the block difficulty is encoded
|
|
|
|
// in a block with the compact form which loses precision.
|
|
|
|
max := btcchain.CompactToBig(activeNetParams.powLimitBits)
|
|
|
|
target := btcchain.CompactToBig(bits)
|
|
|
|
|
|
|
|
difficulty := new(big.Rat).SetFrac(max, target)
|
|
|
|
outString := difficulty.FloatString(2)
|
|
|
|
diff, err := strconv.ParseFloat(outString, 64)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Cannot get difficulty: %v", err)
|
2013-08-06 23:55:22 +02:00
|
|
|
return 0
|
|
|
|
}
|
|
|
|
return diff
|
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
|
|
|
|
// AddWalletListener adds a channel to listen for new messages from a
|
|
|
|
// wallet.
|
2013-11-04 19:31:56 +01:00
|
|
|
func (s *rpcServer) AddWalletListener(c chan []byte) *requestContexts {
|
|
|
|
s.ws.Lock()
|
|
|
|
rc := &requestContexts{
|
|
|
|
// The key is a stringified addressHash.
|
|
|
|
txRequests: make(map[string]interface{}),
|
|
|
|
|
|
|
|
spentRequests: make(map[btcwire.OutPoint]interface{}),
|
|
|
|
minedTxRequests: make(map[btcwire.ShaHash]bool),
|
|
|
|
}
|
|
|
|
s.ws.connections[c] = rc
|
|
|
|
s.ws.Unlock()
|
|
|
|
|
|
|
|
return rc
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveWalletListener removes a wallet listener channel.
|
2013-11-04 19:31:56 +01:00
|
|
|
func (s *rpcServer) RemoveWalletListener(c chan []byte, rc *requestContexts) {
|
|
|
|
s.ws.Lock()
|
|
|
|
|
|
|
|
for k := range rc.txRequests {
|
|
|
|
s.ws.removeGlobalTxRequest(c, k)
|
|
|
|
}
|
|
|
|
for k := range rc.spentRequests {
|
|
|
|
s.ws.removeGlobalSpentRequest(c, &k)
|
|
|
|
}
|
|
|
|
for k := range rc.minedTxRequests {
|
|
|
|
s.ws.removeGlobalMinedTxRequest(c, &k)
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(s.ws.connections, c)
|
|
|
|
s.ws.Unlock()
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// walletListenerDuplicator listens for new wallet listener channels
|
|
|
|
// and duplicates messages sent to walletNotificationMaster to all
|
|
|
|
// connected listeners.
|
|
|
|
func (s *rpcServer) walletListenerDuplicator() {
|
|
|
|
// Duplicate all messages sent across walletNotificationMaster to each
|
|
|
|
// listening wallet.
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case ntfn := <-s.ws.walletNotificationMaster:
|
2013-11-04 19:31:56 +01:00
|
|
|
s.ws.RLock()
|
|
|
|
for c := range s.ws.connections {
|
2013-08-14 22:55:31 +02:00
|
|
|
c <- ntfn
|
|
|
|
}
|
2013-11-04 19:31:56 +01:00
|
|
|
s.ws.RUnlock()
|
2013-08-14 22:55:31 +02:00
|
|
|
|
|
|
|
case <-s.quit:
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// walletReqsNotifications is the handler function for websocket
|
|
|
|
// connections from a btcwallet instance. It reads messages from wallet and
|
|
|
|
// sends back replies, as well as notififying wallets of chain updates.
|
|
|
|
func (s *rpcServer) walletReqsNotifications(ws *websocket.Conn) {
|
|
|
|
// Add wallet notification channel so this handler receives btcd chain
|
|
|
|
// notifications.
|
|
|
|
c := make(chan []byte)
|
2013-11-04 19:31:56 +01:00
|
|
|
rc := s.AddWalletListener(c)
|
|
|
|
defer s.RemoveWalletListener(c, rc)
|
2013-08-14 22:55:31 +02:00
|
|
|
|
|
|
|
// msgs is a channel for all messages received over the websocket.
|
|
|
|
msgs := make(chan []byte)
|
|
|
|
|
|
|
|
// Receive messages from websocket and send across reqs until the
|
|
|
|
// connection is lost.
|
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case <-s.quit:
|
|
|
|
close(msgs)
|
|
|
|
return
|
|
|
|
default:
|
|
|
|
var m []byte
|
|
|
|
if err := websocket.Message.Receive(ws, &m); err != nil {
|
|
|
|
close(msgs)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
msgs <- m
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case m, ok := <-msgs:
|
|
|
|
if !ok {
|
|
|
|
// Wallet disconnected.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// Handle request here.
|
2013-11-04 19:31:56 +01:00
|
|
|
go s.websocketJSONHandler(c, rc, m)
|
2013-08-14 22:55:31 +02:00
|
|
|
case ntfn, _ := <-c:
|
|
|
|
// Send btcd notification to btcwallet instance over
|
|
|
|
// websocket.
|
|
|
|
if err := websocket.Message.Send(ws, ntfn); err != nil {
|
|
|
|
// Wallet disconnected.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
case <-s.quit:
|
|
|
|
// Server closed.
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// websocketJSONHandler parses and handles a marshalled json message,
|
|
|
|
// sending the marshalled reply to a wallet notification channel.
|
2013-11-07 18:47:54 +01:00
|
|
|
func (s *rpcServer) websocketJSONHandler(walletNotification chan []byte,
|
|
|
|
rc *requestContexts, msg []byte) {
|
|
|
|
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
s.wg.Add(1)
|
2013-11-07 18:47:54 +01:00
|
|
|
defer s.wg.Done()
|
2013-08-14 22:55:31 +02:00
|
|
|
|
2013-11-07 18:47:54 +01:00
|
|
|
cmd, jsonErr := parseCmd(msg)
|
|
|
|
if jsonErr != nil {
|
|
|
|
var reply btcjson.Reply
|
|
|
|
if cmd != nil {
|
|
|
|
// Unmarshaling at least a valid JSON-RPC message succeeded.
|
|
|
|
// Use the provided id for errors.
|
|
|
|
id := cmd.Id()
|
|
|
|
reply.Id = &id
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
}
|
2013-11-07 18:47:54 +01:00
|
|
|
reply.Error = jsonErr
|
|
|
|
mreply, _ := json.Marshal(reply)
|
|
|
|
walletNotification <- mreply
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2013-11-07 18:47:54 +01:00
|
|
|
respondToAnyCmd(cmd, s, walletNotification, rc)
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// NotifyBlockConnected creates and marshalls a JSON message to notify
|
|
|
|
// of a new block connected to the main chain. The notification is sent
|
|
|
|
// to each connected wallet.
|
|
|
|
func (s *rpcServer) NotifyBlockConnected(block *btcutil.Block) {
|
2013-11-04 19:31:56 +01:00
|
|
|
hash, err := block.Sha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Error("Bad block; connected block notification dropped.")
|
2013-11-04 19:31:56 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2013-11-08 18:43:10 +01:00
|
|
|
// TODO: remove int32 type conversion.
|
|
|
|
ntfn := btcws.NewBlockConnectedNtfn(hash.String(),
|
|
|
|
int32(block.Height()))
|
|
|
|
mntfn, _ := json.Marshal(ntfn)
|
|
|
|
s.ws.walletNotificationMaster <- mntfn
|
2013-10-23 17:07:00 +02:00
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
// Inform any interested parties about txs mined in this block.
|
2013-11-11 20:19:12 +01:00
|
|
|
s.ws.Lock()
|
2013-11-04 19:31:56 +01:00
|
|
|
for _, tx := range block.Transactions() {
|
|
|
|
if clist, ok := s.ws.minedTxNotifications[*tx.Sha()]; ok {
|
2013-11-12 22:24:32 +01:00
|
|
|
var enext *list.Element
|
|
|
|
for e := clist.Front(); e != nil; e = enext {
|
|
|
|
enext = e.Next()
|
2013-11-04 19:31:56 +01:00
|
|
|
ctx := e.Value.(*notificationCtx)
|
2013-11-08 18:43:10 +01:00
|
|
|
ntfn := btcws.NewTxMinedNtfn(tx.Sha().String())
|
|
|
|
mntfn, _ := json.Marshal(ntfn)
|
|
|
|
ctx.connection <- mntfn
|
2013-11-11 20:19:12 +01:00
|
|
|
s.ws.removeMinedTxRequest(ctx.connection, ctx.rc,
|
2013-11-04 19:31:56 +01:00
|
|
|
tx.Sha())
|
2013-10-23 17:07:00 +02:00
|
|
|
}
|
|
|
|
}
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-11-11 20:19:12 +01:00
|
|
|
s.ws.Unlock()
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
|
2013-10-23 17:07:00 +02:00
|
|
|
// NotifyBlockDisconnected creates and marshals a JSON message to notify
|
2013-08-14 22:55:31 +02:00
|
|
|
// of a new block disconnected from the main chain. The notification is sent
|
|
|
|
// to each connected wallet.
|
|
|
|
func (s *rpcServer) NotifyBlockDisconnected(block *btcutil.Block) {
|
|
|
|
hash, err := block.Sha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Error("Bad block; connected block notification dropped.")
|
2013-08-14 22:55:31 +02:00
|
|
|
return
|
|
|
|
}
|
2013-11-08 18:43:10 +01:00
|
|
|
|
|
|
|
// TODO: remove int32 type conversion.
|
|
|
|
ntfn := btcws.NewBlockDisconnectedNtfn(hash.String(),
|
|
|
|
int32(block.Height()))
|
|
|
|
mntfn, _ := json.Marshal(ntfn)
|
|
|
|
s.ws.walletNotificationMaster <- mntfn
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
|
2013-10-23 17:07:00 +02:00
|
|
|
// NotifyBlockTXs creates and marshals a JSON message to notify wallets
|
2013-08-14 22:55:31 +02:00
|
|
|
// of new transactions (with both spent and unspent outputs) for a watched
|
|
|
|
// address.
|
2013-10-23 17:07:00 +02:00
|
|
|
func (s *rpcServer) NotifyBlockTXs(db btcdb.Db, block *btcutil.Block) {
|
2013-11-04 19:31:56 +01:00
|
|
|
for _, tx := range block.Transactions() {
|
|
|
|
s.newBlockNotifyCheckTxIn(tx)
|
|
|
|
s.newBlockNotifyCheckTxOut(block, tx)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func notifySpentData(ctx *notificationCtx, txhash *btcwire.ShaHash, index uint32,
|
|
|
|
spender *btcutil.Tx) {
|
|
|
|
txStr := ""
|
|
|
|
if spender != nil {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
err := spender.MsgTx().Serialize(&buf)
|
|
|
|
if err != nil {
|
|
|
|
// This really shouldn't ever happen...
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Warnf("Can't serialize tx: %v", err)
|
2013-11-04 19:31:56 +01:00
|
|
|
return
|
2013-10-20 18:50:31 +02:00
|
|
|
}
|
2013-11-04 19:31:56 +01:00
|
|
|
txStr = string(buf.Bytes())
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
2013-10-29 21:48:22 +01:00
|
|
|
|
2013-11-04 19:31:56 +01:00
|
|
|
reply := &btcjson.Reply{
|
|
|
|
Result: struct {
|
|
|
|
TxHash string `json:"txhash"`
|
|
|
|
Index uint32 `json:"index"`
|
|
|
|
SpendingTx string `json:"spender,omitempty"`
|
|
|
|
}{
|
|
|
|
TxHash: txhash.String(),
|
|
|
|
Index: index,
|
|
|
|
SpendingTx: txStr,
|
|
|
|
},
|
|
|
|
Error: nil,
|
|
|
|
Id: &ctx.id,
|
2013-10-29 21:48:22 +01:00
|
|
|
}
|
2013-11-04 19:31:56 +01:00
|
|
|
replyBytes, err := json.Marshal(reply)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Unable to marshal spent notification: %v", err)
|
2013-11-04 19:31:56 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
ctx.connection <- replyBytes
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// newBlockNotifyCheckTxIn is a helper function to iterate through
|
|
|
|
// each transaction input of a new block and perform any checks and
|
|
|
|
// notify listening frontends when necessary.
|
2013-10-29 21:48:22 +01:00
|
|
|
func (s *rpcServer) newBlockNotifyCheckTxIn(tx *btcutil.Tx) {
|
2013-11-04 19:31:56 +01:00
|
|
|
for _, txin := range tx.MsgTx().TxIn {
|
|
|
|
if clist, ok := s.ws.spentNotifications[txin.PreviousOutpoint]; ok {
|
2013-11-12 22:24:32 +01:00
|
|
|
var enext *list.Element
|
|
|
|
for e := clist.Front(); e != nil; e = enext {
|
|
|
|
enext = e.Next()
|
2013-11-04 19:31:56 +01:00
|
|
|
ctx := e.Value.(*notificationCtx)
|
|
|
|
notifySpentData(ctx, &txin.PreviousOutpoint.Hash,
|
|
|
|
uint32(txin.PreviousOutpoint.Index), tx)
|
|
|
|
s.ws.RemoveSpentRequest(ctx.connection, ctx.rc,
|
|
|
|
&txin.PreviousOutpoint)
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// newBlockNotifyCheckTxOut is a helper function to iterate through
|
|
|
|
// each transaction output of a new block and perform any checks and
|
|
|
|
// notify listening frontends when necessary.
|
2013-11-11 18:49:48 +01:00
|
|
|
func (s *rpcServer) newBlockNotifyCheckTxOut(block *btcutil.Block,
|
|
|
|
tx *btcutil.Tx) {
|
2013-11-04 19:31:56 +01:00
|
|
|
|
|
|
|
for i, txout := range tx.MsgTx().TxOut {
|
|
|
|
_, txaddrhash, err := btcscript.ScriptToAddrHash(txout.PkScript)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Debug("Error getting payment address from tx; dropping any Tx notifications.")
|
2013-11-04 19:31:56 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
if idlist, ok := s.ws.txNotifications[string(txaddrhash)]; ok {
|
|
|
|
for e := idlist.Front(); e != nil; e = e.Next() {
|
|
|
|
ctx := e.Value.(*notificationCtx)
|
|
|
|
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
blkhash, err := block.Sha()
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Error("Error getting block sha; dropping Tx notification.")
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
txaddr, err := btcutil.EncodeAddress(txaddrhash, s.server.btcnet)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Error("Error encoding address; dropping Tx notification.")
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
break
|
|
|
|
}
|
|
|
|
reply := &btcjson.Reply{
|
|
|
|
Result: struct {
|
|
|
|
Sender string `json:"sender"`
|
|
|
|
Receiver string `json:"receiver"`
|
|
|
|
BlockHash string `json:"blockhash"`
|
|
|
|
Height int64 `json:"height"`
|
|
|
|
TxHash string `json:"txhash"`
|
|
|
|
Index uint32 `json:"index"`
|
|
|
|
Amount int64 `json:"amount"`
|
|
|
|
PkScript string `json:"pkscript"`
|
|
|
|
}{
|
|
|
|
Sender: "Unknown", // TODO(jrick)
|
|
|
|
Receiver: txaddr,
|
|
|
|
BlockHash: blkhash.String(),
|
|
|
|
Height: block.Height(),
|
2013-10-29 21:48:22 +01:00
|
|
|
TxHash: tx.Sha().String(),
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
Index: uint32(i),
|
|
|
|
Amount: txout.Value,
|
|
|
|
PkScript: btcutil.Base58Encode(txout.PkScript),
|
|
|
|
},
|
|
|
|
Error: nil,
|
2013-11-04 19:31:56 +01:00
|
|
|
Id: &ctx.id,
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
}
|
|
|
|
replyBytes, err := json.Marshal(reply)
|
|
|
|
if err != nil {
|
2013-11-21 19:03:56 +01:00
|
|
|
rpcsLog.Errorf("Unable to marshal tx notification: %v", err)
|
Clean up notification contexts and goroutines after ws disconnect.
This refactors the wallet notification code to reverse the order of
how notification contexts are stored. Before, watched addresses and
outpoints were used as keys, with a special reply channel as the
value. This channel was read from and replies were marshalled and
sent to the main wallet notification chan, but the goroutine handling
this marshalling never exited because the reply channel was never
closed (and couldn't have been, because there was no way to tell it
was handling notifications for any particular wallet).
Notification contexts are now primarily mapped by wallet notification
channels, and code to send the notifications send directly to the
wallet channel, with the previous goroutine reading the reply chan
properly closing.
The RPC code is also refactored with this change as well, to separate
it more from websocket code. Websocket JSON extensions are no longer
available to RPC clients.
While here, unbreak RPC. Previously, replies were never sent back.
This broke when I merged in my websocket code, as sends for the reply
channel in jsonRead blocked before a reader for the channel was
opened. A 3 liner could have fixed this, but doing a proper fix
(changing jsonRead so it did not use the reply channel as it is
unneeded for the standard RPC API) is preferred.
2013-10-16 20:12:00 +02:00
|
|
|
continue
|
|
|
|
}
|
2013-11-04 19:31:56 +01:00
|
|
|
ctx.connection <- replyBytes
|
2013-08-14 22:55:31 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|