Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

i1722-avoid-wallet-rescan-after-init #1723

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ class ErgoWalletActor(settings: ErgoSettings,
}
}

private def emptyWallet: Receive = {
private def expectWalletReading: Receive = {
case ReadWallet(state) =>
val ws = settings.walletSettings
// Try to read wallet from json file or test mnemonic provided in a config file
Expand All @@ -97,7 +97,7 @@ class ErgoWalletActor(settings: ErgoSettings,
ergoWalletService.initWallet(state, settings, walletPass, mnemonicPassOpt) match {
case Success((mnemonic, newState)) =>
log.info("Wallet is initialized")
context.become(loadedWallet(newState))
context.become(loadedWallet(newState.copy(walletPhase = WalletPhase.Created)))
self ! UnlockWallet(walletPass)
sender() ! Success(mnemonic)
case Failure(t) =>
Expand All @@ -112,7 +112,7 @@ class ErgoWalletActor(settings: ErgoSettings,
ergoWalletService.restoreWallet(state, settings, mnemonic, mnemonicPassOpt, walletPass, usePre1627KeyDerivation) match {
case Success(newState) =>
log.info("Wallet is restored")
context.become(loadedWallet(newState))
context.become(loadedWallet(newState.copy(walletPhase = WalletPhase.Restored)))
self ! UnlockWallet(walletPass)
sender() ! Success(())
case Failure(t) =>
Expand Down Expand Up @@ -267,7 +267,9 @@ class ErgoWalletActor(settings: ErgoSettings,
case ScanOnChain(newBlock) =>
if (state.secretIsSet(settings.walletSettings.testMnemonic)) { // scan blocks only if wallet is initialized
val nextBlockHeight = state.expectedNextBlockHeight(newBlock.height, settings.nodeSettings.isFullBlocksPruned)
if (nextBlockHeight == newBlock.height) {
// we want to scan a block either when it is its turn or when wallet is freshly created (no need to load the past)
val walletFreshlyCreated = state.walletPhase == WalletPhase.Created && state.getWalletHeight == 0
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly, only state.getWalletHeight == 0 is not enough as restored wallet has also state.getWalletHeight == 0, that's why the WalletPhase introduction

if (nextBlockHeight == newBlock.height || walletFreshlyCreated) {
log.info(s"Wallet is going to scan a block ${newBlock.id} on chain at height ${newBlock.height}")
val newState =
ergoWalletService.scanBlockUpdate(state, newBlock, settings.walletSettings.dustLimit) match {
Expand All @@ -276,14 +278,17 @@ class ErgoWalletActor(settings: ErgoSettings,
log.error(errorMsg, ex)
state.copy(error = Some(errorMsg))
case Success(updatedState) =>
if (walletFreshlyCreated) {
logger.info(s"Freshly created wallet initialized at height $nextBlockHeight without scanning the past.")
}
updatedState
}
context.become(loadedWallet(newState))
} else if (nextBlockHeight < newBlock.height) {
log.warn(s"Wallet: skipped blocks found starting from $nextBlockHeight, going back to scan them")
self ! ScanInThePast(nextBlockHeight, false)
log.warn(s"Wallet at height ${state.getWalletHeight}: skipped blocks found starting from $nextBlockHeight, going back to scan them")
self ! ScanInThePast(nextBlockHeight, rescan = false)
} else {
log.warn(s"Wallet: block in the past reported at ${newBlock.height}, blockId: ${newBlock.id}")
log.warn(s"Wallet at height ${state.getWalletHeight}: block in the past reported at ${newBlock.height}, blockId: ${newBlock.id}")
}
}

Expand Down Expand Up @@ -492,7 +497,7 @@ class ErgoWalletActor(settings: ErgoSettings,
sender() ! txsToSend
}

override def receive: Receive = emptyWallet
override def receive: Receive = expectWalletReading

private def wrapLegalExc[T](e: Throwable): Failure[T] =
if (e.getMessage.startsWith("Illegal key size")) {
Expand Down Expand Up @@ -524,6 +529,17 @@ object ErgoWalletActor extends ScorexLogging {
walletActorRef
}

/** Wallet transitions either from Default -> Initialized or Default -> Restored */
trait WalletPhase
object WalletPhase {
/** Wallet is expecting either Creation or Restoration */
case object UnInitialized extends WalletPhase
/** New wallet initialized with generated mnemonic in this runtime */
case object Created extends WalletPhase
/** Wallet restored from existing mnemonic in this runtime */
case object Restored extends WalletPhase
}

// Private signals the wallet actor sends to itself
/**
* A signal the wallet actor sends to itself to scan a block in the past
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.ergoplatform._
import org.ergoplatform.modifiers.ErgoFullBlock
import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnsignedErgoTransaction}
import org.ergoplatform.nodeView.state.{ErgoStateContext, UtxoStateReader}
import org.ergoplatform.nodeView.wallet.ErgoWalletActor.WalletPhase
import org.ergoplatform.nodeView.wallet.ErgoWalletService.DeriveNextKeyResult
import org.ergoplatform.nodeView.wallet.models.{ChangeBox, CollectedBoxes}
import org.ergoplatform.nodeView.wallet.persistence.{WalletRegistry, WalletStorage}
Expand Down Expand Up @@ -73,7 +74,7 @@ trait ErgoWalletService {
settings: ErgoSettings,
mnemonic: SecretString,
mnemonicPassOpt: Option[SecretString],
walletPass: SecretString,
walletPass: SecretString,
usePre1627KeyDerivation: Boolean): Try[ErgoWalletState]

/**
Expand Down Expand Up @@ -563,6 +564,7 @@ class ErgoWalletServiceImpl(override val ergoSettings: ErgoSettings) extends Erg
block,
state.outputsFilter,
dustLimit,
state.walletPhase == WalletPhase.Created,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why always Created here?

Copy link
Collaborator Author

@pragmaxim pragmaxim Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The scanBlockTransactions method takes walletCreated: Boolean parameter as there is check inside that requires information whether it was restored or created ...

Copy link
Collaborator Author

@pragmaxim pragmaxim Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could pass WalletPhase argument and it would be probably more readable ... gimme an hour

ergoSettings.walletSettings.walletProfile).map { case (reg, offReg, updatedOutputsFilter) =>
state.copy(registry = reg, offChainRegistry = offReg, outputsFilter = Some(updatedOutputsFilter))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import org.ergoplatform._
import org.ergoplatform.nodeView.history.ErgoHistory.Height
import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader
import org.ergoplatform.nodeView.state.{ErgoStateContext, ErgoStateReader, UtxoStateReader}
import org.ergoplatform.nodeView.wallet.ErgoWalletActor.WalletPhase
import org.ergoplatform.nodeView.wallet.ErgoWalletState.FilterFn
import org.ergoplatform.nodeView.wallet.persistence.{OffChainRegistry, WalletRegistry, WalletStorage}
import org.ergoplatform.settings.{ErgoSettings, Parameters}
Expand All @@ -27,6 +28,7 @@ case class ErgoWalletState(
utxoStateReaderOpt: Option[UtxoStateReader],
parameters: Parameters,
maxInputsToUse: Int,
walletPhase: WalletPhase,
error: Option[String] = None,
rescanInProgress: Boolean
) extends ScorexLogging {
Expand Down Expand Up @@ -90,7 +92,11 @@ case class ErgoWalletState(
}
}

// Read a box from UTXO set if the node has it, otherwise, from the wallet
/**
* Read a box from UTXO set if the node has it, otherwise, from the wallet
* @param boxId of the box to read
* @return maybe ErgoBox
*/
def readBoxFromUtxoWithWalletFallback(boxId: BoxId): Option[ErgoBox] = {
utxoStateReaderOpt match {
case Some(utxoReader) =>
Expand All @@ -100,7 +106,10 @@ case class ErgoWalletState(
}
}

// expected height of a next block when the wallet is receiving a new block with the height blockHeight
/** Get expected height of a next block when the wallet is receiving a new block with the height blockHeight
* @param blockHeight height of the block being currently received
* @param isFullBlocksPruned whether node has all the full blocks and applies them sequentially
*/
def expectedNextBlockHeight(blockHeight: Height, isFullBlocksPruned: Boolean): Height = {
val walletHeight = getWalletHeight
if (!isFullBlocksPruned) {
Expand Down Expand Up @@ -153,6 +162,7 @@ object ErgoWalletState {
utxoStateReaderOpt = None,
parameters,
maxInputsToUse,
walletPhase = WalletPhase.UnInitialized,
rescanInProgress = false
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ object WalletScanLogic extends ScorexLogging {
block: ErgoFullBlock,
cachedOutputsFilter: Option[BloomFilter[Array[Byte]]],
dustLimit: Option[Long],
walletCreated: Boolean,
walletProfile: WalletProfile): Try[(WalletRegistry, OffChainRegistry, BloomFilter[Array[Byte]])] = {
scanBlockTransactions(
registry, offChainRegistry, walletVars,
block.height, block.id, block.transactions, cachedOutputsFilter, dustLimit, walletProfile)
block.height, block.id, block.transactions, cachedOutputsFilter, dustLimit, walletProfile, walletCreated)
}

/**
Expand All @@ -61,6 +62,8 @@ object WalletScanLogic extends ScorexLogging {
* @param blockId - block id
* @param transactions - block transactions
* @param cachedOutputsFilter - Bloom filter for previously created outputs
* @param walletProfile - indicates intended use case for the node wallet
* @param walletCreated - whether wallet was created and not restored
* @return updated wallet database, offchain snapshot and the Bloom filter for wallet outputs
*/
def scanBlockTransactions(registry: WalletRegistry,
Expand All @@ -71,7 +74,8 @@ object WalletScanLogic extends ScorexLogging {
transactions: Seq[ErgoTransaction],
cachedOutputsFilter: Option[BloomFilter[Array[Byte]]],
dustLimit: Option[Long],
walletProfile: WalletProfile): Try[(WalletRegistry, OffChainRegistry, BloomFilter[Array[Byte]])] = {
walletProfile: WalletProfile,
walletCreated: Boolean = false): Try[(WalletRegistry, OffChainRegistry, BloomFilter[Array[Byte]])] = {

// Take unspent wallet outputs Bloom Filter from cache
// or recreate it from unspent outputs stored in the database
Expand Down Expand Up @@ -163,7 +167,7 @@ object WalletScanLogic extends ScorexLogging {
}

// function effects: updating registry and offchainRegistry datasets
registry.updateOnBlock(scanRes, blockId, height)
registry.updateOnBlock(scanRes, blockId, height, walletCreated)
.map { _ =>
//data needed to update the offchain-registry
val walletUnspent = registry.walletUnspentBoxes()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ class WalletRegistry(store: LDBVersionedStore)(ws: WalletSettings) extends Score
* @param blockId - block identifier
* @param blockHeight - block height
*/
def updateOnBlock(scanResults: ScanResults, blockId: ModifierId, blockHeight: Int): Try[Unit] = {
def updateOnBlock(scanResults: ScanResults, blockId: ModifierId, blockHeight: Int, walletCreated: Boolean = false): Try[Unit] = {

// first, put newly created outputs and related transactions into key-value bag
val bag1 = putBoxes(KeyValuePairsBag.empty, scanResults.outputs)
Expand All @@ -252,7 +252,7 @@ class WalletRegistry(store: LDBVersionedStore)(ws: WalletSettings) extends Score

// and update wallet digest
updateDigest(bag3) { case WalletDigest(height, wBalance, wTokensSeq) =>
if (height + 1 != blockHeight) {
if (height + 1 != blockHeight && !walletCreated) {
log.error(s"Blocks were skipped during wallet scanning, from $height until $blockHeight")
Copy link
Collaborator Author

@pragmaxim pragmaxim Nov 8, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the only reason why the walletPhase is being propagated down the WalletRegistry and WalletScanLogic, so if we want to preserve this check, I have to do it ...

}
val spentWalletBoxes = spentBoxesWithTx.map(_._2).filter(_.scans.contains(PaymentsScanId))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import org.ergoplatform._
import org.ergoplatform.db.DBSpec
import org.ergoplatform.modifiers.mempool.{ErgoTransaction, UnconfirmedTransaction}
import org.ergoplatform.nodeView.mempool.ErgoMemPoolReader
import org.ergoplatform.nodeView.wallet.ErgoWalletActor.WalletPhase
import org.ergoplatform.nodeView.wallet.WalletScanLogic.ScanResults
import org.ergoplatform.nodeView.wallet.persistence.{OffChainRegistry, WalletRegistry, WalletStorage}
import org.ergoplatform.nodeView.wallet.requests.{AssetIssueRequest, PaymentRequest}
Expand All @@ -20,6 +21,7 @@ import org.ergoplatform.wallet.boxes.{ErgoBoxSerializer, ReplaceCompactCollectBo
import org.ergoplatform.wallet.crypto.ErgoSignature
import org.ergoplatform.wallet.mnemonic.Mnemonic
import org.ergoplatform.wallet.secrets.ExtendedSecretKey
import org.ergoplatform.wallet.utils.FileUtils
import org.scalacheck.Gen
import org.scalatest.BeforeAndAfterAll
import scorex.db.{LDBKVStore, LDBVersionedStore}
Expand All @@ -37,6 +39,7 @@ class ErgoWalletServiceSpec
with ErgoWalletSupport
with ErgoTransactionGenerators
with DBSpec
with FileUtils
with BeforeAndAfterAll {

override val ergoSettings: ErgoSettings = settings
Expand All @@ -61,6 +64,7 @@ class ErgoWalletServiceSpec
utxoStateReaderOpt = Option.empty,
parameters,
maxInputsToUse = 1000,
walletPhase = WalletPhase.UnInitialized,
rescanInProgress = false
)
}
Expand Down Expand Up @@ -293,6 +297,8 @@ class ErgoWalletServiceSpec
property("it should lock/unlock wallet") {
withVersionedStore(2) { versionedStore =>
withStore { store =>
deleteRecursive(WalletStorage.storageFolder(settings))
deleteRecursive(WalletRegistry.registryFolder(settings))
val walletState = initialState(store, versionedStore)
val walletService = new ErgoWalletServiceImpl(settings)
val pass = Random.nextString(10)
Expand Down