SPV implementation - hodgepodge mishmash not done yet
This commit is contained in:
parent
d27d1211c5
commit
68e02afbf4
9 changed files with 3912 additions and 0 deletions
26
spvsvc/log.go
Normal file
26
spvsvc/log.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package spvsvc
|
||||||
|
|
||||||
|
import "github.com/btcsuite/btclog"
|
||||||
|
|
||||||
|
// log is a logger that is initialized with no output filters. This
|
||||||
|
// means the package will not perform any logging by default until the caller
|
||||||
|
// requests it.
|
||||||
|
var log btclog.Logger
|
||||||
|
|
||||||
|
// The default amount of logging is none.
|
||||||
|
func init() {
|
||||||
|
DisableLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableLog disables all library log output. Logging output is disabled
|
||||||
|
// by default until either UseLogger or SetLogWriter are called.
|
||||||
|
func DisableLog() {
|
||||||
|
log = btclog.Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseLogger uses a specified Logger to output package logging info.
|
||||||
|
// This should be used in preference to SetLogWriter if the caller is also
|
||||||
|
// using btclog.
|
||||||
|
func UseLogger(logger btclog.Logger) {
|
||||||
|
log = logger
|
||||||
|
}
|
76
spvsvc/spvchain/blocklogger.go
Normal file
76
spvsvc/spvchain/blocklogger.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package spvchain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btclog"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// blockProgressLogger provides periodic logging for other services in order
|
||||||
|
// to show users progress of certain "actions" involving some or all current
|
||||||
|
// blocks. Ex: syncing to best chain, indexing all blocks, etc.
|
||||||
|
type blockProgressLogger struct {
|
||||||
|
receivedLogBlocks int64
|
||||||
|
receivedLogTx int64
|
||||||
|
lastBlockLogTime time.Time
|
||||||
|
|
||||||
|
subsystemLogger btclog.Logger
|
||||||
|
progressAction string
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// newBlockProgressLogger returns a new block progress logger.
|
||||||
|
// The progress message is templated as follows:
|
||||||
|
// {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
|
||||||
|
// ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
|
||||||
|
func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger {
|
||||||
|
return &blockProgressLogger{
|
||||||
|
lastBlockLogTime: time.Now(),
|
||||||
|
progressAction: progressMessage,
|
||||||
|
subsystemLogger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogBlockHeight logs a new block height as an information message to show
|
||||||
|
// progress to the user. In order to prevent spam, it limits logging to one
|
||||||
|
// message every 10 seconds with duration and totals included.
|
||||||
|
func (b *blockProgressLogger) LogBlockHeight(block *btcutil.Block) {
|
||||||
|
b.Lock()
|
||||||
|
defer b.Unlock()
|
||||||
|
|
||||||
|
b.receivedLogBlocks++
|
||||||
|
b.receivedLogTx += int64(len(block.MsgBlock().Transactions))
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
duration := now.Sub(b.lastBlockLogTime)
|
||||||
|
if duration < time.Second*10 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate the duration to 10s of milliseconds.
|
||||||
|
durationMillis := int64(duration / time.Millisecond)
|
||||||
|
tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)
|
||||||
|
|
||||||
|
// Log information about new block height.
|
||||||
|
blockStr := "blocks"
|
||||||
|
if b.receivedLogBlocks == 1 {
|
||||||
|
blockStr = "block"
|
||||||
|
}
|
||||||
|
txStr := "transactions"
|
||||||
|
if b.receivedLogTx == 1 {
|
||||||
|
txStr = "transaction"
|
||||||
|
}
|
||||||
|
b.subsystemLogger.Infof("%s %d %s in the last %s (%d %s, height %d, %s)",
|
||||||
|
b.progressAction, b.receivedLogBlocks, blockStr, tDuration, b.receivedLogTx,
|
||||||
|
txStr, block.Height(), block.MsgBlock().Header.Timestamp)
|
||||||
|
|
||||||
|
b.receivedLogBlocks = 0
|
||||||
|
b.receivedLogTx = 0
|
||||||
|
b.lastBlockLogTime = now
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blockProgressLogger) SetLastLogTime(time time.Time) {
|
||||||
|
b.lastBlockLogTime = time
|
||||||
|
}
|
1483
spvsvc/spvchain/blockmanager.go
Normal file
1483
spvsvc/spvchain/blockmanager.go
Normal file
File diff suppressed because it is too large
Load diff
429
spvsvc/spvchain/db.go
Normal file
429
spvsvc/spvchain/db.go
Normal file
|
@ -0,0 +1,429 @@
|
||||||
|
package spvchain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/blockchain"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil/gcs"
|
||||||
|
"github.com/btcsuite/btcwallet/waddrmgr"
|
||||||
|
"github.com/btcsuite/btcwallet/walletdb"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// LatestDBVersion is the most recent database version.
|
||||||
|
LatestDBVersion = 1
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// latestDBVersion is the most recent database version as a variable so
|
||||||
|
// the tests can change it to force errors.
|
||||||
|
latestDBVersion uint32 = LatestDBVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
// Key names for various database fields.
|
||||||
|
var (
|
||||||
|
// Bucket names.
|
||||||
|
spvBucketName = []byte("spv")
|
||||||
|
blockHeaderBucketName = []byte("bh")
|
||||||
|
basicHeaderBucketName = []byte("bfh")
|
||||||
|
basicFilterBucketName = []byte("bf")
|
||||||
|
extHeaderBucketName = []byte("efh")
|
||||||
|
extFilterBucketName = []byte("ef")
|
||||||
|
|
||||||
|
// Db related key names (main bucket).
|
||||||
|
dbVersionName = []byte("dbver")
|
||||||
|
dbCreateDateName = []byte("dbcreated")
|
||||||
|
maxBlockHeightName = []byte("maxblockheight")
|
||||||
|
)
|
||||||
|
|
||||||
|
// uint32ToBytes converts a 32 bit unsigned integer into a 4-byte slice in
|
||||||
|
// little-endian order: 1 -> [1 0 0 0].
|
||||||
|
func uint32ToBytes(number uint32) []byte {
|
||||||
|
buf := make([]byte, 4)
|
||||||
|
binary.LittleEndian.PutUint32(buf, number)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// uint64ToBytes converts a 64 bit unsigned integer into a 8-byte slice in
|
||||||
|
// little-endian order: 1 -> [1 0 0 0 0 0 0 0].
|
||||||
|
func uint64ToBytes(number uint64) []byte {
|
||||||
|
buf := make([]byte, 8)
|
||||||
|
binary.LittleEndian.PutUint64(buf, number)
|
||||||
|
return buf
|
||||||
|
}
|
||||||
|
|
||||||
|
// fetchDBVersion fetches the current manager version from the database.
|
||||||
|
func fetchDBVersion(tx walletdb.Tx) (uint32, error) {
|
||||||
|
bucket := tx.RootBucket().Bucket(spvBucketName)
|
||||||
|
verBytes := bucket.Get(dbVersionName)
|
||||||
|
if verBytes == nil {
|
||||||
|
str := "required version number not stored in database"
|
||||||
|
return 0, log.Error(str)
|
||||||
|
}
|
||||||
|
version := binary.LittleEndian.Uint32(verBytes)
|
||||||
|
return version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// putDBVersion stores the provided version to the database.
|
||||||
|
func putDBVersion(tx walletdb.Tx, version uint32) error {
|
||||||
|
bucket := tx.RootBucket().Bucket(spvBucketName)
|
||||||
|
|
||||||
|
verBytes := uint32ToBytes(version)
|
||||||
|
err := bucket.Put(dbVersionName, verBytes)
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to store version: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// putMaxBlockHeight stores the max block height to the database.
|
||||||
|
func putMaxBlockHeight(tx walletdb.Tx, maxBlockHeight uint32) error {
|
||||||
|
bucket := tx.RootBucket().Bucket(spvBucketName)
|
||||||
|
|
||||||
|
maxBlockHeightBytes := uint32ToBytes(maxBlockHeight)
|
||||||
|
err := bucket.Put(maxBlockHeightName, maxBlockHeightBytes)
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to store max block height: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// putBlock stores the provided block header and height, keyed to the block
|
||||||
|
// hash, in the database.
|
||||||
|
func putBlock(tx walletdb.Tx, header wire.BlockHeader, height uint32) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
err := header.Serialize(&buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = buf.Write(uint32ToBytes(height))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket := tx.RootBucket().Bucket(spvBucketName).Bucket(blockHeaderBucketName)
|
||||||
|
blockHash := header.BlockHash()
|
||||||
|
|
||||||
|
err = bucket.Put(blockHash[:], buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to store SPV block info: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = bucket.Put(uint32ToBytes(height), blockHash[:])
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to store block height info: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// putFilter stores the provided filter, keyed to the block hash, in the
|
||||||
|
// appropriate filter bucket in the database.
|
||||||
|
func putFilter(tx walletdb.Tx, blockHash chainhash.Hash, bucketName []byte,
|
||||||
|
filter *gcs.Filter) error {
|
||||||
|
var buf bytes.Buffer
|
||||||
|
_, err := buf.Write(filter.NBytes())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
bucket := tx.RootBucket().Bucket(spvBucketName).Bucket(bucketName)
|
||||||
|
|
||||||
|
err = bucket.Put(blockHash[:], buf.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to store filter: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// putBasicFilter stores the provided filter, keyed to the block hash, in the
|
||||||
|
// basic filter bucket in the database.
|
||||||
|
func putBasicFilter(tx walletdb.Tx, blockHash chainhash.Hash,
|
||||||
|
filter *gcs.Filter) error {
|
||||||
|
return putFilter(tx, blockHash, basicFilterBucketName, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// putExtFilter stores the provided filter, keyed to the block hash, in the
|
||||||
|
// extended filter bucket in the database.
|
||||||
|
func putExtFilter(tx walletdb.Tx, blockHash chainhash.Hash,
|
||||||
|
filter *gcs.Filter) error {
|
||||||
|
return putFilter(tx, blockHash, extFilterBucketName, filter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// putHeader stores the provided filter, keyed to the block hash, in the
|
||||||
|
// appropriate filter bucket in the database.
|
||||||
|
func putHeader(tx walletdb.Tx, blockHash chainhash.Hash, bucketName []byte,
|
||||||
|
filterTip chainhash.Hash) error {
|
||||||
|
|
||||||
|
bucket := tx.RootBucket().Bucket(spvBucketName).Bucket(bucketName)
|
||||||
|
|
||||||
|
err := bucket.Put(blockHash[:], filterTip[:])
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to store filter header: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// putBasicHeader stores the provided filter, keyed to the block hash, in the
|
||||||
|
// basic filter bucket in the database.
|
||||||
|
func putBasicHeader(tx walletdb.Tx, blockHash chainhash.Hash,
|
||||||
|
filterTip chainhash.Hash) error {
|
||||||
|
return putHeader(tx, blockHash, basicHeaderBucketName, filterTip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// putExtHeader stores the provided filter, keyed to the block hash, in the
|
||||||
|
// extended filter bucket in the database.
|
||||||
|
func putExtHeader(tx walletdb.Tx, blockHash chainhash.Hash,
|
||||||
|
filterTip chainhash.Hash) error {
|
||||||
|
return putHeader(tx, blockHash, extHeaderBucketName, filterTip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockByHash retrieves the block header, filter, and filter tip, based on
|
||||||
|
// the provided block hash, from the database.
|
||||||
|
func GetBlockByHash(tx walletdb.Tx, blockHash chainhash.Hash) (wire.BlockHeader,
|
||||||
|
uint32, error) {
|
||||||
|
//chainhash.Hash, chainhash.Hash,
|
||||||
|
bucket := tx.RootBucket().Bucket(spvBucketName).Bucket(blockHeaderBucketName)
|
||||||
|
blockBytes := bucket.Get(blockHash[:])
|
||||||
|
if len(blockBytes) == 0 {
|
||||||
|
str := "failed to retrieve block info for hash: %s"
|
||||||
|
return wire.BlockHeader{}, 0, log.Errorf(str, blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := bytes.NewReader(blockBytes[:wire.MaxBlockHeaderPayload])
|
||||||
|
var header wire.BlockHeader
|
||||||
|
err := header.Deserialize(buf)
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to deserialize block header for hash: %s"
|
||||||
|
return wire.BlockHeader{}, 0, log.Errorf(str, blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
height := binary.LittleEndian.Uint32(blockBytes[wire.MaxBlockHeaderPayload : wire.MaxBlockHeaderPayload+4])
|
||||||
|
|
||||||
|
return header, height, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockHashByHeight retrieves the hash of a block by its height.
|
||||||
|
func GetBlockHashByHeight(tx walletdb.Tx, height uint32) (chainhash.Hash,
|
||||||
|
error) {
|
||||||
|
bucket := tx.RootBucket().Bucket(spvBucketName).Bucket(blockHeaderBucketName)
|
||||||
|
var hash chainhash.Hash
|
||||||
|
hashBytes := bucket.Get(uint32ToBytes(height))
|
||||||
|
if hashBytes == nil {
|
||||||
|
str := "no block hash for height %v"
|
||||||
|
return hash, log.Errorf(str, height)
|
||||||
|
}
|
||||||
|
hash.SetBytes(hashBytes)
|
||||||
|
return hash, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockByHeight retrieves a block's information by its height.
|
||||||
|
func GetBlockByHeight(tx walletdb.Tx, height uint32) (wire.BlockHeader, uint32,
|
||||||
|
error) {
|
||||||
|
// chainhash.Hash, chainhash.Hash
|
||||||
|
blockHash, err := GetBlockHashByHeight(tx, height)
|
||||||
|
if err != nil {
|
||||||
|
return wire.BlockHeader{}, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetBlockByHash(tx, blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SyncedTo retrieves the most recent block's height and hash.
|
||||||
|
func SyncedTo(tx walletdb.Tx) (*waddrmgr.BlockStamp, error) {
|
||||||
|
header, height, err := LatestBlock(tx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var blockStamp waddrmgr.BlockStamp
|
||||||
|
blockStamp.Hash = header.BlockHash()
|
||||||
|
blockStamp.Height = int32(height)
|
||||||
|
return &blockStamp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LatestBlock retrieves all the info about the latest stored block.
|
||||||
|
func LatestBlock(tx walletdb.Tx) (wire.BlockHeader, uint32, error) {
|
||||||
|
bucket := tx.RootBucket().Bucket(spvBucketName)
|
||||||
|
|
||||||
|
maxBlockHeightBytes := bucket.Get(maxBlockHeightName)
|
||||||
|
if maxBlockHeightBytes == nil {
|
||||||
|
str := "no max block height stored"
|
||||||
|
return wire.BlockHeader{}, 0, log.Error(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
maxBlockHeight := binary.LittleEndian.Uint32(maxBlockHeightBytes)
|
||||||
|
header, height, err := GetBlockByHeight(tx, maxBlockHeight)
|
||||||
|
if err != nil {
|
||||||
|
return wire.BlockHeader{}, 0, err
|
||||||
|
}
|
||||||
|
if height != maxBlockHeight {
|
||||||
|
str := "max block height inconsistent"
|
||||||
|
return wire.BlockHeader{}, 0, log.Error(str)
|
||||||
|
}
|
||||||
|
return header, height, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockLocatorFromHash returns a block locator based on the provided hash.
|
||||||
|
func BlockLocatorFromHash(tx walletdb.Tx, hash chainhash.Hash) blockchain.BlockLocator {
|
||||||
|
locator := make(blockchain.BlockLocator, 0, wire.MaxBlockLocatorsPerMsg)
|
||||||
|
locator = append(locator, &hash)
|
||||||
|
|
||||||
|
// If hash isn't found in DB or this is the genesis block, return
|
||||||
|
// the locator as is
|
||||||
|
_, height, err := GetBlockByHash(tx, hash)
|
||||||
|
if (err != nil) || (height == 0) {
|
||||||
|
return locator
|
||||||
|
}
|
||||||
|
|
||||||
|
decrement := uint32(1)
|
||||||
|
for (height > 0) && (len(locator) < wire.MaxBlockLocatorsPerMsg) {
|
||||||
|
// Decrement by 1 for the first 10 blocks, then double the
|
||||||
|
// jump until we get to the genesis hash
|
||||||
|
if len(locator) > 10 {
|
||||||
|
decrement *= 2
|
||||||
|
}
|
||||||
|
if decrement > height {
|
||||||
|
height = 0
|
||||||
|
} else {
|
||||||
|
height -= decrement
|
||||||
|
}
|
||||||
|
blockHash, err := GetBlockHashByHeight(tx, height)
|
||||||
|
if err != nil {
|
||||||
|
return locator
|
||||||
|
}
|
||||||
|
locator = append(locator, &blockHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
return locator
|
||||||
|
}
|
||||||
|
|
||||||
|
// createSPVNS creates the initial namespace structure needed for all of the
|
||||||
|
// SPV-related data. This includes things such as all of the buckets as well as
|
||||||
|
// the version and creation date.
|
||||||
|
func createSPVNS(namespace walletdb.Namespace, params *chaincfg.Params) error {
|
||||||
|
err := namespace.Update(func(tx walletdb.Tx) error {
|
||||||
|
rootBucket := tx.RootBucket()
|
||||||
|
spvBucket, err := rootBucket.CreateBucketIfNotExists(spvBucketName)
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to create main bucket: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = spvBucket.CreateBucketIfNotExists(blockHeaderBucketName)
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to create block header bucket: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = spvBucket.CreateBucketIfNotExists(basicFilterBucketName)
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to create basic filter bucket: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = spvBucket.CreateBucketIfNotExists(basicHeaderBucketName)
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to create basic header bucket: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = spvBucket.CreateBucketIfNotExists(extFilterBucketName)
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to create extended filter bucket: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = spvBucket.CreateBucketIfNotExists(extHeaderBucketName)
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to create extended header bucket: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
createDate := spvBucket.Get(dbCreateDateName)
|
||||||
|
if createDate != nil {
|
||||||
|
log.Infof("Wallet SPV namespace already created.")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Creating wallet SPV namespace.")
|
||||||
|
|
||||||
|
basicFilter, err := buildBasicFilter(params.GenesisBlock)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
basicFilterTip := makeHeaderForFilter(basicFilter,
|
||||||
|
params.GenesisBlock.Header.PrevBlock)
|
||||||
|
|
||||||
|
extFilter, err := buildExtFilter(params.GenesisBlock)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
extFilterTip := makeHeaderForFilter(extFilter,
|
||||||
|
params.GenesisBlock.Header.PrevBlock)
|
||||||
|
|
||||||
|
err = putBlock(tx, params.GenesisBlock.Header, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = putBasicFilter(tx, *params.GenesisHash, basicFilter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = putBasicHeader(tx, *params.GenesisHash, basicFilterTip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = putExtFilter(tx, *params.GenesisHash, extFilter)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = putExtHeader(tx, *params.GenesisHash, extFilterTip)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = putDBVersion(tx, latestDBVersion)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = putMaxBlockHeight(tx, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = spvBucket.Put(dbCreateDateName,
|
||||||
|
uint64ToBytes(uint64(time.Now().Unix())))
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to store database creation time: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to update database: %v"
|
||||||
|
return log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
73
spvsvc/spvchain/filter.go
Normal file
73
spvsvc/spvchain/filter.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package spvchain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil/gcs"
|
||||||
|
"github.com/btcsuite/btcutil/gcs/builder"
|
||||||
|
)
|
||||||
|
|
||||||
|
func buildBasicFilter(block *wire.MsgBlock) (*gcs.Filter, error) {
|
||||||
|
blockHash := block.BlockHash()
|
||||||
|
b := builder.WithKeyHash(&blockHash)
|
||||||
|
_, err := b.Key()
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to create filter builder: %v"
|
||||||
|
return nil, log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
for i, tx := range block.Transactions {
|
||||||
|
// Skip the inputs for the coinbase transaction
|
||||||
|
if i != 0 {
|
||||||
|
for _, txIn := range tx.TxIn {
|
||||||
|
b.AddOutPoint(txIn.PreviousOutPoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, txOut := range tx.TxOut {
|
||||||
|
b.AddScript(txOut.PkScript)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f, err := b.Build()
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to build filter: %v"
|
||||||
|
return nil, log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildExtFilter(block *wire.MsgBlock) (*gcs.Filter, error) {
|
||||||
|
blockHash := block.BlockHash()
|
||||||
|
b := builder.WithKeyHash(&blockHash)
|
||||||
|
_, err := b.Key()
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to create filter builder: %v"
|
||||||
|
return nil, log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
for i, tx := range block.Transactions {
|
||||||
|
txHash := tx.TxHash()
|
||||||
|
b.AddHash(&txHash)
|
||||||
|
// Skip the inputs for the coinbase transaction
|
||||||
|
if i != 0 {
|
||||||
|
for _, txIn := range tx.TxIn {
|
||||||
|
b.AddScript(txIn.SignatureScript)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f, err := b.Build()
|
||||||
|
if err != nil {
|
||||||
|
str := "failed to build filter: %v"
|
||||||
|
return nil, log.Errorf(str, err)
|
||||||
|
}
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFilterHash(filter *gcs.Filter) chainhash.Hash {
|
||||||
|
return chainhash.HashH(filter.NBytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeHeaderForFilter(filter *gcs.Filter, prevHeader chainhash.Hash) chainhash.Hash {
|
||||||
|
filterTip := make([]byte, 2*chainhash.HashSize)
|
||||||
|
filterHash := getFilterHash(filter)
|
||||||
|
copy(filterTip, filterHash[:])
|
||||||
|
copy(filterTip[chainhash.HashSize:], prevHeader[:])
|
||||||
|
return chainhash.HashH(filterTip)
|
||||||
|
}
|
26
spvsvc/spvchain/log.go
Normal file
26
spvsvc/spvchain/log.go
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
package spvchain
|
||||||
|
|
||||||
|
import "github.com/btcsuite/btclog"
|
||||||
|
|
||||||
|
// log is a logger that is initialized with no output filters. This
|
||||||
|
// means the package will not perform any logging by default until the caller
|
||||||
|
// requests it.
|
||||||
|
var log btclog.Logger
|
||||||
|
|
||||||
|
// The default amount of logging is none.
|
||||||
|
func init() {
|
||||||
|
DisableLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableLog disables all library log output. Logging output is disabled
|
||||||
|
// by default until either UseLogger or SetLogWriter are called.
|
||||||
|
func DisableLog() {
|
||||||
|
log = btclog.Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseLogger uses a specified Logger to output package logging info.
|
||||||
|
// This should be used in preference to SetLogWriter if the caller is also
|
||||||
|
// using btclog.
|
||||||
|
func UseLogger(logger btclog.Logger) {
|
||||||
|
log = logger
|
||||||
|
}
|
149
spvsvc/spvchain/notifications.go
Normal file
149
spvsvc/spvchain/notifications.go
Normal file
|
@ -0,0 +1,149 @@
|
||||||
|
// Copyright (c) 2013-2016 The btcsuite developers
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package spvchain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/addrmgr"
|
||||||
|
"github.com/btcsuite/btcd/connmgr"
|
||||||
|
)
|
||||||
|
|
||||||
|
type getConnCountMsg struct {
|
||||||
|
reply chan int32
|
||||||
|
}
|
||||||
|
|
||||||
|
type getPeersMsg struct {
|
||||||
|
reply chan []*serverPeer
|
||||||
|
}
|
||||||
|
|
||||||
|
type getOutboundGroup struct {
|
||||||
|
key string
|
||||||
|
reply chan int
|
||||||
|
}
|
||||||
|
|
||||||
|
type getAddedNodesMsg struct {
|
||||||
|
reply chan []*serverPeer
|
||||||
|
}
|
||||||
|
|
||||||
|
type disconnectNodeMsg struct {
|
||||||
|
cmp func(*serverPeer) bool
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type connectNodeMsg struct {
|
||||||
|
addr string
|
||||||
|
permanent bool
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type removeNodeMsg struct {
|
||||||
|
cmp func(*serverPeer) bool
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleQuery is the central handler for all queries and commands from other
|
||||||
|
// goroutines related to peer state.
|
||||||
|
func (s *ChainService) handleQuery(state *peerState, querymsg interface{}) {
|
||||||
|
switch msg := querymsg.(type) {
|
||||||
|
case getConnCountMsg:
|
||||||
|
nconnected := int32(0)
|
||||||
|
state.forAllPeers(func(sp *serverPeer) {
|
||||||
|
if sp.Connected() {
|
||||||
|
nconnected++
|
||||||
|
}
|
||||||
|
})
|
||||||
|
msg.reply <- nconnected
|
||||||
|
|
||||||
|
case getPeersMsg:
|
||||||
|
peers := make([]*serverPeer, 0, state.Count())
|
||||||
|
state.forAllPeers(func(sp *serverPeer) {
|
||||||
|
if !sp.Connected() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
peers = append(peers, sp)
|
||||||
|
})
|
||||||
|
msg.reply <- peers
|
||||||
|
|
||||||
|
case connectNodeMsg:
|
||||||
|
// TODO: duplicate oneshots?
|
||||||
|
// Limit max number of total peers.
|
||||||
|
if state.Count() >= MaxPeers {
|
||||||
|
msg.reply <- errors.New("max peers reached")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for _, peer := range state.persistentPeers {
|
||||||
|
if peer.Addr() == msg.addr {
|
||||||
|
if msg.permanent {
|
||||||
|
msg.reply <- errors.New("peer already connected")
|
||||||
|
} else {
|
||||||
|
msg.reply <- errors.New("peer exists as a permanent peer")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
netAddr, err := addrStringToNetAddr(msg.addr)
|
||||||
|
if err != nil {
|
||||||
|
msg.reply <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: if too many, nuke a non-perm peer.
|
||||||
|
go s.connManager.Connect(&connmgr.ConnReq{
|
||||||
|
Addr: netAddr,
|
||||||
|
Permanent: msg.permanent,
|
||||||
|
})
|
||||||
|
msg.reply <- nil
|
||||||
|
case removeNodeMsg:
|
||||||
|
found := disconnectPeer(state.persistentPeers, msg.cmp, func(sp *serverPeer) {
|
||||||
|
// Keep group counts ok since we remove from
|
||||||
|
// the list now.
|
||||||
|
state.outboundGroups[addrmgr.GroupKey(sp.NA())]--
|
||||||
|
})
|
||||||
|
|
||||||
|
if found {
|
||||||
|
msg.reply <- nil
|
||||||
|
} else {
|
||||||
|
msg.reply <- errors.New("peer not found")
|
||||||
|
}
|
||||||
|
case getOutboundGroup:
|
||||||
|
count, ok := state.outboundGroups[msg.key]
|
||||||
|
if ok {
|
||||||
|
msg.reply <- count
|
||||||
|
} else {
|
||||||
|
msg.reply <- 0
|
||||||
|
}
|
||||||
|
// Request a list of the persistent (added) peers.
|
||||||
|
case getAddedNodesMsg:
|
||||||
|
// Respond with a slice of the relavent peers.
|
||||||
|
peers := make([]*serverPeer, 0, len(state.persistentPeers))
|
||||||
|
for _, sp := range state.persistentPeers {
|
||||||
|
peers = append(peers, sp)
|
||||||
|
}
|
||||||
|
msg.reply <- peers
|
||||||
|
case disconnectNodeMsg:
|
||||||
|
// Check outbound peers.
|
||||||
|
found := disconnectPeer(state.outboundPeers, msg.cmp, func(sp *serverPeer) {
|
||||||
|
// Keep group counts ok since we remove from
|
||||||
|
// the list now.
|
||||||
|
state.outboundGroups[addrmgr.GroupKey(sp.NA())]--
|
||||||
|
})
|
||||||
|
if found {
|
||||||
|
// If there are multiple outbound connections to the same
|
||||||
|
// ip:port, continue disconnecting them all until no such
|
||||||
|
// peers are found.
|
||||||
|
for found {
|
||||||
|
found = disconnectPeer(state.outboundPeers, msg.cmp, func(sp *serverPeer) {
|
||||||
|
state.outboundGroups[addrmgr.GroupKey(sp.NA())]--
|
||||||
|
})
|
||||||
|
}
|
||||||
|
msg.reply <- nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.reply <- errors.New("peer not found")
|
||||||
|
}
|
||||||
|
}
|
1373
spvsvc/spvchain/spvchain.go
Normal file
1373
spvsvc/spvchain/spvchain.go
Normal file
File diff suppressed because it is too large
Load diff
277
spvsvc/spvsvc.go
Normal file
277
spvsvc/spvsvc.go
Normal file
|
@ -0,0 +1,277 @@
|
||||||
|
package spvsvc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/addrmgr"
|
||||||
|
"github.com/btcsuite/btcd/connmgr"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcwallet/spvsvc/spvchain"
|
||||||
|
"github.com/btcsuite/btcwallet/wallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SynchronizationService provides an SPV, p2p-based backend for a wallet to
|
||||||
|
// synchronize it with the network and send transactions it signs.
|
||||||
|
type SynchronizationService struct {
|
||||||
|
wallet *wallet.Wallet
|
||||||
|
chainService spvchain.ChainService
|
||||||
|
}
|
||||||
|
|
||||||
|
// SynchronizationServiceOpt is the return type of functional options for
|
||||||
|
// creating a SynchronizationService object.
|
||||||
|
type SynchronizationServiceOpt func(*SynchronizationService) error
|
||||||
|
|
||||||
|
// NewSynchronizationService creates a new SynchronizationService with
|
||||||
|
// functional options.
|
||||||
|
func NewSynchronizationService(opts ...SynchronizationServiceOpt) (*SynchronizationService, error) {
|
||||||
|
s := SynchronizationService{
|
||||||
|
userAgentName: defaultUserAgentName,
|
||||||
|
userAgentVersion: defaultUserAgentVersion,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
err := opt(&s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &s, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserAgent is a functional option to set the user agent information as it
|
||||||
|
// appears to other nodes.
|
||||||
|
func UserAgent(agentName, agentVersion string) SynchronizationServiceOpt {
|
||||||
|
return func(s *SynchronizationService) error {
|
||||||
|
s.userAgentName = agentName
|
||||||
|
s.userAgentVersion = agentVersion
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddrManager is a functional option to create an address manager for the
|
||||||
|
// synchronization service. It takes a string as an argument to specify the
|
||||||
|
// directory in which to store addresses.
|
||||||
|
func AddrManager(dir string) SynchronizationServiceOpt {
|
||||||
|
return func(s *SynchronizationService) error {
|
||||||
|
m := addrmgr.New(dir, spvLookup)
|
||||||
|
s.addrManager = m
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnManagerOpt is the return type of functional options to create a
|
||||||
|
// connection manager for the synchronization service.
|
||||||
|
type ConnManagerOpt func(*connmgr.Config) error
|
||||||
|
|
||||||
|
// ConnManager is a functional option to create a connection manager for the
|
||||||
|
// synchronization service.
|
||||||
|
func ConnManager(opts ...ConnManagerOpt) SynchronizationServiceOpt {
|
||||||
|
return func(s *SynchronizationService) error {
|
||||||
|
c := connmgr.Config{
|
||||||
|
TargetOutbound: defaultTargetOutbound,
|
||||||
|
RetryDuration: connectionRetryInterval,
|
||||||
|
GetNewAddress: s.getNewAddress,
|
||||||
|
}
|
||||||
|
for _, opt := range opts {
|
||||||
|
err := opt(&c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
connManager, err := connmgr.New(&c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.connManager = connManager
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TargetOutbound is a functional option to specify how many outbound
|
||||||
|
// connections should be made by the ConnManager to peers. Defaults to 8.
|
||||||
|
func TargetOutbound(target uint32) ConnManagerOpt {
|
||||||
|
return func(c *connmgr.Config) error {
|
||||||
|
c.TargetOutbound = target
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// RetryDuration is a functional option to specify how long to wait before
|
||||||
|
// retrying a connection request. Defaults to 5s.
|
||||||
|
func RetryDuration(duration time.Duration) ConnManagerOpt {
|
||||||
|
return func(c *connmgr.Config) error {
|
||||||
|
c.RetryDuration = duration
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SynchronizationService) getNewAddress() (net.Addr, error) {
|
||||||
|
if s.addrManager == nil {
|
||||||
|
return nil, log.Error("Couldn't get address for new " +
|
||||||
|
"connection: address manager is nil.")
|
||||||
|
}
|
||||||
|
s.addrManager.Start()
|
||||||
|
for tries := 0; tries < 100; tries++ {
|
||||||
|
addr := s.addrManager.GetAddress()
|
||||||
|
if addr == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
// If we already have peers in this group, skip this address
|
||||||
|
key := addrmgr.GroupKey(addr.NetAddress())
|
||||||
|
if s.outboundGroupCount(key) != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tries < 30 && time.Since(addr.LastAttempt()) < 10*time.Minute {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tries < 50 && fmt.Sprintf("%d", addr.NetAddress().Port) !=
|
||||||
|
s.wallet.ChainParams().DefaultPort {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
addrString := addrmgr.NetAddressKey(addr.NetAddress())
|
||||||
|
return addrStringToNetAddr(addrString)
|
||||||
|
}
|
||||||
|
return nil, log.Error("Couldn't get address for new connection: no " +
|
||||||
|
"valid addresses known.")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SynchronizationService) outboundGroupCount(key string) int {
|
||||||
|
replyChan := make(chan int)
|
||||||
|
s.query <- getOutboundGroup{key: key, reply: replyChan}
|
||||||
|
return <-replyChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// SynchronizeWallet associates a wallet with the consensus RPC client,
|
||||||
|
// synchronizes the wallet with the latest changes to the blockchain, and
|
||||||
|
// continuously updates the wallet through RPC notifications.
|
||||||
|
//
|
||||||
|
// This function does not return without error until the wallet is synchronized
|
||||||
|
// to the current chain state.
|
||||||
|
func (s *SynchronizationService) SynchronizeWallet(w *wallet.Wallet) error {
|
||||||
|
s.wallet = w
|
||||||
|
|
||||||
|
s.wg.Add(3)
|
||||||
|
go s.notificationQueueHandler()
|
||||||
|
go s.processQueuedNotifications()
|
||||||
|
go s.queryHandler()
|
||||||
|
|
||||||
|
return s.syncWithNetwork(w)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SynchronizationService) queryHandler() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SynchronizationService) processQueuedNotifications() {
|
||||||
|
for n := range s.dequeueNotification {
|
||||||
|
var err error
|
||||||
|
notificationSwitch:
|
||||||
|
switch n := n.(type) {
|
||||||
|
case *wire.MsgBlock:
|
||||||
|
if n.BlockHash().String() != "" {
|
||||||
|
break notificationSwitch
|
||||||
|
}
|
||||||
|
case *wire.MsgHeaders:
|
||||||
|
case *wire.MsgInv:
|
||||||
|
case *wire.MsgReject:
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("Cannot handle peer notification: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.wg.Done()
|
||||||
|
}
|
||||||
|
|
||||||
|
// syncWithNetwork brings the wallet up to date with the current chain server
|
||||||
|
// connection. It creates a rescan request and blocks until the rescan has
|
||||||
|
// finished.
|
||||||
|
func (s *SynchronizationService) syncWithNetwork(w *wallet.Wallet) error {
|
||||||
|
/*chainClient := s.rpcClient
|
||||||
|
|
||||||
|
// Request notifications for connected and disconnected blocks.
|
||||||
|
//
|
||||||
|
// TODO(jrick): Either request this notification only once, or when
|
||||||
|
// btcrpcclient is modified to allow some notification request to not
|
||||||
|
// automatically resent on reconnect, include the notifyblocks request
|
||||||
|
// as well. I am leaning towards allowing off all btcrpcclient
|
||||||
|
// notification re-registrations, in which case the code here should be
|
||||||
|
// left as is.
|
||||||
|
err := chainClient.NotifyBlocks()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request notifications for transactions sending to all wallet
|
||||||
|
// addresses.
|
||||||
|
addrs, unspent, err := w.ActiveData()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(jrick): How should this handle a synced height earlier than
|
||||||
|
// the chain server best block?
|
||||||
|
|
||||||
|
// When no addresses have been generated for the wallet, the rescan can
|
||||||
|
// be skipped.
|
||||||
|
//
|
||||||
|
// TODO: This is only correct because activeData above returns all
|
||||||
|
// addresses ever created, including those that don't need to be watched
|
||||||
|
// anymore. This code should be updated when this assumption is no
|
||||||
|
// longer true, but worst case would result in an unnecessary rescan.
|
||||||
|
if len(addrs) == 0 && len(unspent) == 0 {
|
||||||
|
// TODO: It would be ideal if on initial sync wallet saved the
|
||||||
|
// last several recent blocks rather than just one. This would
|
||||||
|
// avoid a full rescan for a one block reorg of the current
|
||||||
|
// chain tip.
|
||||||
|
hash, height, err := chainClient.GetBestBlock()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return w.Manager.SetSyncedTo(&waddrmgr.BlockStamp{
|
||||||
|
Hash: *hash,
|
||||||
|
Height: height,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compare previously-seen blocks against the chain server. If any of
|
||||||
|
// these blocks no longer exist, rollback all of the missing blocks
|
||||||
|
// before catching up with the rescan.
|
||||||
|
iter := w.Manager.NewIterateRecentBlocks()
|
||||||
|
rollback := iter == nil
|
||||||
|
syncBlock := waddrmgr.BlockStamp{
|
||||||
|
Hash: *w.ChainParams().GenesisHash,
|
||||||
|
Height: 0,
|
||||||
|
}
|
||||||
|
for cont := iter != nil; cont; cont = iter.Prev() {
|
||||||
|
bs := iter.BlockStamp()
|
||||||
|
log.Debugf("Checking for previous saved block with height %v hash %v",
|
||||||
|
bs.Height, bs.Hash)
|
||||||
|
_, err = chainClient.GetBlock(&bs.Hash)
|
||||||
|
if err != nil {
|
||||||
|
rollback = true
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug("Found matching block.")
|
||||||
|
syncBlock = bs
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if rollback {
|
||||||
|
err = w.Manager.SetSyncedTo(&syncBlock)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// Rollback unconfirms transactions at and beyond the passed
|
||||||
|
// height, so add one to the new synced-to height to prevent
|
||||||
|
// unconfirming txs from the synced-to block.
|
||||||
|
err = w.TxStore.Rollback(syncBlock.Height + 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.initialRescan(addrs, unspent, w.Manager.SyncedTo()) */
|
||||||
|
return nil
|
||||||
|
}
|
Loading…
Reference in a new issue