2014-01-30 16:14:02 +01:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
|
|
|
|
*
|
|
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
|
|
* copyright notice and this permission notice appear in all copies.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2014-03-31 17:11:37 +02:00
|
|
|
"encoding/hex"
|
2014-01-30 16:14:02 +01:00
|
|
|
"errors"
|
|
|
|
"fmt"
|
2014-06-26 21:58:46 +02:00
|
|
|
"github.com/conformal/btcchain"
|
2014-04-09 05:35:39 +02:00
|
|
|
"github.com/conformal/btcjson"
|
2014-01-30 16:14:02 +01:00
|
|
|
"github.com/conformal/btcutil"
|
2014-05-08 21:48:42 +02:00
|
|
|
"github.com/conformal/btcwallet/txstore"
|
2014-01-30 16:14:02 +01:00
|
|
|
"github.com/conformal/btcwallet/wallet"
|
|
|
|
"github.com/conformal/btcwire"
|
2014-05-30 22:17:51 +02:00
|
|
|
"io/ioutil"
|
2014-04-03 17:00:46 +02:00
|
|
|
"os"
|
2014-06-03 19:10:42 +02:00
|
|
|
"path/filepath"
|
2014-04-03 17:00:46 +02:00
|
|
|
"strings"
|
2014-06-24 23:00:27 +02:00
|
|
|
"sync"
|
2014-07-01 17:02:13 +02:00
|
|
|
"time"
|
2014-01-30 16:14:02 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// Errors relating to accounts.
|
|
|
|
var (
|
|
|
|
ErrAccountExists = errors.New("account already exists")
|
|
|
|
ErrWalletExists = errors.New("wallet already exists")
|
|
|
|
ErrNotFound = errors.New("not found")
|
2014-06-24 23:00:27 +02:00
|
|
|
ErrNoAccounts = errors.New("no accounts")
|
2014-01-30 16:14:02 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// AcctMgr is the global account manager for all opened accounts.
|
|
|
|
var AcctMgr = NewAccountManager()
|
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
type (
|
|
|
|
openAccountsCmd struct{}
|
2014-04-03 17:00:46 +02:00
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
accessAccountRequest struct {
|
|
|
|
name string
|
|
|
|
resp chan *Account
|
|
|
|
}
|
2014-04-03 17:00:46 +02:00
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
accessAllRequest struct {
|
|
|
|
resp chan []*Account
|
|
|
|
}
|
2014-04-03 17:00:46 +02:00
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
accessAccountByAddressRequest struct {
|
|
|
|
address string
|
|
|
|
resp chan *Account
|
|
|
|
}
|
2014-04-03 17:00:46 +02:00
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
markAddressForAccountCmd struct {
|
|
|
|
address string
|
|
|
|
account *Account
|
|
|
|
}
|
2014-04-03 17:00:46 +02:00
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
addAccountCmd struct {
|
|
|
|
a *Account
|
|
|
|
}
|
2014-04-03 17:00:46 +02:00
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
removeAccountCmd struct {
|
|
|
|
a *Account
|
|
|
|
}
|
2014-04-03 17:00:46 +02:00
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
quitCmd struct{}
|
|
|
|
)
|
|
|
|
|
|
|
|
type unlockRequest struct {
|
|
|
|
passphrase []byte
|
|
|
|
timeout time.Duration // Zero value prevents the timeout.
|
|
|
|
err chan error
|
|
|
|
}
|
2014-06-24 23:00:27 +02:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// AccountManager manages a collection of accounts.
|
|
|
|
type AccountManager struct {
|
|
|
|
// The accounts accessed through the account manager are not safe for
|
|
|
|
// concurrent access. The account manager therefore contains a
|
|
|
|
// binary semaphore channel to prevent incorrect access.
|
2014-07-01 17:02:13 +02:00
|
|
|
bsem chan struct{}
|
|
|
|
cmdChan chan interface{}
|
|
|
|
rescanMsgs chan RescanMsg
|
|
|
|
unlockRequests chan unlockRequest
|
|
|
|
lockRequests chan struct{}
|
|
|
|
unlockedState chan bool
|
2014-01-30 16:14:02 +01:00
|
|
|
|
2014-03-25 05:59:24 +01:00
|
|
|
ds *DiskSyncer
|
|
|
|
rm *RescanManager
|
2014-06-24 23:00:27 +02:00
|
|
|
|
|
|
|
wg sync.WaitGroup
|
|
|
|
quit chan struct{}
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewAccountManager returns a new AccountManager.
|
|
|
|
func NewAccountManager() *AccountManager {
|
|
|
|
am := &AccountManager{
|
2014-07-01 17:02:13 +02:00
|
|
|
bsem: make(chan struct{}, 1),
|
|
|
|
cmdChan: make(chan interface{}),
|
|
|
|
rescanMsgs: make(chan RescanMsg, 1),
|
|
|
|
unlockRequests: make(chan unlockRequest),
|
|
|
|
lockRequests: make(chan struct{}),
|
|
|
|
unlockedState: make(chan bool),
|
|
|
|
|
|
|
|
quit: make(chan struct{}),
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
am.ds = NewDiskSyncer(am)
|
2014-03-25 05:59:24 +01:00
|
|
|
am.rm = NewRescanManager(am.rescanMsgs)
|
2014-01-30 16:14:02 +01:00
|
|
|
return am
|
|
|
|
}
|
|
|
|
|
2014-03-25 05:59:24 +01:00
|
|
|
// Start starts the goroutines required to run the AccountManager.
|
2014-01-30 16:14:02 +01:00
|
|
|
func (am *AccountManager) Start() {
|
|
|
|
// Ready the semaphore - can't grab unless the manager has started.
|
|
|
|
am.bsem <- struct{}{}
|
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
am.wg.Add(3)
|
2014-03-25 05:59:24 +01:00
|
|
|
go am.accountHandler()
|
2014-07-01 17:02:13 +02:00
|
|
|
go am.keystoreLocker()
|
2014-03-25 05:59:24 +01:00
|
|
|
go am.rescanListener()
|
2014-06-24 23:11:06 +02:00
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
go am.ds.Start()
|
2014-03-25 05:59:24 +01:00
|
|
|
go am.rm.Start()
|
|
|
|
}
|
2014-01-30 16:14:02 +01:00
|
|
|
|
2014-06-24 23:00:27 +02:00
|
|
|
// Stop shuts down the account manager by stoping all signaling all goroutines
|
|
|
|
// started by Start to close.
|
|
|
|
func (am *AccountManager) Stop() {
|
|
|
|
am.rm.Stop()
|
|
|
|
am.ds.Stop()
|
|
|
|
close(am.quit)
|
|
|
|
}
|
|
|
|
|
|
|
|
// WaitForShutdown blocks until all goroutines started by Start and stopped
|
|
|
|
// with Stop have finished.
|
|
|
|
func (am *AccountManager) WaitForShutdown() {
|
|
|
|
am.rm.WaitForShutdown()
|
|
|
|
am.ds.WaitForShutdown()
|
|
|
|
am.wg.Wait()
|
|
|
|
}
|
|
|
|
|
2014-04-03 17:00:46 +02:00
|
|
|
// accountData is a helper structure to let us centralise logic for adding
|
|
|
|
// and removing accounts.
|
|
|
|
type accountData struct {
|
|
|
|
// maps name to account struct. We could keep a list here for iteration
|
|
|
|
// but iteration over the small amounts we have is likely not worth
|
|
|
|
// the extra complexity.
|
|
|
|
nameToAccount map[string]*Account
|
|
|
|
addressToAccount map[string]*Account
|
|
|
|
}
|
|
|
|
|
|
|
|
func newAccountData() *accountData {
|
|
|
|
return &accountData{
|
|
|
|
nameToAccount: make(map[string]*Account),
|
|
|
|
addressToAccount: make(map[string]*Account),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ad *accountData) addAccount(a *Account) {
|
|
|
|
if _, ok := ad.nameToAccount[a.name]; ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
ad.nameToAccount[a.name] = a
|
|
|
|
for addr := range a.ActivePaymentAddresses() {
|
|
|
|
ad.addressToAccount[addr] = a
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (ad *accountData) removeAccount(a *Account) {
|
|
|
|
a, ok := ad.nameToAccount[a.name]
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
delete(ad.nameToAccount, a.name)
|
|
|
|
for addr := range a.ActivePaymentAddresses() {
|
|
|
|
delete(ad.addressToAccount, addr)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// walletOpenError is a special error type so problems opening wallet
|
|
|
|
// files can be differentiated (by a type assertion) from other errors.
|
|
|
|
type walletOpenError struct {
|
|
|
|
Err string
|
|
|
|
}
|
|
|
|
|
|
|
|
// Error satisifies the builtin error interface.
|
|
|
|
func (e *walletOpenError) Error() string {
|
|
|
|
return e.Err
|
|
|
|
}
|
|
|
|
|
|
|
|
var (
|
|
|
|
// errNoWallet describes an error where a wallet does not exist and
|
|
|
|
// must be created first.
|
|
|
|
errNoWallet = &walletOpenError{
|
|
|
|
Err: "wallet file does not exist",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
|
|
|
// openSavedAccount opens a named account from disk. If the wallet does not
|
|
|
|
// exist, errNoWallet is returned as an error.
|
|
|
|
func openSavedAccount(name string, cfg *config) (*Account, error) {
|
2014-05-23 04:16:50 +02:00
|
|
|
netdir := networkDir(activeNet.Params)
|
2014-04-03 17:00:46 +02:00
|
|
|
if err := checkCreateDir(netdir); err != nil {
|
|
|
|
return nil, &walletOpenError{
|
|
|
|
Err: err.Error(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
wlt := new(wallet.Wallet)
|
2014-05-08 21:48:42 +02:00
|
|
|
txs := txstore.New()
|
2014-04-03 17:00:46 +02:00
|
|
|
a := &Account{
|
2014-06-23 23:59:57 +02:00
|
|
|
name: name,
|
|
|
|
Wallet: wlt,
|
|
|
|
TxStore: txs,
|
|
|
|
lockedOutpoints: map[btcwire.OutPoint]struct{}{},
|
2014-04-03 17:00:46 +02:00
|
|
|
}
|
|
|
|
|
2014-06-03 19:10:42 +02:00
|
|
|
walletPath := accountFilename("wallet.bin", name, netdir)
|
|
|
|
txstorePath := accountFilename("tx.bin", name, netdir)
|
2014-04-03 17:00:46 +02:00
|
|
|
|
|
|
|
// Read wallet file.
|
2014-06-03 19:10:42 +02:00
|
|
|
walletFi, err := os.Open(walletPath)
|
2014-04-03 17:00:46 +02:00
|
|
|
if err != nil {
|
|
|
|
if os.IsNotExist(err) {
|
|
|
|
// Must create and save wallet first.
|
|
|
|
return nil, errNoWallet
|
|
|
|
}
|
|
|
|
msg := fmt.Sprintf("cannot open wallet file: %s", err)
|
|
|
|
return nil, &walletOpenError{msg}
|
|
|
|
}
|
2014-06-03 19:10:42 +02:00
|
|
|
if _, err = wlt.ReadFrom(walletFi); err != nil {
|
|
|
|
if err := walletFi.Close(); err != nil {
|
2014-05-28 06:54:50 +02:00
|
|
|
log.Warnf("Cannot close wallet file: %v", err)
|
|
|
|
}
|
2014-06-03 19:10:42 +02:00
|
|
|
msg := fmt.Sprintf("Cannot read wallet: %s", err)
|
2014-04-03 17:00:46 +02:00
|
|
|
return nil, &walletOpenError{msg}
|
|
|
|
}
|
|
|
|
|
2014-06-03 19:10:42 +02:00
|
|
|
// Read txstore file. If this fails, write a new empty transaction
|
|
|
|
// store to disk, mark the wallet as unsynced, and write the unsynced
|
|
|
|
// wallet to disk.
|
|
|
|
//
|
|
|
|
// This file is opened read/write so it may be truncated if a new empty
|
|
|
|
// transaction store must be written.
|
|
|
|
txstoreFi, err := os.OpenFile(txstorePath, os.O_RDWR, 0)
|
2014-05-28 06:54:50 +02:00
|
|
|
if err != nil {
|
2014-06-03 19:10:42 +02:00
|
|
|
if err := walletFi.Close(); err != nil {
|
|
|
|
log.Warnf("Cannot close wallet file: %v", err)
|
2014-05-30 22:17:51 +02:00
|
|
|
}
|
2014-06-03 19:10:42 +02:00
|
|
|
if err := writeUnsyncedWallet(a, walletPath); err != nil {
|
|
|
|
return nil, err
|
2014-05-30 22:17:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Create and write empty txstore, if it doesn't exist.
|
2014-06-03 19:10:42 +02:00
|
|
|
if !fileExists(txstorePath) {
|
|
|
|
log.Warn("Transaction store file missing")
|
|
|
|
if txstoreFi, err = os.Create(txstorePath); err != nil {
|
|
|
|
return nil, fmt.Errorf("cannot create new "+
|
|
|
|
"txstore file: %v", err)
|
2014-05-30 22:17:51 +02:00
|
|
|
}
|
|
|
|
defer func() {
|
2014-06-03 19:10:42 +02:00
|
|
|
if err := txstoreFi.Close(); err != nil {
|
|
|
|
log.Warnf("Cannot close transaction "+
|
|
|
|
"store file: %v", err)
|
2014-05-30 22:17:51 +02:00
|
|
|
}
|
|
|
|
}()
|
2014-06-03 19:10:42 +02:00
|
|
|
} else {
|
|
|
|
return nil, fmt.Errorf("transaction store file "+
|
|
|
|
"exists but cannot be opened: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if _, err := txs.WriteTo(txstoreFi); err != nil {
|
|
|
|
log.Warn(err)
|
2014-05-30 22:17:51 +02:00
|
|
|
}
|
2014-06-03 19:10:42 +02:00
|
|
|
return a, nil
|
2014-05-28 06:54:50 +02:00
|
|
|
}
|
2014-06-03 19:10:42 +02:00
|
|
|
if _, err = txs.ReadFrom(txstoreFi); err != nil {
|
|
|
|
if err := walletFi.Close(); err != nil {
|
|
|
|
log.Warnf("Cannot close wallet file: %v", err)
|
|
|
|
}
|
|
|
|
if err := writeUnsyncedWallet(a, walletPath); err != nil {
|
|
|
|
return nil, err
|
2014-04-03 17:00:46 +02:00
|
|
|
}
|
2014-05-30 22:17:51 +02:00
|
|
|
|
2014-06-03 19:10:42 +02:00
|
|
|
defer func() {
|
|
|
|
if err := txstoreFi.Close(); err != nil {
|
|
|
|
log.Warnf("Cannot close transaction store "+
|
|
|
|
"file: %v", err)
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
log.Warnf("Cannot read transaction store: %s", err)
|
|
|
|
if _, err := txstoreFi.Seek(0, os.SEEK_SET); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if err := txstoreFi.Truncate(0); err != nil {
|
|
|
|
return nil, err
|
2014-05-30 22:17:51 +02:00
|
|
|
}
|
2014-06-03 19:10:42 +02:00
|
|
|
if _, err := txs.WriteTo(txstoreFi); err != nil {
|
|
|
|
log.Warn("Cannot write new transaction store: %v", err)
|
|
|
|
}
|
|
|
|
log.Infof("Wrote empty transaction store file")
|
|
|
|
return a, nil
|
2014-04-03 17:00:46 +02:00
|
|
|
}
|
|
|
|
|
2014-06-03 19:10:42 +02:00
|
|
|
if err := walletFi.Close(); err != nil {
|
|
|
|
log.Warnf("Cannot close wallet file: %v", err)
|
|
|
|
}
|
|
|
|
if err := txstoreFi.Close(); err != nil {
|
|
|
|
log.Warnf("Cannot close transaction store file: %v", err)
|
|
|
|
}
|
2014-05-30 22:17:51 +02:00
|
|
|
return a, nil
|
2014-04-03 17:00:46 +02:00
|
|
|
}
|
|
|
|
|
2014-06-03 19:10:42 +02:00
|
|
|
// writeUnsyncedWallet sets the wallet unsynced (to handle the case
|
|
|
|
// where the transaction store was unreadable) and atomically writes
|
|
|
|
// the new wallet file back to disk. The current wallet file on disk
|
|
|
|
// should be already closed, or this will error on Windows for ovewriting
|
|
|
|
// an open file.
|
|
|
|
func writeUnsyncedWallet(a *Account, path string) error {
|
|
|
|
// Mark wallet as unsynced and write back to disk. Later calls
|
|
|
|
// to SyncHeight will use the wallet creation height, or possibly
|
|
|
|
// an earlier height for imported keys.
|
|
|
|
netdir, _ := filepath.Split(path)
|
|
|
|
a.SetSyncedWith(nil)
|
|
|
|
tmpwallet, err := ioutil.TempFile(netdir, "wallet.bin")
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("cannot create temporary wallet: %v", err)
|
|
|
|
}
|
|
|
|
if _, err := a.Wallet.WriteTo(tmpwallet); err != nil {
|
|
|
|
return fmt.Errorf("cannot write back unsynced wallet: %v", err)
|
|
|
|
}
|
|
|
|
tmpwalletpath := tmpwallet.Name()
|
|
|
|
if err := tmpwallet.Close(); err != nil {
|
|
|
|
return fmt.Errorf("cannot close temporary wallet file: %v", err)
|
|
|
|
}
|
|
|
|
if err := Rename(tmpwalletpath, path); err != nil {
|
|
|
|
return fmt.Errorf("cannot move temporary wallet file: %v", err)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2014-04-03 17:00:46 +02:00
|
|
|
// openAccounts attempts to open all saved accounts.
|
|
|
|
func openAccounts() *accountData {
|
|
|
|
ad := newAccountData()
|
|
|
|
|
|
|
|
// If the network (account) directory is missing, but the temporary
|
|
|
|
// directory exists, move it. This is unlikely to happen, but possible,
|
|
|
|
// if writing out every account file at once to a tmp directory (as is
|
|
|
|
// done for changing a wallet passphrase) and btcwallet closes after
|
|
|
|
// removing the network directory but before renaming the temporary
|
|
|
|
// directory.
|
2014-05-23 04:16:50 +02:00
|
|
|
netDir := networkDir(activeNet.Params)
|
|
|
|
tmpNetDir := tmpNetworkDir(activeNet.Params)
|
2014-04-03 17:00:46 +02:00
|
|
|
if !fileExists(netDir) && fileExists(tmpNetDir) {
|
|
|
|
if err := Rename(tmpNetDir, netDir); err != nil {
|
|
|
|
log.Errorf("Cannot move temporary network dir: %v", err)
|
|
|
|
return ad
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// The default account must exist, or btcwallet acts as if no
|
|
|
|
// wallets/accounts have been created yet.
|
|
|
|
a, err := openSavedAccount("", cfg)
|
|
|
|
if err != nil {
|
2014-06-03 19:10:42 +02:00
|
|
|
log.Errorf("Cannot open default account: %v", err)
|
|
|
|
return ad
|
2014-04-03 17:00:46 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
ad.addAccount(a)
|
|
|
|
|
|
|
|
// Read all filenames in the account directory, and look for any
|
|
|
|
// filenames matching '*-wallet.bin'. These are wallets for
|
|
|
|
// additional saved accounts.
|
|
|
|
accountDir, err := os.Open(netDir)
|
|
|
|
if err != nil {
|
|
|
|
// Can't continue.
|
|
|
|
log.Errorf("Unable to open account directory: %v", err)
|
|
|
|
return ad
|
|
|
|
}
|
2014-05-28 06:54:50 +02:00
|
|
|
defer func() {
|
|
|
|
if err := accountDir.Close(); err != nil {
|
|
|
|
log.Warnf("Cannot close account directory")
|
|
|
|
}
|
|
|
|
}()
|
2014-04-03 17:00:46 +02:00
|
|
|
fileNames, err := accountDir.Readdirnames(0)
|
|
|
|
if err != nil {
|
|
|
|
// fileNames might be partially set, so log an error and
|
|
|
|
// at least try to open some accounts.
|
|
|
|
log.Errorf("Unable to read all account files: %v", err)
|
|
|
|
}
|
|
|
|
var accountNames []string
|
|
|
|
for _, file := range fileNames {
|
|
|
|
if strings.HasSuffix(file, "-wallet.bin") {
|
|
|
|
name := strings.TrimSuffix(file, "-wallet.bin")
|
|
|
|
accountNames = append(accountNames, name)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Open all additional accounts.
|
|
|
|
for _, acctName := range accountNames {
|
|
|
|
// Log txstore/utxostore errors as these will be recovered
|
|
|
|
// from with a rescan, but wallet errors must be returned
|
|
|
|
// to the caller.
|
|
|
|
a, err := openSavedAccount(acctName, cfg)
|
|
|
|
if err != nil {
|
|
|
|
switch err.(type) {
|
|
|
|
case *walletOpenError:
|
|
|
|
log.Errorf("Error opening account's wallet: %v", err)
|
|
|
|
|
|
|
|
default:
|
|
|
|
log.Warnf("Non-critical error opening an account file: %v", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ad.addAccount(a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return ad
|
|
|
|
}
|
|
|
|
|
2014-03-25 05:59:24 +01:00
|
|
|
// accountHandler maintains accounts and structures for quick lookups for
|
2014-04-03 17:00:46 +02:00
|
|
|
// account information. Access to these structures must be requested via
|
|
|
|
// cmdChan. cmdChan is a single channel for multiple command types since there
|
|
|
|
// is ordering inherent in the commands and ordering between multipl goroutine
|
|
|
|
// reads via select{} is very much undefined. This function never returns and
|
|
|
|
// should be called as a new goroutine.
|
2014-03-25 05:59:24 +01:00
|
|
|
func (am *AccountManager) accountHandler() {
|
2014-04-03 17:00:46 +02:00
|
|
|
ad := openAccounts()
|
2014-01-30 16:14:02 +01:00
|
|
|
|
2014-06-24 23:00:27 +02:00
|
|
|
out:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case c := <-am.cmdChan:
|
|
|
|
switch cmd := c.(type) {
|
|
|
|
case *openAccountsCmd:
|
|
|
|
// Write all old accounts before proceeding.
|
|
|
|
for _, a := range ad.nameToAccount {
|
|
|
|
if err := am.ds.FlushAccount(a); err != nil {
|
|
|
|
log.Errorf("Cannot write previously "+
|
|
|
|
"scheduled account file: %v", err)
|
|
|
|
}
|
2014-05-28 06:54:50 +02:00
|
|
|
}
|
2014-02-28 19:03:23 +01:00
|
|
|
|
2014-06-24 23:00:27 +02:00
|
|
|
ad = openAccounts()
|
|
|
|
case *accessAccountRequest:
|
|
|
|
a, ok := ad.nameToAccount[cmd.name]
|
|
|
|
if !ok {
|
|
|
|
a = nil
|
|
|
|
}
|
|
|
|
cmd.resp <- a
|
2014-02-28 19:03:23 +01:00
|
|
|
|
2014-06-24 23:00:27 +02:00
|
|
|
case *accessAccountByAddressRequest:
|
|
|
|
a, ok := ad.addressToAccount[cmd.address]
|
|
|
|
if !ok {
|
|
|
|
a = nil
|
|
|
|
}
|
|
|
|
cmd.resp <- a
|
2014-01-30 16:14:02 +01:00
|
|
|
|
2014-06-24 23:00:27 +02:00
|
|
|
case *accessAllRequest:
|
|
|
|
s := make([]*Account, 0, len(ad.nameToAccount))
|
|
|
|
for _, a := range ad.nameToAccount {
|
|
|
|
s = append(s, a)
|
|
|
|
}
|
|
|
|
cmd.resp <- s
|
|
|
|
|
|
|
|
case *addAccountCmd:
|
|
|
|
ad.addAccount(cmd.a)
|
|
|
|
case *removeAccountCmd:
|
|
|
|
ad.removeAccount(cmd.a)
|
2014-04-03 17:00:46 +02:00
|
|
|
|
2014-06-24 23:00:27 +02:00
|
|
|
case *markAddressForAccountCmd:
|
|
|
|
// TODO(oga) make sure we own account
|
|
|
|
ad.addressToAccount[cmd.address] = cmd.account
|
|
|
|
}
|
2014-04-03 17:00:46 +02:00
|
|
|
|
2014-06-24 23:00:27 +02:00
|
|
|
case <-am.quit:
|
|
|
|
break out
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
}
|
2014-06-24 23:00:27 +02:00
|
|
|
am.wg.Done()
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
// keystoreLocker manages the lockedness state of all account keystores.
|
|
|
|
func (am *AccountManager) keystoreLocker() {
|
|
|
|
unlocked := false
|
|
|
|
var timeout <-chan time.Time
|
|
|
|
out:
|
|
|
|
for {
|
|
|
|
select {
|
|
|
|
case req := <-am.unlockRequests:
|
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
if err := a.Unlock(req.passphrase); err != nil {
|
|
|
|
req.err <- err
|
|
|
|
continue out
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unlocked = true
|
|
|
|
if req.timeout == 0 {
|
|
|
|
timeout = nil
|
|
|
|
} else {
|
|
|
|
timeout = time.After(req.timeout)
|
|
|
|
}
|
|
|
|
req.err <- nil
|
|
|
|
continue
|
|
|
|
|
|
|
|
case am.unlockedState <- unlocked:
|
|
|
|
continue
|
|
|
|
|
|
|
|
case <-am.quit:
|
|
|
|
break out
|
|
|
|
|
|
|
|
case <-am.lockRequests:
|
|
|
|
case <-timeout:
|
|
|
|
}
|
|
|
|
|
|
|
|
// Select statement fell through by an explicit lock or the
|
|
|
|
// timer expiring. Lock the keystores here.
|
|
|
|
timeout = nil
|
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
if err := a.Lock(); err != nil {
|
|
|
|
log.Errorf("Could not lock wallet for account '%s': %v",
|
|
|
|
a.name, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unlocked = false
|
|
|
|
}
|
|
|
|
am.wg.Done()
|
|
|
|
}
|
|
|
|
|
2014-03-25 05:59:24 +01:00
|
|
|
// rescanListener listens for messages from the rescan manager and marks
|
|
|
|
// accounts and addresses as synced.
|
|
|
|
func (am *AccountManager) rescanListener() {
|
|
|
|
for msg := range am.rescanMsgs {
|
|
|
|
AcctMgr.Grab()
|
|
|
|
switch e := msg.(type) {
|
|
|
|
case *RescanStartedMsg:
|
|
|
|
// Log the newly-started rescan.
|
|
|
|
n := 0
|
|
|
|
for _, addrs := range e.Addresses {
|
|
|
|
n += len(addrs)
|
|
|
|
}
|
|
|
|
noun := pickNoun(n, "address", "addresses")
|
|
|
|
log.Infof("Started rescan at height %d for %d %s", e.StartHeight, n, noun)
|
|
|
|
|
|
|
|
case *RescanProgressMsg:
|
2014-03-28 17:28:59 +01:00
|
|
|
for acct, addrs := range e.Addresses {
|
|
|
|
for i := range addrs {
|
|
|
|
err := acct.SetSyncStatus(addrs[i], wallet.PartialSync(e.Height))
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error marking address partially synced: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
am.ds.ScheduleWalletWrite(acct)
|
|
|
|
err := am.ds.FlushAccount(acct)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not write rescan progress: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Infof("Rescanned through block height %d", e.Height)
|
2014-03-25 05:59:24 +01:00
|
|
|
|
|
|
|
case *RescanFinishedMsg:
|
|
|
|
if e.Error != nil {
|
2014-06-04 02:52:04 +02:00
|
|
|
log.Errorf("Rescan failed: %v", e.Error)
|
2014-03-25 05:59:24 +01:00
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
n := 0
|
|
|
|
for acct, addrs := range e.Addresses {
|
2014-03-28 17:28:59 +01:00
|
|
|
n += len(addrs)
|
2014-03-25 05:59:24 +01:00
|
|
|
for i := range addrs {
|
2014-03-27 16:32:56 +01:00
|
|
|
err := acct.SetSyncStatus(addrs[i], wallet.FullSync{})
|
2014-03-25 05:59:24 +01:00
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Error marking address synced: %v", err)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2014-03-28 17:28:59 +01:00
|
|
|
am.ds.ScheduleWalletWrite(acct)
|
|
|
|
err := am.ds.FlushAccount(acct)
|
|
|
|
if err != nil {
|
|
|
|
log.Errorf("Could not write rescan progress: %v", err)
|
|
|
|
}
|
2014-03-25 05:59:24 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
noun := pickNoun(n, "address", "addresses")
|
|
|
|
log.Infof("Finished rescan for %d %s", n, noun)
|
2014-06-12 18:39:26 +02:00
|
|
|
|
|
|
|
default:
|
|
|
|
// Unexpected rescan message type.
|
|
|
|
panic(e)
|
2014-03-25 05:59:24 +01:00
|
|
|
}
|
|
|
|
AcctMgr.Release()
|
|
|
|
}
|
2014-06-24 23:11:06 +02:00
|
|
|
am.wg.Done()
|
2014-03-25 05:59:24 +01:00
|
|
|
}
|
|
|
|
|
2014-02-04 21:48:20 +01:00
|
|
|
// Grab grabs the account manager's binary semaphore. A custom semaphore
|
|
|
|
// is used instead of a sync.Mutex so the account manager's disk syncer
|
|
|
|
// can grab the semaphore from a select statement.
|
2014-01-30 16:14:02 +01:00
|
|
|
func (am *AccountManager) Grab() {
|
|
|
|
<-am.bsem
|
|
|
|
}
|
|
|
|
|
2014-02-04 21:48:20 +01:00
|
|
|
// Release releases exclusive ownership of the AccountManager.
|
2014-01-30 16:14:02 +01:00
|
|
|
func (am *AccountManager) Release() {
|
|
|
|
am.bsem <- struct{}{}
|
|
|
|
}
|
|
|
|
|
2014-04-03 17:00:46 +02:00
|
|
|
// OpenAccounts triggers the manager to reopen all known accounts.
|
2014-02-28 19:03:23 +01:00
|
|
|
func (am *AccountManager) OpenAccounts() {
|
2014-04-03 17:00:46 +02:00
|
|
|
am.cmdChan <- &openAccountsCmd{}
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// Account returns the account specified by name, or ErrNotFound
|
|
|
|
// as an error if the account is not found.
|
|
|
|
func (am *AccountManager) Account(name string) (*Account, error) {
|
2014-04-03 17:00:46 +02:00
|
|
|
respChan := make(chan *Account)
|
|
|
|
am.cmdChan <- &accessAccountRequest{
|
2014-01-30 16:14:02 +01:00
|
|
|
name: name,
|
2014-04-03 17:00:46 +02:00
|
|
|
resp: respChan,
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
2014-04-03 17:00:46 +02:00
|
|
|
resp := <-respChan
|
|
|
|
if resp == nil {
|
2014-01-30 16:14:02 +01:00
|
|
|
return nil, ErrNotFound
|
|
|
|
}
|
2014-04-03 17:00:46 +02:00
|
|
|
return resp, nil
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
2014-04-03 17:00:46 +02:00
|
|
|
// AccountByAddress returns the account specified by address, or
|
|
|
|
// ErrNotFound as an error if the account is not found.
|
2014-06-24 23:00:27 +02:00
|
|
|
func (am *AccountManager) AccountByAddress(addr btcutil.Address) (*Account, error) {
|
2014-04-03 17:00:46 +02:00
|
|
|
respChan := make(chan *Account)
|
2014-06-24 23:00:27 +02:00
|
|
|
req := accessAccountByAddressRequest{
|
2014-04-03 17:00:46 +02:00
|
|
|
address: addr.EncodeAddress(),
|
|
|
|
resp: respChan,
|
|
|
|
}
|
2014-06-24 23:00:27 +02:00
|
|
|
select {
|
|
|
|
case am.cmdChan <- &req:
|
|
|
|
resp := <-respChan
|
|
|
|
if resp == nil {
|
|
|
|
return nil, ErrNotFound
|
|
|
|
}
|
|
|
|
return resp, nil
|
|
|
|
case <-am.quit:
|
|
|
|
return nil, ErrNoAccounts
|
2014-04-03 17:00:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MarkAddressForAccount labels the given account as containing the provided
|
|
|
|
// address.
|
|
|
|
func (am *AccountManager) MarkAddressForAccount(address btcutil.Address,
|
|
|
|
account *Account) {
|
|
|
|
// TODO(oga) really this entire dance should be carried out implicitly
|
|
|
|
// instead of requiring explicit messaging from the account to the
|
|
|
|
// manager.
|
2014-06-24 23:00:27 +02:00
|
|
|
req := markAddressForAccountCmd{
|
2014-04-03 17:00:46 +02:00
|
|
|
address: address.EncodeAddress(),
|
|
|
|
account: account,
|
|
|
|
}
|
2014-06-24 23:00:27 +02:00
|
|
|
select {
|
|
|
|
case am.cmdChan <- &req:
|
|
|
|
case <-am.quit:
|
|
|
|
}
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
2014-04-18 00:00:52 +02:00
|
|
|
// Address looks up an address if it is known to wallet at all.
|
2014-06-24 23:00:27 +02:00
|
|
|
func (am *AccountManager) Address(addr btcutil.Address) (wallet.WalletAddress, error) {
|
2014-04-18 00:00:52 +02:00
|
|
|
a, err := am.AccountByAddress(addr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return a.Address(addr)
|
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// AllAccounts returns a slice of all managed accounts.
|
|
|
|
func (am *AccountManager) AllAccounts() []*Account {
|
2014-04-03 17:00:46 +02:00
|
|
|
respChan := make(chan []*Account)
|
2014-06-24 23:00:27 +02:00
|
|
|
req := accessAllRequest{
|
2014-04-03 17:00:46 +02:00
|
|
|
resp: respChan,
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
2014-06-24 23:00:27 +02:00
|
|
|
select {
|
|
|
|
case am.cmdChan <- &req:
|
|
|
|
return <-respChan
|
|
|
|
case <-am.quit:
|
|
|
|
return nil
|
|
|
|
}
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// AddAccount adds an account to the collection managed by an AccountManager.
|
|
|
|
func (am *AccountManager) AddAccount(a *Account) {
|
2014-06-24 23:00:27 +02:00
|
|
|
req := addAccountCmd{
|
2014-04-03 17:00:46 +02:00
|
|
|
a: a,
|
|
|
|
}
|
2014-06-24 23:00:27 +02:00
|
|
|
select {
|
|
|
|
case am.cmdChan <- &req:
|
|
|
|
case <-am.quit:
|
|
|
|
}
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveAccount removes an account to the collection managed by an
|
|
|
|
// AccountManager.
|
|
|
|
func (am *AccountManager) RemoveAccount(a *Account) {
|
2014-06-24 23:00:27 +02:00
|
|
|
req := removeAccountCmd{
|
2014-04-03 17:00:46 +02:00
|
|
|
a: a,
|
|
|
|
}
|
2014-06-24 23:00:27 +02:00
|
|
|
select {
|
|
|
|
case am.cmdChan <- &req:
|
|
|
|
case <-am.quit:
|
|
|
|
}
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// RegisterNewAccount adds a new memory account to the account manager,
|
|
|
|
// and immediately writes the account to disk.
|
|
|
|
func (am *AccountManager) RegisterNewAccount(a *Account) error {
|
|
|
|
am.AddAccount(a)
|
|
|
|
|
|
|
|
// Ensure that the new account is written out to disk.
|
|
|
|
am.ds.ScheduleWalletWrite(a)
|
|
|
|
am.ds.ScheduleTxStoreWrite(a)
|
|
|
|
if err := am.ds.FlushAccount(a); err != nil {
|
|
|
|
am.RemoveAccount(a)
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rollback rolls back each managed Account to the state before the block
|
|
|
|
// specified by height and hash was connected to the main chain.
|
2014-05-28 06:54:50 +02:00
|
|
|
func (am *AccountManager) Rollback(height int32, hash *btcwire.ShaHash) error {
|
2014-01-30 16:14:02 +01:00
|
|
|
for _, a := range am.AllAccounts() {
|
2014-05-28 06:54:50 +02:00
|
|
|
if err := a.TxStore.Rollback(height); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-02-24 20:35:30 +01:00
|
|
|
am.ds.ScheduleTxStoreWrite(a)
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
2014-05-28 06:54:50 +02:00
|
|
|
return nil
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
2014-06-12 19:54:58 +02:00
|
|
|
// BlockNotify notifies all wallet clients of any changes from the new block,
|
2014-01-30 16:14:02 +01:00
|
|
|
// including changed balances. Each account is then set to be synced
|
|
|
|
// with the latest block.
|
|
|
|
func (am *AccountManager) BlockNotify(bs *wallet.BlockStamp) {
|
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
// TODO: need a flag or check that the utxo store was actually
|
|
|
|
// modified, or this will notify even if there are no balance
|
|
|
|
// changes, or sending these notifications as the utxos are added.
|
|
|
|
confirmed := a.CalculateBalance(1)
|
|
|
|
unconfirmed := a.CalculateBalance(0) - confirmed
|
2014-06-12 18:39:26 +02:00
|
|
|
server.NotifyWalletBalance(a.name, confirmed)
|
|
|
|
server.NotifyWalletBalanceUnconfirmed(a.name, unconfirmed)
|
2014-01-30 16:14:02 +01:00
|
|
|
|
|
|
|
// If this is the default account, update the block all accounts
|
|
|
|
// are synced with, and schedule a wallet write.
|
|
|
|
if a.Name() == "" {
|
|
|
|
a.Wallet.SetSyncedWith(bs)
|
|
|
|
am.ds.ScheduleWalletWrite(a)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// RecordMinedTx searches through each account's TxStore, searching for a
|
|
|
|
// sent transaction with the same txid as from a txmined notification. If
|
|
|
|
// the transaction IDs match, the record in the TxStore is updated with
|
|
|
|
// the full information about the newly-mined tx, and the TxStore is
|
|
|
|
// scheduled to be written to disk..
|
2014-05-08 21:48:42 +02:00
|
|
|
func (am *AccountManager) RecordSpendingTx(tx *btcutil.Tx, block *txstore.Block) error {
|
2014-01-30 16:14:02 +01:00
|
|
|
for _, a := range am.AllAccounts() {
|
2014-03-04 00:15:05 +01:00
|
|
|
// TODO(jrick): This needs to iterate through each txout's
|
|
|
|
// addresses and find whether this account's keystore contains
|
2014-03-04 01:31:57 +01:00
|
|
|
// any of the addresses this tx sends to.
|
2014-05-08 21:48:42 +02:00
|
|
|
txr, err := a.TxStore.InsertTx(tx, block)
|
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 err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
// When received as a notification, we don't know what the inputs are.
|
|
|
|
if _, err := txr.AddDebits(nil); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-02-24 20:35:30 +01:00
|
|
|
am.ds.ScheduleTxStoreWrite(a)
|
2014-01-30 16:14:02 +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
|
|
|
return nil
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// CalculateBalance returns the balance, calculated using minconf block
|
|
|
|
// confirmations, of an account.
|
|
|
|
func (am *AccountManager) CalculateBalance(account string, minconf int) (float64, error) {
|
|
|
|
a, err := am.Account(account)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return a.CalculateBalance(minconf), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// CreateEncryptedWallet creates a new default account with a wallet file
|
|
|
|
// encrypted with passphrase.
|
|
|
|
func (am *AccountManager) CreateEncryptedWallet(passphrase []byte) error {
|
|
|
|
if len(am.AllAccounts()) != 0 {
|
|
|
|
return ErrWalletExists
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get current block's height and hash.
|
|
|
|
bs, err := GetCurBlock()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create new wallet in memory.
|
|
|
|
wlt, err := wallet.NewWallet("", "Default acccount", passphrase,
|
2014-05-23 04:16:50 +02:00
|
|
|
activeNet.Params, &bs, cfg.KeypoolSize)
|
2014-01-30 16:14:02 +01:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create new account and begin managing with the global account
|
|
|
|
// manager. Registering will fail if the new account can not be
|
|
|
|
// written immediately to disk.
|
|
|
|
a := &Account{
|
2014-06-23 23:59:57 +02:00
|
|
|
Wallet: wlt,
|
|
|
|
TxStore: txstore.New(),
|
|
|
|
lockedOutpoints: map[btcwire.OutPoint]struct{}{},
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
if err := am.RegisterNewAccount(a); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Begin tracking account against a connected btcd.
|
|
|
|
a.Track()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ChangePassphrase unlocks all account wallets with the old
|
|
|
|
// passphrase, and re-encrypts each using the new passphrase.
|
|
|
|
func (am *AccountManager) ChangePassphrase(old, new []byte) error {
|
2014-07-01 17:02:13 +02:00
|
|
|
// Keystores must be unlocked to change their passphrase.
|
|
|
|
err := am.UnlockWallets(old, 0)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
accts := am.AllAccounts()
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// Change passphrase for each unlocked wallet.
|
|
|
|
for _, a := range accts {
|
2014-07-01 17:02:13 +02:00
|
|
|
err = a.Wallet.ChangePassphrase(new)
|
|
|
|
if err != nil {
|
2014-01-30 16:14:02 +01:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
am.LockWallets()
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// Immediately write out to disk.
|
|
|
|
return am.ds.WriteBatch(accts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LockWallets locks all managed account wallets.
|
2014-07-01 17:02:13 +02:00
|
|
|
func (am *AccountManager) LockWallets() {
|
|
|
|
am.lockRequests <- struct{}{}
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
2014-07-01 17:02:13 +02:00
|
|
|
// UnlockWallets unlocks all managed account's wallets, locking them again after
|
|
|
|
// the timeout expires, or resetting a previous timeout if one is still running.
|
|
|
|
func (am *AccountManager) UnlockWallets(passphrase []byte, timeout time.Duration) error {
|
|
|
|
req := unlockRequest{
|
|
|
|
passphrase: passphrase,
|
|
|
|
timeout: timeout,
|
|
|
|
err: make(chan error, 1),
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
2014-07-01 17:02:13 +02:00
|
|
|
am.unlockRequests <- req
|
|
|
|
return <-req.err
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// DumpKeys returns all WIF-encoded private keys associated with all
|
|
|
|
// accounts. All wallets must be unlocked for this operation to succeed.
|
|
|
|
func (am *AccountManager) DumpKeys() ([]string, error) {
|
2014-06-12 20:58:23 +02:00
|
|
|
keys := []string{}
|
2014-01-30 16:14:02 +01:00
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
switch walletKeys, err := a.DumpPrivKeys(); err {
|
|
|
|
case wallet.ErrWalletLocked:
|
|
|
|
return nil, err
|
|
|
|
|
|
|
|
case nil:
|
|
|
|
keys = append(keys, walletKeys...)
|
|
|
|
|
|
|
|
default: // any other non-nil error
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
return keys, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DumpWIFPrivateKey searches through all accounts for the bitcoin
|
|
|
|
// payment address addr and returns the WIF-encdoded private key.
|
|
|
|
func (am *AccountManager) DumpWIFPrivateKey(addr btcutil.Address) (string, error) {
|
2014-04-09 17:09:24 +02:00
|
|
|
a, err := am.AccountByAddress(addr)
|
|
|
|
if err != nil {
|
|
|
|
return "", err
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
2014-04-09 17:09:24 +02:00
|
|
|
return a.DumpWIFPrivateKey(addr)
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// ListAccounts returns a map of account names to their current account
|
|
|
|
// balances. The balances are calculated using minconf confirmations.
|
|
|
|
func (am *AccountManager) ListAccounts(minconf int) map[string]float64 {
|
|
|
|
// Create and fill a map of account names and their balances.
|
|
|
|
pairs := make(map[string]float64)
|
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
pairs[a.name] = a.CalculateBalance(minconf)
|
|
|
|
}
|
|
|
|
return pairs
|
|
|
|
}
|
|
|
|
|
2014-04-09 05:04:10 +02:00
|
|
|
// ListSinceBlock returns a slice of objects representing all transactions in
|
|
|
|
// the wallets since the given block.
|
2014-01-30 16:14:02 +01:00
|
|
|
// To be used for the listsinceblock command.
|
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 (am *AccountManager) ListSinceBlock(since, curBlockHeight int32,
|
|
|
|
minconf int) ([]btcjson.ListTransactionsResult, error) {
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// Create and fill a map of account names and their balances.
|
2014-06-12 20:58:23 +02:00
|
|
|
txList := []btcjson.ListTransactionsResult{}
|
2014-01-30 16:14:02 +01:00
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
txTmp, err := a.ListSinceBlock(since, curBlockHeight, minconf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-04-09 05:04:10 +02:00
|
|
|
txList = append(txList, txTmp...)
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
2014-04-09 05:04:10 +02:00
|
|
|
return txList, nil
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
2014-02-03 19:29:25 +01:00
|
|
|
// accountTx represents an account/transaction pair to be used by
|
2014-02-24 20:35:30 +01:00
|
|
|
// GetTransaction.
|
2014-02-03 19:29:25 +01:00
|
|
|
type accountTx struct {
|
|
|
|
Account string
|
2014-05-08 21:48:42 +02:00
|
|
|
Tx *txstore.TxRecord
|
2014-02-03 19:29:25 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// GetTransaction returns an array of accountTx to fully represent the effect of
|
|
|
|
// a transaction on locally known wallets. If we know nothing about a
|
|
|
|
// transaction an empty array will be returned.
|
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 (am *AccountManager) GetTransaction(txSha *btcwire.ShaHash) []accountTx {
|
2014-02-03 19:29:25 +01:00
|
|
|
accumulatedTxen := []accountTx{}
|
|
|
|
|
|
|
|
for _, a := range am.AllAccounts() {
|
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 _, record := range a.TxStore.Records() {
|
|
|
|
if *record.Tx().Sha() != *txSha {
|
2014-02-03 19:29:25 +01:00
|
|
|
continue
|
|
|
|
}
|
2014-02-24 20:35:30 +01:00
|
|
|
|
|
|
|
atx := accountTx{
|
|
|
|
Account: a.name,
|
|
|
|
Tx: record,
|
|
|
|
}
|
|
|
|
accumulatedTxen = append(accumulatedTxen, atx)
|
2014-02-03 19:29:25 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return accumulatedTxen
|
|
|
|
}
|
|
|
|
|
2014-04-09 05:04:10 +02:00
|
|
|
// ListUnspent returns a slice of objects representing the unspent wallet
|
|
|
|
// transactions fitting the given criteria. The confirmations will be more than
|
|
|
|
// minconf, less than maxconf and if addresses is populated only the addresses
|
|
|
|
// contained within it will be considered. If we know nothing about a
|
2014-02-13 19:43:52 +01:00
|
|
|
// transaction an empty array will be returned.
|
|
|
|
func (am *AccountManager) ListUnspent(minconf, maxconf int,
|
2014-05-09 22:55:46 +02:00
|
|
|
addresses map[string]bool) ([]*btcjson.ListUnspentResult, 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
|
|
|
|
2014-02-13 19:43:52 +01:00
|
|
|
bs, err := GetCurBlock()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-03-31 17:11:37 +02:00
|
|
|
filter := len(addresses) != 0
|
|
|
|
|
2014-06-12 20:58:23 +02:00
|
|
|
results := []*btcjson.ListUnspentResult{}
|
2014-02-13 19:43:52 +01:00
|
|
|
for _, a := range am.AllAccounts() {
|
2014-05-23 17:31:05 +02:00
|
|
|
unspent, err := a.TxStore.SortedUnspentOutputs()
|
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 err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2014-05-23 17:31:05 +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
|
|
|
for _, credit := range unspent {
|
2014-05-07 05:48:12 +02:00
|
|
|
confs := credit.Confirmations(bs.Height)
|
|
|
|
if int(confs) < minconf || int(confs) > maxconf {
|
2014-03-31 17:11:37 +02:00
|
|
|
continue
|
|
|
|
}
|
2014-06-26 21:58:46 +02:00
|
|
|
if credit.IsCoinbase() {
|
|
|
|
if !credit.Confirmed(btcchain.CoinbaseMaturity, bs.Height) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
2014-06-27 22:05:00 +02:00
|
|
|
if a.LockedOutpoint(*credit.OutPoint()) {
|
|
|
|
continue
|
|
|
|
}
|
2014-03-31 17:11:37 +02:00
|
|
|
|
2014-05-23 04:16:50 +02:00
|
|
|
_, addrs, _, _ := credit.Addresses(activeNet.Params)
|
2014-03-31 17:11:37 +02:00
|
|
|
if filter {
|
|
|
|
for _, addr := range addrs {
|
|
|
|
_, ok := addresses[addr.EncodeAddress()]
|
|
|
|
if ok {
|
|
|
|
goto include
|
|
|
|
}
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
include:
|
2014-05-09 22:55:46 +02:00
|
|
|
result := &btcjson.ListUnspentResult{
|
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
|
|
|
TxId: credit.Tx().Sha().String(),
|
2014-05-09 22:55:46 +02:00
|
|
|
Vout: credit.OutputIndex,
|
2014-04-09 05:35:39 +02:00
|
|
|
Account: a.Name(),
|
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
|
|
|
ScriptPubKey: hex.EncodeToString(credit.TxOut().PkScript),
|
|
|
|
Amount: credit.Amount().ToUnit(btcutil.AmountBTC),
|
2014-05-09 22:55:46 +02:00
|
|
|
Confirmations: int64(confs),
|
2014-03-31 17:11:37 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// BUG: this should be a JSON array so that all
|
|
|
|
// addresses can be included, or removed (and the
|
|
|
|
// caller extracts addresses from the pkScript).
|
|
|
|
if len(addrs) > 0 {
|
2014-04-09 05:35:39 +02:00
|
|
|
result.Address = addrs[0].EncodeAddress()
|
2014-03-31 17:11:37 +02:00
|
|
|
}
|
|
|
|
|
2014-04-09 05:35:39 +02:00
|
|
|
results = append(results, result)
|
2014-02-13 19:43:52 +01:00
|
|
|
}
|
|
|
|
}
|
2014-03-31 17:11:37 +02:00
|
|
|
|
2014-04-09 05:35:39 +02:00
|
|
|
return results, nil
|
2014-02-13 19:43:52 +01:00
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// RescanActiveAddresses begins a rescan for all active addresses for
|
2014-06-16 21:19:32 +02:00
|
|
|
// each account. If markBestBlock is non-nil, the block described by
|
|
|
|
// the blockstamp is used to mark the synced-with height of the wallet
|
|
|
|
// just before the rescan is submitted and started. This allows the
|
|
|
|
// caller to mark the progress that the rescan is expected to complete
|
|
|
|
// through, if the account otherwise does not contain any recently
|
|
|
|
// seen blocks.
|
|
|
|
func (am *AccountManager) RescanActiveAddresses(markBestBlock *wallet.BlockStamp) error {
|
2014-03-25 05:59:24 +01:00
|
|
|
var job *RescanJob
|
2014-06-16 21:19:32 +02:00
|
|
|
var defaultAcct *Account
|
2014-03-25 05:59:24 +01:00
|
|
|
for _, a := range am.AllAccounts() {
|
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
|
|
|
acctJob, err := a.RescanActiveJob()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2014-03-25 05:59:24 +01:00
|
|
|
if job == nil {
|
|
|
|
job = acctJob
|
|
|
|
} else {
|
|
|
|
job.Merge(acctJob)
|
|
|
|
}
|
2014-06-16 21:19:32 +02:00
|
|
|
|
|
|
|
if a.name == "" {
|
|
|
|
defaultAcct = a
|
|
|
|
}
|
2014-03-25 05:59:24 +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 job != nil {
|
2014-06-16 21:19:32 +02:00
|
|
|
if markBestBlock != nil {
|
|
|
|
defaultAcct.SetSyncedWith(markBestBlock)
|
|
|
|
}
|
|
|
|
|
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
|
|
|
// Submit merged job and block until rescan completes.
|
|
|
|
jobFinished := am.rm.SubmitJob(job)
|
|
|
|
<-jobFinished
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
2014-03-25 05:59:24 +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
|
|
|
return nil
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
|
2014-02-24 20:35:30 +01:00
|
|
|
func (am *AccountManager) ResendUnminedTxs() {
|
2014-03-25 05:59:24 +01:00
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
a.ResendUnminedTxs()
|
2014-02-24 20:35:30 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
// Track begins tracking all addresses in all accounts for updates from
|
|
|
|
// btcd.
|
|
|
|
func (am *AccountManager) Track() {
|
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
a.Track()
|
|
|
|
}
|
|
|
|
}
|