Merge pull request #751 from wpaulino/txindex

Remove txindex requirement for full nodes
This commit is contained in:
Olaoluwa Osuntokun 2018-04-17 18:20:10 -07:00 committed by GitHub
commit 1f4a02e814
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 317 additions and 147 deletions

@ -46,7 +46,9 @@ type chainUpdate struct {
}
// TODO(roasbeef): generalize struct below:
// * move chans to config, allow outside callers to handle send conditions
// * move chans to config
// * extract common code
// * allow outside callers to handle send conditions
// BitcoindNotifier implements the ChainNotifier interface using a bitcoind
// chain client. Multiple concurrent clients are supported. All notifications
@ -235,17 +237,25 @@ out:
}
b.spendNotifications[op][msg.spendID] = msg
b.chainConn.NotifySpent([]*wire.OutPoint{&op})
case *confirmationsNotification:
chainntnfs.Log.Infof("New confirmations "+
case *confirmationNotification:
chainntnfs.Log.Infof("New confirmation "+
"subscription: txid=%v, numconfs=%v",
msg.TxID, msg.NumConfirmations)
// Lookup whether the transaction is already included in the
// active chain.
txConf, err := b.historicalConfDetails(msg.TxID)
_, currentHeight, err := b.chainConn.GetBestBlock()
if err != nil {
chainntnfs.Log.Error(err)
}
// Lookup whether the transaction is already included in the
// active chain.
txConf, err := b.historicalConfDetails(
msg.TxID, msg.heightHint, uint32(currentHeight),
)
if err != nil {
chainntnfs.Log.Error(err)
}
err = b.txConfNotifier.Register(&msg.ConfNtfn, txConf)
if err != nil {
chainntnfs.Log.Error(err)
@ -367,60 +377,127 @@ func (b *BitcoindNotifier) handleRelevantTx(tx chain.RelevantTx, bestHeight int3
// historicalConfDetails looks up whether a transaction is already included in a
// block in the active chain and, if so, returns details about the confirmation.
func (b *BitcoindNotifier) historicalConfDetails(txid *chainhash.Hash,
heightHint, currentHeight uint32) (*chainntnfs.TxConfirmation, error) {
// First, we'll attempt to retrieve the transaction details using the
// backend node's transaction index.
txConf, err := b.confDetailsFromTxIndex(txid)
if err != nil {
return nil, err
}
if txConf != nil {
return txConf, nil
}
// If the backend node's transaction index is not enabled, then we'll
// fall back to manually scanning the chain's blocks, looking for the
// block where the transaction was included in.
return b.confDetailsManually(txid, heightHint, currentHeight)
}
// confDetailsFromTxIndex looks up whether a transaction is already included
// in a block in the active chain by using the backend node's transaction index.
// If the transaction is found, its confirmation details are returned.
// Otherwise, nil is returned.
func (b *BitcoindNotifier) confDetailsFromTxIndex(txid *chainhash.Hash,
) (*chainntnfs.TxConfirmation, error) {
// If the transaction already has some or all of the confirmations,
// If the transaction has some or all of its confirmations required,
// then we may be able to dispatch it immediately.
// TODO: fall back to scanning blocks if txindex isn't on.
tx, err := b.chainConn.GetRawTransactionVerbose(txid)
if err != nil || tx == nil || tx.BlockHash == "" {
if err == nil {
if err != nil {
// Avoid returning an error if the transaction index is not
// enabled to proceed with fallback methods.
jsonErr, ok := err.(*btcjson.RPCError)
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
return nil, fmt.Errorf("unable to query for txid "+
"%v: %v", txid, err)
}
}
// Make sure we actually retrieved a transaction that is included in a
// block. Without this, we won't be able to retrieve its confirmation
// details.
if tx == nil || tx.BlockHash == "" {
return nil, nil
}
// Do not return an error if the transaction was not found.
if jsonErr, ok := err.(*btcjson.RPCError); ok {
if jsonErr.Code == btcjson.ErrRPCNoTxInfo {
return nil, nil
}
}
return nil, fmt.Errorf("unable to query for txid(%v): %v", txid, err)
}
// As we need to fully populate the returned TxConfirmation struct,
// grab the block in which the transaction was confirmed so we can
// locate its exact index within the block.
blockHash, err := chainhash.NewHashFromStr(tx.BlockHash)
if err != nil {
return nil, fmt.Errorf("unable to get block hash %v for historical "+
"dispatch: %v", tx.BlockHash, err)
return nil, fmt.Errorf("unable to get block hash %v for "+
"historical dispatch: %v", tx.BlockHash, err)
}
block, err := b.chainConn.GetBlockVerbose(blockHash)
if err != nil {
return nil, fmt.Errorf("unable to get block hash: %v", err)
return nil, fmt.Errorf("unable to get block with hash %v for "+
"historical dispatch: %v", blockHash, err)
}
// If the block obtained, locate the transaction's index within the
// If the block was obtained, locate the transaction's index within the
// block so we can give the subscriber full confirmation details.
txIndex := -1
targetTxidStr := txid.String()
for i, txHash := range block.Tx {
for txIndex, txHash := range block.Tx {
if txHash == targetTxidStr {
txIndex = i
break
}
}
if txIndex == -1 {
return nil, fmt.Errorf("unable to locate tx %v in block %v",
txid, blockHash)
}
txConf := chainntnfs.TxConfirmation{
return &chainntnfs.TxConfirmation{
BlockHash: blockHash,
BlockHeight: uint32(block.Height),
TxIndex: uint32(txIndex),
}, nil
}
return &txConf, nil
}
// We return an error because we should have found the transaction
// within the block, but didn't.
return nil, fmt.Errorf("unable to locate tx %v in block %v", txid,
blockHash)
}
// confDetailsManually looks up whether a transaction is already included in a
// block in the active chain by scanning the chain's blocks, starting from the
// earliest height the transaction could have been included in, to the current
// height in the chain. If the transaction is found, its confirmation details
// are returned. Otherwise, nil is returned.
func (b *BitcoindNotifier) confDetailsManually(txid *chainhash.Hash,
heightHint, currentHeight uint32) (*chainntnfs.TxConfirmation, error) {
targetTxidStr := txid.String()
// Begin scanning blocks at every height to determine where the
// transaction was included in.
for height := heightHint; height <= currentHeight; height++ {
blockHash, err := b.chainConn.GetBlockHash(int64(height))
if err != nil {
return nil, fmt.Errorf("unable to get hash from block "+
"with height %d", height)
}
block, err := b.chainConn.GetBlockVerbose(blockHash)
if err != nil {
return nil, fmt.Errorf("unable to get block with hash "+
"%v: %v", blockHash, err)
}
for txIndex, txHash := range block.Tx {
// If we're able to find the transaction in this block,
// return its confirmation details.
if txHash == targetTxidStr {
return &chainntnfs.TxConfirmation{
BlockHash: blockHash,
BlockHeight: height,
TxIndex: uint32(txIndex),
}, nil
}
}
}
// If we reach here, then we were not able to find the transaction
// within a block, so we avoid returning an error.
return nil, nil
}
// notifyBlockEpochs notifies all registered block epoch clients of the newly
@ -451,6 +528,8 @@ type spendNotification struct {
spendChan chan *chainntnfs.SpendDetail
spendID uint64
heightHint uint32
}
// spendCancel is a message sent to the BitcoindNotifier when a client wishes
@ -466,9 +545,10 @@ type spendCancel struct {
// RegisterSpendNtfn registers an intent to be notified once the target
// outpoint has been spent by a transaction on-chain. Once a spend of the target
// outpoint has been detected, the details of the spending event will be sent
// across the 'Spend' channel.
// across the 'Spend' channel. The heightHint should represent the earliest
// height in the chain where the transaction could have been spent in.
func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
_ uint32, _ bool) (*chainntnfs.SpendEvent, error) {
heightHint uint32, _ bool) (*chainntnfs.SpendEvent, error) {
ntfn := &spendNotification{
targetOutpoint: outpoint,
@ -486,21 +566,46 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
return nil, err
}
// The following conditional checks to ensure that when a spend notification
// is registered, the output hasn't already been spent. If the output
// is no longer in the UTXO set, the chain will be rescanned from the point
// where the output was added. The rescan will dispatch the notification.
txout, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
// The following conditional checks to ensure that when a spend
// notification is registered, the output hasn't already been spent. If
// the output is no longer in the UTXO set, the chain will be rescanned
// from the point where the output was added. The rescan will dispatch
// the notification.
txOut, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
if err != nil {
return nil, err
}
if txout == nil {
// TODO: fall back to scanning blocks if txindex isn't on.
transaction, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
if txOut == nil {
// First, we'll attempt to retrieve the transaction's block hash
// using the backend's transaction index.
tx, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
if err != nil {
// Avoid returning an error if the transaction was not
// found to proceed with fallback methods.
jsonErr, ok := err.(*btcjson.RPCError)
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
return nil, fmt.Errorf("unable to query for "+
"txid %v: %v", outpoint.Hash, err)
}
}
var blockHash *chainhash.Hash
if tx != nil && tx.BlockHash != "" {
// If we're able to retrieve a valid block hash from the
// transaction, then we'll use it as our rescan starting
// point.
blockHash, err = chainhash.NewHashFromStr(tx.BlockHash)
if err != nil {
return nil, err
}
} else {
// Otherwise, we'll attempt to retrieve the hash for the
// block at the heightHint.
blockHash, err = b.chainConn.GetBlockHash(
int64(heightHint),
)
if err != nil {
return nil, err
}
}
@ -510,8 +615,8 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// error when scanning for blocks. This can happens in the case
// of a race condition, wherein the output itself is unspent,
// and only arrives in the mempool after the getxout call.
if transaction != nil && transaction.BlockHash != "" {
startHash, err := chainhash.NewHashFromStr(transaction.BlockHash)
if blockHash != nil {
startHash, err := chainhash.NewHashFromStr(tx.BlockHash)
if err != nil {
return nil, err
}
@ -564,7 +669,6 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
}
}
}
}
}
@ -599,22 +703,24 @@ func (b *BitcoindNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// confirmationNotification represents a client's intent to receive a
// notification once the target txid reaches numConfirmations confirmations.
type confirmationsNotification struct {
type confirmationNotification struct {
chainntnfs.ConfNtfn
heightHint uint32
}
// RegisterConfirmationsNtfn registers a notification with BitcoindNotifier
// which will be triggered once the txid reaches numConfs number of
// confirmations.
func (b *BitcoindNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
numConfs, _ uint32) (*chainntnfs.ConfirmationEvent, error) {
numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
ntfn := &confirmationsNotification{
chainntnfs.ConfNtfn{
ntfn := &confirmationNotification{
ConfNtfn: chainntnfs.ConfNtfn{
TxID: txid,
NumConfirmations: numConfs,
Event: chainntnfs.NewConfirmationEvent(numConfs),
},
heightHint: heightHint,
}
select {

@ -298,17 +298,20 @@ out:
b.spendNotifications[op] = make(map[uint64]*spendNotification)
}
b.spendNotifications[op][msg.spendID] = msg
case *confirmationsNotification:
chainntnfs.Log.Infof("New confirmations "+
case *confirmationNotification:
chainntnfs.Log.Infof("New confirmation "+
"subscription: txid=%v, numconfs=%v",
msg.TxID, msg.NumConfirmations)
// Lookup whether the transaction is already included in the
// active chain.
txConf, err := b.historicalConfDetails(msg.TxID)
txConf, err := b.historicalConfDetails(
msg.TxID, msg.heightHint, uint32(currentHeight),
)
if err != nil {
chainntnfs.Log.Error(err)
}
err = b.txConfNotifier.Register(&msg.ConfNtfn, txConf)
if err != nil {
chainntnfs.Log.Error(err)
@ -458,59 +461,128 @@ out:
// historicalConfDetails looks up whether a transaction is already included in a
// block in the active chain and, if so, returns details about the confirmation.
func (b *BtcdNotifier) historicalConfDetails(txid *chainhash.Hash,
heightHint, currentHeight uint32) (*chainntnfs.TxConfirmation, error) {
// First, we'll attempt to retrieve the transaction details using the
// backend node's transaction index.
txConf, err := b.confDetailsFromTxIndex(txid)
if err != nil {
return nil, err
}
if txConf != nil {
return txConf, nil
}
// If the backend node's transaction index is not enabled, then we'll
// fall back to manually scanning the chain's blocks, looking for the
// block where the transaction was included in.
return b.confDetailsManually(txid, heightHint, currentHeight)
}
// confDetailsFromTxIndex looks up whether a transaction is already included
// in a block in the active chain by using the backend node's transaction index.
// If the transaction is found, its confirmation details are returned.
// Otherwise, nil is returned.
func (b *BtcdNotifier) confDetailsFromTxIndex(txid *chainhash.Hash,
) (*chainntnfs.TxConfirmation, error) {
// If the transaction already has some or all of the confirmations,
// If the transaction has some or all of its confirmations required,
// then we may be able to dispatch it immediately.
tx, err := b.chainConn.GetRawTransactionVerbose(txid)
if err != nil || tx == nil || tx.BlockHash == "" {
if err == nil {
if err != nil {
// Avoid returning an error if the transaction index is not
// enabled to proceed with fallback methods.
jsonErr, ok := err.(*btcjson.RPCError)
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
return nil, fmt.Errorf("unable to query for txid "+
"%v: %v", txid, err)
}
}
// Make sure we actually retrieved a transaction that is included in a
// block. Without this, we won't be able to retrieve its confirmation
// details.
if tx == nil || tx.BlockHash == "" {
return nil, nil
}
// Do not return an error if the transaction was not found.
if jsonErr, ok := err.(*btcjson.RPCError); ok {
if jsonErr.Code == btcjson.ErrRPCNoTxInfo {
return nil, nil
}
}
return nil, fmt.Errorf("unable to query for txid(%v): %v", txid, err)
}
// As we need to fully populate the returned TxConfirmation struct,
// grab the block in which the transaction was confirmed so we can
// locate its exact index within the block.
blockHash, err := chainhash.NewHashFromStr(tx.BlockHash)
if err != nil {
return nil, fmt.Errorf("unable to get block hash %v for historical "+
"dispatch: %v", tx.BlockHash, err)
return nil, fmt.Errorf("unable to get block hash %v for "+
"historical dispatch: %v", tx.BlockHash, err)
}
block, err := b.chainConn.GetBlockVerbose(blockHash)
if err != nil {
return nil, fmt.Errorf("unable to get block hash: %v", err)
return nil, fmt.Errorf("unable to get block with hash %v for "+
"historical dispatch: %v", blockHash, err)
}
// If the block obtained, locate the transaction's index within the
// If the block was obtained, locate the transaction's index within the
// block so we can give the subscriber full confirmation details.
txIndex := -1
targetTxidStr := txid.String()
for i, txHash := range block.Tx {
for txIndex, txHash := range block.Tx {
if txHash == targetTxidStr {
txIndex = i
break
}
}
if txIndex == -1 {
return nil, fmt.Errorf("unable to locate tx %v in block %v",
txid, blockHash)
}
txConf := chainntnfs.TxConfirmation{
return &chainntnfs.TxConfirmation{
BlockHash: blockHash,
BlockHeight: uint32(block.Height),
TxIndex: uint32(txIndex),
}, nil
}
return &txConf, nil
}
// We return an error because we should have found the transaction
// within the block, but didn't.
return nil, fmt.Errorf("unable to locate tx %v in block %v", txid,
blockHash)
}
// confDetailsManually looks up whether a transaction is already included in a
// block in the active chain by scanning the chain's blocks, starting from the
// earliest height the transaction could have been included in, to the current
// height in the chain. If the transaction is found, its confirmation details
// are returned. Otherwise, nil is returned.
func (b *BtcdNotifier) confDetailsManually(txid *chainhash.Hash,
heightHint, currentHeight uint32) (*chainntnfs.TxConfirmation, error) {
targetTxidStr := txid.String()
// Begin scanning blocks at every height to determine where the
// transaction was included in.
for height := heightHint; height <= currentHeight; height++ {
blockHash, err := b.chainConn.GetBlockHash(int64(height))
if err != nil {
return nil, fmt.Errorf("unable to get hash from block "+
"with height %d", height)
}
// TODO: fetch the neutrino filters instead.
block, err := b.chainConn.GetBlockVerbose(blockHash)
if err != nil {
return nil, fmt.Errorf("unable to get block with hash "+
"%v: %v", blockHash, err)
}
for txIndex, txHash := range block.Tx {
// If we're able to find the transaction in this block,
// return its confirmation details.
if txHash == targetTxidStr {
return &chainntnfs.TxConfirmation{
BlockHash: blockHash,
BlockHeight: height,
TxIndex: uint32(txIndex),
}, nil
}
}
}
// If we reach here, then we were not able to find the transaction
// within a block, so we avoid returning an error.
return nil, nil
}
// handleBlocksConnected applies a chain update for a new block. Any watched
@ -600,7 +672,10 @@ type spendNotification struct {
spendChan chan *chainntnfs.SpendDetail
spendID uint64
mempool bool
heightHint uint32
}
// spendCancel is a message sent to the BtcdNotifier when a client wishes to
@ -616,14 +691,16 @@ type spendCancel struct {
// RegisterSpendNtfn registers an intent to be notified once the target
// outpoint has been spent by a transaction on-chain. Once a spend of the target
// outpoint has been detected, the details of the spending event will be sent
// across the 'Spend' channel.
// across the 'Spend' channel. The heightHint should represent the earliest
// height in the chain where the transaction could have been spent in.
func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
_ uint32, mempool bool) (*chainntnfs.SpendEvent, error) {
heightHint uint32, mempool bool) (*chainntnfs.SpendEvent, error) {
ntfn := &spendNotification{
targetOutpoint: outpoint,
spendChan: make(chan *chainntnfs.SpendDetail, 1),
spendID: atomic.AddUint64(&b.spendClientCounter, 1),
heightHint: heightHint,
mempool: mempool,
}
@ -637,37 +714,59 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
return nil, err
}
// The following conditional checks to ensure that when a spend notification
// is registered, the output hasn't already been spent. If the output
// is no longer in the UTXO set, the chain will be rescanned from the point
// where the output was added. The rescan will dispatch the notification.
txout, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
// The following conditional checks to ensure that when a spend
// notification is registered, the output hasn't already been spent. If
// the output is no longer in the UTXO set, the chain will be rescanned
// from the point where the output was added. The rescan will dispatch
// the notification.
txOut, err := b.chainConn.GetTxOut(&outpoint.Hash, outpoint.Index, true)
if err != nil {
return nil, err
}
if txout == nil {
transaction, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
if txOut == nil {
// First, we'll attempt to retrieve the transaction's block hash
// using the backend's transaction index.
tx, err := b.chainConn.GetRawTransactionVerbose(&outpoint.Hash)
if err != nil {
// Avoid returning an error if the transaction was not
// found to proceed with fallback methods.
jsonErr, ok := err.(*btcjson.RPCError)
if !ok || jsonErr.Code != btcjson.ErrRPCNoTxInfo {
return nil, fmt.Errorf("unable to query for "+
"txid %v: %v", outpoint.Hash, err)
}
}
var blockHash *chainhash.Hash
if tx != nil && tx.BlockHash != "" {
// If we're able to retrieve a valid block hash from the
// transaction, then we'll use it as our rescan starting
// point.
blockHash, err = chainhash.NewHashFromStr(tx.BlockHash)
if err != nil {
return nil, err
}
} else {
// Otherwise, we'll attempt to retrieve the hash for the
// block at the heightHint.
blockHash, err = b.chainConn.GetBlockHash(
int64(heightHint),
)
if err != nil {
return nil, err
}
}
// We'll only request a rescan if the transaction has actually
// been included within a block. Otherwise, we'll encounter an
// error when scanning for blocks. This can happens in the case
// error when scanning for blocks. This can happen in the case
// of a race condition, wherein the output itself is unspent,
// and only arrives in the mempool after the getxout call.
if transaction != nil && transaction.BlockHash != "" {
blockhash, err := chainhash.NewHashFromStr(transaction.BlockHash)
if err != nil {
return nil, err
}
if blockHash != nil {
ops := []*wire.OutPoint{outpoint}
if err := b.chainConn.Rescan(blockhash, nil, ops); err != nil {
err = b.chainConn.Rescan(blockHash, nil, ops)
if err != nil {
chainntnfs.Log.Errorf("Rescan for spend "+
"notification txout failed: %v", err)
return nil, err
@ -706,22 +805,24 @@ func (b *BtcdNotifier) RegisterSpendNtfn(outpoint *wire.OutPoint,
// confirmationNotification represents a client's intent to receive a
// notification once the target txid reaches numConfirmations confirmations.
type confirmationsNotification struct {
type confirmationNotification struct {
chainntnfs.ConfNtfn
heightHint uint32
}
// RegisterConfirmationsNtfn registers a notification with BtcdNotifier
// which will be triggered once the txid reaches numConfs number of
// confirmations.
func (b *BtcdNotifier) RegisterConfirmationsNtfn(txid *chainhash.Hash,
numConfs, _ uint32) (*chainntnfs.ConfirmationEvent, error) {
numConfs, heightHint uint32) (*chainntnfs.ConfirmationEvent, error) {
ntfn := &confirmationsNotification{
chainntnfs.ConfNtfn{
ntfn := &confirmationNotification{
ConfNtfn: chainntnfs.ConfNtfn{
TxID: txid,
NumConfirmations: numConfs,
Event: chainntnfs.NewConfirmationEvent(numConfs),
},
heightHint: heightHint,
}
select {

@ -163,7 +163,6 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
var (
err error
cleanUp func()
btcdConn *chain.RPCClient
bitcoindConn *chain.BitcoindClient
)
@ -446,7 +445,6 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
}
walletConfig.ChainSource = chainRPC
btcdConn = chainRPC
// If we're not in simnet or regtest mode, then we'll attempt
// to use a proper fee estimator for testnet.
@ -522,41 +520,6 @@ func newChainControlFromConfig(cfg *config, chanDB *channeldb.DB,
cc.wallet = wallet
// As a final check, if we're using the RPC backend, we'll ensure that
// the btcd node has the txindex set. Atm, this is required in order to
// properly perform historical confirmation+spend dispatches.
if homeChainConfig.Node != "neutrino" {
// In order to check to see if we have the txindex up to date
// and active, we'll try to fetch the first transaction in the
// latest block via the index. If this doesn't succeed, then we
// know it isn't active (or just not yet up to date).
bestHash, _, err := cc.chainIO.GetBestBlock()
if err != nil {
return nil, nil, fmt.Errorf("unable to get current "+
"best hash: %v", err)
}
bestBlock, err := cc.chainIO.GetBlock(bestHash)
if err != nil {
return nil, nil, fmt.Errorf("unable to get current "+
"block hash: %v", err)
}
firstTxHash := bestBlock.Transactions[0].TxHash()
switch homeChainConfig.Node {
case "btcd":
_, err = btcdConn.GetRawTransaction(&firstTxHash)
case "bitcoind":
_, err = bitcoindConn.GetRawTransactionVerbose(&firstTxHash)
}
if err != nil {
// If the node doesn't have the txindex set, then we'll
// halt startup, as we can't proceed in this state.
return nil, nil, fmt.Errorf("%s detected to not "+
"have --txindex active, cannot proceed",
homeChainConfig.Node)
}
}
return cc, cleanUp, nil
}