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 (
|
|
|
|
"container/list"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"github.com/conformal/btcutil"
|
|
|
|
"github.com/conformal/btcwallet/tx"
|
|
|
|
"github.com/conformal/btcwallet/wallet"
|
|
|
|
"github.com/conformal/btcwire"
|
2014-03-18 21:31:06 +01: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")
|
|
|
|
)
|
|
|
|
|
|
|
|
// AcctMgr is the global account manager for all opened accounts.
|
|
|
|
var AcctMgr = NewAccountManager()
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
bsem chan struct{}
|
|
|
|
|
2014-02-28 19:03:23 +01:00
|
|
|
openAccounts chan struct{}
|
2014-01-30 16:14:02 +01:00
|
|
|
accessAccount chan *accessAccountRequest
|
|
|
|
accessAll chan *accessAllRequest
|
|
|
|
add chan *Account
|
|
|
|
remove chan *Account
|
|
|
|
|
|
|
|
ds *DiskSyncer // might move to inside Start
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewAccountManager returns a new AccountManager.
|
|
|
|
func NewAccountManager() *AccountManager {
|
|
|
|
am := &AccountManager{
|
|
|
|
bsem: make(chan struct{}, 1),
|
2014-02-28 19:03:23 +01:00
|
|
|
openAccounts: make(chan struct{}, 1),
|
2014-01-30 16:14:02 +01:00
|
|
|
accessAccount: make(chan *accessAccountRequest),
|
|
|
|
accessAll: make(chan *accessAllRequest),
|
|
|
|
add: make(chan *Account),
|
|
|
|
remove: make(chan *Account),
|
|
|
|
}
|
|
|
|
am.ds = NewDiskSyncer(am)
|
|
|
|
return am
|
|
|
|
}
|
|
|
|
|
|
|
|
// Start maintains accounts and structures for quick lookups for account
|
|
|
|
// information. Access to these structures must be done through with the
|
|
|
|
// channels in the AccountManger struct fields. This function never returns
|
|
|
|
// and should be called as a new goroutine.
|
|
|
|
func (am *AccountManager) Start() {
|
|
|
|
// Ready the semaphore - can't grab unless the manager has started.
|
|
|
|
am.bsem <- struct{}{}
|
|
|
|
|
|
|
|
// Start the account manager's disk syncer.
|
|
|
|
go am.ds.Start()
|
|
|
|
|
|
|
|
// List and map of all accounts.
|
|
|
|
l := list.New()
|
|
|
|
m := make(map[string]*Account)
|
|
|
|
|
|
|
|
for {
|
|
|
|
select {
|
2014-02-28 19:03:23 +01:00
|
|
|
case <-am.openAccounts:
|
|
|
|
// Write all old accounts before proceeding.
|
|
|
|
for e := l.Front(); e != nil; e = e.Next() {
|
|
|
|
a := e.Value.(*Account)
|
|
|
|
am.ds.FlushAccount(a)
|
|
|
|
}
|
|
|
|
|
|
|
|
m = OpenAccounts()
|
|
|
|
|
|
|
|
l.Init()
|
|
|
|
for _, a := range m {
|
|
|
|
l.PushBack(a)
|
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
case access := <-am.accessAccount:
|
|
|
|
a, ok := m[access.name]
|
|
|
|
access.resp <- &accessAccountResponse{
|
|
|
|
a: a,
|
|
|
|
ok: ok,
|
|
|
|
}
|
|
|
|
|
|
|
|
case access := <-am.accessAll:
|
|
|
|
s := make([]*Account, 0, l.Len())
|
|
|
|
for e := l.Front(); e != nil; e = e.Next() {
|
|
|
|
s = append(s, e.Value.(*Account))
|
|
|
|
}
|
|
|
|
access.resp <- s
|
|
|
|
|
|
|
|
case a := <-am.add:
|
|
|
|
if _, ok := m[a.name]; ok {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
m[a.name] = a
|
|
|
|
l.PushBack(a)
|
|
|
|
|
|
|
|
case a := <-am.remove:
|
|
|
|
if _, ok := m[a.name]; ok {
|
|
|
|
delete(m, a.name)
|
|
|
|
for e := l.Front(); e != nil; e = e.Next() {
|
|
|
|
v := e.Value.(*Account)
|
|
|
|
if v == a {
|
|
|
|
l.Remove(e)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-02-28 19:03:23 +01:00
|
|
|
func (am *AccountManager) OpenAccounts() {
|
|
|
|
am.openAccounts <- struct{}{}
|
|
|
|
}
|
|
|
|
|
2014-01-30 16:14:02 +01:00
|
|
|
type accessAccountRequest struct {
|
|
|
|
name string
|
|
|
|
resp chan *accessAccountResponse
|
|
|
|
}
|
|
|
|
|
|
|
|
type accessAccountResponse struct {
|
|
|
|
a *Account
|
|
|
|
ok bool
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
req := &accessAccountRequest{
|
|
|
|
name: name,
|
|
|
|
resp: make(chan *accessAccountResponse),
|
|
|
|
}
|
|
|
|
am.accessAccount <- req
|
|
|
|
resp := <-req.resp
|
|
|
|
if !resp.ok {
|
|
|
|
return nil, ErrNotFound
|
|
|
|
}
|
|
|
|
return resp.a, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type accessAllRequest struct {
|
|
|
|
resp chan []*Account
|
|
|
|
}
|
|
|
|
|
|
|
|
// AllAccounts returns a slice of all managed accounts.
|
|
|
|
func (am *AccountManager) AllAccounts() []*Account {
|
|
|
|
req := &accessAllRequest{
|
|
|
|
resp: make(chan []*Account),
|
|
|
|
}
|
|
|
|
am.accessAll <- req
|
|
|
|
return <-req.resp
|
|
|
|
}
|
|
|
|
|
|
|
|
// AddAccount adds an account to the collection managed by an AccountManager.
|
|
|
|
func (am *AccountManager) AddAccount(a *Account) {
|
|
|
|
am.add <- a
|
|
|
|
}
|
|
|
|
|
|
|
|
// RemoveAccount removes an account to the collection managed by an
|
|
|
|
// AccountManager.
|
|
|
|
func (am *AccountManager) RemoveAccount(a *Account) {
|
|
|
|
am.remove <- a
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
func (am *AccountManager) Rollback(height int32, hash *btcwire.ShaHash) {
|
2014-02-28 19:03:23 +01:00
|
|
|
log.Infof("Rolling back tx history since block height %v", height)
|
2014-01-30 16:14:02 +01:00
|
|
|
|
|
|
|
for _, a := range am.AllAccounts() {
|
2014-02-24 20:35:30 +01:00
|
|
|
a.TxStore.Rollback(height)
|
|
|
|
am.ds.ScheduleTxStoreWrite(a)
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// BlockNotify notifies all frontends of any changes from the new block,
|
|
|
|
// 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-02-18 04:18:30 +01:00
|
|
|
NotifyWalletBalance(allClients, a.name, confirmed)
|
|
|
|
NotifyWalletBalanceUnconfirmed(allClients, a.name,
|
2014-01-30 16:14:02 +01:00
|
|
|
unconfirmed)
|
|
|
|
|
|
|
|
// 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-02-24 20:35:30 +01:00
|
|
|
func (am *AccountManager) RecordSpendingTx(tx_ *btcutil.Tx, block *tx.BlockDetails) {
|
2014-03-18 21:31:06 +01:00
|
|
|
now := time.Now()
|
|
|
|
var created time.Time
|
|
|
|
if block != nil && now.After(block.Time) {
|
|
|
|
created = block.Time
|
|
|
|
} else {
|
|
|
|
created = now
|
|
|
|
}
|
|
|
|
|
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-03-18 21:31:06 +01:00
|
|
|
a.TxStore.InsertSignedTx(tx_, created, block)
|
2014-02-24 20:35:30 +01:00
|
|
|
am.ds.ScheduleTxStoreWrite(a)
|
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,
|
|
|
|
cfg.Net(), &bs, cfg.KeypoolSize)
|
|
|
|
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-02-26 19:56:31 +01:00
|
|
|
Wallet: wlt,
|
|
|
|
TxStore: tx.NewStore(),
|
2014-01-30 16:14:02 +01:00
|
|
|
}
|
|
|
|
if err := am.RegisterNewAccount(a); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Mark all active payment addresses as belonging to this account.
|
|
|
|
//
|
|
|
|
// TODO(jrick) move this to the account manager
|
|
|
|
for addr := range a.ActivePaymentAddresses() {
|
|
|
|
MarkAddressForAccount(addr, "")
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
|
|
|
accts := am.AllAccounts()
|
|
|
|
|
|
|
|
for _, a := range accts {
|
|
|
|
if locked := a.Wallet.IsLocked(); !locked {
|
|
|
|
if err := a.Wallet.Lock(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := a.Wallet.Unlock(old); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer a.Wallet.Lock()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Change passphrase for each unlocked wallet.
|
|
|
|
for _, a := range accts {
|
|
|
|
if err := a.Wallet.ChangePassphrase(new); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Immediately write out to disk.
|
|
|
|
return am.ds.WriteBatch(accts)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LockWallets locks all managed account wallets.
|
|
|
|
func (am *AccountManager) LockWallets() error {
|
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
if err := a.Lock(); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// UnlockWallets unlocks all managed account's wallets. If any wallet unlocks
|
|
|
|
// fail, all successfully unlocked wallets are locked again.
|
|
|
|
func (am *AccountManager) UnlockWallets(passphrase string) error {
|
|
|
|
accts := am.AllAccounts()
|
|
|
|
unlockedAccts := make([]*Account, 0, len(accts))
|
|
|
|
|
|
|
|
for _, a := range accts {
|
|
|
|
if err := a.Unlock([]byte(passphrase)); err != nil {
|
|
|
|
for _, ua := range unlockedAccts {
|
|
|
|
ua.Lock()
|
|
|
|
}
|
|
|
|
return fmt.Errorf("cannot unlock account %v: %v",
|
|
|
|
a.name, err)
|
|
|
|
}
|
|
|
|
unlockedAccts = append(unlockedAccts, a)
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
|
|
|
var keys []string
|
|
|
|
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) {
|
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
switch wif, err := a.DumpWIFPrivateKey(addr); err {
|
|
|
|
case wallet.ErrAddressNotFound:
|
|
|
|
// Move on to the next account.
|
|
|
|
continue
|
|
|
|
|
|
|
|
case nil:
|
|
|
|
return wif, nil
|
|
|
|
|
|
|
|
default: // all other non-nil errors
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return "", errors.New("address does not refer to a key")
|
|
|
|
}
|
|
|
|
|
|
|
|
// NotifyBalances notifies a wallet frontend of all confirmed and unconfirmed
|
|
|
|
// account balances.
|
|
|
|
func (am *AccountManager) NotifyBalances(frontend chan []byte) {
|
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
balance := a.CalculateBalance(1)
|
|
|
|
unconfirmed := a.CalculateBalance(0) - balance
|
|
|
|
NotifyWalletBalance(frontend, a.name, balance)
|
|
|
|
NotifyWalletBalanceUnconfirmed(frontend, a.name, unconfirmed)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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
|
|
|
|
}
|
|
|
|
|
|
|
|
// ListSinceBlock returns a slice of maps of strings to interface containing
|
|
|
|
// structures defining all transactions in the wallets since the given block.
|
|
|
|
// To be used for the listsinceblock command.
|
|
|
|
func (am *AccountManager) ListSinceBlock(since, curBlockHeight int32, minconf int) ([]map[string]interface{}, error) {
|
|
|
|
// Create and fill a map of account names and their balances.
|
|
|
|
txInfoList := []map[string]interface{}{}
|
|
|
|
for _, a := range am.AllAccounts() {
|
|
|
|
txTmp, err := a.ListSinceBlock(since, curBlockHeight, minconf)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
txInfoList = append(txInfoList, txTmp...)
|
|
|
|
}
|
|
|
|
return txInfoList, nil
|
|
|
|
}
|
|
|
|
|
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-02-24 20:35:30 +01:00
|
|
|
Tx tx.Record
|
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.
|
2014-02-24 20:35:30 +01:00
|
|
|
func (am *AccountManager) GetTransaction(txsha *btcwire.ShaHash) []accountTx {
|
2014-02-03 19:29:25 +01:00
|
|
|
accumulatedTxen := []accountTx{}
|
|
|
|
|
|
|
|
for _, a := range am.AllAccounts() {
|
2014-02-24 20:35:30 +01:00
|
|
|
for _, record := range a.TxStore.SortedRecords() {
|
|
|
|
if *record.TxSha() != *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-02-13 19:43:52 +01:00
|
|
|
// ListUnspent returns an array of objects representing the unspent
|
|
|
|
// wallet transactions fitting the given criteria. The confirmations will be
|
|
|
|
// more then minconf, less than maxconf and if addresses is populated only the
|
|
|
|
// addresses contained within it will be considered.
|
|
|
|
// a transaction on locally known wallets. If we know nothing about a
|
|
|
|
// transaction an empty array will be returned.
|
|
|
|
func (am *AccountManager) ListUnspent(minconf, maxconf int,
|
|
|
|
addresses map[string]bool) ([]map[string]interface{}, error) {
|
|
|
|
bs, err := GetCurBlock()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2014-02-24 20:35:30 +01:00
|
|
|
infos := []map[string]interface{}{}
|
2014-02-13 19:43:52 +01:00
|
|
|
for _, a := range am.AllAccounts() {
|
2014-02-24 20:35:30 +01:00
|
|
|
for _, record := range a.TxStore.UnspentOutputs() {
|
|
|
|
info := record.TxInfo(a.name, bs.Height, cfg.Net())[0]
|
|
|
|
infos = append(infos, info)
|
2014-02-13 19:43:52 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2014-02-24 20:35:30 +01:00
|
|
|
return infos, 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
|
|
|
|
// each account.
|
|
|
|
//
|
|
|
|
// TODO(jrick): batch addresses for all accounts together so multiple
|
|
|
|
// rescan commands can be avoided.
|
|
|
|
func (am *AccountManager) RescanActiveAddresses() {
|
|
|
|
for _, account := range am.AllAccounts() {
|
|
|
|
account.RescanActiveAddresses()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-02-24 20:35:30 +01:00
|
|
|
func (am *AccountManager) ResendUnminedTxs() {
|
|
|
|
for _, account := range am.AllAccounts() {
|
|
|
|
account.ResendUnminedTxs()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
}
|