mirror of
https://github.com/bitcoin-s/bitcoin-s.git
synced 2025-01-19 05:43:51 +01:00
Rework NodeTestUtil
to use a specific bestBlockHash
(#5332)
* Rework NodeTestUtil to use a specific bestBlockHash, this is useful in reorg situations Use param in reorg test, modify scaladoc WIP * Cleanup * Try to fix usage of stopHashBE * Cleanup * WIP: Fix getCompactFilterStartHeight() * Revert logback-test.xml * Fix bug with isFiltersSynced() in reorg situations * scalafmt, fix compile * Fix another bug with isFiltersSynced() * Fix compile
This commit is contained in:
parent
5e81ec5ed2
commit
b39736fb8d
@ -1181,6 +1181,8 @@ case class CompactFilterHeadersMessage(
|
|||||||
filterHashes: Vector[DoubleSha256Digest])
|
filterHashes: Vector[DoubleSha256Digest])
|
||||||
extends DataPayload {
|
extends DataPayload {
|
||||||
|
|
||||||
|
val stopHashBE: DoubleSha256DigestBE = stopHash.flip
|
||||||
|
|
||||||
/** The number of hashes in this message */
|
/** The number of hashes in this message */
|
||||||
val filterHashesLength: CompactSizeUInt = CompactSizeUInt(
|
val filterHashesLength: CompactSizeUInt = CompactSizeUInt(
|
||||||
UInt64(filterHashes.length))
|
UInt64(filterHashes.length))
|
||||||
|
@ -389,20 +389,26 @@ class NeutrinoNodeTest extends NodeTestWithCachedBitcoindPair {
|
|||||||
val bitcoind0 = bitcoinds(0)
|
val bitcoind0 = bitcoinds(0)
|
||||||
val bitcoind1 = bitcoinds(1)
|
val bitcoind1 = bitcoinds(1)
|
||||||
for {
|
for {
|
||||||
_ <- NodeTestUtil.awaitAllSync(node, bitcoind0)
|
_ <- NodeTestUtil.awaitSyncAndIBD(node, bitcoind0)
|
||||||
//disconnect bitcoind1 as we don't need it
|
//disconnect bitcoind1 as we don't need it
|
||||||
nodeUri1 <- NodeTestUtil.getNodeURIFromBitcoind(bitcoind1)
|
nodeUri1 <- NodeTestUtil.getNodeURIFromBitcoind(bitcoind1)
|
||||||
_ <- bitcoind1.disconnectNode(nodeUri1)
|
_ <- bitcoind1.disconnectNode(nodeUri1)
|
||||||
bestBlockHash0 <- bitcoind0.getBestBlockHash()
|
bestBlockHash0 <- bitcoind0.getBestBlockHash()
|
||||||
|
//invalidate blockhash to force a reorg when next block is generated
|
||||||
_ <- bitcoind0.invalidateBlock(bestBlockHash0)
|
_ <- bitcoind0.invalidateBlock(bestBlockHash0)
|
||||||
|
_ <- AsyncUtil.retryUntilSatisfiedF(
|
||||||
|
() => node.getConnectionCount.map(_ == 1),
|
||||||
|
1.second)
|
||||||
//now generate a block, make sure we sync with them
|
//now generate a block, make sure we sync with them
|
||||||
hashes <- bitcoind0.generate(1)
|
hashes0 <- bitcoind0.generate(1)
|
||||||
chainApi <- node.chainApiFromDb()
|
chainApi <- node.chainApiFromDb()
|
||||||
_ <- AsyncUtil.retryUntilSatisfiedF(() =>
|
_ <- AsyncUtil.retryUntilSatisfiedF(() =>
|
||||||
chainApi.getHeader(hashes.head).map(_.isDefined))
|
chainApi.getHeader(hashes0.head).map(_.isDefined))
|
||||||
//generate another block to make sure the reorg is complete
|
//generate another block to make sure the reorg is complete
|
||||||
_ <- bitcoind0.generate(1)
|
hashes1 <- bitcoind0.generate(1)
|
||||||
_ <- NodeTestUtil.awaitAllSync(node, bitcoind0)
|
_ <- NodeTestUtil.awaitAllSync(node = node,
|
||||||
|
bitcoind = bitcoind0,
|
||||||
|
bestBlockHashBE = Some(hashes1.head))
|
||||||
} yield succeed
|
} yield succeed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -74,7 +74,7 @@ case class PeerManager(
|
|||||||
private def syncCompactFilters(
|
private def syncCompactFilters(
|
||||||
bestFilterHeader: CompactFilterHeaderDb,
|
bestFilterHeader: CompactFilterHeaderDb,
|
||||||
chainApi: ChainApi,
|
chainApi: ChainApi,
|
||||||
compactFilterStartHeight: Int,
|
compactFilterStartHeightOpt: Option[Int],
|
||||||
nodeState: SyncNodeState)(implicit
|
nodeState: SyncNodeState)(implicit
|
||||||
chainAppConfig: ChainAppConfig): Future[Unit] = {
|
chainAppConfig: ChainAppConfig): Future[Unit] = {
|
||||||
val syncPeer = nodeState.syncPeer
|
val syncPeer = nodeState.syncPeer
|
||||||
@ -97,15 +97,13 @@ case class PeerManager(
|
|||||||
|
|
||||||
sendCompactFilterHeaderMsgF.flatMap { isSyncFilterHeaders =>
|
sendCompactFilterHeaderMsgF.flatMap { isSyncFilterHeaders =>
|
||||||
// If we have started syncing filters
|
// If we have started syncing filters
|
||||||
if (
|
if (!isSyncFilterHeaders) {
|
||||||
!isSyncFilterHeaders && compactFilterStartHeight < bestFilterHeader.height
|
|
||||||
) {
|
|
||||||
PeerManager
|
PeerManager
|
||||||
.sendNextGetCompactFilterCommand(
|
.sendNextGetCompactFilterCommand(
|
||||||
peerMessageSenderApi = peerMsgSender,
|
peerMessageSenderApi = peerMsgSender,
|
||||||
chainApi = chainApi,
|
chainApi = chainApi,
|
||||||
filterBatchSize = chainAppConfig.filterBatchSize,
|
filterBatchSize = chainAppConfig.filterBatchSize,
|
||||||
startHeightOpt = Some(compactFilterStartHeight),
|
startHeightOpt = compactFilterStartHeightOpt,
|
||||||
stopBlockHash = bestFilterHeader.blockHashBE,
|
stopBlockHash = bestFilterHeader.blockHashBE,
|
||||||
peer = syncPeer
|
peer = syncPeer
|
||||||
)
|
)
|
||||||
@ -927,19 +925,19 @@ case class PeerManager(
|
|||||||
} else {
|
} else {
|
||||||
syncCompactFilters(bestFilterHeader = bestFilterHeader,
|
syncCompactFilters(bestFilterHeader = bestFilterHeader,
|
||||||
chainApi = chainApi,
|
chainApi = chainApi,
|
||||||
compactFilterStartHeight = bestFilter.height,
|
compactFilterStartHeightOpt = None,
|
||||||
nodeState = nodeState)
|
nodeState = nodeState)
|
||||||
}
|
}
|
||||||
case (Some(bestFilterHeader), None) =>
|
case (Some(bestFilterHeader), None) =>
|
||||||
val compactFilterStartHeightF =
|
val compactFilterStartHeightOptF =
|
||||||
PeerManager.getCompactFilterStartHeight(chainApi,
|
PeerManager.getCompactFilterStartHeight(chainApi,
|
||||||
walletCreationTimeOpt)
|
walletCreationTimeOpt)
|
||||||
for {
|
for {
|
||||||
compactFilterStartHeight <- compactFilterStartHeightF
|
compactFilterStartHeightOpt <- compactFilterStartHeightOptF
|
||||||
_ <- syncCompactFilters(bestFilterHeader = bestFilterHeader,
|
_ <- syncCompactFilters(bestFilterHeader = bestFilterHeader,
|
||||||
chainApi = chainApi,
|
chainApi = chainApi,
|
||||||
compactFilterStartHeight =
|
compactFilterStartHeightOpt =
|
||||||
compactFilterStartHeight,
|
compactFilterStartHeightOpt,
|
||||||
nodeState = nodeState)
|
nodeState = nodeState)
|
||||||
} yield ()
|
} yield ()
|
||||||
|
|
||||||
@ -1112,11 +1110,10 @@ object PeerManager extends Logging {
|
|||||||
chainApi.nextFilterHeaderBatchRange(stopBlockHash = stopBlockHash,
|
chainApi.nextFilterHeaderBatchRange(stopBlockHash = stopBlockHash,
|
||||||
batchSize = filterBatchSize,
|
batchSize = filterBatchSize,
|
||||||
startHeightOpt = startHeightOpt)
|
startHeightOpt = startHeightOpt)
|
||||||
|
_ = logger.info(
|
||||||
|
s"Requesting compact filters from $filterSyncMarkerOpt with peer=$peer startHeightOpt=$startHeightOpt stopBlockHashBE=$stopBlockHash")
|
||||||
res <- filterSyncMarkerOpt match {
|
res <- filterSyncMarkerOpt match {
|
||||||
case Some(filterSyncMarker) =>
|
case Some(filterSyncMarker) =>
|
||||||
logger.info(
|
|
||||||
s"Requesting compact filters from $filterSyncMarker with peer=$peer")
|
|
||||||
|
|
||||||
peerMessageSenderApi
|
peerMessageSenderApi
|
||||||
.sendGetCompactFiltersMessage(filterSyncMarker)
|
.sendGetCompactFiltersMessage(filterSyncMarker)
|
||||||
.map(_ => true)
|
.map(_ => true)
|
||||||
@ -1167,11 +1164,11 @@ object PeerManager extends Logging {
|
|||||||
def getCompactFilterStartHeight(
|
def getCompactFilterStartHeight(
|
||||||
chainApi: ChainApi,
|
chainApi: ChainApi,
|
||||||
walletCreationTimeOpt: Option[Instant])(implicit
|
walletCreationTimeOpt: Option[Instant])(implicit
|
||||||
ec: ExecutionContext): Future[Int] = {
|
ec: ExecutionContext): Future[Option[Int]] = {
|
||||||
chainApi.getBestFilter().flatMap {
|
chainApi.getBestFilter().flatMap {
|
||||||
case Some(f) =>
|
case Some(_) =>
|
||||||
//we have already started syncing filters, return the height of the last filter seen
|
//we have already started syncing filters, return the height of the last filter seen
|
||||||
Future.successful(f.height + 1)
|
Future.successful(None)
|
||||||
case None =>
|
case None =>
|
||||||
walletCreationTimeOpt match {
|
walletCreationTimeOpt match {
|
||||||
case Some(instant) =>
|
case Some(instant) =>
|
||||||
@ -1189,10 +1186,11 @@ object PeerManager extends Logging {
|
|||||||
//want to choose the maximum out of these too
|
//want to choose the maximum out of these too
|
||||||
//if our internal chainstate filter count is > creationTimeHeight
|
//if our internal chainstate filter count is > creationTimeHeight
|
||||||
//we just want to start syncing from our last seen filter
|
//we just want to start syncing from our last seen filter
|
||||||
Math.max(height, filterCount)
|
val result = Math.max(height, filterCount)
|
||||||
|
Some(result)
|
||||||
}
|
}
|
||||||
case None =>
|
case None =>
|
||||||
Future.successful(0)
|
Future.successful(None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -182,9 +182,10 @@ case class DataMessageHandler(
|
|||||||
for {
|
for {
|
||||||
sortedBlockFilters <- sortedBlockFiltersF
|
sortedBlockFilters <- sortedBlockFiltersF
|
||||||
sortedFilterMessages = sortedBlockFilters.map(_._2)
|
sortedFilterMessages = sortedBlockFilters.map(_._2)
|
||||||
|
filterBestBlockHashBE = sortedFilterMessages.lastOption
|
||||||
|
.map(_.blockHashBE)
|
||||||
_ = logger.debug(
|
_ = logger.debug(
|
||||||
s"Processing ${filterBatch.size} filters bestBlockHashBE=${sortedFilterMessages.lastOption
|
s"Processing ${filterBatch.size} filters bestBlockHashBE=${filterBestBlockHashBE}")
|
||||||
.map(_.blockHashBE)}")
|
|
||||||
newChainApi <- chainApi.processFilters(sortedFilterMessages)
|
newChainApi <- chainApi.processFilters(sortedFilterMessages)
|
||||||
sortedGolombFilters = sortedBlockFilters.map(x =>
|
sortedGolombFilters = sortedBlockFilters.map(x =>
|
||||||
(x._1, x._3))
|
(x._1, x._3))
|
||||||
@ -524,7 +525,7 @@ case class DataMessageHandler(
|
|||||||
private def sendFirstGetCompactFilterCommand(
|
private def sendFirstGetCompactFilterCommand(
|
||||||
peerMessageSenderApi: PeerMessageSenderApi,
|
peerMessageSenderApi: PeerMessageSenderApi,
|
||||||
stopBlockHash: DoubleSha256DigestBE,
|
stopBlockHash: DoubleSha256DigestBE,
|
||||||
startHeight: Int,
|
startHeightOpt: Option[Int],
|
||||||
syncNodeState: SyncNodeState): Future[Option[NodeState.FilterSync]] = {
|
syncNodeState: SyncNodeState): Future[Option[NodeState.FilterSync]] = {
|
||||||
logger.info(s"Beginning to sync filters to stopBlockHashBE=$stopBlockHash")
|
logger.info(s"Beginning to sync filters to stopBlockHashBE=$stopBlockHash")
|
||||||
|
|
||||||
@ -538,7 +539,7 @@ case class DataMessageHandler(
|
|||||||
}
|
}
|
||||||
|
|
||||||
sendNextGetCompactFilterCommand(peerMessageSenderApi = peerMessageSenderApi,
|
sendNextGetCompactFilterCommand(peerMessageSenderApi = peerMessageSenderApi,
|
||||||
startHeightOpt = Some(startHeight),
|
startHeightOpt = startHeightOpt,
|
||||||
stopBlockHash = stopBlockHash,
|
stopBlockHash = stopBlockHash,
|
||||||
fs = fs)
|
fs = fs)
|
||||||
}
|
}
|
||||||
@ -756,7 +757,9 @@ case class DataMessageHandler(
|
|||||||
val recoveredStateF: Future[NodeState] = getHeadersF.recoverWith {
|
val recoveredStateF: Future[NodeState] = getHeadersF.recoverWith {
|
||||||
case _: DuplicateHeaders =>
|
case _: DuplicateHeaders =>
|
||||||
logger.warn(s"Received duplicate headers from ${peer} in state=$state")
|
logger.warn(s"Received duplicate headers from ${peer} in state=$state")
|
||||||
Future.successful(headerSyncState)
|
val d = DoneSyncing(headerSyncState.peersWithServices,
|
||||||
|
headerSyncState.waitingForDisconnection)
|
||||||
|
Future.successful(d)
|
||||||
case _: InvalidBlockHeader =>
|
case _: InvalidBlockHeader =>
|
||||||
logger.warn(
|
logger.warn(
|
||||||
s"Invalid headers of count $count sent from ${peer} in state=$state")
|
s"Invalid headers of count $count sent from ${peer} in state=$state")
|
||||||
@ -792,8 +795,7 @@ case class DataMessageHandler(
|
|||||||
val blockCountF = chainApi.getBlockCount()
|
val blockCountF = chainApi.getBlockCount()
|
||||||
val bestBlockHashF = chainApi.getBestBlockHash()
|
val bestBlockHashF = chainApi.getBestBlockHash()
|
||||||
for {
|
for {
|
||||||
_ <- chainApi.processFilterHeaders(filterHeaders,
|
_ <- chainApi.processFilterHeaders(filterHeaders, filterHeader.stopHashBE)
|
||||||
filterHeader.stopHash.flip)
|
|
||||||
filterHeaderCount <- chainApi.getFilterHeaderCount()
|
filterHeaderCount <- chainApi.getFilterHeaderCount()
|
||||||
blockCount <- blockCountF
|
blockCount <- blockCountF
|
||||||
bestBlockHash <- bestBlockHashF
|
bestBlockHash <- bestBlockHashF
|
||||||
@ -804,18 +806,17 @@ case class DataMessageHandler(
|
|||||||
sendNextGetCompactFilterHeadersCommand(
|
sendNextGetCompactFilterHeadersCommand(
|
||||||
peerMessageSenderApi = peerMessageSenderApi,
|
peerMessageSenderApi = peerMessageSenderApi,
|
||||||
syncPeer = peer,
|
syncPeer = peer,
|
||||||
prevStopHash = filterHeader.stopHash.flip,
|
prevStopHash = filterHeader.stopHashBE,
|
||||||
stopHash = bestBlockHash).map(_ => filterHeaderSync)
|
stopHash = bestBlockHash).map(_ => filterHeaderSync)
|
||||||
} else {
|
} else {
|
||||||
for {
|
for {
|
||||||
startHeight <- PeerManager.getCompactFilterStartHeight(
|
startHeightOpt <- PeerManager.getCompactFilterStartHeight(
|
||||||
chainApi,
|
chainApi,
|
||||||
walletCreationTimeOpt)
|
walletCreationTimeOpt)
|
||||||
bestBlockHash <- bestBlockHashF
|
|
||||||
filterSyncStateOpt <- sendFirstGetCompactFilterCommand(
|
filterSyncStateOpt <- sendFirstGetCompactFilterCommand(
|
||||||
peerMessageSenderApi = peerMessageSenderApi,
|
peerMessageSenderApi = peerMessageSenderApi,
|
||||||
stopBlockHash = bestBlockHash,
|
stopBlockHash = filterHeader.stopHashBE,
|
||||||
startHeight = startHeight,
|
startHeightOpt = startHeightOpt,
|
||||||
syncNodeState = filterHeaderSync)
|
syncNodeState = filterHeaderSync)
|
||||||
} yield {
|
} yield {
|
||||||
filterSyncStateOpt match {
|
filterSyncStateOpt match {
|
||||||
|
@ -79,6 +79,29 @@ abstract class NodeTestUtil extends P2PLogger {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private def isSameBestFilter(
|
||||||
|
node: NeutrinoNode,
|
||||||
|
rpc: BitcoindRpcClient,
|
||||||
|
bestBlockHashBEOpt: Option[DoubleSha256DigestBE])(implicit
|
||||||
|
ec: ExecutionContext): Future[Boolean] = {
|
||||||
|
|
||||||
|
val bestBlockHashBEF = bestBlockHashBEOpt match {
|
||||||
|
case Some(bestBlockHash) => Future.successful(bestBlockHash)
|
||||||
|
case None => rpc.getBestBlockHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
bestBlockHashBE <- bestBlockHashBEF
|
||||||
|
chainApi <- node.chainApiFromDb()
|
||||||
|
bestFilterOpt <- chainApi.getBestFilter()
|
||||||
|
} yield {
|
||||||
|
bestFilterOpt match {
|
||||||
|
case Some(bestFilter) => bestFilter.blockHashBE == bestBlockHashBE
|
||||||
|
case None => false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def isSameBestFilterHeight(node: NeutrinoNode, rpc: BitcoindRpcClient)(
|
def isSameBestFilterHeight(node: NeutrinoNode, rpc: BitcoindRpcClient)(
|
||||||
implicit ec: ExecutionContext): Future[Boolean] = {
|
implicit ec: ExecutionContext): Future[Boolean] = {
|
||||||
val rpcCountF = rpc.getBlockCount()
|
val rpcCountF = rpc.getBlockCount()
|
||||||
@ -137,24 +160,35 @@ abstract class NodeTestUtil extends P2PLogger {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Awaits sync between the given node and bitcoind client */
|
/** Awaits sync between the given node and bitcoind client */
|
||||||
def awaitCompactFiltersSync(node: NeutrinoNode, rpc: BitcoindRpcClient)(
|
def awaitCompactFiltersSync(
|
||||||
implicit sys: ActorSystem): Future[Unit] = {
|
node: NeutrinoNode,
|
||||||
|
rpc: BitcoindRpcClient,
|
||||||
|
bestBlockHashBEOpt: Option[DoubleSha256DigestBE] = None)(implicit
|
||||||
|
sys: ActorSystem): Future[Unit] = {
|
||||||
import sys.dispatcher
|
import sys.dispatcher
|
||||||
TestAsyncUtil
|
TestAsyncUtil
|
||||||
.retryUntilSatisfiedF(() => isSameBestFilterHeight(node, rpc),
|
.retryUntilSatisfiedF(
|
||||||
1.second,
|
() => isSameBestFilter(node, rpc, bestBlockHashBEOpt),
|
||||||
maxTries = syncTries)
|
1.second,
|
||||||
|
maxTries = syncTries)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The future doesn't complete until the nodes best hash is the given hash */
|
/** The future doesn't complete until the nodes best hash is the given hash */
|
||||||
def awaitBestHash(node: Node, bitcoind: BitcoindRpcClient)(implicit
|
def awaitBestHash(
|
||||||
|
node: Node,
|
||||||
|
bitcoind: BitcoindRpcClient,
|
||||||
|
bestHashOpt: Option[DoubleSha256DigestBE] = None)(implicit
|
||||||
system: ActorSystem): Future[Unit] = {
|
system: ActorSystem): Future[Unit] = {
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
def bestBitcoinSHashF: Future[DoubleSha256DigestBE] = {
|
def bestBitcoinSHashF: Future[DoubleSha256DigestBE] = {
|
||||||
node.chainApiFromDb().flatMap(_.getBestBlockHash())
|
node.chainApiFromDb().flatMap(_.getBestBlockHash())
|
||||||
}
|
}
|
||||||
def bestBitcoindHashF: Future[DoubleSha256DigestBE] =
|
def bestBitcoindHashF: Future[DoubleSha256DigestBE] = {
|
||||||
bitcoind.getBestBlockHash()
|
bestHashOpt match {
|
||||||
|
case Some(bestHash) => Future.successful(bestHash)
|
||||||
|
case None => bitcoind.getBestBlockHash()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TestAsyncUtil.retryUntilSatisfiedF(
|
TestAsyncUtil.retryUntilSatisfiedF(
|
||||||
() => {
|
() => {
|
||||||
@ -168,14 +202,22 @@ abstract class NodeTestUtil extends P2PLogger {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Awaits header, filter header and filter sync between the neutrino node and rpc client */
|
/** Awaits header, filter header and filter sync between the neutrino node and rpc client
|
||||||
def awaitAllSync(node: NeutrinoNode, bitcoind: BitcoindRpcClient)(implicit
|
* @param the node we are syncing
|
||||||
|
* @param bitcoind the node we are syncing against
|
||||||
|
* @param bestBlockHashBE the best block hash we are expected to sync to, this is useful for reorg situations.
|
||||||
|
* If None given, we use bitcoind's best block header
|
||||||
|
*/
|
||||||
|
def awaitAllSync(
|
||||||
|
node: NeutrinoNode,
|
||||||
|
bitcoind: BitcoindRpcClient,
|
||||||
|
bestBlockHashBE: Option[DoubleSha256DigestBE] = None)(implicit
|
||||||
system: ActorSystem): Future[Unit] = {
|
system: ActorSystem): Future[Unit] = {
|
||||||
import system.dispatcher
|
import system.dispatcher
|
||||||
for {
|
for {
|
||||||
_ <- NodeTestUtil.awaitBestHash(node, bitcoind)
|
_ <- NodeTestUtil.awaitBestHash(node, bitcoind, bestBlockHashBE)
|
||||||
_ <- NodeTestUtil.awaitCompactFilterHeadersSync(node, bitcoind)
|
_ <- NodeTestUtil.awaitCompactFilterHeadersSync(node, bitcoind)
|
||||||
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind)
|
_ <- NodeTestUtil.awaitCompactFiltersSync(node, bitcoind, bestBlockHashBE)
|
||||||
} yield ()
|
} yield ()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user