454d290b68
This commit converts the waddrmgr package to use the new walletdb package semantics. Since waddrmgr no longer controls the database, it is unable to make a copy of the database and return it as the old ExportWatchingOnly function required. As a result, it has been renamed to ConvertToWatchingOnly and it now modifies the namespace provided to it. The idea is that the caller which does control the database can now make a copy of the database, get the waddrmgr namespace in the database copy and invoke the new function to modify it. This also works well with other packages that might also need to make modifications for watching-only mode. In addition, the following changes are made: - All places that worked with database paths now work with the walletdb.Namespace interface - The managerTx code is replaced to use the walletdb.Tx interface - The code which checks if the manager already exists is updated to work with the walletdb.Namespace interface - The LatestDbVersion constant is now LatestMgrVersion since it no longer controls the database
242 lines
7.7 KiB
Go
242 lines
7.7 KiB
Go
/*
|
|
* Copyright (c) 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 waddrmgr
|
|
|
|
import (
|
|
"sync"
|
|
|
|
"github.com/conformal/btcwallet/walletdb"
|
|
"github.com/conformal/btcwire"
|
|
)
|
|
|
|
const (
|
|
// maxRecentHashes is the maximum number of hashes to keep in history
|
|
// for the purposes of rollbacks.
|
|
maxRecentHashes = 20
|
|
)
|
|
|
|
// BlockStamp defines a block (by height and a unique hash) and is
|
|
// used to mark a point in the blockchain that an address manager element is
|
|
// synced to.
|
|
type BlockStamp struct {
|
|
Height int32
|
|
Hash btcwire.ShaHash
|
|
}
|
|
|
|
// syncState houses the sync state of the manager. It consists of the recently
|
|
// seen blocks as height, as well as the start and current sync block stamps.
|
|
type syncState struct {
|
|
// startBlock is the first block that can be safely used to start a
|
|
// rescan. It is either the block the manager was created with, or
|
|
// the earliest block provided with imported addresses or scripts.
|
|
startBlock BlockStamp
|
|
|
|
// syncedTo is the current block the addresses in the manager are known
|
|
// to be synced against.
|
|
syncedTo BlockStamp
|
|
|
|
// recentHeight is the most recently seen sync height.
|
|
recentHeight int32
|
|
|
|
// recentHashes is a list of the last several seen block hashes.
|
|
recentHashes []btcwire.ShaHash
|
|
}
|
|
|
|
// iter returns a BlockIterator that can be used to iterate over the recently
|
|
// seen blocks in the sync state.
|
|
func (s *syncState) iter(mtx *sync.RWMutex) *BlockIterator {
|
|
if s.recentHeight == -1 || len(s.recentHashes) == 0 {
|
|
return nil
|
|
}
|
|
return &BlockIterator{
|
|
mtx: mtx,
|
|
height: s.recentHeight,
|
|
index: len(s.recentHashes) - 1,
|
|
syncInfo: s,
|
|
}
|
|
}
|
|
|
|
// newSyncState returns a new sync state with the provided parameters.
|
|
func newSyncState(startBlock, syncedTo *BlockStamp, recentHeight int32,
|
|
recentHashes []btcwire.ShaHash) *syncState {
|
|
|
|
return &syncState{
|
|
startBlock: *startBlock,
|
|
syncedTo: *syncedTo,
|
|
recentHeight: recentHeight,
|
|
recentHashes: recentHashes,
|
|
}
|
|
}
|
|
|
|
// BlockIterator allows for the forwards and backwards iteration of recently
|
|
// seen blocks.
|
|
type BlockIterator struct {
|
|
mtx *sync.RWMutex
|
|
height int32
|
|
index int
|
|
syncInfo *syncState
|
|
}
|
|
|
|
// Next returns the next recently seen block or false if there is not one.
|
|
func (it *BlockIterator) Next() bool {
|
|
it.mtx.RLock()
|
|
defer it.mtx.RUnlock()
|
|
|
|
if it.index+1 >= len(it.syncInfo.recentHashes) {
|
|
return false
|
|
}
|
|
it.index++
|
|
return true
|
|
}
|
|
|
|
// Prev returns the previous recently seen block or false if there is not one.
|
|
func (it *BlockIterator) Prev() bool {
|
|
it.mtx.RLock()
|
|
defer it.mtx.RUnlock()
|
|
|
|
if it.index-1 < 0 {
|
|
return false
|
|
}
|
|
it.index--
|
|
return true
|
|
}
|
|
|
|
// BlockStamp returns the block stamp associated with the recently seen block
|
|
// the iterator is currently pointing to.
|
|
func (it *BlockIterator) BlockStamp() BlockStamp {
|
|
it.mtx.RLock()
|
|
defer it.mtx.RUnlock()
|
|
|
|
return BlockStamp{
|
|
Height: it.syncInfo.recentHeight -
|
|
int32(len(it.syncInfo.recentHashes)-1-it.index),
|
|
Hash: it.syncInfo.recentHashes[it.index],
|
|
}
|
|
}
|
|
|
|
// NewIterateRecentBlocks returns an iterator for recently-seen blocks.
|
|
// The iterator starts at the most recently-added block, and Prev should
|
|
// be used to access earlier blocks.
|
|
//
|
|
// NOTE: Ideally this should not really be a part of the address manager as it
|
|
// is intended for syncing purposes. It is being exposed here for now to go
|
|
// with the other syncing code. Ultimately, all syncing code should probably
|
|
// go into its own package and share the data store.
|
|
func (m *Manager) NewIterateRecentBlocks() *BlockIterator {
|
|
m.mtx.RLock()
|
|
defer m.mtx.RUnlock()
|
|
|
|
return m.syncState.iter(&m.mtx)
|
|
}
|
|
|
|
// SetSyncedTo marks the address manager to be in sync with the recently-seen
|
|
// block described by the blockstamp. When the provided blockstamp is nil,
|
|
// the oldest blockstamp of the block the manager was created at and of all
|
|
// imported addresses will be used. This effectively allows the manager to be
|
|
// marked as unsynced back to the oldest known point any of the addresses have
|
|
// appeared in the block chain.
|
|
func (m *Manager) SetSyncedTo(bs *BlockStamp) error {
|
|
m.mtx.Lock()
|
|
defer m.mtx.Unlock()
|
|
|
|
// Update the recent history.
|
|
//
|
|
// NOTE: The values in the memory sync state aren't directly modified
|
|
// here in case the forthcoming db update fails. The memory sync state
|
|
// is updated with these values as needed after the db updates.
|
|
recentHeight := m.syncState.recentHeight
|
|
recentHashes := m.syncState.recentHashes
|
|
if bs == nil {
|
|
// Use the stored start blockstamp and reset recent hashes and
|
|
// height when the provided blockstamp is nil.
|
|
bs = &m.syncState.startBlock
|
|
recentHeight = m.syncState.startBlock.Height
|
|
recentHashes = nil
|
|
|
|
} else if bs.Height < recentHeight {
|
|
// When the new block stamp height is prior to the most recently
|
|
// seen height, a rollback is being performed. Thus, when the
|
|
// previous block stamp is already saved, remove anything after
|
|
// it. Otherwise, the rollback must be too far in history, so
|
|
// clear the recent hashes and set the recent height to the
|
|
// current block stamp height.
|
|
numHashes := len(recentHashes)
|
|
idx := numHashes - 1 - int(recentHeight-bs.Height)
|
|
if idx >= 0 && idx < numHashes && recentHashes[idx] == bs.Hash {
|
|
// subslice out the removed hashes.
|
|
recentHeight = bs.Height
|
|
recentHashes = recentHashes[:idx]
|
|
} else {
|
|
recentHeight = bs.Height
|
|
recentHashes = nil
|
|
}
|
|
|
|
} else if bs.Height != recentHeight+1 {
|
|
// At this point the new block stamp height is after the most
|
|
// recently seen block stamp, so it should be the next height in
|
|
// sequence. When this is not the case, the recent history is
|
|
// no longer valid, so clear the recent hashes and set the
|
|
// recent height to the current block stamp height.
|
|
recentHeight = bs.Height
|
|
recentHashes = nil
|
|
} else {
|
|
// The only case left is when the new block stamp height is the
|
|
// next height in sequence after the most recently seen block
|
|
// stamp, so update it accordingly.
|
|
recentHeight = bs.Height
|
|
}
|
|
|
|
// Enforce maximum number of recent hashes.
|
|
if len(recentHashes) == maxRecentHashes {
|
|
// Shift everything down one position and add the new hash in
|
|
// the last position.
|
|
copy(recentHashes, recentHashes[1:])
|
|
recentHashes[maxRecentHashes-1] = bs.Hash
|
|
} else {
|
|
recentHashes = append(recentHashes, bs.Hash)
|
|
}
|
|
|
|
// Update the database.
|
|
err := m.namespace.Update(func(tx walletdb.Tx) error {
|
|
err := putSyncedTo(tx, bs)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return putRecentBlocks(tx, recentHeight, recentHashes)
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update memory now that the database is updated.
|
|
m.syncState.syncedTo = *bs
|
|
m.syncState.recentHashes = recentHashes
|
|
m.syncState.recentHeight = recentHeight
|
|
return nil
|
|
}
|
|
|
|
// SyncedTo returns details about the block height and hash that the address
|
|
// manager is synced through at the very least. The intention is that callers
|
|
// can use this information for intelligently initiating rescans to sync back to
|
|
// the best chain from the last known good block.
|
|
func (m *Manager) SyncedTo() BlockStamp {
|
|
m.mtx.Lock()
|
|
defer m.mtx.Unlock()
|
|
|
|
return m.syncState.syncedTo
|
|
}
|