2013-08-21 16:37:30 +02:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2013 Conformal Systems LLC <info@conformal.com>
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"github.com/conformal/btcjson"
|
|
|
|
"sync"
|
2013-08-21 19:25:22 +02:00
|
|
|
"time"
|
2013-08-21 16:37:30 +02:00
|
|
|
)
|
|
|
|
|
2013-08-21 17:14:21 +02:00
|
|
|
// Errors
|
|
|
|
var (
|
|
|
|
// Standard JSON-RPC 2.0 errors
|
|
|
|
InvalidRequest = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -32600,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Invalid request",
|
|
|
|
}
|
|
|
|
MethodNotFound = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -32601,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Method not found",
|
|
|
|
}
|
|
|
|
InvalidParams = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -32602,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Invalid paramaters",
|
|
|
|
}
|
|
|
|
InternalError = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -32603,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Internal error",
|
|
|
|
}
|
|
|
|
ParseError = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -32700,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Parse error",
|
|
|
|
}
|
|
|
|
|
|
|
|
// General application defined errors
|
|
|
|
MiscError = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -1,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Miscellaneous error",
|
|
|
|
}
|
|
|
|
ForbiddenBySafeMode = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -2,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Server is in safe mode, and command is not allowed in safe mode",
|
|
|
|
}
|
|
|
|
TypeError = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -3,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Unexpected type was passed as parameter",
|
|
|
|
}
|
|
|
|
InvalidAddressOrKey = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -5,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Invalid address or key",
|
|
|
|
}
|
|
|
|
OutOfMemory = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -7,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Ran out of memory during operation",
|
|
|
|
}
|
|
|
|
InvalidParameter = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -8,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Invalid, missing or duplicate parameter",
|
|
|
|
}
|
|
|
|
DatabaseError = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -20,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Database error",
|
|
|
|
}
|
|
|
|
DeserializationError = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -22,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Error parsing or validating structure in raw format",
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wallet errors
|
|
|
|
WalletError = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -4,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Unspecified problem with wallet",
|
|
|
|
}
|
|
|
|
WalletInsufficientFunds = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -6,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Not enough funds in wallet or account",
|
|
|
|
}
|
|
|
|
WalletInvalidAccountName = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -11,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Invalid account name",
|
|
|
|
}
|
|
|
|
WalletKeypoolRanOut = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -12,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Keypool ran out, call keypoolrefill first",
|
|
|
|
}
|
|
|
|
WalletUnlockNeeded = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -13,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Enter the wallet passphrase with walletpassphrase first",
|
|
|
|
}
|
|
|
|
WalletPassphraseIncorrect = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -14,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "The wallet passphrase entered was incorrect",
|
|
|
|
}
|
|
|
|
WalletWrongEncState = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -15,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Command given in wrong wallet encryption state",
|
|
|
|
}
|
|
|
|
WalletEncryptionFailed = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -16,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Failed to encrypt the wallet",
|
|
|
|
}
|
|
|
|
WalletAlreadyUnlocked = btcjson.Error{
|
2013-08-21 19:25:22 +02:00
|
|
|
Code: -17,
|
2013-08-21 17:14:21 +02:00
|
|
|
Message: "Wallet is already unlocked",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2013-08-21 16:37:30 +02:00
|
|
|
var (
|
|
|
|
// seq holds the btcwallet sequence number for frontend messages
|
|
|
|
// which must be sent to and received from btcd. A Mutex protects
|
|
|
|
// against concurrent access.
|
|
|
|
seq = struct {
|
|
|
|
sync.Mutex
|
|
|
|
n uint64
|
|
|
|
}{}
|
|
|
|
|
|
|
|
// replyRouter maps uint64 ids to reply channels, so btcd replies can
|
|
|
|
// be routed to the correct frontend.
|
|
|
|
replyRouter = struct {
|
|
|
|
sync.Mutex
|
|
|
|
m map[uint64]chan []byte
|
|
|
|
}{
|
|
|
|
m: make(map[uint64]chan []byte),
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// ProcessFrontendMsg checks the message sent from a frontend. If the
|
|
|
|
// message method is one that must be handled by btcwallet, the request
|
|
|
|
// is processed here. Otherwise, the message is sent to btcd.
|
|
|
|
func ProcessFrontendMsg(reply chan []byte, msg []byte) {
|
|
|
|
cmd, err := btcjson.JSONGetMethod(msg)
|
|
|
|
if err != nil {
|
|
|
|
log.Error("Unable to parse JSON method from message.")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch cmd {
|
2013-08-21 20:46:20 +02:00
|
|
|
// Standard bitcoind methods
|
2013-08-21 16:37:30 +02:00
|
|
|
case "getaddressesbyaccount":
|
|
|
|
GetAddressesByAccount(reply, msg)
|
|
|
|
case "getnewaddress":
|
|
|
|
GetNewAddress(reply, msg)
|
|
|
|
case "walletlock":
|
|
|
|
WalletLock(reply, msg)
|
|
|
|
case "walletpassphrase":
|
|
|
|
WalletPassphrase(reply, msg)
|
2013-08-21 20:46:20 +02:00
|
|
|
|
|
|
|
// btcwallet extensions
|
|
|
|
case "walletislocked":
|
|
|
|
WalletIsLocked(reply, msg)
|
|
|
|
|
2013-08-21 16:37:30 +02:00
|
|
|
default:
|
|
|
|
// btcwallet does not understand method. Pass to btcd.
|
|
|
|
log.Info("Unknown btcwallet method", cmd)
|
|
|
|
|
|
|
|
seq.Lock()
|
|
|
|
n := seq.n
|
|
|
|
seq.n++
|
|
|
|
seq.Unlock()
|
|
|
|
|
|
|
|
var m map[string]interface{}
|
|
|
|
json.Unmarshal(msg, &m)
|
|
|
|
m["id"] = fmt.Sprintf("btcwallet(%v)-%v", n, m["id"])
|
|
|
|
newMsg, err := json.Marshal(m)
|
|
|
|
if err != nil {
|
|
|
|
log.Info("Error marshalling json: " + err.Error())
|
|
|
|
}
|
|
|
|
replyRouter.Lock()
|
|
|
|
replyRouter.m[n] = reply
|
|
|
|
replyRouter.Unlock()
|
|
|
|
btcdMsgs <- newMsg
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-21 17:14:21 +02:00
|
|
|
// ReplyError creates and marshalls a btcjson.Reply with the error e,
|
|
|
|
// sending the reply to a reply channel.
|
|
|
|
func ReplyError(reply chan []byte, id interface{}, e *btcjson.Error) {
|
|
|
|
r := btcjson.Reply{
|
|
|
|
Error: e,
|
2013-08-21 19:25:22 +02:00
|
|
|
Id: &id,
|
|
|
|
}
|
2013-08-21 21:28:15 +02:00
|
|
|
if mr, err := json.Marshal(r); err == nil {
|
2013-08-21 19:25:22 +02:00
|
|
|
reply <- mr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// ReplySuccess creates and marshalls a btcjson.Reply with the result r,
|
|
|
|
// sending the reply to a reply channel.
|
|
|
|
func ReplySuccess(reply chan []byte, id interface{}, result interface{}) {
|
|
|
|
r := btcjson.Reply{
|
|
|
|
Result: result,
|
2013-08-21 19:43:05 +02:00
|
|
|
Id: &id,
|
2013-08-21 17:14:21 +02:00
|
|
|
}
|
2013-08-21 21:26:00 +02:00
|
|
|
if mr, err := json.Marshal(r); err == nil {
|
2013-08-21 17:14:21 +02:00
|
|
|
reply <- mr
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-21 16:37:30 +02:00
|
|
|
// GetAddressesByAccount Gets all addresses for an account.
|
|
|
|
func GetAddressesByAccount(reply chan []byte, msg []byte) {
|
|
|
|
var v map[string]interface{}
|
|
|
|
json.Unmarshal(msg, &v)
|
|
|
|
params := v["params"].([]interface{})
|
2013-08-21 19:43:05 +02:00
|
|
|
|
|
|
|
var result interface{}
|
2013-08-21 16:37:30 +02:00
|
|
|
if w := wallets[params[0].(string)]; w != nil {
|
2013-08-21 19:43:05 +02:00
|
|
|
result = w.GetActiveAddresses()
|
2013-08-21 16:37:30 +02:00
|
|
|
} else {
|
2013-08-21 19:43:05 +02:00
|
|
|
result = []interface{}{}
|
2013-08-21 16:37:30 +02:00
|
|
|
}
|
2013-08-21 19:43:05 +02:00
|
|
|
ReplySuccess(reply, v["id"], result)
|
2013-08-21 16:37:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetNewAddress gets or generates a new address for an account.
|
|
|
|
//
|
|
|
|
// TODO(jrick): support non-default account wallets.
|
|
|
|
func GetNewAddress(reply chan []byte, msg []byte) {
|
|
|
|
var v map[string]interface{}
|
|
|
|
json.Unmarshal(msg, &v)
|
|
|
|
params := v["params"].([]interface{})
|
|
|
|
if len(params) == 0 || params[0].(string) == "" {
|
|
|
|
if w := wallets[""]; w != nil {
|
|
|
|
addr := w.NextUnusedAddress()
|
2013-08-21 19:43:05 +02:00
|
|
|
ReplySuccess(reply, v["id"], addr)
|
2013-08-21 16:37:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-21 20:46:20 +02:00
|
|
|
// WalletIsLocked returns whether the wallet used by the specified
|
|
|
|
// account, or default account, is locked.
|
|
|
|
func WalletIsLocked(reply chan []byte, msg []byte) {
|
|
|
|
var v map[string]interface{}
|
|
|
|
json.Unmarshal(msg, &v)
|
|
|
|
params := v["params"].([]interface{})
|
|
|
|
account := ""
|
|
|
|
if len(params) > 0 {
|
|
|
|
if acct, ok := params[0].(string); ok {
|
|
|
|
account = acct
|
|
|
|
} else {
|
|
|
|
ReplyError(reply, v["id"], &InvalidParams)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if w := wallets[account]; w != nil {
|
|
|
|
result := w.IsLocked()
|
|
|
|
ReplySuccess(reply, v["id"], result)
|
|
|
|
} else {
|
|
|
|
ReplyError(reply, v["id"], &WalletInvalidAccountName)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-21 16:37:30 +02:00
|
|
|
// WalletLock locks the wallet.
|
|
|
|
//
|
|
|
|
// TODO(jrick): figure out how multiple wallets/accounts will work
|
|
|
|
// with this.
|
|
|
|
func WalletLock(reply chan []byte, msg []byte) {
|
2013-08-21 19:25:22 +02:00
|
|
|
var v map[string]interface{}
|
|
|
|
json.Unmarshal(msg, &v)
|
|
|
|
if w := wallets[""]; w != nil {
|
|
|
|
if err := w.Lock(); err != nil {
|
|
|
|
ReplyError(reply, v["id"], &WalletWrongEncState)
|
|
|
|
} else {
|
|
|
|
ReplySuccess(reply, v["id"], nil)
|
2013-08-22 18:00:37 +02:00
|
|
|
NotifyWalletLockStateChange(reply, true)
|
2013-08-21 19:25:22 +02:00
|
|
|
}
|
|
|
|
}
|
2013-08-21 16:37:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// WalletPassphrase stores the decryption key for the default account,
|
|
|
|
// unlocking the wallet.
|
|
|
|
//
|
|
|
|
// TODO(jrick): figure out how multiple wallets/accounts will work
|
|
|
|
// with this.
|
|
|
|
func WalletPassphrase(reply chan []byte, msg []byte) {
|
|
|
|
var v map[string]interface{}
|
|
|
|
json.Unmarshal(msg, &v)
|
|
|
|
params := v["params"].([]interface{})
|
|
|
|
if len(params) != 2 {
|
2013-08-21 18:07:57 +02:00
|
|
|
ReplyError(reply, v["id"], &InvalidParams)
|
2013-08-21 16:37:30 +02:00
|
|
|
return
|
|
|
|
}
|
2013-08-21 18:07:57 +02:00
|
|
|
passphrase, ok1 := params[0].(string)
|
|
|
|
timeout, ok2 := params[1].(float64)
|
|
|
|
if !ok1 || !ok2 {
|
|
|
|
ReplyError(reply, v["id"], &InvalidParams)
|
2013-08-21 16:37:30 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if w := wallets[""]; w != nil {
|
2013-08-21 18:07:57 +02:00
|
|
|
if err := w.Unlock([]byte(passphrase)); err != nil {
|
|
|
|
ReplyError(reply, v["id"], &WalletPassphraseIncorrect)
|
|
|
|
return
|
|
|
|
}
|
2013-08-21 19:29:36 +02:00
|
|
|
ReplySuccess(reply, v["id"], nil)
|
2013-08-22 18:00:37 +02:00
|
|
|
NotifyWalletLockStateChange(reply, false)
|
2013-08-21 16:37:30 +02:00
|
|
|
go func() {
|
|
|
|
time.Sleep(time.Second * time.Duration(int64(timeout)))
|
|
|
|
w.Lock()
|
2013-08-22 18:00:37 +02:00
|
|
|
NotifyWalletLockStateChange(reply, true)
|
2013-08-21 16:37:30 +02:00
|
|
|
}()
|
|
|
|
}
|
|
|
|
}
|
2013-08-22 18:00:37 +02:00
|
|
|
|
|
|
|
// NotifyWalletLockStateChange sends a notification to all frontends
|
|
|
|
// that the wallet has just been locked or unlocked.
|
|
|
|
func NotifyWalletLockStateChange(reply chan []byte, locked bool) {
|
|
|
|
var id interface{} = "btcwallet:newwalletlockstate"
|
|
|
|
m := btcjson.Reply{
|
|
|
|
Result: locked,
|
2013-08-22 19:14:21 +02:00
|
|
|
Id: &id,
|
2013-08-22 18:00:37 +02:00
|
|
|
}
|
|
|
|
msg, _ := json.Marshal(&m)
|
|
|
|
frontendNotificationMaster <- msg
|
|
|
|
}
|