2013-08-21 16:37:30 +02:00
|
|
|
/*
|
2014-01-03 19:34:37 +01:00
|
|
|
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
|
2013-08-21 16:37:30 +02:00
|
|
|
*
|
|
|
|
* 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 (
|
2014-02-24 20:35:30 +01:00
|
|
|
"bytes"
|
2014-04-18 00:13:53 +02:00
|
|
|
"crypto/ecdsa"
|
2014-02-12 03:14:07 +01:00
|
|
|
"encoding/base64"
|
2013-10-04 21:02:17 +02:00
|
|
|
"encoding/hex"
|
2014-04-09 18:07:09 +02:00
|
|
|
"encoding/json"
|
2014-04-18 00:13:53 +02:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2014-02-12 03:14:07 +01:00
|
|
|
"github.com/conformal/btcec"
|
2013-08-21 16:37:30 +02:00
|
|
|
"github.com/conformal/btcjson"
|
2014-02-24 20:35:30 +01:00
|
|
|
"github.com/conformal/btcscript"
|
2013-12-10 22:15:25 +01:00
|
|
|
"github.com/conformal/btcutil"
|
2013-11-22 19:42:25 +01:00
|
|
|
"github.com/conformal/btcwallet/tx"
|
2013-09-03 15:49:16 +02:00
|
|
|
"github.com/conformal/btcwallet/wallet"
|
2013-10-07 18:35:32 +02:00
|
|
|
"github.com/conformal/btcwire"
|
2013-11-12 18:01:32 +01:00
|
|
|
"github.com/conformal/btcws"
|
2013-12-17 19:18:09 +01:00
|
|
|
"sync"
|
2013-08-21 19:25:22 +02:00
|
|
|
"time"
|
2013-08-21 16:37:30 +02:00
|
|
|
)
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
type cmdHandler func(btcjson.Cmd) (interface{}, *btcjson.Error)
|
2013-11-12 18:01:32 +01:00
|
|
|
|
2013-11-18 19:31:58 +01:00
|
|
|
var rpcHandlers = map[string]cmdHandler{
|
2013-12-30 18:44:51 +01:00
|
|
|
// Standard bitcoind methods (implemented)
|
2014-03-20 12:12:13 +01:00
|
|
|
"addmultisigaddress": AddMultiSigAddress,
|
|
|
|
"createmultisig": CreateMultiSig,
|
2014-01-27 15:30:42 +01:00
|
|
|
"dumpprivkey": DumpPrivKey,
|
|
|
|
"getaccount": GetAccount,
|
|
|
|
"getaccountaddress": GetAccountAddress,
|
|
|
|
"getaddressesbyaccount": GetAddressesByAccount,
|
|
|
|
"getbalance": GetBalance,
|
2014-01-29 18:14:24 +01:00
|
|
|
"getinfo": GetInfo,
|
2014-01-27 15:30:42 +01:00
|
|
|
"getnewaddress": GetNewAddress,
|
2014-02-03 16:52:02 +01:00
|
|
|
"getrawchangeaddress": GetRawChangeAddress,
|
2014-02-03 19:00:28 +01:00
|
|
|
"getreceivedbyaccount": GetReceivedByAccount,
|
2014-02-03 19:29:25 +01:00
|
|
|
"gettransaction": GetTransaction,
|
2014-01-27 15:30:42 +01:00
|
|
|
"importprivkey": ImportPrivKey,
|
|
|
|
"keypoolrefill": KeypoolRefill,
|
|
|
|
"listaccounts": ListAccounts,
|
2014-01-27 18:53:32 +01:00
|
|
|
"listsinceblock": ListSinceBlock,
|
2014-01-27 15:30:42 +01:00
|
|
|
"listtransactions": ListTransactions,
|
2014-02-13 19:43:52 +01:00
|
|
|
"listunspent": ListUnspent,
|
2014-01-27 15:30:42 +01:00
|
|
|
"sendfrom": SendFrom,
|
|
|
|
"sendmany": SendMany,
|
2014-02-04 17:33:29 +01:00
|
|
|
"sendtoaddress": SendToAddress,
|
2014-01-27 15:30:42 +01:00
|
|
|
"settxfee": SetTxFee,
|
2014-02-12 03:14:07 +01:00
|
|
|
"signmessage": SignMessage,
|
2014-04-18 00:13:53 +02:00
|
|
|
"signrawtransaction": SignRawTransaction,
|
2014-02-14 00:16:21 +01:00
|
|
|
"validateaddress": ValidateAddress,
|
2014-02-12 03:14:07 +01:00
|
|
|
"verifymessage": VerifyMessage,
|
2014-01-27 15:30:42 +01:00
|
|
|
"walletlock": WalletLock,
|
|
|
|
"walletpassphrase": WalletPassphrase,
|
|
|
|
"walletpassphrasechange": WalletPassphraseChange,
|
2013-11-12 18:01:32 +01:00
|
|
|
|
2013-12-30 18:44:51 +01:00
|
|
|
// Standard bitcoind methods (currently unimplemented)
|
2014-01-27 15:30:42 +01:00
|
|
|
"backupwallet": Unimplemented,
|
|
|
|
"dumpwallet": Unimplemented,
|
|
|
|
"getblocktemplate": Unimplemented,
|
|
|
|
"getreceivedbyaddress": Unimplemented,
|
|
|
|
"gettxout": Unimplemented,
|
|
|
|
"gettxoutsetinfo": Unimplemented,
|
|
|
|
"getwork": Unimplemented,
|
|
|
|
"importwallet": Unimplemented,
|
|
|
|
"listaddressgroupings": Unimplemented,
|
|
|
|
"listlockunspent": Unimplemented,
|
|
|
|
"listreceivedbyaccount": Unimplemented,
|
|
|
|
"listreceivedbyaddress": Unimplemented,
|
|
|
|
"lockunspent": Unimplemented,
|
|
|
|
"move": Unimplemented,
|
|
|
|
"setaccount": Unimplemented,
|
2014-02-03 21:01:25 +01:00
|
|
|
"stop": Unimplemented,
|
2013-12-30 18:44:51 +01:00
|
|
|
|
|
|
|
// Standard bitcoind methods which won't be implemented by btcwallet.
|
|
|
|
"encryptwallet": Unsupported,
|
|
|
|
|
2013-11-18 19:31:58 +01:00
|
|
|
// Extensions not exclusive to websocket connections.
|
2013-11-12 18:01:32 +01:00
|
|
|
"createencryptedwallet": CreateEncryptedWallet,
|
2013-11-18 19:31:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Extensions exclusive to websocket connections.
|
|
|
|
var wsHandlers = map[string]cmdHandler{
|
Implement exporting a watching-only wallet.
This change allows for the use of watching-only wallets. Unlike
normal, "hot" wallets, watching-only wallets do not contain any
private keys, and can be used in situations where you want to keep one
wallet online to create new receiving addresses and watch for received
transactions, while keeping the hot wallet offline (possibly on an
air-gapped computer).
Two (websocket) extension RPC calls have been added:
First, exportwatchingwallet, which will export the current hot wallet
to a watching-only wallet, saving either to disk or returning the
base64-encoded wallet files to the caller.
Second, recoveraddresses, which is used to recover the next n
addresses from the address chain. This is used to "sync" a watching
wallet with the hot wallet, or vice versa.
2014-01-21 20:45:28 +01:00
|
|
|
"exportwatchingwallet": ExportWatchingWallet,
|
2013-12-30 17:10:06 +01:00
|
|
|
"getaddressbalance": GetAddressBalance,
|
2013-12-30 21:11:41 +01:00
|
|
|
"getunconfirmedbalance": GetUnconfirmedBalance,
|
2013-12-30 17:10:06 +01:00
|
|
|
"listaddresstransactions": ListAddressTransactions,
|
|
|
|
"listalltransactions": ListAllTransactions,
|
Implement exporting a watching-only wallet.
This change allows for the use of watching-only wallets. Unlike
normal, "hot" wallets, watching-only wallets do not contain any
private keys, and can be used in situations where you want to keep one
wallet online to create new receiving addresses and watch for received
transactions, while keeping the hot wallet offline (possibly on an
air-gapped computer).
Two (websocket) extension RPC calls have been added:
First, exportwatchingwallet, which will export the current hot wallet
to a watching-only wallet, saving either to disk or returning the
base64-encoded wallet files to the caller.
Second, recoveraddresses, which is used to recover the next n
addresses from the address chain. This is used to "sync" a watching
wallet with the hot wallet, or vice versa.
2014-01-21 20:45:28 +01:00
|
|
|
"recoveraddresses": RecoverAddresses,
|
2013-12-30 17:10:06 +01:00
|
|
|
"walletislocked": WalletIsLocked,
|
2013-11-12 18:01:32 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// Channels to control RPCGateway
|
|
|
|
var (
|
|
|
|
// Incoming requests from frontends
|
|
|
|
clientRequests = make(chan *ClientRequest)
|
|
|
|
|
|
|
|
// Incoming notifications from a bitcoin server (btcd)
|
|
|
|
svrNtfns = make(chan btcjson.Cmd)
|
|
|
|
)
|
|
|
|
|
|
|
|
// ErrServerBusy is a custom JSON-RPC error for when a client's request
|
|
|
|
// could not be added to the server request queue for handling.
|
|
|
|
var ErrServerBusy = btcjson.Error{
|
|
|
|
Code: -32000,
|
|
|
|
Message: "Server busy",
|
|
|
|
}
|
|
|
|
|
2014-04-09 18:07:09 +02:00
|
|
|
// ErrServerBusyRaw is the raw JSON encoding of ErrServerBusy.
|
|
|
|
var ErrServerBusyRaw = json.RawMessage(`{"code":-32000,"message":"Server busy"}`)
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// RPCGateway is the common entry point for all client RPC requests and
|
|
|
|
// server notifications. If a request needs to be handled by btcwallet,
|
|
|
|
// it is sent to WalletRequestProcessor's request queue, or dropped if the
|
|
|
|
// queue is full. If a request is unhandled, it is recreated with a new
|
|
|
|
// JSON-RPC id and sent to btcd for handling. Notifications are also queued
|
|
|
|
// if they cannot be immediately handled, but are never dropped (queue may
|
|
|
|
// grow infinitely large).
|
|
|
|
func RPCGateway() {
|
|
|
|
var ntfnQueue []btcjson.Cmd
|
|
|
|
unreadChan := make(chan btcjson.Cmd)
|
|
|
|
|
|
|
|
for {
|
|
|
|
var ntfnOut chan btcjson.Cmd
|
|
|
|
var oldestNtfn btcjson.Cmd
|
|
|
|
if len(ntfnQueue) > 0 {
|
|
|
|
ntfnOut = handleNtfn
|
|
|
|
oldestNtfn = ntfnQueue[0]
|
|
|
|
} else {
|
|
|
|
ntfnOut = unreadChan
|
2013-11-12 18:01:32 +01:00
|
|
|
}
|
2013-08-21 16:37:30 +02:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
select {
|
|
|
|
case r := <-clientRequests:
|
|
|
|
// Check whether to handle request or send to btcd.
|
|
|
|
_, std := rpcHandlers[r.request.Method()]
|
|
|
|
_, ext := wsHandlers[r.request.Method()]
|
|
|
|
if std || ext {
|
|
|
|
select {
|
|
|
|
case requestQueue <- r:
|
|
|
|
default:
|
|
|
|
// Server busy with too many requests.
|
2014-04-09 18:07:09 +02:00
|
|
|
resp := RawRPCResponse{
|
|
|
|
Error: &ErrServerBusyRaw,
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
2014-04-09 18:07:09 +02:00
|
|
|
r.response <- resp
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
r.request.SetId(<-NewJSONID)
|
|
|
|
request := &ServerRequest{
|
|
|
|
request: r.request,
|
|
|
|
response: r.response,
|
|
|
|
}
|
|
|
|
CurrentServerConn().SendRequest(request)
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case n := <-svrNtfns:
|
|
|
|
ntfnQueue = append(ntfnQueue, n)
|
2013-11-12 18:01:32 +01:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ntfnOut <- oldestNtfn:
|
|
|
|
ntfnQueue = ntfnQueue[1:]
|
|
|
|
}
|
2013-08-21 17:14:21 +02:00
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
}
|
2013-11-12 18:01:32 +01:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// Channels to control WalletRequestProcessor
|
|
|
|
var (
|
|
|
|
requestQueue = make(chan *ClientRequest, 100)
|
|
|
|
handleNtfn = make(chan btcjson.Cmd)
|
|
|
|
)
|
|
|
|
|
|
|
|
// WalletRequestProcessor processes client requests and btcd notifications.
|
|
|
|
func WalletRequestProcessor() {
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case r := <-requestQueue:
|
2014-03-28 04:18:23 +01:00
|
|
|
method := r.request.Method()
|
|
|
|
f, ok := rpcHandlers[method]
|
|
|
|
if !ok && r.ws {
|
|
|
|
f, ok = wsHandlers[method]
|
|
|
|
}
|
|
|
|
if !ok {
|
|
|
|
f = Unimplemented
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
2014-03-28 04:18:23 +01:00
|
|
|
|
|
|
|
AcctMgr.Grab()
|
|
|
|
result, jsonErr := f(r.request)
|
|
|
|
AcctMgr.Release()
|
|
|
|
|
2014-04-09 18:07:09 +02:00
|
|
|
if jsonErr != nil {
|
|
|
|
b, _ := json.Marshal(jsonErr)
|
|
|
|
r.response <- RawRPCResponse{
|
|
|
|
Error: (*json.RawMessage)(&b),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
b, _ := json.Marshal(result)
|
|
|
|
r.response <- RawRPCResponse{
|
|
|
|
Result: (*json.RawMessage)(&b),
|
|
|
|
}
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
case n := <-handleNtfn:
|
2014-03-28 04:18:23 +01:00
|
|
|
f, ok := notificationHandlers[n.Method()]
|
|
|
|
if !ok {
|
|
|
|
// Ignore unhandled notifications.
|
|
|
|
continue
|
|
|
|
}
|
2014-02-28 19:03:23 +01:00
|
|
|
|
2014-03-28 04:18:23 +01:00
|
|
|
AcctMgr.Grab()
|
|
|
|
err := f(n)
|
|
|
|
AcctMgr.Release()
|
|
|
|
switch err {
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
case tx.ErrInconsistentStore:
|
2014-03-28 04:18:23 +01:00
|
|
|
// Assume this is a broken btcd reordered
|
|
|
|
// notifications. Restart the connection
|
|
|
|
// to reload accounts files from their last
|
|
|
|
// known good state.
|
2014-03-28 17:28:59 +01:00
|
|
|
log.Warn("Reconnecting to recover from " +
|
2014-03-28 04:18:23 +01:00
|
|
|
"out-of-order btcd notification")
|
|
|
|
s := CurrentServerConn()
|
|
|
|
if btcd, ok := s.(*BtcdRPCConn); ok {
|
|
|
|
AcctMgr.Grab()
|
|
|
|
btcd.Close()
|
|
|
|
AcctMgr.OpenAccounts()
|
|
|
|
AcctMgr.Release()
|
2014-02-28 19:03:23 +01:00
|
|
|
}
|
2014-03-28 04:18:23 +01:00
|
|
|
|
|
|
|
case nil: // ignore
|
|
|
|
default:
|
|
|
|
log.Warn(err)
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2013-08-21 17:14:21 +02:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// Unimplemented handles an unimplemented RPC request with the
|
2013-12-30 18:44:51 +01:00
|
|
|
// appropiate error.
|
2014-01-03 19:34:37 +01:00
|
|
|
func Unimplemented(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
return nil, &btcjson.ErrUnimplemented
|
2013-12-30 18:44:51 +01:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// Unsupported handles a standard bitcoind RPC request which is
|
2013-12-30 18:44:51 +01:00
|
|
|
// unsupported by btcwallet due to design differences.
|
2014-01-03 19:34:37 +01:00
|
|
|
func Unsupported(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
e := btcjson.Error{
|
2013-12-30 18:44:51 +01:00
|
|
|
Code: -1,
|
2014-01-03 19:34:37 +01:00
|
|
|
Message: "Request unsupported by btcwallet",
|
2013-12-30 18:44:51 +01:00
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-30 18:44:51 +01:00
|
|
|
}
|
|
|
|
|
2014-03-20 12:12:13 +01:00
|
|
|
// makeMultiSigScript is a heper function to combine common logic for
|
|
|
|
// AddMultiSig and CreateMultiSig.
|
|
|
|
// all error codes are rpc parse error here to match bitcoind which just throws
|
|
|
|
// a runtime exception. *sigh*.
|
|
|
|
func makeMultiSigScript(keys []string, nRequired int) ([]byte, *btcjson.Error) {
|
|
|
|
keysesPrecious := make([]*btcutil.AddressPubKey, len(keys))
|
|
|
|
|
|
|
|
// The address list will made up either of addreseses (pubkey hash), for
|
|
|
|
// which we need to look up the keys in wallet, straight pubkeys, or a
|
|
|
|
// mixture of the two.
|
|
|
|
for i, a := range keys {
|
|
|
|
// try to parse as pubkey address
|
|
|
|
a, err := btcutil.DecodeAddress(a, cfg.Net())
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrParse.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch addr := a.(type) {
|
|
|
|
case *btcutil.AddressPubKey:
|
|
|
|
keysesPrecious[i] = addr
|
|
|
|
case *btcutil.AddressPubKeyHash:
|
2014-04-18 00:00:52 +02:00
|
|
|
ainfo, err := AcctMgr.Address(addr)
|
2014-03-20 12:12:13 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrParse.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-09 02:18:52 +02:00
|
|
|
apkinfo := ainfo.(wallet.PubKeyAddress)
|
2014-03-20 12:12:13 +01:00
|
|
|
|
|
|
|
// This will be an addresspubkey
|
2014-04-09 02:18:52 +02:00
|
|
|
a, err := btcutil.DecodeAddress(apkinfo.ExportPubKey(),
|
2014-03-20 12:12:13 +01:00
|
|
|
cfg.Net())
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrParse.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
apk := a.(*btcutil.AddressPubKey)
|
|
|
|
keysesPrecious[i] = apk
|
|
|
|
default:
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrParse.Code,
|
|
|
|
Message: "key is not a pubkey or pubkey hash address",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
script, err := btcscript.MultiSigScript(keysesPrecious, nRequired)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrParse.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return script, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddMultiSigAddress handles an addmultisigaddress request by adding a
|
|
|
|
// multisig address to the given wallet.
|
|
|
|
func AddMultiSigAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcjson.AddMultisigAddressCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
|
|
|
acct, err := AcctMgr.Account(cmd.Account)
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
|
|
|
case ErrNotFound:
|
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
|
|
|
|
|
|
|
default:
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
|
|
|
|
script, jsonerr := makeMultiSigScript(cmd.Keys, cmd.NRequired)
|
|
|
|
if jsonerr != nil {
|
|
|
|
return nil, jsonerr
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(oga) blockstamp current block?
|
|
|
|
address, err := acct.ImportScript(script, &wallet.BlockStamp{})
|
2014-04-18 00:13:53 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
2014-03-20 12:12:13 +01:00
|
|
|
|
|
|
|
return address.EncodeAddress(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateMultiSig handles an createmultisig request by returning a
|
|
|
|
// multisig address for the given inputs.
|
|
|
|
func CreateMultiSig(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcjson.CreateMultisigCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
|
|
|
script, jsonerr := makeMultiSigScript(cmd.Keys, cmd.NRequired)
|
|
|
|
if jsonerr != nil {
|
|
|
|
return nil, jsonerr
|
|
|
|
}
|
|
|
|
|
|
|
|
address, err := btcutil.NewAddressScriptHash(script, cfg.Net())
|
|
|
|
if err != nil {
|
|
|
|
// above is a valid script, shouldn't happen.
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-09 19:57:28 +02:00
|
|
|
return btcjson.CreateMultiSigResult{
|
|
|
|
Address: address.EncodeAddress(),
|
|
|
|
RedeemScript: hex.EncodeToString(script),
|
2014-03-20 12:12:13 +01:00
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// DumpPrivKey handles a dumpprivkey request with the private key
|
|
|
|
// for a single address, or an appropiate error if the wallet
|
2013-11-20 02:18:11 +01:00
|
|
|
// is locked.
|
2014-01-03 19:34:37 +01:00
|
|
|
func DumpPrivKey(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-20 02:18:11 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.DumpPrivKeyCmd)
|
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-11-20 02:18:11 +01:00
|
|
|
}
|
|
|
|
|
2014-03-19 02:47:12 +01:00
|
|
|
addr, err := btcutil.DecodeAddress(cmd.Address, cfg.Net())
|
2014-01-06 18:24:29 +01:00
|
|
|
if err != nil {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInvalidAddressOrKey
|
2014-01-06 18:24:29 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
switch key, err := AcctMgr.DumpWIFPrivateKey(addr); err {
|
2013-12-02 20:56:06 +01:00
|
|
|
case nil:
|
|
|
|
// Key was found.
|
2014-01-03 19:34:37 +01:00
|
|
|
return key, nil
|
2013-12-02 20:56:06 +01:00
|
|
|
|
|
|
|
case wallet.ErrWalletLocked:
|
|
|
|
// Address was found, but the private key isn't
|
|
|
|
// accessible.
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletUnlockNeeded
|
2013-11-20 02:18:11 +01:00
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
default: // all other non-nil errors
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
2013-11-20 02:18:11 +01:00
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-11-20 02:18:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// DumpWallet handles a dumpwallet request by returning all private
|
|
|
|
// keys in a wallet, or an appropiate error if the wallet is locked.
|
2013-11-20 02:44:37 +01:00
|
|
|
// TODO: finish this to match bitcoind by writing the dump to a file.
|
2014-01-03 19:34:37 +01:00
|
|
|
func DumpWallet(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-20 02:18:11 +01:00
|
|
|
// Type assert icmd to access parameters.
|
2014-01-03 19:34:37 +01:00
|
|
|
_, ok := icmd.(*btcjson.DumpWalletCmd)
|
2013-11-20 02:18:11 +01:00
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-11-20 02:18:11 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
switch keys, err := AcctMgr.DumpKeys(); err {
|
2013-12-02 20:56:06 +01:00
|
|
|
case nil:
|
|
|
|
// Reply with sorted WIF encoded private keys
|
2014-01-03 19:34:37 +01:00
|
|
|
return keys, nil
|
2013-11-20 02:18:11 +01:00
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
case wallet.ErrWalletLocked:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletUnlockNeeded
|
2013-11-20 02:18:11 +01:00
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
default: // any other non-nil error
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
2013-11-20 02:18:11 +01:00
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-11-20 02:18:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Implement exporting a watching-only wallet.
This change allows for the use of watching-only wallets. Unlike
normal, "hot" wallets, watching-only wallets do not contain any
private keys, and can be used in situations where you want to keep one
wallet online to create new receiving addresses and watch for received
transactions, while keeping the hot wallet offline (possibly on an
air-gapped computer).
Two (websocket) extension RPC calls have been added:
First, exportwatchingwallet, which will export the current hot wallet
to a watching-only wallet, saving either to disk or returning the
base64-encoded wallet files to the caller.
Second, recoveraddresses, which is used to recover the next n
addresses from the address chain. This is used to "sync" a watching
wallet with the hot wallet, or vice versa.
2014-01-21 20:45:28 +01:00
|
|
|
// ExportWatchingWallet handles an exportwatchingwallet request by exporting
|
|
|
|
// the current account wallet as a watching wallet (with no private keys), and
|
|
|
|
// either writing the exported wallet to disk, or base64-encoding serialized
|
|
|
|
// account files and sending them back in the response.
|
|
|
|
func ExportWatchingWallet(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcws.ExportWatchingWalletCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
Implement exporting a watching-only wallet.
This change allows for the use of watching-only wallets. Unlike
normal, "hot" wallets, watching-only wallets do not contain any
private keys, and can be used in situations where you want to keep one
wallet online to create new receiving addresses and watch for received
transactions, while keeping the hot wallet offline (possibly on an
air-gapped computer).
Two (websocket) extension RPC calls have been added:
First, exportwatchingwallet, which will export the current hot wallet
to a watching-only wallet, saving either to disk or returning the
base64-encoded wallet files to the caller.
Second, recoveraddresses, which is used to recover the next n
addresses from the address chain. This is used to "sync" a watching
wallet with the hot wallet, or vice versa.
2014-01-21 20:45:28 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
Implement exporting a watching-only wallet.
This change allows for the use of watching-only wallets. Unlike
normal, "hot" wallets, watching-only wallets do not contain any
private keys, and can be used in situations where you want to keep one
wallet online to create new receiving addresses and watch for received
transactions, while keeping the hot wallet offline (possibly on an
air-gapped computer).
Two (websocket) extension RPC calls have been added:
First, exportwatchingwallet, which will export the current hot wallet
to a watching-only wallet, saving either to disk or returning the
base64-encoded wallet files to the caller.
Second, recoveraddresses, which is used to recover the next n
addresses from the address chain. This is used to "sync" a watching
wallet with the hot wallet, or vice versa.
2014-01-21 20:45:28 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
|
|
|
|
|
|
|
default: // all other non-nil errors
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
|
|
|
|
wa, err := a.ExportWatchingWallet()
|
|
|
|
if err != nil {
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
|
|
|
|
if cmd.Download {
|
|
|
|
switch m, err := wa.exportBase64(); err {
|
|
|
|
case nil:
|
|
|
|
return m, nil
|
|
|
|
|
|
|
|
default:
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create export directory, write files there.
|
2014-01-29 05:04:10 +01:00
|
|
|
if err = wa.ExportToDirectory("watchingwallet"); err != nil {
|
Implement exporting a watching-only wallet.
This change allows for the use of watching-only wallets. Unlike
normal, "hot" wallets, watching-only wallets do not contain any
private keys, and can be used in situations where you want to keep one
wallet online to create new receiving addresses and watch for received
transactions, while keeping the hot wallet offline (possibly on an
air-gapped computer).
Two (websocket) extension RPC calls have been added:
First, exportwatchingwallet, which will export the current hot wallet
to a watching-only wallet, saving either to disk or returning the
base64-encoded wallet files to the caller.
Second, recoveraddresses, which is used to recover the next n
addresses from the address chain. This is used to "sync" a watching
wallet with the hot wallet, or vice versa.
2014-01-21 20:45:28 +01:00
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// GetAddressesByAccount handles a getaddressesbyaccount request by returning
|
2013-11-12 18:01:32 +01:00
|
|
|
// all addresses for an account, or an error if the requested account does
|
|
|
|
// not exist.
|
2014-01-03 19:34:37 +01:00
|
|
|
func GetAddressesByAccount(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-12 18:01:32 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.GetAddressesByAccountCmd)
|
2013-09-09 20:14:57 +02:00
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
2013-10-08 04:17:27 +02:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
switch a, err := AcctMgr.Account(cmd.Account); err {
|
2013-12-02 20:56:06 +01:00
|
|
|
case nil:
|
2014-01-03 19:34:37 +01:00
|
|
|
// Return sorted active payment addresses.
|
|
|
|
return a.SortedActivePaymentAddresses(), nil
|
2013-12-02 20:56:06 +01:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-10-08 04:17:27 +02:00
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
default: // all other non-nil errors
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-02 20:56:06 +01:00
|
|
|
}
|
2013-08-21 16:37:30 +02:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// GetBalance handles a getbalance request by returning the balance for an
|
2013-11-12 18:01:32 +01:00
|
|
|
// account (wallet), or an error if the requested account does not
|
|
|
|
// exist.
|
2014-01-03 19:34:37 +01:00
|
|
|
func GetBalance(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-12 18:01:32 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.GetBalanceCmd)
|
2013-09-09 20:14:57 +02:00
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
2013-09-03 17:00:01 +02:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
balance, err := AcctMgr.CalculateBalance(cmd.Account, cmd.MinConf)
|
2013-12-02 20:56:06 +01:00
|
|
|
if err != nil {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-09-03 17:00:01 +02:00
|
|
|
}
|
2013-11-12 18:01:32 +01:00
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// Return calculated balance.
|
|
|
|
return balance, nil
|
2013-09-03 17:00:01 +02:00
|
|
|
}
|
|
|
|
|
2014-01-29 18:14:24 +01:00
|
|
|
// GetInfo handles a getinfo request by returning the a structure containing
|
|
|
|
// information about the current state of btcwallet.
|
|
|
|
// exist.
|
|
|
|
func GetInfo(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
// Call down to btcd for all of the information in this command known
|
|
|
|
// by them. This call can not realistically ever fail.
|
|
|
|
gicmd, _ := btcjson.NewGetInfoCmd(<-NewJSONID)
|
2014-04-09 18:07:09 +02:00
|
|
|
response := <-CurrentServerConn().SendRequest(NewServerRequest(gicmd))
|
|
|
|
|
|
|
|
var info btcjson.InfoResult
|
|
|
|
_, jsonErr := response.FinishUnmarshal(&info)
|
|
|
|
if jsonErr != nil {
|
|
|
|
return nil, jsonErr
|
2014-01-29 18:14:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
balance := float64(0.0)
|
2014-01-30 16:14:02 +01:00
|
|
|
accounts := AcctMgr.ListAccounts(1)
|
2014-01-29 18:14:24 +01:00
|
|
|
for _, v := range accounts {
|
|
|
|
balance += v
|
|
|
|
}
|
2014-04-09 18:07:09 +02:00
|
|
|
info.WalletVersion = int(wallet.VersCurrent.Uint32())
|
|
|
|
info.Balance = balance
|
2014-01-29 18:14:24 +01:00
|
|
|
// Keypool times are not tracked. set to current time.
|
2014-04-09 18:07:09 +02:00
|
|
|
info.KeypoolOldest = time.Now().Unix()
|
|
|
|
info.KeypoolSize = int(cfg.KeypoolSize)
|
2014-01-29 18:14:24 +01:00
|
|
|
TxFeeIncrement.Lock()
|
2014-04-09 18:07:09 +02:00
|
|
|
info.PaytxFee = float64(TxFeeIncrement.i) / float64(btcutil.SatoshiPerBitcoin)
|
2014-01-29 18:14:24 +01:00
|
|
|
TxFeeIncrement.Unlock()
|
|
|
|
/*
|
|
|
|
* We don't set the following since they don't make much sense in the
|
|
|
|
* wallet architecture:
|
2014-04-09 18:07:09 +02:00
|
|
|
* - unlocked_until
|
|
|
|
* - errors
|
2014-01-29 18:14:24 +01:00
|
|
|
*/
|
|
|
|
|
2014-04-09 18:07:09 +02:00
|
|
|
return info, nil
|
2014-01-29 18:14:24 +01:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// GetAccount handles a getaccount request by returning the account name
|
|
|
|
// associated with a single address.
|
|
|
|
func GetAccount(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-12-30 22:31:33 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.GetAccountCmd)
|
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-12-30 22:31:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Is address valid?
|
2014-03-19 02:47:12 +01:00
|
|
|
addr, err := btcutil.DecodeAddress(cmd.Address, cfg.Net())
|
2014-01-06 18:24:29 +01:00
|
|
|
if err != nil {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInvalidAddressOrKey
|
2014-01-06 18:24:29 +01:00
|
|
|
}
|
2014-02-26 21:22:48 +01:00
|
|
|
if !addr.IsForNet(cfg.Net()) {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInvalidAddressOrKey
|
2013-12-30 22:31:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Look up account which holds this address.
|
2014-04-03 17:00:46 +02:00
|
|
|
acct, err := AcctMgr.AccountByAddress(addr)
|
2013-12-30 22:31:33 +01:00
|
|
|
if err == ErrNotFound {
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-30 22:31:33 +01:00
|
|
|
Code: btcjson.ErrInvalidAddressOrKey.Code,
|
|
|
|
Message: "Address not found in wallet",
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-30 22:31:33 +01:00
|
|
|
}
|
|
|
|
|
2014-04-03 17:00:46 +02:00
|
|
|
return acct.Name(), nil
|
2013-12-30 22:31:33 +01:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// GetAccountAddress handles a getaccountaddress by returning the most
|
2013-12-31 19:11:47 +01:00
|
|
|
// recently-created chained address that has not yet been used (does not yet
|
|
|
|
// appear in the blockchain, or any tx that has arrived in the btcd mempool).
|
|
|
|
// If the most recently-requested address has been used, a new address (the
|
|
|
|
// next chained address in the keypool) is used. This can fail if the keypool
|
|
|
|
// runs out (and will return btcjson.ErrWalletKeypoolRanOut if that happens).
|
2014-01-03 19:34:37 +01:00
|
|
|
func GetAccountAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-12-31 19:11:47 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.GetAccountAddressCmd)
|
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-12-31 19:11:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Lookup account for this request.
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
2013-12-31 19:11:47 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-12-31 19:11:47 +01:00
|
|
|
|
|
|
|
default: // all other non-nil errors
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-31 19:11:47 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-31 19:11:47 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
switch addr, err := a.CurrentAddress(); err {
|
|
|
|
case nil:
|
2014-01-03 19:34:37 +01:00
|
|
|
return addr.EncodeAddress(), nil
|
2013-12-31 19:11:47 +01:00
|
|
|
|
|
|
|
case wallet.ErrWalletLocked:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletKeypoolRanOut
|
2013-12-31 19:11:47 +01:00
|
|
|
|
|
|
|
default: // all other non-nil errors
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-31 19:11:47 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-31 19:11:47 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// GetAddressBalance handles a getaddressbalance extension request by
|
|
|
|
// returning the current balance (sum of unspent transaction output amounts)
|
|
|
|
// for a single address.
|
|
|
|
func GetAddressBalance(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-12-10 22:15:25 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcws.GetAddressBalanceCmd)
|
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-12-10 22:15:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Is address valid?
|
2014-03-19 02:47:12 +01:00
|
|
|
addr, err := btcutil.DecodeAddress(cmd.Address, cfg.Net())
|
2014-01-06 18:24:29 +01:00
|
|
|
if err != nil {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInvalidAddressOrKey
|
2014-01-06 18:24:29 +01:00
|
|
|
}
|
2013-12-10 22:15:25 +01:00
|
|
|
|
2014-04-03 17:00:46 +02:00
|
|
|
// Get the account which holds the address in the request.
|
|
|
|
// This should not fail, so if it does, return an internal
|
|
|
|
// error to the frontend.
|
|
|
|
a, err := AcctMgr.AccountByAddress(addr)
|
|
|
|
if err != nil {
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-10 22:15:25 +01:00
|
|
|
Code: btcjson.ErrInvalidAddressOrKey.Code,
|
|
|
|
Message: "Address not found in wallet",
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-10 22:15:25 +01:00
|
|
|
}
|
|
|
|
|
2014-04-16 23:22:39 +02:00
|
|
|
return a.CalculateAddressBalance(addr, int(cmd.Minconf)), nil
|
2013-12-10 22:15:25 +01:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// GetUnconfirmedBalance handles a getunconfirmedbalance extension request
|
|
|
|
// by returning the current unconfirmed balance of an account.
|
|
|
|
func GetUnconfirmedBalance(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-12-30 21:11:41 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcws.GetUnconfirmedBalanceCmd)
|
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-12-30 21:11:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Get the account included in the request.
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
2013-12-30 21:11:41 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-12-30 21:11:41 +01:00
|
|
|
|
|
|
|
default:
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-30 21:11:41 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-30 21:11:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
confirmed := a.CalculateBalance(1)
|
|
|
|
unconfirmed := a.CalculateBalance(0) - confirmed
|
2014-01-03 19:34:37 +01:00
|
|
|
return unconfirmed, nil
|
2013-12-30 21:11:41 +01:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// ImportPrivKey handles an importprivkey request by parsing
|
2013-11-20 02:18:11 +01:00
|
|
|
// a WIF-encoded private key and adding it to an account.
|
2014-01-03 19:34:37 +01:00
|
|
|
func ImportPrivKey(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-20 02:18:11 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.ImportPrivKeyCmd)
|
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-11-20 02:18:11 +01:00
|
|
|
}
|
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
// Get the acount included in the request. Yes, Label is the
|
|
|
|
// account name...
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(cmd.Label)
|
2013-12-02 20:56:06 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-11-20 02:18:11 +01:00
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
default:
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-11-20 02:18:11 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-11-20 02:18:11 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
pk, net, compressed, err := btcutil.DecodePrivateKey(cmd.PrivKey)
|
|
|
|
if err != nil || net != a.Net() {
|
|
|
|
return nil, &btcjson.ErrInvalidAddressOrKey
|
|
|
|
}
|
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
// Import the private key, handling any errors.
|
2014-01-30 16:14:02 +01:00
|
|
|
bs := &wallet.BlockStamp{}
|
2014-03-17 15:24:14 +01:00
|
|
|
switch _, err := a.ImportPrivateKey(pk, compressed, bs, cmd.Rescan); err {
|
2013-12-02 20:56:06 +01:00
|
|
|
case nil:
|
|
|
|
// If the import was successful, reply with nil.
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, nil
|
2013-12-02 20:56:06 +01:00
|
|
|
|
2014-01-15 20:07:08 +01:00
|
|
|
case wallet.ErrDuplicate:
|
|
|
|
// Do not return duplicate key errors to the client.
|
|
|
|
return nil, nil
|
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
case wallet.ErrWalletLocked:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletUnlockNeeded
|
2013-12-02 20:56:06 +01:00
|
|
|
|
|
|
|
default:
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
2013-11-20 02:18:11 +01:00
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-11-20 02:18:11 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-22 18:28:09 +01:00
|
|
|
// KeypoolRefill handles the keypoolrefill command. Since we handle the keypool
|
|
|
|
// automatically this does nothing since refilling is never manually required.
|
|
|
|
func KeypoolRefill(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2013-11-15 17:44:24 +01:00
|
|
|
// NotifyBalances notifies an attached frontend of the current confirmed
|
2013-10-14 22:39:15 +02:00
|
|
|
// and unconfirmed account balances.
|
|
|
|
//
|
2013-12-02 20:56:06 +01:00
|
|
|
// TODO(jrick): Switch this to return a single JSON object
|
|
|
|
// (map[string]interface{}) of all accounts and their balances, instead of
|
|
|
|
// separate notifications for each account.
|
2013-11-15 17:44:24 +01:00
|
|
|
func NotifyBalances(frontend chan []byte) {
|
2014-01-30 16:14:02 +01:00
|
|
|
AcctMgr.NotifyBalances(frontend)
|
2013-10-09 17:23:54 +02:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// GetNewAddress handlesa getnewaddress request by returning a new
|
|
|
|
// address for an account. If the account does not exist or the keypool
|
|
|
|
// ran out with a locked wallet, an appropiate error is returned.
|
|
|
|
func GetNewAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-12 18:01:32 +01:00
|
|
|
// Type assert icmd to access parameters.
|
2013-11-12 18:40:20 +01:00
|
|
|
cmd, ok := icmd.(*btcjson.GetNewAddressCmd)
|
2013-09-09 20:14:57 +02:00
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
2013-11-12 18:01:32 +01:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
2013-12-02 20:56:06 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-09-03 15:49:16 +02:00
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
case ErrBtcdDisconnected:
|
2014-01-30 16:14:02 +01:00
|
|
|
return nil, &ErrBtcdDisconnected
|
2013-12-02 20:56:06 +01:00
|
|
|
|
|
|
|
default: // all other non-nil errors
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-11-21 17:57:28 +01:00
|
|
|
}
|
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
addr, err := a.NewAddress()
|
2014-02-03 16:52:02 +01:00
|
|
|
if err != nil {
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the new payment address string.
|
|
|
|
return addr.EncodeAddress(), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetRawChangeAddress handles a getrawchangeaddress request by creating
|
|
|
|
// and returning a new change address for an account.
|
|
|
|
//
|
|
|
|
// Note: bitcoind allows specifying the account as an optional parameter,
|
|
|
|
// but ignores the parameter.
|
|
|
|
func GetRawChangeAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcjson.GetRawChangeAddressCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
2013-12-02 20:56:06 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
2014-02-03 16:52:02 +01:00
|
|
|
break
|
2013-12-02 20:56:06 +01:00
|
|
|
|
2014-02-03 16:52:02 +01:00
|
|
|
case ErrNotFound:
|
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-12-02 20:56:06 +01:00
|
|
|
|
|
|
|
default: // all other non-nil errors
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-11-21 17:57:28 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-11-12 18:01:32 +01:00
|
|
|
}
|
2014-02-03 16:52:02 +01:00
|
|
|
|
|
|
|
addr, err := a.NewChangeAddress()
|
|
|
|
if err != nil {
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the new payment address string.
|
|
|
|
return addr.EncodeAddress(), nil
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
2014-02-03 19:00:28 +01:00
|
|
|
// GetReceivedByAccount handles a getreceivedbyaccount request by returning
|
|
|
|
// the total amount received by addresses of an account.
|
|
|
|
func GetReceivedByAccount(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcjson.GetReceivedByAccountCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
|
|
|
case ErrNotFound:
|
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
|
|
|
|
|
|
|
default: // all other non-nil errors
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
|
|
|
|
amt, err := a.TotalReceived(cmd.MinConf)
|
|
|
|
if err != nil {
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
|
|
|
|
return amt, nil
|
|
|
|
}
|
|
|
|
|
2014-02-03 19:29:25 +01:00
|
|
|
func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.GetTransactionCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
2014-02-24 20:35:30 +01:00
|
|
|
txsha, err := btcwire.NewShaHashFromStr(cmd.Txid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.ErrDecodeHexString
|
|
|
|
}
|
|
|
|
|
|
|
|
accumulatedTxen := AcctMgr.GetTransaction(txsha)
|
2014-02-03 19:29:25 +01:00
|
|
|
if len(accumulatedTxen) == 0 {
|
|
|
|
return nil, &btcjson.ErrNoTxInfo
|
|
|
|
}
|
|
|
|
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
received := btcutil.Amount(0)
|
|
|
|
var debitTx *tx.TxRecord
|
|
|
|
var debitAccount string
|
2014-04-11 20:10:27 +02:00
|
|
|
|
|
|
|
ret := btcjson.GetTransactionResult{
|
2014-04-11 20:51:41 +02:00
|
|
|
Details: []btcjson.GetTransactionDetailsResult{},
|
|
|
|
WalletConflicts: []string{},
|
2014-04-11 20:10:27 +02:00
|
|
|
}
|
2014-04-11 20:58:04 +02:00
|
|
|
details := []btcjson.GetTransactionDetailsResult{}
|
2014-02-03 19:29:25 +01:00
|
|
|
for _, e := range accumulatedTxen {
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
for _, cred := range e.Tx.Credits() {
|
|
|
|
// Change is ignored.
|
|
|
|
if cred.Change() {
|
2014-02-24 20:35:30 +01:00
|
|
|
continue
|
2014-02-03 19:29:25 +01:00
|
|
|
}
|
2014-02-24 20:35:30 +01:00
|
|
|
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
received += cred.Amount()
|
2014-04-11 20:10:27 +02:00
|
|
|
|
|
|
|
var addr string
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
_, addrs, _, _ := cred.Addresses(cfg.Net())
|
2014-04-11 20:10:27 +02:00
|
|
|
if len(addrs) == 1 {
|
|
|
|
addr = addrs[0].EncodeAddress()
|
|
|
|
}
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
|
2014-04-11 20:10:27 +02:00
|
|
|
details = append(details, btcjson.GetTransactionDetailsResult{
|
|
|
|
Account: e.Account,
|
2014-02-03 19:29:25 +01:00
|
|
|
// TODO(oga) We don't mine for now so there
|
|
|
|
// won't be any special coinbase types. If the
|
|
|
|
// tx is a coinbase then we should handle it
|
|
|
|
// specially with the category depending on
|
|
|
|
// whether it is an orphan or in the blockchain.
|
2014-04-11 20:10:27 +02:00
|
|
|
Category: "receive",
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
Amount: cred.Amount().ToUnit(btcutil.AmountBTC),
|
2014-04-11 20:10:27 +02:00
|
|
|
Address: addr,
|
2014-02-03 19:29:25 +01:00
|
|
|
})
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if e.Tx.Debits() != nil {
|
|
|
|
// There should only be a single debits record for any
|
|
|
|
// of the account's transaction records.
|
|
|
|
debitTx = e.Tx
|
|
|
|
debitAccount = e.Account
|
2014-02-03 19:29:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
totalAmount := received
|
|
|
|
if debitTx != nil {
|
|
|
|
debits := debitTx.Debits()
|
|
|
|
totalAmount -= debits.InputAmount()
|
2014-04-11 20:10:27 +02:00
|
|
|
info := btcjson.GetTransactionDetailsResult{
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
Account: debitAccount,
|
2014-04-11 20:10:27 +02:00
|
|
|
Category: "send",
|
2014-02-24 20:35:30 +01:00
|
|
|
// negative since it is a send
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
Amount: (-debits.OutputAmount(true)).ToUnit(btcutil.AmountBTC),
|
|
|
|
Fee: debits.Fee().ToUnit(btcutil.AmountBTC),
|
2014-04-11 20:10:27 +02:00
|
|
|
}
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
_, addrs, _, _ := debitTx.Credits()[0].Addresses(cfg.Net())
|
2014-04-11 20:10:27 +02:00
|
|
|
if len(addrs) == 1 {
|
|
|
|
info.Address = addrs[0].EncodeAddress()
|
2014-02-24 20:35:30 +01:00
|
|
|
}
|
2014-04-11 20:10:27 +02:00
|
|
|
ret.Fee += info.Fee
|
2014-02-24 20:35:30 +01:00
|
|
|
// Add sent information to front.
|
2014-04-11 20:10:27 +02:00
|
|
|
ret.Details = append(ret.Details, info)
|
|
|
|
|
2014-02-24 20:35:30 +01:00
|
|
|
}
|
2014-04-11 20:10:27 +02:00
|
|
|
ret.Details = append(ret.Details, details...)
|
2014-02-24 20:35:30 +01:00
|
|
|
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
ret.Amount = totalAmount.ToUnit(btcutil.AmountBTC)
|
|
|
|
|
2014-02-03 19:29:25 +01:00
|
|
|
// Generic information should be the same, so just use the first one.
|
|
|
|
first := accumulatedTxen[0]
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
ret.TxID = first.Tx.Tx().Sha().String()
|
2014-04-11 20:10:27 +02:00
|
|
|
|
2014-04-16 23:22:39 +02:00
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
buf.Grow(first.Tx.Tx().MsgTx().SerializeSize())
|
2014-04-11 20:10:27 +02:00
|
|
|
err = first.Tx.Tx().MsgTx().Serialize(buf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-02-03 19:29:25 +01:00
|
|
|
}
|
2014-04-11 20:10:27 +02:00
|
|
|
ret.Hex = hex.EncodeToString(buf.Bytes())
|
|
|
|
|
|
|
|
// TODO(oga) technically we have different time and
|
|
|
|
// timereceived depending on if a transaction was send or
|
|
|
|
// receive. We ideally should provide the correct numbers for
|
|
|
|
// both. Right now they will always be the same
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
ret.Time = first.Tx.Received().Unix()
|
|
|
|
ret.TimeReceived = first.Tx.Received().Unix()
|
|
|
|
if txr := first.Tx; txr.BlockHeight != -1 {
|
|
|
|
txBlock, err := txr.Block()
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
2014-02-03 19:29:25 +01:00
|
|
|
bs, err := GetCurBlock()
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
ret.BlockIndex = int64(first.Tx.Tx().Index())
|
|
|
|
ret.BlockHash = txBlock.Hash.String()
|
|
|
|
ret.BlockTime = txBlock.Time.Unix()
|
2014-05-07 05:48:12 +02:00
|
|
|
ret.Confirmations = int64(txr.Confirmations(bs.Height))
|
2014-02-03 19:29:25 +01:00
|
|
|
}
|
|
|
|
// TODO(oga) if the tx is a coinbase we should set "generated" to true.
|
|
|
|
// Since we do not mine this currently is never the case.
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// ListAccounts handles a listaccounts request by returning a map of account
|
|
|
|
// names to their balances.
|
|
|
|
func ListAccounts(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-12 18:01:32 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.ListAccountsCmd)
|
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-10-09 17:23:54 +02:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// Return the map. This will be marshaled into a JSON object.
|
2014-01-30 16:14:02 +01:00
|
|
|
return AcctMgr.ListAccounts(cmd.MinConf), nil
|
2013-10-09 17:23:54 +02:00
|
|
|
}
|
|
|
|
|
2014-01-27 18:53:32 +01:00
|
|
|
// ListSinceBlock handles a listsinceblock request by returning an array of maps
|
|
|
|
// with details of sent and received wallet transactions since the given block.
|
|
|
|
func ListSinceBlock(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcjson.ListSinceBlockCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
|
|
|
height := int32(-1)
|
|
|
|
if cmd.BlockHash != "" {
|
2014-01-30 16:14:02 +01:00
|
|
|
br, err := GetBlock(CurrentServerConn(), cmd.BlockHash)
|
2014-01-27 18:53:32 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
height = int32(br.Height)
|
|
|
|
}
|
|
|
|
|
|
|
|
bs, err := GetCurBlock()
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// For the result we need the block hash for the last block counted
|
|
|
|
// in the blockchain due to confirmations. We send this off now so that
|
|
|
|
// it can arrive asynchronously while we figure out the rest.
|
|
|
|
gbh, err := btcjson.NewGetBlockHashCmd(<-NewJSONID,
|
|
|
|
int64(bs.Height)+1-int64(cmd.TargetConfirmations))
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-09 18:07:09 +02:00
|
|
|
bhChan := CurrentServerConn().SendRequest(NewServerRequest(gbh))
|
2014-01-27 18:53:32 +01:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
txInfoList, err := AcctMgr.ListSinceBlock(height, bs.Height,
|
2014-01-27 18:53:32 +01:00
|
|
|
cmd.TargetConfirmations)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Done with work, get the response.
|
|
|
|
response := <-bhChan
|
2014-04-09 18:07:09 +02:00
|
|
|
var hash string
|
|
|
|
_, jsonErr := response.FinishUnmarshal(&hash)
|
|
|
|
if jsonErr != nil {
|
|
|
|
return nil, jsonErr
|
2014-01-27 18:53:32 +01:00
|
|
|
}
|
|
|
|
|
2014-04-12 02:33:15 +02:00
|
|
|
res := btcjson.ListSinceBlockResult{
|
|
|
|
Transactions: txInfoList,
|
|
|
|
LastBlock: hash,
|
|
|
|
}
|
2014-01-27 18:53:32 +01:00
|
|
|
|
|
|
|
return res, nil
|
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// ListTransactions handles a listtransactions request by returning an
|
|
|
|
// array of maps with details of sent and recevied wallet transactions.
|
|
|
|
func ListTransactions(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-22 19:42:25 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.ListTransactionsCmd)
|
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-11-22 19:42:25 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
2013-12-02 20:56:06 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-11-22 19:42:25 +01:00
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
default: // all other non-nil errors
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
2013-11-22 19:42:25 +01:00
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-11-22 19:42:25 +01:00
|
|
|
}
|
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
switch txList, err := a.ListTransactions(cmd.From, cmd.Count); err {
|
|
|
|
case nil:
|
2014-01-03 19:34:37 +01:00
|
|
|
// Return the list of tx information.
|
|
|
|
return txList, nil
|
2013-12-02 20:56:06 +01:00
|
|
|
|
|
|
|
case ErrBtcdDisconnected:
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: "btcd disconnected",
|
2013-11-22 19:42:25 +01:00
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-11-22 19:42:25 +01:00
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
default:
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-02 20:56:06 +01:00
|
|
|
}
|
2013-11-22 19:42:25 +01:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// ListAddressTransactions handles a listaddresstransactions request by
|
|
|
|
// returning an array of maps with details of spent and received wallet
|
|
|
|
// transactions. The form of the reply is identical to listtransactions,
|
|
|
|
// but the array elements are limited to transaction details which are
|
|
|
|
// about the addresess included in the request.
|
|
|
|
func ListAddressTransactions(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-12-30 17:10:06 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcws.ListAddressTransactionsCmd)
|
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-12-30 17:10:06 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
2013-12-30 17:10:06 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-12-30 17:10:06 +01:00
|
|
|
|
|
|
|
default: // all other non-nil errors
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-30 17:10:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-30 17:10:06 +01:00
|
|
|
}
|
|
|
|
|
2014-01-06 18:24:29 +01:00
|
|
|
// Decode addresses.
|
2013-12-30 17:10:06 +01:00
|
|
|
pkHashMap := make(map[string]struct{})
|
2014-01-06 18:24:29 +01:00
|
|
|
for _, addrStr := range cmd.Addresses {
|
2014-03-19 02:47:12 +01:00
|
|
|
addr, err := btcutil.DecodeAddress(addrStr, cfg.Net())
|
2014-01-06 18:24:29 +01:00
|
|
|
if err != nil {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInvalidAddressOrKey
|
2014-01-06 18:24:29 +01:00
|
|
|
}
|
|
|
|
apkh, ok := addr.(*btcutil.AddressPubKeyHash)
|
2014-02-26 21:22:48 +01:00
|
|
|
if !ok || !apkh.IsForNet(cfg.Net()) {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInvalidAddressOrKey
|
2013-12-30 17:10:06 +01:00
|
|
|
}
|
2014-01-06 18:24:29 +01:00
|
|
|
pkHashMap[string(addr.ScriptAddress())] = struct{}{}
|
2013-12-30 17:10:06 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
txList, err := a.ListAddressTransactions(pkHashMap)
|
|
|
|
if err != nil {
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-30 17:10:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-30 17:10:06 +01:00
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return txList, nil
|
2013-12-30 17:10:06 +01:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// ListAllTransactions handles a listalltransactions request by returning
|
|
|
|
// a map with details of sent and recevied wallet transactions. This is
|
|
|
|
// similar to ListTransactions, except it takes only a single optional
|
|
|
|
// argument for the account name and replies with all transactions.
|
|
|
|
func ListAllTransactions(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-12-02 23:34:36 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcws.ListAllTransactionsCmd)
|
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-12-02 23:34:36 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
2013-12-02 23:34:36 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-12-02 23:34:36 +01:00
|
|
|
|
|
|
|
default: // all other non-nil errors
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 23:34:36 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-02 23:34:36 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
switch txList, err := a.ListAllTransactions(); err {
|
|
|
|
case nil:
|
2014-01-03 19:34:37 +01:00
|
|
|
// Return the list of tx information.
|
|
|
|
return txList, nil
|
2013-12-02 23:34:36 +01:00
|
|
|
|
|
|
|
case ErrBtcdDisconnected:
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 23:34:36 +01:00
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: "btcd disconnected",
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-02 23:34:36 +01:00
|
|
|
|
|
|
|
default:
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 23:34:36 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-02 23:34:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-13 19:43:52 +01:00
|
|
|
// ListUnspent handles the listunspent command.
|
|
|
|
func ListUnspent(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcjson.ListUnspentCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
addresses := make(map[string]bool)
|
|
|
|
if len(cmd.Addresses) != 0 {
|
|
|
|
// confirm that all of them are good:
|
|
|
|
for _, as := range cmd.Addresses {
|
2014-03-19 02:47:12 +01:00
|
|
|
a, err := btcutil.DecodeAddress(as, cfg.Net())
|
2014-02-13 19:43:52 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.ErrInvalidAddressOrKey
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, ok := addresses[a.EncodeAddress()]; ok {
|
|
|
|
// duplicate
|
|
|
|
return nil, &btcjson.ErrInvalidParameter
|
|
|
|
}
|
|
|
|
addresses[a.EncodeAddress()] = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
results, err := AcctMgr.ListUnspent(cmd.MinConf, cmd.MaxConf, addresses)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results, nil
|
|
|
|
}
|
|
|
|
|
2014-02-04 17:33:29 +01:00
|
|
|
// sendPairs is a helper routine to reduce duplicated code when creating and
|
|
|
|
// sending payment transactions.
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
func sendPairs(icmd btcjson.Cmd, account string, amounts map[string]btcutil.Amount,
|
2014-02-04 17:33:29 +01:00
|
|
|
minconf int) (interface{}, *btcjson.Error) {
|
2013-11-12 18:01:32 +01:00
|
|
|
// Check that the account specified in the request exists.
|
2014-02-04 17:33:29 +01:00
|
|
|
a, err := AcctMgr.Account(account)
|
2013-12-02 20:56:06 +01:00
|
|
|
if err != nil {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-10-29 07:19:40 +01:00
|
|
|
}
|
2013-11-12 18:01:32 +01:00
|
|
|
|
|
|
|
// Create transaction, replying with an error if the creation
|
|
|
|
// was not successful.
|
2014-02-04 17:33:29 +01:00
|
|
|
createdTx, err := a.txToPairs(amounts, minconf)
|
2013-11-12 18:01:32 +01:00
|
|
|
switch {
|
|
|
|
case err == ErrNonPositiveAmount:
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-11-12 18:01:32 +01:00
|
|
|
Code: btcjson.ErrInvalidParameter.Code,
|
|
|
|
Message: "amount must be positive",
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-11-12 18:01:32 +01:00
|
|
|
|
|
|
|
case err == wallet.ErrWalletLocked:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletUnlockNeeded
|
2013-11-12 18:01:32 +01:00
|
|
|
|
2014-02-04 17:33:29 +01:00
|
|
|
case err != nil: // any other non-nil error
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-11-12 18:01:32 +01:00
|
|
|
Code: btcjson.ErrInternal.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// Mark txid as having send history so handlers adding receive history
|
|
|
|
// wait until all send history has been written.
|
2014-02-24 20:35:30 +01:00
|
|
|
SendTxHistSyncChans.add <- *createdTx.tx.Sha()
|
2014-01-03 19:34:37 +01:00
|
|
|
|
2014-01-29 05:04:10 +01:00
|
|
|
// If a change address was added, sync wallet to disk and request
|
|
|
|
// transaction notifications to the change address.
|
2014-04-09 00:49:02 +02:00
|
|
|
if createdTx.changeAddr != nil {
|
2014-01-30 16:14:02 +01:00
|
|
|
AcctMgr.ds.ScheduleWalletWrite(a)
|
|
|
|
if err := AcctMgr.ds.FlushAccount(a); err != nil {
|
2014-01-29 05:04:10 +01:00
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: "Cannot write account: " + err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
2013-11-06 18:37:46 +01:00
|
|
|
}
|
2014-04-09 00:49:02 +02:00
|
|
|
a.ReqNewTxsForAddress(createdTx.changeAddr)
|
2013-11-06 18:37:46 +01:00
|
|
|
}
|
2013-10-28 19:13:20 +01:00
|
|
|
|
2014-04-16 23:22:39 +02:00
|
|
|
serializedTx := bytes.NewBuffer(nil)
|
|
|
|
serializedTx.Grow(createdTx.tx.MsgTx().SerializeSize())
|
2014-02-24 20:35:30 +01:00
|
|
|
createdTx.tx.MsgTx().Serialize(serializedTx)
|
|
|
|
hextx := hex.EncodeToString(serializedTx.Bytes())
|
|
|
|
txSha, jsonErr := SendRawTransaction(CurrentServerConn(), hextx)
|
|
|
|
if jsonErr != nil {
|
|
|
|
SendTxHistSyncChans.remove <- *createdTx.tx.Sha()
|
|
|
|
return nil, jsonErr
|
2013-10-15 15:42:10 +02:00
|
|
|
}
|
2013-11-12 18:01:32 +01:00
|
|
|
|
2014-02-24 20:35:30 +01:00
|
|
|
return handleSendRawTxReply(icmd, txSha, a, createdTx)
|
2014-02-04 17:33:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// SendFrom handles a sendfrom RPC request by creating a new transaction
|
|
|
|
// spending unspent transaction outputs for a wallet to another payment
|
|
|
|
// address. Leftover inputs not sent to the payment address or a fee for
|
|
|
|
// the miner are sent back to a new address in the wallet. Upon success,
|
|
|
|
// the TxID for the created transaction is returned.
|
|
|
|
func SendFrom(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.SendFromCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check that signed integer parameters are positive.
|
|
|
|
if cmd.Amount < 0 {
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrInvalidParameter.Code,
|
|
|
|
Message: "amount must be positive",
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
if cmd.MinConf < 0 {
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrInvalidParameter.Code,
|
|
|
|
Message: "minconf must be positive",
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
// Create map of address and amount pairs.
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
pairs := map[string]btcutil.Amount{
|
|
|
|
cmd.ToAddress: btcutil.Amount(cmd.Amount),
|
2014-02-04 17:33:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return sendPairs(cmd, cmd.FromAccount, pairs, cmd.MinConf)
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// SendMany handles a sendmany RPC request by creating a new transaction
|
|
|
|
// spending unspent transaction outputs for a wallet to any number of
|
|
|
|
// payment addresses. Leftover inputs not sent to the payment address
|
|
|
|
// or a fee for the miner are sent back to a new address in the wallet.
|
|
|
|
// Upon success, the TxID for the created transaction is returned.
|
|
|
|
func SendMany(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-12 18:01:32 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.SendManyCmd)
|
2013-09-09 20:14:57 +02:00
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
2013-11-12 18:01:32 +01:00
|
|
|
// Check that minconf is positive.
|
|
|
|
if cmd.MinConf < 0 {
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-11-12 18:01:32 +01:00
|
|
|
Code: btcjson.ErrInvalidParameter.Code,
|
|
|
|
Message: "minconf must be positive",
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
// Recreate address/amount pairs, using btcutil.Amount.
|
|
|
|
pairs := make(map[string]btcutil.Amount, len(cmd.Amounts))
|
|
|
|
for k, v := range cmd.Amounts {
|
|
|
|
pairs[k] = btcutil.Amount(v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return sendPairs(cmd, cmd.FromAccount, pairs, cmd.MinConf)
|
2014-02-04 17:33:29 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// SendToAddress handles a sendtoaddress RPC request by creating a new
|
|
|
|
// transaction spending unspent transaction outputs for a wallet to another
|
|
|
|
// payment address. Leftover inputs not sent to the payment address or a fee
|
|
|
|
// for the miner are sent back to a new address in the wallet. Upon success,
|
|
|
|
// the TxID for the created transaction is returned.
|
|
|
|
func SendToAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.SendToAddressCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
2014-02-04 17:33:29 +01:00
|
|
|
// Check that signed integer parameters are positive.
|
|
|
|
if cmd.Amount < 0 {
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-11-12 18:01:32 +01:00
|
|
|
Code: btcjson.ErrInvalidParameter.Code,
|
|
|
|
Message: "amount must be positive",
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-09-09 20:14:57 +02:00
|
|
|
}
|
|
|
|
|
2014-02-04 17:33:29 +01:00
|
|
|
// Mock up map of address and amount pairs.
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
pairs := map[string]btcutil.Amount{
|
|
|
|
cmd.Address: btcutil.Amount(cmd.Amount),
|
2013-11-12 18:01:32 +01:00
|
|
|
}
|
Perform smarter UTXO tracking.
This change fixes many issues with the tracking of unspent transaction
outputs. First, notifications for when UTXOs arse spent are now
requested from btcd, and when spent, will be removed from the
UtxoStore.
Second, when transactions are created, the unconfirmed (not yet in a
block) Utxo (with block height -1 and zeroed block hash) is added to
the wallet's UtxoStore. Notifications for when this UTXO is spent are
also requested from btcd. After the tx appears in a block, because
the UTXO has a pkScript to be spent by another owned wallet address, a
notification with the UTXO will be sent to btcwallet. We already
store the unconfirmed UTXO, so at this point the actual block height
and hash are filled in.
Finally, when calculating the balance, if confirmations is zero,
unconfirmed UTXOs (block height -1) will be included in the balance.
Otherwise, they are ignored.
2013-10-22 15:55:53 +02:00
|
|
|
|
2014-02-04 17:33:29 +01:00
|
|
|
return sendPairs(cmd, "", pairs, 1)
|
2013-11-12 18:01:32 +01:00
|
|
|
}
|
2013-10-15 16:27:54 +02:00
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
// Channels to manage SendBeforeReceiveHistorySync.
|
|
|
|
var SendTxHistSyncChans = struct {
|
|
|
|
add, done, remove chan btcwire.ShaHash
|
|
|
|
access chan SendTxHistSyncRequest
|
|
|
|
}{
|
|
|
|
add: make(chan btcwire.ShaHash),
|
|
|
|
remove: make(chan btcwire.ShaHash),
|
|
|
|
done: make(chan btcwire.ShaHash),
|
|
|
|
access: make(chan SendTxHistSyncRequest),
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendTxHistSyncRequest requests a SendTxHistSyncResponse from
|
|
|
|
// SendBeforeReceiveHistorySync.
|
|
|
|
type SendTxHistSyncRequest struct {
|
2014-02-24 20:35:30 +01:00
|
|
|
txsha btcwire.ShaHash
|
2013-12-17 19:18:09 +01:00
|
|
|
response chan SendTxHistSyncResponse
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendTxHistSyncResponse is the response
|
|
|
|
type SendTxHistSyncResponse struct {
|
|
|
|
c chan struct{}
|
|
|
|
ok bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// SendBeforeReceiveHistorySync manages a set of transaction hashes
|
2014-02-24 20:35:30 +01:00
|
|
|
// created by this wallet. For each newly added txsha, a channel is
|
|
|
|
// created. Once the send history has been recorded, the txsha should
|
2013-12-17 19:18:09 +01:00
|
|
|
// be messaged across done, causing the internal channel to be closed.
|
|
|
|
// Before receive history is recorded, access should be used to check
|
|
|
|
// if there are or were any goroutines writing send history, and if
|
|
|
|
// so, wait until the channel is closed after a done message.
|
|
|
|
func SendBeforeReceiveHistorySync(add, done, remove chan btcwire.ShaHash,
|
|
|
|
access chan SendTxHistSyncRequest) {
|
|
|
|
|
|
|
|
m := make(map[btcwire.ShaHash]chan struct{})
|
|
|
|
for {
|
|
|
|
select {
|
2014-02-24 20:35:30 +01:00
|
|
|
case txsha := <-add:
|
|
|
|
m[txsha] = make(chan struct{})
|
2013-12-17 19:18:09 +01:00
|
|
|
|
2014-02-24 20:35:30 +01:00
|
|
|
case txsha := <-remove:
|
|
|
|
delete(m, txsha)
|
2013-12-17 19:18:09 +01:00
|
|
|
|
2014-02-24 20:35:30 +01:00
|
|
|
case txsha := <-done:
|
|
|
|
if c, ok := m[txsha]; ok {
|
2013-12-17 19:18:09 +01:00
|
|
|
close(c)
|
|
|
|
}
|
|
|
|
|
|
|
|
case req := <-access:
|
2014-02-24 20:35:30 +01:00
|
|
|
c, ok := m[req.txsha]
|
2013-12-17 19:18:09 +01:00
|
|
|
req.response <- SendTxHistSyncResponse{c: c, ok: ok}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
func handleSendRawTxReply(icmd btcjson.Cmd, txIDStr string, a *Account, txInfo *CreatedTx) (interface{}, *btcjson.Error) {
|
2013-11-22 19:42:25 +01:00
|
|
|
// Add to transaction store.
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
txr, err := a.TxStore.InsertTx(txInfo.tx, nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("Error adding sent tx history: %v", err)
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
debits, err := txr.AddDebits(txInfo.inputs)
|
2014-02-28 19:03:23 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Warnf("Error adding sent tx history: %v", err)
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
2014-01-30 16:14:02 +01:00
|
|
|
AcctMgr.ds.ScheduleTxStoreWrite(a)
|
2013-11-22 19:42:25 +01:00
|
|
|
|
2013-12-02 23:34:36 +01:00
|
|
|
// Notify frontends of new SendTx.
|
|
|
|
bs, err := GetCurBlock()
|
|
|
|
if err == nil {
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
ltr, err := debits.ToJSON(a.Name(), bs.Height, a.Net())
|
|
|
|
if err != nil {
|
|
|
|
log.Warnf("Error adding sent tx history: %v", err)
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
for _, details := range ltr {
|
2014-02-24 20:35:30 +01:00
|
|
|
NotifyNewTxDetails(allClients, a.Name(), details)
|
2013-12-02 23:34:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-12-17 19:18:09 +01:00
|
|
|
// Signal that received notifiations are ok to add now.
|
2014-02-24 20:35:30 +01:00
|
|
|
SendTxHistSyncChans.done <- *txInfo.tx.Sha()
|
2013-12-17 19:18:09 +01:00
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
// Disk sync tx and utxo stores.
|
2014-01-30 16:14:02 +01:00
|
|
|
if err := AcctMgr.ds.FlushAccount(a); err != nil {
|
2014-01-29 05:04:10 +01:00
|
|
|
log.Errorf("cannot write account: %v", err)
|
2013-11-12 18:01:32 +01:00
|
|
|
}
|
|
|
|
|
2013-11-22 19:42:25 +01:00
|
|
|
// Notify all frontends of account's new unconfirmed and
|
|
|
|
// confirmed balance.
|
|
|
|
confirmed := a.CalculateBalance(1)
|
|
|
|
unconfirmed := a.CalculateBalance(0) - confirmed
|
2014-02-18 04:18:30 +01:00
|
|
|
NotifyWalletBalance(allClients, a.name, confirmed)
|
|
|
|
NotifyWalletBalanceUnconfirmed(allClients, a.name, unconfirmed)
|
2013-11-22 19:42:25 +01:00
|
|
|
|
2013-11-12 18:01:32 +01:00
|
|
|
// The comments to be saved differ based on the underlying type
|
|
|
|
// of the cmd, so switch on the type to check whether it is a
|
|
|
|
// SendFromCmd or SendManyCmd.
|
|
|
|
//
|
|
|
|
// TODO(jrick): If message succeeded in being sent, save the
|
|
|
|
// transaction details with comments.
|
|
|
|
switch cmd := icmd.(type) {
|
|
|
|
case *btcjson.SendFromCmd:
|
|
|
|
_ = cmd.Comment
|
|
|
|
_ = cmd.CommentTo
|
|
|
|
|
|
|
|
case *btcjson.SendManyCmd:
|
|
|
|
_ = cmd.Comment
|
2014-02-04 17:33:29 +01:00
|
|
|
case *btcjson.SendToAddressCmd:
|
|
|
|
_ = cmd.Comment
|
|
|
|
_ = cmd.CommentTo
|
2013-11-12 18:01:32 +01:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
log.Infof("Successfully sent transaction %v", txIDStr)
|
|
|
|
return txIDStr, nil
|
2013-09-03 15:49:16 +02:00
|
|
|
}
|
|
|
|
|
2013-12-04 01:22:47 +01:00
|
|
|
// SetTxFee sets the transaction fee per kilobyte added to transactions.
|
2014-01-03 19:34:37 +01:00
|
|
|
func SetTxFee(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-12 18:01:32 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.SetTxFeeCmd)
|
2013-10-07 21:14:39 +02:00
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-10-07 21:14:39 +02:00
|
|
|
}
|
2013-11-12 18:01:32 +01:00
|
|
|
|
|
|
|
// Check that amount is not negative.
|
|
|
|
if cmd.Amount < 0 {
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-11-12 18:01:32 +01:00
|
|
|
Code: btcjson.ErrInvalidParams.Code,
|
|
|
|
Message: "amount cannot be negative",
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-10-07 21:14:39 +02:00
|
|
|
}
|
|
|
|
|
2013-11-12 18:01:32 +01:00
|
|
|
// Set global tx fee.
|
2013-12-04 01:22:47 +01:00
|
|
|
TxFeeIncrement.Lock()
|
Another day, another tx store implementation.
The last transaction store was a great example of how not to write
scalable software. For a variety of reasons, it was very slow at
processing transaction inserts. Among them:
1) Every single transaction record being saved in a linked list
(container/list), and inserting into this list would be an O(n)
operation so that records could be ordered by receive date.
2) Every single transaction in the above mentioned list was iterated
over in order to find double spends which must be removed. It is
silly to do this check for mined transactions, which already have
been checked for this by btcd. Worse yet, if double spends were
found, the list would be iterated a second (or third, or fourth)
time for each removed transaction.
3) All spend tracking for signed-by-wallet transactions was found on
each transaction insert, even if the now spent previous transaction
outputs were known by the caller.
This list could keep going on, but you get the idea. It was bad.
To resolve these issues a new transaction store had to be implemented.
The new implementation:
1) Tracks mined and unmined transactions in different data structures.
Mined transactions are cheap to track because the required double
spend checks have already been performed by the chain server, and
double spend checks are only required to be performed on
newly-inserted mined transactions which may conflict with previous
unmined transactions.
2) Saves mined transactions grouped by block first, and then by their
transaction index. Lookup keys for mined transactions are simply
the block height (in the best chain, that's all we save) and index
of the transaction in the block. This makes looking up any
arbitrary transaction almost an O(1) operation (almost, because
block height and block indexes are mapped to their slice indexes
with a Go map).
3) Saves records in each transaction for whether the outputs are
wallet credits (spendable by wallet) and for whether inputs debit
from previous credits. Both structures point back to the source
or spender (credits point to the transaction that spends them, or
nil for unspent credits, and debits include keys to lookup the
transaction credits they spent. While complicated to keep track
of, this greatly simplifies the spent tracking for transactions
across rollbacks and transaction removals.
4) Implements double spend checking as an almost O(1) operation. A
Go map is used to map each previous outpoint for all unconfirmed
transactions to the unconfirmed tx record itself. Checking for
double spends on confirmed transaction inserts only involves
looking up each previous outpoint of the inserted tx in this map.
If a double spend is found, removal is simplified by only
removing the transaction and its spend chain from store maps,
rather than iterating a linked list several times over to remove
each dead transaction in the spend chain.
5) Allows the caller to specify the previous credits which are spent
by a debiting transaction. When a transaction is created by
wallet, the previous outputs are already known, and by passing
their record types to the AddDebits method, lookups for each
previously unspent credit are omitted.
6) Bookkeeps all blocks with transactions with unspent credits, and
bookkeeps the transaction indexes of all transactions with unspent
outputs for a single block. For the case where the caller adding a
debit record does not know what credits a transaction debits from,
these bookkeeping structures allow the store to only consider known
unspent transactions, rather than searching through both spent and
unspents.
7) Saves amount deltas for the entire balance as a result of each
block, due to transactions within that block. This improves the
performance of calculating the full balance by not needing to
iterate over every transaction, and then every credit, to determine
if a credit is spent or unspent. When transactions are moved from
unconfirmed to a block structure, the amount deltas are incremented
by the amount of all transaction credits (both spent and unspent)
and debited by the total amount the transaction spends from
previous wallet credits. For the common case of calculating a
balance with just one confirmation, the only involves iterating
over each block structure and adding the (possibly negative)
amount delta. Coinbase rewards are saved similarly, but with a
different amount variable so they can be seperatly included or
excluded.
Due to all of the changes in how the store internally works, the
serialization format has changed. To simplify the serialization
logic, support for reading the last store file version has been
removed. Past this change, a rescan (run automatically) will be
required to rebuild the transaction history.
2014-05-05 23:12:05 +02:00
|
|
|
TxFeeIncrement.i = btcutil.Amount(cmd.Amount)
|
2013-12-04 01:22:47 +01:00
|
|
|
TxFeeIncrement.Unlock()
|
2013-10-07 21:14:39 +02:00
|
|
|
|
2013-11-12 18:01:32 +01:00
|
|
|
// A boolean true result is returned upon success.
|
2014-01-03 19:34:37 +01:00
|
|
|
return true, nil
|
2013-10-07 21:14:39 +02:00
|
|
|
}
|
|
|
|
|
2014-02-12 03:14:07 +01:00
|
|
|
// SignMessage signs the given message with the private key for the given
|
|
|
|
// address
|
|
|
|
func SignMessage(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.SignMessageCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
2014-03-19 02:47:12 +01:00
|
|
|
addr, err := btcutil.DecodeAddress(cmd.Address, cfg.Net())
|
2014-02-12 03:14:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
2014-04-03 17:00:46 +02:00
|
|
|
Code: btcjson.ErrParse.Code,
|
2014-02-12 03:14:07 +01:00
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-18 00:00:52 +02:00
|
|
|
ainfo, err := AcctMgr.Address(addr)
|
2014-04-03 17:00:46 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.ErrInvalidAddressOrKey
|
|
|
|
}
|
|
|
|
|
2014-04-09 02:18:52 +02:00
|
|
|
pka := ainfo.(wallet.PubKeyAddress)
|
|
|
|
privkey, err := pka.PrivKey()
|
2014-02-12 03:14:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fullmsg := "Bitcoin Signed Message:\n" + cmd.Message
|
|
|
|
sigbytes, err := btcec.SignCompact(btcec.S256(), privkey,
|
2014-03-06 01:34:44 +01:00
|
|
|
btcwire.DoubleSha256([]byte(fullmsg)), ainfo.Compressed())
|
2014-02-12 03:14:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return base64.StdEncoding.EncodeToString(sigbytes), nil
|
|
|
|
}
|
|
|
|
|
2013-11-12 18:01:32 +01:00
|
|
|
// CreateEncryptedWallet creates a new account with an encrypted
|
|
|
|
// wallet. If an account with the same name as the requested account
|
|
|
|
// name already exists, an invalid account name error is returned to
|
|
|
|
// the client.
|
2013-10-07 18:35:32 +02:00
|
|
|
//
|
2013-10-09 01:33:22 +02:00
|
|
|
// Wallets will be created on TestNet3, or MainNet if btcwallet is run with
|
|
|
|
// the --mainnet option.
|
2014-01-03 19:34:37 +01:00
|
|
|
func CreateEncryptedWallet(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-12 18:01:32 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcws.CreateEncryptedWalletCmd)
|
2013-09-09 20:14:57 +02:00
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-09-03 15:49:16 +02:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
err := AcctMgr.CreateEncryptedWallet([]byte(cmd.Passphrase))
|
2013-12-02 20:56:06 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
// A nil reply is sent upon successful wallet creation.
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, nil
|
2013-10-29 07:19:40 +01:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrWalletExists:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-09-03 15:49:16 +02:00
|
|
|
|
2013-12-02 20:56:06 +01:00
|
|
|
case ErrBtcdDisconnected:
|
2014-01-30 16:14:02 +01:00
|
|
|
return nil, &ErrBtcdDisconnected
|
2013-11-12 18:01:32 +01:00
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
default: // all other non-nil errors
|
|
|
|
return nil, &btcjson.ErrInternal
|
2013-10-15 22:55:28 +02:00
|
|
|
}
|
2013-08-21 16:37:30 +02:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// RecoverAddresses recovers the next n addresses from an account's wallet.
|
Implement exporting a watching-only wallet.
This change allows for the use of watching-only wallets. Unlike
normal, "hot" wallets, watching-only wallets do not contain any
private keys, and can be used in situations where you want to keep one
wallet online to create new receiving addresses and watch for received
transactions, while keeping the hot wallet offline (possibly on an
air-gapped computer).
Two (websocket) extension RPC calls have been added:
First, exportwatchingwallet, which will export the current hot wallet
to a watching-only wallet, saving either to disk or returning the
base64-encoded wallet files to the caller.
Second, recoveraddresses, which is used to recover the next n
addresses from the address chain. This is used to "sync" a watching
wallet with the hot wallet, or vice versa.
2014-01-21 20:45:28 +01:00
|
|
|
func RecoverAddresses(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcws.RecoverAddressesCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
Implement exporting a watching-only wallet.
This change allows for the use of watching-only wallets. Unlike
normal, "hot" wallets, watching-only wallets do not contain any
private keys, and can be used in situations where you want to keep one
wallet online to create new receiving addresses and watch for received
transactions, while keeping the hot wallet offline (possibly on an
air-gapped computer).
Two (websocket) extension RPC calls have been added:
First, exportwatchingwallet, which will export the current hot wallet
to a watching-only wallet, saving either to disk or returning the
base64-encoded wallet files to the caller.
Second, recoveraddresses, which is used to recover the next n
addresses from the address chain. This is used to "sync" a watching
wallet with the hot wallet, or vice versa.
2014-01-21 20:45:28 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
Implement exporting a watching-only wallet.
This change allows for the use of watching-only wallets. Unlike
normal, "hot" wallets, watching-only wallets do not contain any
private keys, and can be used in situations where you want to keep one
wallet online to create new receiving addresses and watch for received
transactions, while keeping the hot wallet offline (possibly on an
air-gapped computer).
Two (websocket) extension RPC calls have been added:
First, exportwatchingwallet, which will export the current hot wallet
to a watching-only wallet, saving either to disk or returning the
base64-encoded wallet files to the caller.
Second, recoveraddresses, which is used to recover the next n
addresses from the address chain. This is used to "sync" a watching
wallet with the hot wallet, or vice versa.
2014-01-21 20:45:28 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
|
|
|
|
|
|
|
default: // all other non-nil errors
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.RecoverAddresses(cmd.N); err != nil {
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2014-04-18 00:13:53 +02:00
|
|
|
// pendingTx is used for async fetching of transaction dependancies in
|
|
|
|
// SignRawTransaction.
|
|
|
|
type pendingTx struct {
|
|
|
|
resp chan RawRPCResponse
|
|
|
|
inputs []uint32 // list of inputs that care about this tx.
|
|
|
|
}
|
|
|
|
|
|
|
|
// keyInfo is used to store provided keys in SignRawTransaction.
|
|
|
|
type keyInfo struct {
|
|
|
|
key *ecdsa.PrivateKey
|
|
|
|
compressed bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// SignRawTransaction handles the signrawtransaction command.
|
|
|
|
func SignRawTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcjson.SignRawTransactionCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
|
|
|
serializedTx, err := hex.DecodeString(cmd.RawTx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.ErrDecodeHexString
|
|
|
|
}
|
|
|
|
msgTx := btcwire.NewMsgTx()
|
|
|
|
err = msgTx.Deserialize(bytes.NewBuffer(serializedTx))
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: "TX decode failed",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// First we add the stuff we have been given.
|
|
|
|
// TODO(oga) really we probably should look these up with btcd anyway
|
|
|
|
// to make sure that they match the blockchain if present.
|
|
|
|
inputs := make(map[btcwire.OutPoint][]byte)
|
|
|
|
scripts := make(map[string][]byte)
|
|
|
|
for _, rti := range cmd.Inputs {
|
|
|
|
inputSha, err := btcwire.NewShaHashFromStr(rti.Txid)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
script, err := hex.DecodeString(rti.ScriptPubKey)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// redeemScript is only actually used iff the user provided
|
|
|
|
// private keys. In which case, it is used to get the scripts
|
|
|
|
// for signing. If the user did not provide keys then we always
|
|
|
|
// get scripts from the wallet.
|
|
|
|
// Empty strings are ok for this one and hex.DecodeString will
|
|
|
|
// DTRT.
|
|
|
|
if len(cmd.PrivKeys) != 0 {
|
|
|
|
redeemScript, err := hex.DecodeString(rti.RedeemScript)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
addr, err := btcutil.NewAddressScriptHash(redeemScript,
|
|
|
|
cfg.Net())
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
scripts[addr.String()] = redeemScript
|
|
|
|
}
|
|
|
|
inputs[btcwire.OutPoint{
|
|
|
|
Hash: *inputSha,
|
|
|
|
Index: uint32(rti.Vout),
|
|
|
|
}] = script
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now we go and look for any inputs that we were not provided by
|
|
|
|
// querying btcd with getrawtransaction. We queue up a bunch of async
|
|
|
|
// requests and will wait for replies after we have checked the rest of
|
|
|
|
// the arguments.
|
|
|
|
requested := make(map[btcwire.ShaHash]*pendingTx)
|
|
|
|
for _, txIn := range msgTx.TxIn {
|
|
|
|
// Did we get this txin from the arguments?
|
|
|
|
if _, ok := inputs[txIn.PreviousOutpoint]; ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Are we already fetching this tx? If so mark us as interested
|
|
|
|
// in this outpoint. (N.B. that any *sane* tx will only
|
|
|
|
// reference each outpoint once, since anything else is a double
|
|
|
|
// spend. We don't check this ourselves to save having to scan
|
|
|
|
// the array, it will fail later if so).
|
|
|
|
if ptx, ok := requested[txIn.PreviousOutpoint.Hash]; ok {
|
|
|
|
ptx.inputs = append(ptx.inputs,
|
|
|
|
txIn.PreviousOutpoint.Index)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// Never heard of this one before, request it.
|
|
|
|
requested[txIn.PreviousOutpoint.Hash] = &pendingTx{
|
|
|
|
resp: GetRawTransactionAsync(CurrentServerConn(),
|
|
|
|
&txIn.PreviousOutpoint.Hash),
|
|
|
|
inputs: []uint32{txIn.PreviousOutpoint.Index},
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse list of private keys, if present. If there are any keys here
|
|
|
|
// they are the keys that we may use for signing. If empty we will
|
|
|
|
// use any keys known to us already.
|
|
|
|
var keys map[string]keyInfo
|
|
|
|
if len(cmd.PrivKeys) != 0 {
|
|
|
|
keys = make(map[string]keyInfo)
|
|
|
|
|
|
|
|
for _, key := range cmd.PrivKeys {
|
|
|
|
key, net, compressed, err :=
|
|
|
|
btcutil.DecodePrivateKey(key)
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if net != cfg.Net() {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: "key network doesn't match " +
|
|
|
|
"wallet's",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
privk, pubk := btcec.PrivKeyFromBytes(btcec.S256(),
|
|
|
|
key)
|
|
|
|
|
|
|
|
var addr btcutil.Address
|
|
|
|
if compressed {
|
|
|
|
pkc := pubk.SerializeCompressed()
|
|
|
|
addr, err = btcutil.NewAddressPubKey(pkc,
|
|
|
|
cfg.Net())
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pku := pubk.SerializeUncompressed()
|
|
|
|
addr, err = btcutil.NewAddressPubKey(pku,
|
|
|
|
cfg.Net())
|
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrDeserialization.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
keys[addr.EncodeAddress()] = keyInfo{
|
|
|
|
key: privk,
|
|
|
|
compressed: compressed,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
hashType := btcscript.SigHashAll
|
|
|
|
if cmd.Flags != "" {
|
|
|
|
switch cmd.Flags {
|
|
|
|
case "ALL":
|
|
|
|
hashType = btcscript.SigHashAll
|
|
|
|
case "NONE":
|
|
|
|
hashType = btcscript.SigHashNone
|
|
|
|
case "SINGLE":
|
|
|
|
hashType = btcscript.SigHashSingle
|
|
|
|
case "ALL|ANYONECANPAY":
|
|
|
|
hashType = btcscript.SigHashAll |
|
|
|
|
btcscript.SigHashAnyOneCanPay
|
|
|
|
case "NONE|ANYONECANPAY":
|
|
|
|
hashType = btcscript.SigHashNone |
|
|
|
|
btcscript.SigHashAnyOneCanPay
|
|
|
|
case "SINGLE|ANYONECANPAY":
|
|
|
|
hashType = btcscript.SigHashSingle |
|
|
|
|
btcscript.SigHashAnyOneCanPay
|
|
|
|
default:
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrInvalidParameter.Code,
|
|
|
|
Message: "Invalid sighash parameter",
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// We have checked the rest of the args. now we can collect the async
|
|
|
|
// txs. TODO(oga) If we don't mind the possibility of wasting work we
|
|
|
|
// could move waiting to the following loop and be slightly more
|
|
|
|
// asynchronous.
|
|
|
|
for txid, ptx := range requested {
|
|
|
|
tx, err := GetRawTransactionAsyncResult(ptx.resp)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, input := range ptx.inputs {
|
|
|
|
if input >= uint32(len(tx.MsgTx().TxOut)) {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrInvalidParameter.Code,
|
|
|
|
Message: fmt.Sprintf("input %s:%d "+
|
|
|
|
"is not in tx", txid.String(),
|
|
|
|
input),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
inputs[btcwire.OutPoint{
|
|
|
|
Hash: txid,
|
|
|
|
Index: input,
|
|
|
|
}] = tx.MsgTx().TxOut[input].PkScript
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// All args collected. Now we can sign all the inputs that we can.
|
|
|
|
// `complete' denotes that we successfully signed all outputs and that
|
|
|
|
// all scripts will run to completion. This is returned as part of the
|
|
|
|
// reply.
|
|
|
|
complete := true
|
|
|
|
for i, txIn := range msgTx.TxIn {
|
|
|
|
input, ok := inputs[txIn.PreviousOutpoint]
|
|
|
|
if !ok {
|
|
|
|
// failure to find previous is actually an error since
|
|
|
|
// we failed above if we don't have all the inputs.
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: fmt.Sprintf("%s:%d not found",
|
|
|
|
txIn.PreviousOutpoint.Hash,
|
|
|
|
txIn.PreviousOutpoint.Index),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set up our callbacks that we pass to btcscript so it can
|
|
|
|
// look up the appropriate keys and scripts by address.
|
|
|
|
getKey := btcscript.KeyClosure(func(addr btcutil.Address) (
|
|
|
|
*ecdsa.PrivateKey, bool, error) {
|
|
|
|
if len(keys) != 0 {
|
|
|
|
info, ok := keys[addr.EncodeAddress()]
|
|
|
|
if !ok {
|
|
|
|
return nil, false,
|
|
|
|
errors.New("no key for address")
|
|
|
|
}
|
|
|
|
return info.key, info.compressed, nil
|
|
|
|
}
|
|
|
|
address, err := AcctMgr.Address(addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
pka, ok := address.(wallet.PubKeyAddress)
|
|
|
|
if !ok {
|
|
|
|
return nil, false, errors.New("address is not " +
|
|
|
|
"a pubkey address")
|
|
|
|
}
|
|
|
|
|
|
|
|
key, err := pka.PrivKey()
|
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return key, pka.Compressed(), nil
|
|
|
|
})
|
|
|
|
|
|
|
|
getScript := btcscript.ScriptClosure(func(
|
|
|
|
addr btcutil.Address) ([]byte, error) {
|
|
|
|
// If keys were provided then we can only use the
|
|
|
|
// scripts provided with our inputs, too.
|
|
|
|
if len(keys) != 0 {
|
|
|
|
script, ok := scripts[addr.EncodeAddress()]
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("no script for " +
|
|
|
|
"address")
|
|
|
|
}
|
|
|
|
return script, nil
|
|
|
|
}
|
|
|
|
address, err := AcctMgr.Address(addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
sa, ok := address.(wallet.ScriptAddress)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("addres is not a script" +
|
|
|
|
" address")
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO(oga) we could possible speed things up further
|
|
|
|
// by returning the addresses, class and nrequired here
|
|
|
|
// thus avoiding recomputing them.
|
|
|
|
return sa.Script(), nil
|
|
|
|
})
|
|
|
|
|
|
|
|
// SigHashSingle inputs can only be signed if there's a
|
|
|
|
// corresponding output. However this could be already signed,
|
|
|
|
// so we always verify the output.
|
|
|
|
if (hashType & btcscript.SigHashSingle) == 0 ||
|
|
|
|
i < len(msgTx.TxOut) {
|
|
|
|
|
|
|
|
script, err := btcscript.SignTxOutput(cfg.Net(),
|
|
|
|
msgTx, i, input, byte(hashType), getKey,
|
|
|
|
getScript, txIn.SignatureScript)
|
|
|
|
// Failure to sign isn't an error, it just means that
|
|
|
|
// the tx isn't complete.
|
|
|
|
if err != nil {
|
|
|
|
complete = false
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
txIn.SignatureScript = script
|
|
|
|
}
|
|
|
|
|
|
|
|
// Either it was already signed or we just signed it.
|
|
|
|
// Find out if it is completely satisfied or still needs more.
|
|
|
|
engine, err := btcscript.NewScript(txIn.SignatureScript, input,
|
|
|
|
i, msgTx, btcscript.ScriptBip16|
|
|
|
|
btcscript.ScriptCanonicalSignatures)
|
|
|
|
if err != nil || engine.Execute() != nil {
|
|
|
|
complete = false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := bytes.NewBuffer(nil)
|
|
|
|
buf.Grow(msgTx.SerializeSize())
|
|
|
|
|
|
|
|
// Buffer is the right size, this should never fail so no need to
|
|
|
|
// come up with some synthetic error code for it.
|
|
|
|
_ = msgTx.Serialize(buf)
|
|
|
|
|
|
|
|
return btcjson.SignRawTransactionResult{
|
|
|
|
Hex: hex.EncodeToString(buf.Bytes()),
|
|
|
|
Complete: complete,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
2014-02-14 00:16:21 +01:00
|
|
|
// ValidateAddress handles the validateaddress command.
|
|
|
|
func ValidateAddress(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcjson.ValidateAddressCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
2014-04-07 20:21:20 +02:00
|
|
|
result := btcjson.ValidateAddressResult{}
|
2014-03-19 02:47:12 +01:00
|
|
|
addr, err := btcutil.DecodeAddress(cmd.Address, cfg.Net())
|
2014-02-14 00:16:21 +01:00
|
|
|
if err != nil {
|
2014-04-07 20:21:20 +02:00
|
|
|
return result, nil
|
2014-02-14 00:16:21 +01:00
|
|
|
}
|
|
|
|
|
2014-04-07 20:21:20 +02:00
|
|
|
// We could put whether or not the address is a script here,
|
|
|
|
// by checking the type of "addr", however, the reference
|
|
|
|
// implementation only puts that information if the script is
|
|
|
|
// "ismine", and we follow that behaviour.
|
|
|
|
result.Address = addr.EncodeAddress()
|
|
|
|
result.IsValid = true
|
|
|
|
|
2014-04-18 00:00:52 +02:00
|
|
|
// We can't use AcctMgr.Address() here since we also need the account
|
|
|
|
// name.
|
|
|
|
if account, err := AcctMgr.AccountByAddress(addr); err == nil {
|
2014-02-14 00:16:21 +01:00
|
|
|
// we ignore these errors because if this call passes this can't
|
|
|
|
// realistically fail.
|
2014-04-09 02:18:52 +02:00
|
|
|
ainfo, _ := account.Address(addr)
|
2014-02-14 00:16:21 +01:00
|
|
|
|
2014-04-07 20:21:20 +02:00
|
|
|
result.IsMine = true
|
|
|
|
result.Account = account.name
|
2014-02-14 00:16:21 +01:00
|
|
|
|
2014-04-09 02:18:52 +02:00
|
|
|
if pka, ok := ainfo.(wallet.PubKeyAddress); ok {
|
|
|
|
result.IsCompressed = pka.Compressed()
|
|
|
|
result.PubKey = pka.ExportPubKey()
|
2014-03-13 20:13:39 +01:00
|
|
|
|
2014-04-09 02:18:52 +02:00
|
|
|
} else if sa, ok := ainfo.(wallet.ScriptAddress); ok {
|
2014-04-07 20:21:20 +02:00
|
|
|
result.IsScript = true
|
2014-04-09 02:18:52 +02:00
|
|
|
addresses := sa.Addresses()
|
|
|
|
addrStrings := make([]string, len(addresses))
|
|
|
|
for i, a := range addresses {
|
2014-03-13 20:13:39 +01:00
|
|
|
addrStrings[i] = a.EncodeAddress()
|
|
|
|
}
|
2014-04-07 20:21:20 +02:00
|
|
|
result.Addresses = addrStrings
|
2014-04-09 02:18:52 +02:00
|
|
|
result.Hex = hex.EncodeToString(sa.Script())
|
2014-03-13 20:13:39 +01:00
|
|
|
|
2014-04-09 02:18:52 +02:00
|
|
|
class := sa.ScriptClass()
|
2014-03-13 20:13:39 +01:00
|
|
|
// script type
|
2014-04-07 20:21:20 +02:00
|
|
|
result.Script = class.String()
|
2014-03-13 20:13:39 +01:00
|
|
|
if class == btcscript.MultiSigTy {
|
2014-04-09 02:18:52 +02:00
|
|
|
result.SigsRequired = sa.RequiredSigs()
|
2014-03-13 20:13:39 +01:00
|
|
|
}
|
2014-03-06 01:34:44 +01:00
|
|
|
}
|
2014-02-14 00:16:21 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
2014-02-12 03:14:07 +01:00
|
|
|
// VerifyMessage handles the verifymessage command by verifying the provided
|
|
|
|
// compact signature for the given address and message.
|
|
|
|
func VerifyMessage(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcjson.VerifyMessageCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
2014-03-19 02:47:12 +01:00
|
|
|
addr, err := btcutil.DecodeAddress(cmd.Address, cfg.Net())
|
2014-02-12 03:14:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
2014-04-03 17:00:46 +02:00
|
|
|
Code: btcjson.ErrParse.Code,
|
2014-02-12 03:14:07 +01:00
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-04-03 17:00:46 +02:00
|
|
|
// First check we know about the address and get the keys.
|
2014-04-18 00:00:52 +02:00
|
|
|
ainfo, err := AcctMgr.Address(addr)
|
2014-04-03 17:00:46 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.ErrInvalidAddressOrKey
|
|
|
|
}
|
|
|
|
|
2014-04-09 02:18:52 +02:00
|
|
|
pka := ainfo.(wallet.PubKeyAddress)
|
|
|
|
privkey, err := pka.PrivKey()
|
2014-02-12 03:14:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// decode base64 signature
|
2014-02-13 19:42:22 +01:00
|
|
|
sig, err := base64.StdEncoding.DecodeString(cmd.Signature)
|
2014-02-12 03:14:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Validate the signature - this just shows that it was valid at all.
|
|
|
|
// we will compare it with the key next.
|
|
|
|
pk, wasCompressed, err := btcec.RecoverCompact(btcec.S256(), sig,
|
2014-02-13 19:42:22 +01:00
|
|
|
btcwire.DoubleSha256([]byte("Bitcoin Signed Message:\n"+
|
|
|
|
cmd.Message)))
|
2014-02-12 03:14:07 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, &btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return boolean if keys match.
|
|
|
|
return (pk.X.Cmp(privkey.X) == 0 && pk.Y.Cmp(privkey.Y) == 0 &&
|
2014-03-06 01:34:44 +01:00
|
|
|
ainfo.Compressed() == wasCompressed), nil
|
2014-02-12 03:14:07 +01:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
// WalletIsLocked handles the walletislocked extension request by
|
|
|
|
// returning the current lock state (false for unlocked, true for locked)
|
|
|
|
// of an account. An error is returned if the requested account does not
|
|
|
|
// exist.
|
|
|
|
func WalletIsLocked(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-12 18:01:32 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcws.WalletIsLockedCmd)
|
2013-09-09 20:14:57 +02:00
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-08-21 20:46:20 +02:00
|
|
|
}
|
2013-10-29 07:19:40 +01:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
a, err := AcctMgr.Account(cmd.Account)
|
2013-12-02 20:56:06 +01:00
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
break
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case ErrNotFound:
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrWalletInvalidAccountName
|
2013-12-02 20:56:06 +01:00
|
|
|
|
|
|
|
default: // all other non-nil errors
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-08-21 20:46:20 +02:00
|
|
|
}
|
2013-11-12 18:01:32 +01:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
return a.Wallet.IsLocked(), nil
|
2013-08-21 20:46:20 +02:00
|
|
|
}
|
|
|
|
|
2014-01-27 21:48:12 +01:00
|
|
|
// WalletLock handles a walletlock request by locking the all account
|
|
|
|
// wallets, returning an error if any wallet is not encrypted (for example,
|
|
|
|
// a watching-only wallet).
|
2014-01-03 19:34:37 +01:00
|
|
|
func WalletLock(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2014-01-30 16:14:02 +01:00
|
|
|
if err := AcctMgr.LockWallets(); err != nil {
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
2013-08-21 19:25:22 +02:00
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-02 20:56:06 +01:00
|
|
|
}
|
|
|
|
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, nil
|
2013-08-21 16:37:30 +02:00
|
|
|
}
|
|
|
|
|
2013-11-12 18:01:32 +01:00
|
|
|
// WalletPassphrase responds to the walletpassphrase request by unlocking
|
|
|
|
// the wallet. The decryption key is saved in the wallet until timeout
|
|
|
|
// seconds expires, after which the wallet is locked.
|
2014-01-03 19:34:37 +01:00
|
|
|
func WalletPassphrase(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
2013-11-12 18:01:32 +01:00
|
|
|
// Type assert icmd to access parameters.
|
|
|
|
cmd, ok := icmd.(*btcjson.WalletPassphraseCmd)
|
2013-09-09 20:14:57 +02:00
|
|
|
if !ok {
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &btcjson.ErrInternal
|
2013-08-21 16:37:30 +02:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
if err := AcctMgr.UnlockWallets(cmd.Passphrase); err != nil {
|
2014-01-03 19:34:37 +01:00
|
|
|
e := btcjson.Error{
|
2013-12-02 20:56:06 +01:00
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
2013-08-21 18:07:57 +02:00
|
|
|
}
|
2014-01-03 19:34:37 +01:00
|
|
|
return nil, &e
|
2013-12-02 20:56:06 +01:00
|
|
|
}
|
|
|
|
|
2014-01-27 21:48:12 +01:00
|
|
|
go func(timeout int64) {
|
|
|
|
time.Sleep(time.Second * time.Duration(timeout))
|
2014-03-20 17:21:52 +01:00
|
|
|
AcctMgr.Grab()
|
2014-01-30 16:14:02 +01:00
|
|
|
_ = AcctMgr.LockWallets()
|
2014-03-20 17:21:52 +01:00
|
|
|
AcctMgr.Release()
|
2014-01-27 21:48:12 +01:00
|
|
|
}(cmd.Timeout)
|
2013-12-02 20:56:06 +01:00
|
|
|
|
2014-01-27 21:48:12 +01:00
|
|
|
return nil, nil
|
2013-08-21 16:37:30 +02:00
|
|
|
}
|
2013-08-22 18:00:37 +02:00
|
|
|
|
2014-01-27 15:30:42 +01:00
|
|
|
// WalletPassphraseChange responds to the walletpassphrasechange request
|
|
|
|
// by unlocking all accounts with the provided old passphrase, and
|
|
|
|
// re-encrypting each private key with an AES key derived from the new
|
|
|
|
// passphrase.
|
|
|
|
//
|
|
|
|
// If the old passphrase is correct and the passphrase is changed, all
|
|
|
|
// wallets will be immediately locked.
|
|
|
|
func WalletPassphraseChange(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
|
|
|
|
cmd, ok := icmd.(*btcjson.WalletPassphraseChangeCmd)
|
|
|
|
if !ok {
|
|
|
|
return nil, &btcjson.ErrInternal
|
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
err := AcctMgr.ChangePassphrase([]byte(cmd.OldPassphrase),
|
2014-01-27 15:30:42 +01:00
|
|
|
[]byte(cmd.NewPassphrase))
|
|
|
|
switch err {
|
|
|
|
case nil:
|
|
|
|
return nil, nil
|
|
|
|
|
|
|
|
case wallet.ErrWrongPassphrase:
|
|
|
|
return nil, &btcjson.ErrWalletPassphraseIncorrect
|
|
|
|
|
|
|
|
default: // all other non-nil errors
|
|
|
|
e := btcjson.Error{
|
|
|
|
Code: btcjson.ErrWallet.Code,
|
|
|
|
Message: err.Error(),
|
|
|
|
}
|
|
|
|
return nil, &e
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-10-14 22:39:15 +02:00
|
|
|
// AccountNtfn is a struct for marshalling any generic notification
|
|
|
|
// about a account for a wallet frontend.
|
|
|
|
//
|
|
|
|
// TODO(jrick): move to btcjson so it can be shared with frontends?
|
2013-10-09 17:23:54 +02:00
|
|
|
type AccountNtfn struct {
|
|
|
|
Account string `json:"account"`
|
|
|
|
Notification interface{} `json:"notification"`
|
|
|
|
}
|
|
|
|
|
2013-08-22 18:00:37 +02:00
|
|
|
// NotifyWalletLockStateChange sends a notification to all frontends
|
|
|
|
// that the wallet has just been locked or unlocked.
|
2013-10-09 17:23:54 +02:00
|
|
|
func NotifyWalletLockStateChange(account string, locked bool) {
|
2013-12-13 17:00:31 +01:00
|
|
|
ntfn := btcws.NewWalletLockStateNtfn(account, locked)
|
|
|
|
mntfn, _ := ntfn.MarshalJSON()
|
2014-02-18 04:18:30 +01:00
|
|
|
allClients <- mntfn
|
2013-08-22 18:00:37 +02:00
|
|
|
}
|
2013-10-09 17:23:54 +02:00
|
|
|
|
2013-10-14 22:39:15 +02:00
|
|
|
// NotifyWalletBalance sends a confirmed account balance notification
|
|
|
|
// to a frontend.
|
2013-10-09 17:23:54 +02:00
|
|
|
func NotifyWalletBalance(frontend chan []byte, account string, balance float64) {
|
2013-12-13 17:00:31 +01:00
|
|
|
ntfn := btcws.NewAccountBalanceNtfn(account, balance, true)
|
|
|
|
mntfn, _ := ntfn.MarshalJSON()
|
|
|
|
frontend <- mntfn
|
2013-10-09 17:23:54 +02:00
|
|
|
}
|
|
|
|
|
2013-12-02 23:34:36 +01:00
|
|
|
// NotifyWalletBalanceUnconfirmed sends a confirmed account balance
|
2013-10-14 22:39:15 +02:00
|
|
|
// notification to a frontend.
|
2013-10-09 17:23:54 +02:00
|
|
|
func NotifyWalletBalanceUnconfirmed(frontend chan []byte, account string, balance float64) {
|
2013-12-13 17:00:31 +01:00
|
|
|
ntfn := btcws.NewAccountBalanceNtfn(account, balance, false)
|
|
|
|
mntfn, _ := ntfn.MarshalJSON()
|
|
|
|
frontend <- mntfn
|
2013-10-09 17:23:54 +02:00
|
|
|
}
|
2013-12-02 23:34:36 +01:00
|
|
|
|
|
|
|
// NotifyNewTxDetails sends details of a new transaction to a frontend.
|
|
|
|
func NotifyNewTxDetails(frontend chan []byte, account string,
|
2014-04-09 05:04:10 +02:00
|
|
|
details btcjson.ListTransactionsResult) {
|
2013-12-02 23:34:36 +01:00
|
|
|
|
2014-04-09 05:04:10 +02:00
|
|
|
ntfn := btcws.NewTxNtfn(account, &details)
|
2013-12-02 23:34:36 +01:00
|
|
|
mntfn, _ := ntfn.MarshalJSON()
|
|
|
|
frontend <- mntfn
|
|
|
|
}
|
2013-12-17 19:18:09 +01:00
|
|
|
|
|
|
|
// NotifiedRecvTxRequest is used to check whether the outpoint of
|
|
|
|
// a received transaction has already been notified due to
|
|
|
|
// arriving first in the btcd mempool.
|
|
|
|
type NotifiedRecvTxRequest struct {
|
|
|
|
op btcwire.OutPoint
|
|
|
|
response chan NotifiedRecvTxResponse
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotifiedRecvTxResponse is the response of a NotifiedRecvTxRequest
|
|
|
|
// request.
|
|
|
|
type NotifiedRecvTxResponse bool
|
|
|
|
|
|
|
|
// NotifiedRecvTxChans holds the channels to manage
|
|
|
|
// StoreNotifiedMempoolTxs.
|
|
|
|
var NotifiedRecvTxChans = struct {
|
|
|
|
add, remove chan btcwire.OutPoint
|
|
|
|
access chan NotifiedRecvTxRequest
|
|
|
|
}{
|
|
|
|
add: make(chan btcwire.OutPoint),
|
|
|
|
remove: make(chan btcwire.OutPoint),
|
|
|
|
access: make(chan NotifiedRecvTxRequest),
|
|
|
|
}
|
|
|
|
|
|
|
|
// StoreNotifiedMempoolRecvTxs maintains a set of previously-sent
|
|
|
|
// received transaction notifications originating from the btcd
|
|
|
|
// mempool. This is used to prevent duplicate frontend transaction
|
|
|
|
// notifications once a mempool tx is mined into a block.
|
|
|
|
func StoreNotifiedMempoolRecvTxs(add, remove chan btcwire.OutPoint,
|
|
|
|
access chan NotifiedRecvTxRequest) {
|
|
|
|
|
|
|
|
m := make(map[btcwire.OutPoint]struct{})
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case op := <-add:
|
|
|
|
m[op] = struct{}{}
|
|
|
|
|
|
|
|
case op := <-remove:
|
|
|
|
if _, ok := m[op]; ok {
|
|
|
|
delete(m, op)
|
|
|
|
}
|
|
|
|
|
|
|
|
case req := <-access:
|
|
|
|
_, ok := m[req.op]
|
|
|
|
req.response <- NotifiedRecvTxResponse(ok)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotifyBalanceSyncerChans holds channels for accessing
|
|
|
|
// the NotifyBalanceSyncer goroutine.
|
|
|
|
var NotifyBalanceSyncerChans = struct {
|
|
|
|
add chan NotifyBalanceWorker
|
|
|
|
remove chan btcwire.ShaHash
|
|
|
|
access chan NotifyBalanceRequest
|
|
|
|
}{
|
|
|
|
add: make(chan NotifyBalanceWorker),
|
|
|
|
remove: make(chan btcwire.ShaHash),
|
|
|
|
access: make(chan NotifyBalanceRequest),
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotifyBalanceWorker holds a block hash to add a worker to
|
|
|
|
// NotifyBalanceSyncer and uses a chan to returns the WaitGroup
|
|
|
|
// which should be decremented with Done after the worker is finished.
|
|
|
|
type NotifyBalanceWorker struct {
|
|
|
|
block btcwire.ShaHash
|
|
|
|
wg chan *sync.WaitGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotifyBalanceRequest is used by the blockconnected notification handler
|
|
|
|
// to access and wait on the the WaitGroup for workers currently processing
|
|
|
|
// transactions for a block. If no handlers have been added, a nil
|
|
|
|
// WaitGroup is returned.
|
|
|
|
type NotifyBalanceRequest struct {
|
|
|
|
block btcwire.ShaHash
|
|
|
|
wg chan *sync.WaitGroup
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotifyBalanceSyncer maintains a map of block hashes to WaitGroups
|
|
|
|
// for worker goroutines that must finish before it is safe to notify
|
|
|
|
// frontends of a new balance in the blockconnected notification handler.
|
|
|
|
func NotifyBalanceSyncer(add chan NotifyBalanceWorker,
|
|
|
|
remove chan btcwire.ShaHash,
|
|
|
|
access chan NotifyBalanceRequest) {
|
|
|
|
|
|
|
|
m := make(map[btcwire.ShaHash]*sync.WaitGroup)
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case worker := <-add:
|
|
|
|
wg, ok := m[worker.block]
|
|
|
|
if !ok {
|
|
|
|
wg = &sync.WaitGroup{}
|
|
|
|
m[worker.block] = wg
|
|
|
|
}
|
|
|
|
wg.Add(1)
|
|
|
|
m[worker.block] = wg
|
|
|
|
worker.wg <- wg
|
|
|
|
|
|
|
|
case block := <-remove:
|
|
|
|
if _, ok := m[block]; ok {
|
|
|
|
delete(m, block)
|
|
|
|
}
|
|
|
|
|
|
|
|
case req := <-access:
|
|
|
|
req.wg <- m[req.block]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|