Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/main/scala/org/ergoplatform/mining/CandidateGenerator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,15 @@ class CandidateGenerator(
case gen @ GenerateCandidate(txsToInclude, reply, forced, optPk) =>
val senderOpt = if (reply) Some(sender()) else None
val effectiveMinerPk = optPk.getOrElse(minerPk)
if (!forced && cachedFor(state.cachedCandidate, txsToInclude, effectiveMinerPk)) {
if (
!forced &&
cachedFor(state.cachedCandidate, txsToInclude, effectiveMinerPk) &&
!hasCandidateExpired(
state.cachedCandidate,
state.solvedBlock,
candidateGenInterval
)
) {
senderOpt.foreach(_ ! StatusReply.success(state.cachedCandidate.get))
} else {
val start = System.currentTimeMillis()
Expand Down
121 changes: 116 additions & 5 deletions src/test/scala/org/ergoplatform/mining/CandidateGeneratorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,117 @@ class CandidateGeneratorSpec extends AnyFlatSpec with Matchers with ErgoTestHelp
system.terminate()
}

it should "refresh an expired candidate after an earlier mempool change" in new TestKit(
ActorSystem()
) {
val testProbe = new TestProbe(system)
system.eventStream.subscribe(testProbe.ref, newBlockSignal)

val regenerationInterval = 5.seconds
val testDir =
s"${defaultSettings.directory}-expired-cache-${System.currentTimeMillis()}"
val testSettings = ErgoSettingsReader.read()
.copy(
nodeSettings = defaultSettings.nodeSettings
.copy(blockCandidateGenerationInterval = regenerationInterval),
chainSettings = defaultSettings.chainSettings.copy(blockInterval = 1.seconds),
directory = testDir
)

val viewHolderRef: ActorRef = ErgoNodeViewRef(testSettings)
val readersHolderRef: ActorRef = ErgoReadersHolderRef(viewHolderRef)
val candidateGenerator: ActorRef = CandidateGenerator(
defaultMinerSecret.publicImage,
readersHolderRef,
viewHolderRef,
testSettings
)

val powScheme = testSettings.chainSettings.powScheme

candidateGenerator.tell(
GenerateCandidate(Seq.empty, reply = true, forced = false),
testProbe.ref
)
val initialCandidate = testProbe.expectMsgPF(candidateGenDelay) {
case StatusReply.Success(c: Candidate) => c
}
val initialBlock = powScheme
.proveCandidate(initialCandidate.candidateBlock, defaultMinerSecret.w, 0, 1000)
.get
candidateGenerator.tell(initialBlock.header.powSolution, testProbe.ref)

var ackSeen = false
var appliedSeen = false
testProbe.fishForMessage(blockValidationDelay) {
case StatusReply.Success(()) =>
ackSeen = true
ackSeen && appliedSeen
case FullBlockApplied(header) if header.id == initialBlock.header.id =>
appliedSeen = true
ackSeen && appliedSeen
case _ => false
}

val readers: Readers = await((readersHolderRef ? GetReaders).mapTo[Readers])
val prop = DLogProverInput(
BigIntegers.fromUnsignedByteArray("expired-cache-test".getBytes())
).publicImage
val rewardBox = readers.h.bestFullBlockOpt.get.transactions.last.outputs.last
val unsignedTx = new UnsignedErgoTransaction(
IndexedSeq(Input(rewardBox.id, emptyProverResult)),
IndexedSeq(),
IndexedSeq(
new ErgoBoxCandidate(
rewardBox.value,
ErgoTree.fromSigmaBoolean(prop),
readers.s.stateContext.currentHeight
)
)
)
val tx = ErgoTransaction(
defaultProver
.sign(unsignedTx, IndexedSeq(rewardBox), IndexedSeq(), readers.s.stateContext)
.get
)

candidateGenerator.tell(
GenerateCandidate(Seq.empty, reply = true, forced = true),
testProbe.ref
)
val freshCandidate = testProbe.expectMsgPF(candidateGenDelay) {
case StatusReply.Success(c: Candidate) => c
}

val poolWithTx = ErgoMemPool.empty(testSettings).put(UnconfirmedTransaction(tx, None))
candidateGenerator.tell(ChangedMempool(poolWithTx), testProbe.ref)
candidateGenerator.tell(
GenerateCandidate(Seq.empty, reply = true, forced = false),
testProbe.ref
)
val unexpiredCandidate = testProbe.expectMsgPF(candidateGenDelay) {
case StatusReply.Success(c: Candidate) => c
}
unexpiredCandidate.candidateBlock shouldBe freshCandidate.candidateBlock
unexpiredCandidate.candidateBlock.transactions.map(_.id) should not contain tx.id

val remainingMillis = regenerationInterval.toMillis -
(System.currentTimeMillis() - freshCandidate.candidateBlock.timestamp) + 200
remainingMillis should be > 200L
testProbe.expectNoMessage(remainingMillis.millis)

candidateGenerator.tell(
GenerateCandidate(Seq.empty, reply = true, forced = false),
testProbe.ref
)
val regeneratedCandidate = testProbe.expectMsgPF(candidateGenDelay) {
case StatusReply.Success(c: Candidate) => c
}

regeneratedCandidate.candidateBlock.transactions.map(_.id) should contain(tx.id)
system.terminate()
}

it should "accept solution for previous candidate after regeneration" in new TestKit(ActorSystem()) {
val testProbe = new TestProbe(system)
system.eventStream.subscribe(testProbe.ref, newBlockSignal)
Expand Down Expand Up @@ -831,28 +942,28 @@ class CandidateGeneratorSpec extends AnyFlatSpec with Matchers with ErgoTestHelp
system.eventStream.subscribe(testProbe.ref, newBlockSignal)

val testDir = s"${defaultSettings.directory}-ignore-cache-${System.currentTimeMillis()}"
val settingsWithShortRegeneration: ErgoSettings =
val settingsWithLongCache: ErgoSettings =
ErgoSettingsReader.read()
.copy(
nodeSettings = defaultSettings.nodeSettings
.copy(blockCandidateGenerationInterval = 1.millis),
.copy(blockCandidateGenerationInterval = 1.minute),
chainSettings =
ErgoSettingsReader.read().chainSettings.copy(blockInterval = 1.seconds),
directory = testDir
)

val viewHolderRef: ActorRef = ErgoNodeViewRef(settingsWithShortRegeneration)
val viewHolderRef: ActorRef = ErgoNodeViewRef(settingsWithLongCache)
val readersHolderRef: ActorRef = ErgoReadersHolderRef(viewHolderRef)

val candidateGenerator: ActorRef =
CandidateGenerator(
defaultMinerSecret.publicImage,
readersHolderRef,
viewHolderRef,
settingsWithShortRegeneration
settingsWithLongCache
)

val powScheme = settingsWithShortRegeneration.chainSettings.powScheme
val powScheme = settingsWithLongCache.chainSettings.powScheme

// First mine a block to establish chain (needed for avg mining time calculation)
candidateGenerator.tell(GenerateCandidate(Seq.empty, reply = true, forced = false), testProbe.ref)
Expand Down
Loading