diff --git a/node/src/main/scala/com/wavesplatform/Application.scala b/node/src/main/scala/com/wavesplatform/Application.scala index c30df6c270f..af3f6230016 100644 --- a/node/src/main/scala/com/wavesplatform/Application.scala +++ b/node/src/main/scala/com/wavesplatform/Application.scala @@ -18,6 +18,7 @@ import com.wavesplatform.api.http.eth.EthRpcRoute import com.wavesplatform.api.http.leasing.LeaseApiRoute import com.wavesplatform.api.http.utils.UtilsApiRoute import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.consensus.PoSSelector import com.wavesplatform.database.{DBExt, Keys, openDB} import com.wavesplatform.events.{BlockchainUpdateTriggers, UtxEvent} @@ -31,6 +32,7 @@ import com.wavesplatform.mining.{Miner, MinerDebugInfo, MinerImpl} import com.wavesplatform.network.* import com.wavesplatform.settings.WavesSettings import com.wavesplatform.state.appender.{BlockAppender, ExtensionAppender, MicroblockAppender} +import com.wavesplatform.state.reader.CompositeBlockchain import com.wavesplatform.state.{Blockchain, BlockchainUpdaterImpl, Diff, Height, TxMeta} import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.smart.script.trace.TracedResult @@ -123,10 +125,20 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con val establishedConnections = new ConcurrentHashMap[Channel, PeerInfo] val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) - val utxStorage = - new UtxPoolImpl(time, blockchainUpdater, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable, utxEvents.onNext) + val utxStorage = new UtxPoolImpl(time, blockchainUpdater, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable, utxEvents.onNext) maybeUtx = Some(utxStorage) + def blockchainWithDiscardedDiffs(): CompositeBlockchain = { + def blockchain = CompositeBlockchain(blockchainUpdater, utxStorage.discardedMicrosDiff()) + utxStorage.priorityPool.optimisticRead(blockchain)(_ => true) + } + + def totalLiquidDiff(): Diff = { + def liquidDiff = blockchainUpdater.bestLiquidDiff.getOrElse(Diff()) + def totalDiff = liquidDiff.combineE(utxStorage.discardedMicrosDiff()).explicitGet() + utxStorage.priorityPool.optimisticRead(totalDiff)(_ => true) + } + val timer = new HashedWheelTimer() val utxSynchronizerLogger = LoggerFacade(LoggerFactory.getLogger(classOf[TransactionPublisher])) val timedTxValidator = @@ -223,18 +235,17 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con override def utxEvents: Observable[UtxEvent] = app.utxEvents override val transactionsApi: CommonTransactionsApi = CommonTransactionsApi( - blockchainUpdater.bestLiquidDiff.map(diff => Height(blockchainUpdater.height) -> diff), + Some(Height(blockchainUpdater.height) -> totalLiquidDiff()), db, - blockchainUpdater, + () => blockchainWithDiscardedDiffs(), utxStorage, tx => transactionPublisher.validateAndBroadcast(tx, None), loadBlockAt(db, blockchainUpdater) ) override val blocksApi: CommonBlocksApi = CommonBlocksApi(blockchainUpdater, loadBlockMetaAt(db, blockchainUpdater), loadBlockInfoAt(db, blockchainUpdater)) - override val accountsApi: CommonAccountsApi = - CommonAccountsApi(() => blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), db, blockchainUpdater) - override val assetsApi: CommonAssetsApi = CommonAssetsApi(() => blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), db, blockchainUpdater) + override val accountsApi: CommonAccountsApi = CommonAccountsApi(() => totalLiquidDiff(), db, () => blockchainWithDiscardedDiffs()) + override val assetsApi: CommonAssetsApi = CommonAssetsApi(() => totalLiquidDiff(), db, () => blockchainWithDiscardedDiffs()) } extensions = settings.extensions.map { extensionClassName => @@ -361,14 +372,14 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con )(heavyRequestScheduler) val apiRoutes = Seq( - new EthRpcRoute(blockchainUpdater, extensionContext.transactionsApi, time), + EthRpcRoute(() => blockchainWithDiscardedDiffs(), extensionContext.transactionsApi, time), NodeApiRoute(settings.restAPISettings, blockchainUpdater, () => shutdown()), BlocksApiRoute(settings.restAPISettings, extensionContext.blocksApi, time, routeTimeout), TransactionsApiRoute( settings.restAPISettings, extensionContext.transactionsApi, wallet, - blockchainUpdater, + () => blockchainWithDiscardedDiffs(), () => utxStorage.size, transactionPublisher, time, @@ -387,7 +398,7 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con AddressApiRoute( settings.restAPISettings, wallet, - blockchainUpdater, + () => blockchainWithDiscardedDiffs(), transactionPublisher, time, limitedScheduler, @@ -423,7 +434,7 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con settings.restAPISettings, wallet, transactionPublisher, - blockchainUpdater, + () => blockchainWithDiscardedDiffs(), time, extensionContext.accountsApi, extensionContext.assetsApi, @@ -434,7 +445,6 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con LeaseApiRoute( settings.restAPISettings, wallet, - blockchainUpdater, transactionPublisher, time, extensionContext.accountsApi, @@ -446,7 +456,7 @@ class Application(val actorSystem: ActorSystem, val settings: WavesSettings, con wallet, transactionPublisher, time, - blockchainUpdater, + () => blockchainWithDiscardedDiffs(), routeTimeout ), RewardApiRoute(blockchainUpdater) diff --git a/node/src/main/scala/com/wavesplatform/Importer.scala b/node/src/main/scala/com/wavesplatform/Importer.scala index 226fd871e25..caf44697dfc 100644 --- a/node/src/main/scala/com/wavesplatform/Importer.scala +++ b/node/src/main/scala/com/wavesplatform/Importer.scala @@ -134,7 +134,7 @@ object Importer extends ScorexLogging { CommonTransactionsApi( blockchainUpdater.bestLiquidDiff.map(diff => Height(blockchainUpdater.height) -> diff), db, - blockchainUpdater, + () => blockchainUpdater, utxPool, _ => Future.successful(TracedResult.wrapE(Left(GenericError("Not implemented during import")))), Application.loadBlockAt(db, blockchainUpdater) @@ -142,9 +142,9 @@ object Importer extends ScorexLogging { override def blocksApi: CommonBlocksApi = CommonBlocksApi(blockchainUpdater, Application.loadBlockMetaAt(db, blockchainUpdater), Application.loadBlockInfoAt(db, blockchainUpdater)) override def accountsApi: CommonAccountsApi = - CommonAccountsApi(() => blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), db, blockchainUpdater) + CommonAccountsApi(() => blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), db, () => blockchainUpdater) override def assetsApi: CommonAssetsApi = - CommonAssetsApi(() => blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), db, blockchainUpdater) + CommonAssetsApi(() => blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), db, () => blockchainUpdater) } } diff --git a/node/src/main/scala/com/wavesplatform/api/common/CommonAccountsApi.scala b/node/src/main/scala/com/wavesplatform/api/common/CommonAccountsApi.scala index 56952c030f8..199ab4ab8e8 100644 --- a/node/src/main/scala/com/wavesplatform/api/common/CommonAccountsApi.scala +++ b/node/src/main/scala/com/wavesplatform/api/common/CommonAccountsApi.scala @@ -55,21 +55,24 @@ object CommonAccountsApi { final case class BalanceDetails(regular: Long, generating: Long, available: Long, effective: Long, leaseIn: Long, leaseOut: Long) - def apply(diff: () => Diff, db: DB, blockchain: Blockchain): CommonAccountsApi = new CommonAccountsApi { + def apply(diff: () => Diff, db: DB, blockchain: () => Blockchain): CommonAccountsApi = new CommonAccountsApi { - override def balance(address: Address, confirmations: Int = 0): Long = - blockchain.balance(address, blockchain.height, confirmations) + override def balance(address: Address, confirmations: Int = 0): Long = { + val bc = blockchain() + bc.balance(address, bc.height, confirmations) + } override def effectiveBalance(address: Address, confirmations: Int = 0): Long = { - blockchain.effectiveBalance(address, confirmations) + blockchain().effectiveBalance(address, confirmations) } override def balanceDetails(address: Address): Either[String, BalanceDetails] = { - val portfolio = blockchain.wavesPortfolio(address) + val bc = blockchain() + val portfolio = bc.wavesPortfolio(address) portfolio.effectiveBalance.map(effectiveBalance => BalanceDetails( portfolio.balance, - blockchain.generatingBalance(address), + bc.generatingBalance(address), portfolio.balance - portfolio.lease.out, effectiveBalance, portfolio.lease.in, @@ -78,26 +81,29 @@ object CommonAccountsApi { ) } - override def assetBalance(address: Address, asset: IssuedAsset): Long = blockchain.balance(address, asset) + override def assetBalance(address: Address, asset: IssuedAsset): Long = blockchain().balance(address, asset) override def portfolio(address: Address): Observable[(IssuedAsset, Long)] = { val currentDiff = diff() + val bc = blockchain() db.resourceObservable.flatMap { resource => - Observable.fromIterator(Task(assetBalanceIterator(resource, address, currentDiff, includeNft(blockchain)))) + Observable.fromIterator(Task(assetBalanceIterator(resource, address, currentDiff, includeNft(bc)))) } } override def nftList(address: Address, after: Option[IssuedAsset]): Observable[(IssuedAsset, AssetDescription)] = { val currentDiff = diff() + val bc = blockchain() db.resourceObservable.flatMap { resource => - Observable.fromIterator(Task(nftIterator(resource, address, currentDiff, after, blockchain.assetDescription))) + Observable.fromIterator(Task(nftIterator(resource, address, currentDiff, after, bc.assetDescription))) } } - override def script(address: Address): Option[AccountScriptInfo] = blockchain.accountScript(address) + override def script(address: Address): Option[AccountScriptInfo] = + blockchain().accountScript(address) override def data(address: Address, key: String): Option[DataEntry[?]] = - blockchain.accountData(address, key) + blockchain().accountData(address, key) override def dataStream(address: Address, regex: Option[String]): Observable[DataEntry[?]] = Observable.defer { val pattern = regex.map(_.r.pattern) @@ -124,12 +130,14 @@ object CommonAccountsApi { Observable.fromIterable((entriesFromDiff.values ++ entries).filterNot(_.isEmpty)) } - override def resolveAlias(alias: Alias): Either[ValidationError, Address] = blockchain.resolveAlias(alias) + override def resolveAlias(alias: Alias): Either[ValidationError, Address] = + blockchain().resolveAlias(alias) - override def activeLeases(address: Address): Observable[LeaseInfo] = + override def activeLeases(address: Address): Observable[LeaseInfo] = { + val bc = blockchain() addressTransactions( db, - Some(Height(blockchain.height) -> diff()), + Some(Height(bc.height) -> diff()), address, None, Set(TransactionType.Lease, TransactionType.InvokeScript, TransactionType.InvokeExpression, TransactionType.Ethereum), @@ -141,7 +149,7 @@ object CommonAccountsApi { lt.id(), lt.id(), lt.sender.toAddress, - blockchain.resolveAlias(lt.recipient).explicitGet(), + bc.resolveAlias(lt.recipient).explicitGet(), lt.amount.value, leaseHeight, LeaseInfo.Status.Active @@ -153,13 +161,15 @@ object CommonAccountsApi { extractLeases(address, scriptResult, tx.id(), height) case _ => Seq() } + } private def extractLeases(subject: Address, result: InvokeScriptResult, txId: ByteStr, height: Height): Seq[LeaseInfo] = { + val bc = blockchain() (for { lease <- result.leases - details <- blockchain.leaseDetails(lease.id) if details.isActive + details <- bc.leaseDetails(lease.id) if details.isActive sender = details.sender.toAddress - recipient <- blockchain.resolveAlias(lease.recipient).toOption if subject == sender || subject == recipient + recipient <- bc.resolveAlias(lease.recipient).toOption if subject == sender || subject == recipient } yield LeaseInfo( lease.id, txId, @@ -180,26 +190,29 @@ object CommonAccountsApi { Right(recipientAddress) } - def leaseInfo(leaseId: ByteStr): Option[LeaseInfo] = blockchain.leaseDetails(leaseId) map { ld => - LeaseInfo( - leaseId, - ld.sourceId, - ld.sender.toAddress, - blockchain.resolveAlias(ld.recipient).orElse(resolveDisabledAlias(leaseId)).explicitGet(), - ld.amount, - ld.height, - ld.status match { - case Status.Active => LeaseInfo.Status.Active - case Status.Cancelled(_, _) => LeaseInfo.Status.Canceled - case Status.Expired(_) => LeaseInfo.Status.Expired - }, - ld.status.cancelHeight, - ld.status.cancelTransactionId - ) + def leaseInfo(leaseId: ByteStr): Option[LeaseInfo] = { + val bc = blockchain() + bc.leaseDetails(leaseId) map { ld => + LeaseInfo( + leaseId, + ld.sourceId, + ld.sender.toAddress, + bc.resolveAlias(ld.recipient).orElse(resolveDisabledAlias(leaseId)).explicitGet(), + ld.amount, + ld.height, + ld.status match { + case Status.Active => LeaseInfo.Status.Active + case Status.Cancelled(_, _) => LeaseInfo.Status.Canceled + case Status.Expired(_) => LeaseInfo.Status.Expired + }, + ld.status.cancelHeight, + ld.status.cancelTransactionId + ) + } } private[this] def leaseIsActive(id: ByteStr): Boolean = - blockchain.leaseDetails(id).exists(_.isActive) + blockchain().leaseDetails(id).exists(_.isActive) } } diff --git a/node/src/main/scala/com/wavesplatform/api/common/CommonAssetsApi.scala b/node/src/main/scala/com/wavesplatform/api/common/CommonAssetsApi.scala index 9039424ee81..5098c94bae7 100644 --- a/node/src/main/scala/com/wavesplatform/api/common/CommonAssetsApi.scala +++ b/node/src/main/scala/com/wavesplatform/api/common/CommonAssetsApi.scala @@ -23,26 +23,28 @@ trait CommonAssetsApi { object CommonAssetsApi { final case class AssetInfo(description: AssetDescription, issueTransaction: Option[IssueTransaction], sponsorBalance: Option[Long]) - def apply(diff: () => Diff, db: DB, blockchain: Blockchain): CommonAssetsApi = new CommonAssetsApi { + def apply(diff: () => Diff, db: DB, blockchain: () => Blockchain): CommonAssetsApi = new CommonAssetsApi { def description(assetId: IssuedAsset): Option[AssetDescription] = - blockchain.assetDescription(assetId) + blockchain().assetDescription(assetId) - def fullInfo(assetId: IssuedAsset): Option[AssetInfo] = + def fullInfo(assetId: IssuedAsset): Option[AssetInfo] = { + val bc = blockchain() for { - assetInfo <- blockchain.assetDescription(assetId) - sponsorBalance = if (assetInfo.sponsorship != 0) Some(blockchain.wavesPortfolio(assetInfo.issuer.toAddress).spendableBalance) else None + assetInfo <- bc.assetDescription(assetId) + sponsorBalance = if (assetInfo.sponsorship != 0) Some(bc.wavesPortfolio(assetInfo.issuer.toAddress).spendableBalance) else None } yield AssetInfo( assetInfo, - blockchain.transactionInfo(assetId.id).collect { case (tm, it: IssueTransaction) if tm.succeeded => it }, + bc.transactionInfo(assetId.id).collect { case (tm, it: IssueTransaction) if tm.succeeded => it }, sponsorBalance ) + } override def wavesDistribution(height: Int, after: Option[Address]): Observable[(Address, Long)] = balanceDistribution( db, height, after, - if (height == blockchain.height) diff().portfolios else Map.empty[Address, Portfolio], + if (height == blockchain().height) diff().portfolios else Map.empty[Address, Portfolio], KeyTags.WavesBalance.prefixBytes, bs => AddressId.fromByteArray(bs.slice(2, bs.length - 4)), _.balance @@ -53,7 +55,7 @@ object CommonAssetsApi { db, height, after, - if (height == blockchain.height) diff().portfolios else Map.empty[Address, Portfolio], + if (height == blockchain().height) diff().portfolios else Map.empty[Address, Portfolio], KeyTags.AssetBalance.prefixBytes ++ asset.id.arr, bs => AddressId.fromByteArray(bs.slice(2 + crypto.DigestLength, bs.length - 4)), _.assets.getOrElse(asset, 0L) diff --git a/node/src/main/scala/com/wavesplatform/api/common/CommonTransactionsApi.scala b/node/src/main/scala/com/wavesplatform/api/common/CommonTransactionsApi.scala index a4e3315a4a3..0aba47db34a 100644 --- a/node/src/main/scala/com/wavesplatform/api/common/CommonTransactionsApi.scala +++ b/node/src/main/scala/com/wavesplatform/api/common/CommonTransactionsApi.scala @@ -47,7 +47,7 @@ object CommonTransactionsApi { def apply( maybeDiff: => Option[(Height, Diff)], db: DB, - blockchain: Blockchain, + blockchain: () => Blockchain, utx: UtxPool, publishTransaction: Transaction => Future[TracedResult[ValidationError, Boolean]], blockAt: Int => Option[(BlockMeta, Seq[(TxMeta, Transaction)])] @@ -63,7 +63,7 @@ object CommonTransactionsApi { common.addressTransactions(db, maybeDiff, subject, sender, transactionTypes, fromId) override def transactionById(transactionId: ByteStr): Option[TransactionMeta] = - blockchain.transactionInfo(transactionId).map(common.loadTransactionMeta(db, maybeDiff)) + blockchain().transactionInfo(transactionId).map(common.loadTransactionMeta(db, maybeDiff)) override def unconfirmedTransactions: Seq[Transaction] = utx.all @@ -72,7 +72,7 @@ object CommonTransactionsApi { override def calculateFee(tx: Transaction): Either[ValidationError, (Asset, Long, Long)] = FeeValidation - .getMinFee(blockchain, tx) + .getMinFee(blockchain(), tx) .map { case FeeDetails(asset, _, feeInAsset, feeInWaves) => (asset, feeInAsset, feeInWaves) @@ -83,7 +83,7 @@ object CommonTransactionsApi { override def transactionProofs(transactionIds: List[ByteStr]): List[TransactionProof] = for { transactionId <- transactionIds - (txm, tx) <- blockchain.transactionInfo(transactionId) + (txm, tx) <- blockchain().transactionInfo(transactionId) (meta, allTransactions) <- blockAt(txm.height) if meta.header.version >= Block.ProtoBlockVersion transactionProof <- block.transactionProof(tx, allTransactions.map(_._2)) } yield transactionProof diff --git a/node/src/main/scala/com/wavesplatform/api/http/AddressApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/AddressApiRoute.scala index d06d41b7c4f..83e46bc6c26 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/AddressApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/AddressApiRoute.scala @@ -37,7 +37,7 @@ import scala.util.{Success, Try} case class AddressApiRoute( settings: RestAPISettings, wallet: Wallet, - blockchain: Blockchain, + blockchain: () => Blockchain, transactionPublisher: TransactionPublisher, time: Time, limitedScheduler: Scheduler, @@ -61,7 +61,8 @@ case class AddressApiRoute( def scriptInfo: Route = (path("scriptInfo" / AddrSegment) & get) { address => completeLimited { - val scriptInfoOpt = blockchain.accountScript(address) + val bc = blockchain() + val scriptInfoOpt = bc.accountScript(address) val callableComplexitiesOpt = for { scriptInfo <- scriptInfoOpt @@ -69,7 +70,7 @@ case class AddressApiRoute( case ContractScriptImpl(_, DApp(_, _, _, Some(vf))) => Some(vf.u.name) case _ => None } - complexities <- scriptInfo.complexitiesByEstimator.get(blockchain.estimator.version) + complexities <- scriptInfo.complexitiesByEstimator.get(bc.estimator.version) } yield verifierName.fold(complexities)(complexities - _) val callableComplexities = callableComplexitiesOpt.getOrElse(Map[String, Long]()) @@ -84,7 +85,7 @@ case class AddressApiRoute( "complexity" -> maxComplexity, "verifierComplexity" -> verifierComplexity, "callableComplexities" -> callableComplexities, - "extraFee" -> (if (blockchain.hasPaidVerifier(address)) FeeValidation.ScriptExtraFee else 0L) + "extraFee" -> (if (bc.hasPaidVerifier(address)) FeeValidation.ScriptExtraFee else 0L) ) } } @@ -120,7 +121,7 @@ case class AddressApiRoute( def balances: Route = (path("balance") & get & parameters("height".as[Int].?, "address".as[String].*, "asset".?)) { (maybeHeight, addresses, assetId) => - val height = maybeHeight.getOrElse(blockchain.height) + val height = maybeHeight.getOrElse(blockchain().height) validateBalanceDepth(height)( complete( balancesJson(height, addresses.toSeq, assetId.fold(Waves: Asset)(a => IssuedAsset(ByteStr.decodeBase58(a).get))) @@ -129,7 +130,7 @@ case class AddressApiRoute( } def balancesPost: Route = (path("balance") & (post & entity(as[JsObject]))) { request => - val height = (request \ "height").asOpt[Int].getOrElse(blockchain.height) + val height = (request \ "height").asOpt[Int].getOrElse(blockchain().height) val addresses = (request \ "addresses").as[Seq[String]] val assetId = (request \ "asset").asOpt[String] validateBalanceDepth(height)(complete(balancesJson(height, addresses, assetId.fold(Waves: Asset)(a => IssuedAsset(ByteStr.decodeBase58(a).get))))) @@ -157,7 +158,7 @@ case class AddressApiRoute( def balanceWithConfirmations: Route = { (path("balance" / AddrSegment / IntNumber) & get) { case (address, confirmations) => - validateBalanceDepth(blockchain.height - confirmations)( + validateBalanceDepth(blockchain().height - confirmations)( complete(balanceJson(address, confirmations)) ) } @@ -171,7 +172,7 @@ case class AddressApiRoute( def effectiveBalanceWithConfirmations: Route = { path("effectiveBalance" / AddrSegment / IntNumber) { (address, confirmations) => - validateBalanceDepth(blockchain.height - confirmations)( + validateBalanceDepth(blockchain().height - confirmations)( complete(effectiveBalanceJson(address, confirmations)) ) } @@ -210,12 +211,12 @@ case class AddressApiRoute( complete(ApiError.fromValidationError(GenericError(s"Cannot compile regex"))) }, _ => accountData(address, Some(matches)) - ) - } ~ anyParam("key", limit = settings.dataKeysRequestLimit) { keys => - extractMethod.filter(_ != HttpMethods.GET || keys.nonEmpty) { _ => - val result = Either - .cond(keys.nonEmpty, (), DataKeysNotSpecified) - .map(_ => accountDataList(address, keys.toSeq*)) + ) + } ~ anyParam("key", limit = settings.dataKeysRequestLimit) { keys => + extractMethod.filter(_ != HttpMethods.GET || keys.nonEmpty) { _ => + val result = Either + .cond(keys.nonEmpty, (), DataKeysNotSpecified) + .map(_ => accountDataList(address, keys.toSeq*)) complete(result) } @@ -244,9 +245,10 @@ case class AddressApiRoute( } } - private def balancesJson(height: Int, addresses: Seq[String], assetId: Asset): ToResponseMarshallable = + private def balancesJson(height: Int, addresses: Seq[String], assetId: Asset): ToResponseMarshallable = { + val bc = blockchain() if (addresses.length > settings.transactionsByAddressLimit) TooBigArrayAllocation - else if (height < 1 || height > blockchain.height) CustomValidationError(s"Illegal height: $height") + else if (height < 1 || height > bc.height) CustomValidationError(s"Illegal height: $height") else { implicit val balancesWrites: Writes[(String, Long)] = Writes[(String, Long)] { b => Json.obj("id" -> b._1, "balance" -> b._2) @@ -255,10 +257,11 @@ case class AddressApiRoute( val balances = for { addressStr <- addresses.toSet[String] address <- Address.fromString(addressStr).toOption - } yield blockchain.balanceAtHeight(address, height, assetId).fold(addressStr -> 0L)(addressStr -> _._2) + } yield bc.balanceAtHeight(address, height, assetId).fold(addressStr -> 0L)(addressStr -> _._2) ToResponseMarshallable(balances) } + } private def balanceJson(acc: Address, confirmations: Int) = { Balance(acc.toString, confirmations, commonAccountsApi.balance(acc, confirmations)) @@ -267,7 +270,7 @@ case class AddressApiRoute( private def balanceJson(acc: Address) = Balance(acc.toString, 0, commonAccountsApi.balance(acc)) private def scriptMetaJson(account: Address): Either[ValidationError.ScriptParseError, AccountScriptMeta] = { - val accountScript = blockchain.accountScript(account) + val accountScript = blockchain().accountScript(account) accountScript .map(_.script) @@ -280,8 +283,9 @@ case class AddressApiRoute( } private[this] def validateBalanceDepth(height: Int): Directive0 = { - if (height < blockchain.height - maxBalanceDepth) - complete(CustomValidationError(s"Unable to get balance past height ${blockchain.height - maxBalanceDepth}")) + val bc = blockchain() + if (height < bc.height - maxBalanceDepth) + complete(CustomValidationError(s"Unable to get balance past height ${bc.height - maxBalanceDepth}")) else pass } @@ -329,7 +333,7 @@ case class AddressApiRoute( case (Success(msgBytes), Success(signatureBytes), Success(pubKeyBytes)) => val account = PublicKey(pubKeyBytes) val isValid = account.toAddress == address && - crypto.verify(signatureBytes, msgBytes, PublicKey(pubKeyBytes), blockchain.isFeatureActivated(BlockchainFeatures.RideV6)) + crypto.verify(signatureBytes, msgBytes, PublicKey(pubKeyBytes), blockchain().isFeatureActivated(BlockchainFeatures.RideV6)) Right(Json.obj("valid" -> isValid)) case _ => Left(InvalidMessage) } diff --git a/node/src/main/scala/com/wavesplatform/api/http/TransactionsApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/TransactionsApiRoute.scala index 2766e9d98ef..8e0896d68d8 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/TransactionsApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/TransactionsApiRoute.scala @@ -35,7 +35,7 @@ case class TransactionsApiRoute( settings: RestAPISettings, commonApi: CommonTransactionsApi, wallet: Wallet, - blockchain: Blockchain, + blockchain: () => Blockchain, utxPoolSize: () => Int, transactionPublisher: TransactionPublisher, time: Time, @@ -45,7 +45,7 @@ case class TransactionsApiRoute( with AuthRoute { import TransactionsApiRoute.* - private[this] val serializer = TransactionJsonSerializer(blockchain, commonApi) + private[this] val serializer = TransactionJsonSerializer(blockchain(), commonApi) private[this] implicit val transactionMetaWrites = OWrites[TransactionMeta](serializer.transactionWithMetaJson) override lazy val route: Route = @@ -86,12 +86,13 @@ case class TransactionsApiRoute( private[this] def loadTransactionStatus(id: ByteStr): JsObject = { import Status.* - val statusJson = blockchain.transactionInfo(id) match { + val bc = blockchain() + val statusJson = bc.transactionInfo(id) match { case Some((tm, tx)) => Json.obj( "status" -> Confirmed, "height" -> JsNumber(tm.height), - "confirmations" -> (blockchain.height - tm.height).max(0) + "confirmations" -> (bc.height - tm.height).max(0) ) ++ serializer.metaJson(tm) case None => commonApi.unconfirmedTransactionById(id) match { diff --git a/node/src/main/scala/com/wavesplatform/api/http/alias/AliasApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/alias/AliasApiRoute.scala index 09eb90011cf..da9a28e6722 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/alias/AliasApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/alias/AliasApiRoute.scala @@ -21,7 +21,7 @@ case class AliasApiRoute( wallet: Wallet, transactionPublisher: TransactionPublisher, time: Time, - blockchain: Blockchain, + blockchain: () => Blockchain, routeTimeout: RouteTimeout ) extends ApiRoute with BroadcastRoute @@ -43,7 +43,7 @@ case class AliasApiRoute( Alias .create(aliasName) .flatMap { a => - blockchain.resolveAlias(a).bimap(_ => TxValidationError.AliasDoesNotExist(a), addr => Json.obj("address" -> addr.toString)) + blockchain().resolveAlias(a).bimap(_ => TxValidationError.AliasDoesNotExist(a), addr => Json.obj("address" -> addr.toString)) } } } diff --git a/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala index 07b982201dd..1c8133f71c8 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/assets/AssetsApiRoute.scala @@ -1,6 +1,5 @@ package com.wavesplatform.api.http.assets -import java.util.concurrent.* import akka.NotUsed import akka.http.scaladsl.marshalling.{ToResponseMarshallable, ToResponseMarshaller} import akka.http.scaladsl.model.headers.Accept @@ -14,8 +13,8 @@ import cats.syntax.either.* import cats.syntax.traverse.* import com.wavesplatform.account.Address import com.wavesplatform.api.common.{CommonAccountsApi, CommonAssetsApi} -import com.wavesplatform.api.http.ApiError.* import com.wavesplatform.api.http.* +import com.wavesplatform.api.http.ApiError.* import com.wavesplatform.api.http.assets.AssetsApiRoute.DistributionParams import com.wavesplatform.api.http.requests.* import com.wavesplatform.common.state.ByteStr @@ -25,12 +24,12 @@ import com.wavesplatform.settings.RestAPISettings import com.wavesplatform.state.{AssetDescription, AssetScriptInfo, Blockchain} import com.wavesplatform.transaction.Asset.IssuedAsset import com.wavesplatform.transaction.EthereumTransaction.Invocation -import com.wavesplatform.transaction.{EthereumTransaction, TransactionFactory} import com.wavesplatform.transaction.TxValidationError.GenericError import com.wavesplatform.transaction.assets.IssueTransaction import com.wavesplatform.transaction.assets.exchange.Order import com.wavesplatform.transaction.assets.exchange.OrderJson.* import com.wavesplatform.transaction.smart.{InvokeExpressionTransaction, InvokeScriptTransaction} +import com.wavesplatform.transaction.{EthereumTransaction, TransactionFactory} import com.wavesplatform.utils.Time import com.wavesplatform.wallet.Wallet import io.netty.util.concurrent.DefaultThreadFactory @@ -38,13 +37,14 @@ import monix.eval.Task import monix.execution.Scheduler import play.api.libs.json.* +import java.util.concurrent.* import scala.concurrent.Future case class AssetsApiRoute( settings: RestAPISettings, wallet: Wallet, transactionPublisher: TransactionPublisher, - blockchain: Blockchain, + blockchain: () => Blockchain, time: Time, commonAccountApi: CommonAccountsApi, commonAssetsApi: CommonAssetsApi, @@ -176,7 +176,7 @@ case class AssetsApiRoute( assets match { case Some(assets) => Task { - assets.map(asset => asset -> blockchain.balance(address, asset)) + assets.map(asset => asset -> blockchain().balance(address, asset)) } case None => commonAccountApi @@ -207,7 +207,7 @@ case class AssetsApiRoute( } def balanceDistribution(assetId: IssuedAsset): Route = - balanceDistribution(assetId, blockchain.height, Int.MaxValue, None) { l => + balanceDistribution(assetId, blockchain().height, Int.MaxValue, None) { l => Json.toJson(l.map { case (a, b) => a.toString -> b }.toMap) } @@ -215,7 +215,7 @@ case class AssetsApiRoute( optionalHeaderValueByType(Accept) { accept => val paramsEi: Either[ValidationError, DistributionParams] = AssetsApiRoute - .validateDistributionParams(blockchain, heightParam, limitParam, settings.distributionAddressLimit, afterParam, maxDistributionDepth) + .validateDistributionParams(blockchain(), heightParam, limitParam, settings.distributionAddressLimit, afterParam, maxDistributionDepth) paramsEi match { case Right((height, limit, after)) => @@ -251,7 +251,7 @@ case class AssetsApiRoute( .toListL } { case (assetId, assetDesc) => AssetsApiRoute - .jsonDetails(blockchain)(assetId, assetDesc, full = true) + .jsonDetails(blockchain())(assetId, assetDesc, full = true) .valueOr(err => throw new IllegalArgumentException(err)) } } @@ -261,13 +261,14 @@ case class AssetsApiRoute( Json.obj( "address" -> address, "assetId" -> assetId.id.toString, - "balance" -> JsNumber(BigDecimal(blockchain.balance(address, assetId))) + "balance" -> JsNumber(BigDecimal(blockchain().balance(address, assetId))) ) private def assetDetails(assetId: IssuedAsset, full: Boolean): Either[ApiError, JsObject] = { + val bc = blockchain() for { - description <- blockchain.assetDescription(assetId).toRight(AssetDoesNotExist(assetId)) - result <- AssetsApiRoute.jsonDetails(blockchain)(assetId, description, full).leftMap(CustomValidationError(_)) + description <- bc.assetDescription(assetId).toRight(AssetDoesNotExist(assetId)) + result <- AssetsApiRoute.jsonDetails(bc)(assetId, description, full).leftMap(CustomValidationError(_)) } yield result } } diff --git a/node/src/main/scala/com/wavesplatform/api/http/eth/EthRpcRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/eth/EthRpcRoute.scala index 76bedd63855..3899796eef0 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/eth/EthRpcRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/eth/EthRpcRoute.scala @@ -31,14 +31,15 @@ import scala.concurrent.Future import scala.jdk.CollectionConverters.* import scala.util.Try -class EthRpcRoute(blockchain: Blockchain, transactionsApi: CommonTransactionsApi, time: Time) extends ApiRoute { +case class EthRpcRoute(blockchain: () => Blockchain, transactionsApi: CommonTransactionsApi, time: Time) extends ApiRoute { val route: Route = pathPrefix("eth") { (path("assets") & anyParam("id", nonEmpty = true, limit = 100).massValidateEthereumIds) { erc20Ids => + val bc = blockchain() val results = erc20Ids .map(id => id -> (for { - wavesId <- blockchain.resolveERC20Address(ERC20Address(id)) - assetDesc <- blockchain.assetDescription(wavesId) + wavesId <- bc.resolveERC20Address(ERC20Address(id)) + assetDesc <- bc.assetDescription(wavesId) } yield (wavesId, assetDesc)) ) .map { case (id, assetOpt) => Validated.fromOption(assetOpt, EthEncoding.toHexString(id.arr)).toValidatedNel } @@ -51,25 +52,26 @@ class EthRpcRoute(blockchain: Blockchain, transactionsApi: CommonTransactionsApi case Validated.Valid(assets) => val jsons = for { (assetId, desc) <- assets - } yield AssetsApiRoute.jsonDetails(blockchain)(assetId, desc, full = false) + } yield AssetsApiRoute.jsonDetails(bc)(assetId, desc, full = false) complete(jsons.sequence.leftMap(CustomValidationError(_)).map(JsArray(_))) // TODO: Only first error is displayed } } ~ (get & path("abi" / AddrSegment)) { addr => - complete(blockchain.accountScript(addr).map(as => ABIConverter(as.script).jsonABI)) + complete(blockchain().accountScript(addr).map(as => ABIConverter(as.script).jsonABI)) } ~ (pathEndOrSingleSlash & post & entity(as[JsObject])) { jso => + val bc = blockchain() val id = (jso \ "id").getOrElse(JsNull) (jso \ "method").asOpt[String] match { case Some("eth_chainId" | "net_version") => - resp(id, quantity(blockchain.settings.addressSchemeCharacter.toInt)) + resp(id, quantity(bc.settings.addressSchemeCharacter.toInt)) case Some("eth_blockNumber") => - resp(id, quantity(blockchain.height)) + resp(id, quantity(bc.height)) case Some("eth_getTransactionCount") => resp(id, quantity(time.getTimestamp())) case Some("eth_getBlockByNumber") => extractParam1[String](jso) { str => val blockNumberOpt = str match { case "earliest" => Some(1).asRight - case "latest" => Some(blockchain.height).asRight + case "latest" => Some(bc.height).asRight case "pending" => None.asRight case _ => Try(Some(Integer.parseInt(str.drop(2), 16))) @@ -89,7 +91,7 @@ class EthRpcRoute(blockchain: Blockchain, transactionsApi: CommonTransactionsApi val blockId = ByteStr(toBytes(str)) resp( id, - blockchain.heightOf(blockId).flatMap(blockchain.blockHeader).fold[JsValue](JsNull) { _ => + bc.heightOf(blockId).flatMap(bc.blockHeader).fold[JsValue](JsNull) { _ => Json.obj( "baseFeePerGas" -> "0x0" ) @@ -102,7 +104,7 @@ class EthRpcRoute(blockchain: Blockchain, transactionsApi: CommonTransactionsApi resp( id, toHexString( - BigInt(blockchain.balance(address)) * EthereumTransaction.AmountMultiplier + BigInt(bc.balance(address)) * EthereumTransaction.AmountMultiplier ) ) } @@ -133,7 +135,7 @@ class EthRpcRoute(blockchain: Blockchain, transactionsApi: CommonTransactionsApi Json.obj( "transactionHash" -> toHexString(tm.transaction.id().arr), "transactionIndex" -> "0x1", - "blockHash" -> toHexString(blockchain.lastBlockId.get.arr), + "blockHash" -> toHexString(bc.lastBlockId.get.arr), "blockNumber" -> toHexString(BigInteger.valueOf(tm.height)), "from" -> toHexString(tx.senderAddress().publicKeyHash), "to" -> tx.underlying.getTo, @@ -160,7 +162,7 @@ class EthRpcRoute(blockchain: Blockchain, transactionsApi: CommonTransactionsApi Json.obj( "hash" -> toHexString(tm.transaction.id().arr), "nonce" -> "0x1", - "blockHash" -> toHexString(blockchain.lastBlockId.get.arr), + "blockHash" -> toHexString(bc.lastBlockId.get.arr), "blockNumber" -> toHexString(BigInteger.valueOf(tm.height)), "transactionIndex" -> "0x1", "from" -> toHexString(tx.senderAddress().publicKeyHash), @@ -195,7 +197,7 @@ class EthRpcRoute(blockchain: Blockchain, transactionsApi: CommonTransactionsApi id, encodeResponse( new Uint256( - assetId(contractAddress).fold(0L)(ia => blockchain.balance(Address.fromHexString(dataString.takeRight(40)), ia)) + assetId(contractAddress).fold(0L)(ia => bc.balance(Address.fromHexString(dataString.takeRight(40)), ia)) ) ) ) @@ -234,7 +236,7 @@ class EthRpcRoute(blockchain: Blockchain, transactionsApi: CommonTransactionsApi case Some("eth_getCode") => extractParam1[String](jso) { str => val address = Address.fromHexString(str) - resp(id, if (blockchain.hasDApp(address) || assetDescription(str).isDefined) "0xff" else "0x") + resp(id, if (bc.hasDApp(address) || assetDescription(str).isDefined) "0xff" else "0x") } case _ => log.trace(s"Unexpected call: ${Json.stringify(jso)}") @@ -257,10 +259,10 @@ class EthRpcRoute(blockchain: Blockchain, transactionsApi: CommonTransactionsApi private[this] def resp(id: JsValue, resp: Future[JsValueWrapper]) = complete(resp.map(r => Json.obj("id" -> id, "jsonrpc" -> "2.0", "result" -> r))) private[this] def assetDescription(contractAddress: String) = - assetId(contractAddress).flatMap(blockchain.assetDescription) + assetId(contractAddress).flatMap(blockchain().assetDescription) private[this] def assetId(contractAddress: String): Option[IssuedAsset] = - blockchain.resolveERC20Address(ERC20Address(ByteStr(toBytes(contractAddress)))) + blockchain().resolveERC20Address(ERC20Address(ByteStr(toBytes(contractAddress)))) private[this] def encodeResponse(values: Type*): String = "0x" + FunctionEncoder.encodeConstructor(values.map(Type.unwrap).asJava) diff --git a/node/src/main/scala/com/wavesplatform/api/http/leasing/LeaseApiRoute.scala b/node/src/main/scala/com/wavesplatform/api/http/leasing/LeaseApiRoute.scala index bd7a0c6a2b1..868cb11a440 100644 --- a/node/src/main/scala/com/wavesplatform/api/http/leasing/LeaseApiRoute.scala +++ b/node/src/main/scala/com/wavesplatform/api/http/leasing/LeaseApiRoute.scala @@ -2,24 +2,22 @@ package com.wavesplatform.api.http.leasing import akka.http.scaladsl.server.Route import com.wavesplatform.api.common.{CommonAccountsApi, LeaseInfo} -import com.wavesplatform.api.http.{BroadcastRoute, *} -import com.wavesplatform.api.http.requests.{LeaseCancelRequest, LeaseRequest} import com.wavesplatform.api.http.ApiError.{InvalidIds, TransactionDoesNotExist} +import com.wavesplatform.api.http.* +import com.wavesplatform.api.http.requests.{LeaseCancelRequest, LeaseRequest} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.common.utils.Base58 import com.wavesplatform.network.TransactionPublisher import com.wavesplatform.settings.RestAPISettings -import com.wavesplatform.state.Blockchain import com.wavesplatform.transaction.* import com.wavesplatform.utils.Time import com.wavesplatform.wallet.Wallet -import play.api.libs.json.JsonConfiguration.Aux import play.api.libs.json.* +import play.api.libs.json.JsonConfiguration.Aux case class LeaseApiRoute( settings: RestAPISettings, wallet: Wallet, - blockchain: Blockchain, transactionPublisher: TransactionPublisher, time: Time, commonAccountApi: CommonAccountsApi, diff --git a/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala index 9696ecf1a2e..7a7b95fd36a 100644 --- a/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala +++ b/node/src/main/scala/com/wavesplatform/state/BlockchainUpdaterImpl.scala @@ -52,7 +52,7 @@ class BlockchainUpdaterImpl( private val lock = new ReentrantReadWriteLock(true) private def writeLock[B](f: => B): B = inLock(lock.writeLock(), f) - def readLock[B](f: => B): B = inLock(lock.readLock(), f) + private def readLock[B](f: => B): B = inLock(lock.readLock(), f) private lazy val maxBlockReadinessAge = wavesSettings.minerSettings.intervalAfterLastBlockThenGenerationIsAllowed.toMillis diff --git a/node/src/main/scala/com/wavesplatform/state/InvokeScriptResult.scala b/node/src/main/scala/com/wavesplatform/state/InvokeScriptResult.scala index 7aaa5d62c82..6fef5c67bd1 100644 --- a/node/src/main/scala/com/wavesplatform/state/InvokeScriptResult.scala +++ b/node/src/main/scala/com/wavesplatform/state/InvokeScriptResult.scala @@ -85,12 +85,6 @@ object InvokeScriptResult { implicit val jsonWrites = Json.writes[Lease] } - def paymentsFromPortfolio(addr: Address, portfolio: Portfolio): Seq[Payment] = { - val waves = InvokeScriptResult.Payment(addr, Waves, portfolio.balance) - val assets = portfolio.assets.map { case (assetId, amount) => InvokeScriptResult.Payment(addr, assetId, amount) } - (assets.toVector ++ Some(waves)).filter(_.amount != 0) - } - implicit val issueFormat = Writes[Issue] { iss => Json.obj( "assetId" -> iss.id, diff --git a/node/src/main/scala/com/wavesplatform/state/reader/CompositeBlockchain.scala b/node/src/main/scala/com/wavesplatform/state/reader/CompositeBlockchain.scala index 2dfe7c09c68..ca24b8d870a 100644 --- a/node/src/main/scala/com/wavesplatform/state/reader/CompositeBlockchain.scala +++ b/node/src/main/scala/com/wavesplatform/state/reader/CompositeBlockchain.scala @@ -92,7 +92,7 @@ final class CompositeBlockchain private ( } override def balanceSnapshots(address: Address, from: Int, to: Option[BlockId]): Seq[BalanceSnapshot] = - if (maybeDiff.isEmpty || to.exists(id => inner.heightOf(id).isDefined)) { + if (maybeDiff.isEmpty || to.exists(id => inner.heightOf(id).exists(_ != height))) { inner.balanceSnapshots(address, from, to) } else { val balance = this.balance(address) diff --git a/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala b/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala index 954c92c8783..d1e44280af1 100644 --- a/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala +++ b/node/src/main/scala/com/wavesplatform/utx/UtxPoolImpl.scala @@ -6,6 +6,7 @@ import java.util.concurrent.ConcurrentHashMap import com.wavesplatform.ResponsivenessLogs import com.wavesplatform.account.Address import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.consensus.TransactionsOrdering import com.wavesplatform.events.UtxEvent import com.wavesplatform.lang.ValidationError @@ -177,6 +178,9 @@ class UtxPoolImpl( def resetPriorityPool(): Unit = priorityPool.setPriorityDiffs(Seq.empty) + def discardedMicrosDiff(): Diff = + priorityPool.validPriorityDiffs.fold(Diff())(_.combineE(_).explicitGet()) + private[this] def removeFromOrdPool(txId: ByteStr): Option[Transaction] = { for (tx <- Option(transactions.remove(txId))) yield { PoolMetrics.removeTransaction(tx) diff --git a/node/src/test/scala/com/wavesplatform/api/common/CommonAccountApiSpec.scala b/node/src/test/scala/com/wavesplatform/api/common/CommonAccountApiSpec.scala index 0b4ac1df431..a293082adf8 100644 --- a/node/src/test/scala/com/wavesplatform/api/common/CommonAccountApiSpec.scala +++ b/node/src/test/scala/com/wavesplatform/api/common/CommonAccountApiSpec.scala @@ -10,9 +10,9 @@ import com.wavesplatform.lang.v1.compiler.TestCompiler import com.wavesplatform.lang.v1.traits.domain.{Lease, Recipient} import com.wavesplatform.settings.TestFunctionalitySettings import com.wavesplatform.state.{DataEntry, Diff, EmptyDataEntry, StringDataEntry, diffs} -import com.wavesplatform.test.DomainPresets.RideV4 +import com.wavesplatform.test.DomainPresets.{RideV4, RideV6} import com.wavesplatform.test.FreeSpec -import com.wavesplatform.transaction.TxHelpers.data +import com.wavesplatform.transaction.TxHelpers.{data, secondAddress} import com.wavesplatform.transaction.TxVersion.V2 import com.wavesplatform.transaction.{DataTransaction, GenesisTransaction, TxHelpers} import com.wavesplatform.{BlocksTransactionsHelpers, history} @@ -34,21 +34,25 @@ class CommonAccountApiSpec extends FreeSpec with WithDomain with BlocksTransacti val data4 = data(acc, Seq(EmptyDataEntry("test"), EmptyDataEntry("test1")), version = V2) val data5 = data(acc, Seq(EmptyDataEntry("test2"), entry1, entry2), version = V2) - withDomain(RideV4) { d => - val commonAccountsApi = CommonAccountsApi(() => d.blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), d.db, d.blockchainUpdater) - def dataList(): Set[DataEntry[_]] = commonAccountsApi.dataStream(acc.toAddress, None).toListL.runSyncUnsafe().toSet - - d.appendBlock(genesis) - d.appendMicroBlock(data1) - dataList() shouldBe Set(entry1) - d.appendBlock(data2) - dataList() shouldBe Set(entry1, entry2) - d.appendMicroBlock(data3) - dataList() shouldBe Set(entry1, entry2, entry3) - d.appendBlock(data4) - dataList() shouldBe Set(entry3) - d.appendMicroBlock(data5) - dataList() shouldBe Set(entry1, entry2) + + withDomain( + RideV4 + ) { d => + val commonAccountsApi = CommonAccountsApi(() => d.blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), d.db, () => d.blockchainUpdater) + def dataList(): Set[DataEntry[_]] = commonAccountsApi.dataStream(acc.toAddress, None).toListL.runSyncUnsafe().toSet + + d.appendBlock(genesis) + d.appendMicroBlock(data1) + dataList() shouldBe Set(entry1) + d.appendBlock(data2) + dataList() shouldBe Set(entry1, entry2) + d.appendMicroBlock(data3) + dataList() shouldBe Set(entry1, entry2, entry3) + d.appendBlock(data4) + dataList() shouldBe Set(entry3) + d.appendMicroBlock(data5) + dataList() shouldBe Set(entry1, entry2) + } } @@ -71,7 +75,7 @@ class CommonAccountApiSpec extends FreeSpec with WithDomain with BlocksTransacti forAll(preconditions) { case (acc, block1, mb1, block2, mb2) => withDomain(domainSettingsWithFS(TestFunctionalitySettings.withFeatures(BlockchainFeatures.NG, BlockchainFeatures.DataTransaction))) { d => - val commonAccountsApi = CommonAccountsApi(() => d.blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), d.db, d.blockchainUpdater) + val commonAccountsApi = CommonAccountsApi(() => d.blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), d.db, () => d.blockchainUpdater) def dataList(): Set[DataEntry[_]] = commonAccountsApi.dataStream(acc.toAddress, Some("test_.*")).toListL.runSyncUnsafe().toSet d.appendBlock(block1) @@ -145,7 +149,7 @@ class CommonAccountApiSpec extends FreeSpec with WithDomain with BlocksTransacti invoke ) - val api = CommonAccountsApi(() => Diff.empty, d.db, d.blockchain) + val api = CommonAccountsApi(() => Diff.empty, d.db, () => d.blockchain) val leaseId = Lease.calculateId( Lease( Recipient.Address(ByteStr(TxHelpers.defaultAddress.bytes)), @@ -159,4 +163,21 @@ class CommonAccountApiSpec extends FreeSpec with WithDomain with BlocksTransacti ) } } + + "Take into account discarded diffs" in { + withDomain(RideV6) { d => + val recipient = secondAddress + val startBalance = d.balance(recipient) + + val lastBlock = d.appendBlock().id() + val transfer = TxHelpers.transfer(amount = 123) + + d.appendMicroBlock(transfer) + d.accountsApi.balance(recipient) - startBalance shouldBe 123 + + d.appendKeyBlock(Some(lastBlock)) + d.utxPool.priorityPool.validPriorityDiffs.head.portfolios(secondAddress).balance shouldBe 123 + d.accountsApi.balance(recipient) - startBalance shouldBe 123 + } + } } diff --git a/node/src/test/scala/com/wavesplatform/api/eth/EthRpcRouteSpec.scala b/node/src/test/scala/com/wavesplatform/api/eth/EthRpcRouteSpec.scala index c7aaf127b80..887baa2e50f 100644 --- a/node/src/test/scala/com/wavesplatform/api/eth/EthRpcRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/api/eth/EthRpcRouteSpec.scala @@ -27,7 +27,7 @@ class EthRpcRouteSpec extends RouteSpec("/eth") with WithDomain with EthHelpers Post( routePath(""), Json.obj("method" -> method, "params" -> Json.arr(params*), "id" -> "test") - ) ~> new EthRpcRoute(d.blockchain, d.commonApi.transactions, ntpTime).route ~> check(body) + ) ~> EthRpcRoute(() => d.blockchain, d.transactionsApi, ntpTime).route ~> check(body) } "eth_chainId" in withDomain(DefaultWavesSettings) { d => @@ -270,7 +270,7 @@ class EthRpcRouteSpec extends RouteSpec("/eth") with WithDomain with EthHelpers val issue3 = d.blockchain.accountData(randomKP.toAddress, "assetId").get.asInstanceOf[BinaryDataEntry].value - new EthRpcRoute(d.blockchain, d.commonApi.transactions, ntpTime).route + EthRpcRoute(() => d.blockchain, d.transactionsApi, ntpTime).route .anyParamTest(routePath("/assets"), "id")( EthEncoding.toHexString(issue1.id().arr.take(20)), EthEncoding.toHexString(issue2.id().arr.take(20)), @@ -324,13 +324,13 @@ class EthRpcRouteSpec extends RouteSpec("/eth") with WithDomain with EthHelpers "absence of id" in withDomain() { d => Post(routePath(""), Json.obj("method" -> "eth_chainId")) - ~> new EthRpcRoute(d.blockchain, d.commonApi.transactions, ntpTime).route + ~> EthRpcRoute(() => d.blockchain, d.transactionsApi, ntpTime).route ~> check { responseAs[JsObject] shouldBe Json.obj("id" -> null, "jsonrpc" -> "2.0", "result" -> "0x54") } } "absence of method" in withDomain() { d => Post(routePath(""), Json.obj()) - ~> new EthRpcRoute(d.blockchain, d.commonApi.transactions, ntpTime).route + ~> EthRpcRoute(() => d.blockchain, d.transactionsApi, ntpTime).route ~> check { responseAs[JsObject] shouldBe Json.obj() } } diff --git a/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala b/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala index 4dd1bf88b49..14fb2875c30 100644 --- a/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala +++ b/node/src/test/scala/com/wavesplatform/api/http/CustomJsonMarshallerSpec.scala @@ -62,11 +62,7 @@ class CustomJsonMarshallerSpec } private val transactionsRoute = - TransactionsApiRoute( - restAPISettings, - transactionsApi, - testWallet, - blockchain, + TransactionsApiRoute(restAPISettings, transactionsApi, testWallet, () => blockchain, () => utx.size, publisher, ntpTime, @@ -110,11 +106,7 @@ class CustomJsonMarshallerSpec pending // todo: fix when distributions/portfolio become testable } - private val assetsRoute = AssetsApiRoute( - restAPISettings, - testWallet, - publisher, - blockchain, + private val assetsRoute = AssetsApiRoute(restAPISettings, testWallet, publisher, () => blockchain, ntpTime, accountsApi, assetsApi, diff --git a/node/src/test/scala/com/wavesplatform/db/TestUtxPool.scala b/node/src/test/scala/com/wavesplatform/db/TestUtxPool.scala new file mode 100644 index 00000000000..4fb67c12883 --- /dev/null +++ b/node/src/test/scala/com/wavesplatform/db/TestUtxPool.scala @@ -0,0 +1,20 @@ +package com.wavesplatform.db +import com.wavesplatform.settings.UtxSettings +import com.wavesplatform.state.{Blockchain, Diff} +import com.wavesplatform.utils.Time +import com.wavesplatform.utx.UtxPoolImpl + +class TestUtxPool( + time: Time, + blockchain: Blockchain, + utxSettings: UtxSettings, + maxTxErrorLogSize: Int, + isMiningEnabled: Boolean, + beforeSetPriorityDiffs: () => Unit +) extends UtxPoolImpl(time, blockchain, utxSettings, maxTxErrorLogSize, isMiningEnabled) { + + override def setPriorityDiffs(discDiffs: Seq[Diff]): Unit = { + beforeSetPriorityDiffs() + super.setPriorityDiffs(discDiffs) + } +} diff --git a/node/src/test/scala/com/wavesplatform/db/WithState.scala b/node/src/test/scala/com/wavesplatform/db/WithState.scala index ed417bb5154..4c887aeba55 100644 --- a/node/src/test/scala/com/wavesplatform/db/WithState.scala +++ b/node/src/test/scala/com/wavesplatform/db/WithState.scala @@ -168,7 +168,8 @@ trait WithDomain extends WithState { _: Suite => settings: WavesSettings = DomainPresets.SettingsFromDefaultConfig.addFeatures(BlockchainFeatures.SmartAccounts), // SmartAccounts to allow V2 transfers by default balances: Seq[AddrWithBalance] = Seq.empty, - wrapDB: DB => DB = identity + wrapDB: DB => DB = identity, + beforeSetPriorityDiffs: () => Unit = () => () )(test: Domain => A): A = withLevelDBWriter(settings) { blockchain => var domain: Domain = null @@ -180,7 +181,7 @@ trait WithDomain extends WithState { _: Suite => BlockchainUpdateTriggers.combined(domain.triggers), loadActiveLeases(db, _, _) ) - domain = Domain(wrapDB(db), bcu, blockchain, settings) + domain = Domain(wrapDB(db), bcu, blockchain, settings, beforeSetPriorityDiffs) val genesis = balances.map { case AddrWithBalance(address, amount) => TxHelpers.genesis(address, amount) } diff --git a/node/src/test/scala/com/wavesplatform/history/Domain.scala b/node/src/test/scala/com/wavesplatform/history/Domain.scala index 9010071d1eb..8451ef3b397 100644 --- a/node/src/test/scala/com/wavesplatform/history/Domain.scala +++ b/node/src/test/scala/com/wavesplatform/history/Domain.scala @@ -10,6 +10,7 @@ import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.consensus.nxt.NxtLikeConsensusBlockData import com.wavesplatform.consensus.{PoSCalculator, PoSSelector} import com.wavesplatform.database.{DBExt, Keys, LevelDBWriter} +import com.wavesplatform.db.TestUtxPool import com.wavesplatform.events.BlockchainUpdateTriggers import com.wavesplatform.features.BlockchainFeatures.{BlockV5, RideV6} import com.wavesplatform.lagonaki.mocks.TestBlock @@ -18,13 +19,13 @@ import com.wavesplatform.lang.script.Script import com.wavesplatform.settings.WavesSettings import com.wavesplatform.state.* import com.wavesplatform.state.diffs.TransactionDiffer +import com.wavesplatform.state.reader.CompositeBlockchain import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves} import com.wavesplatform.transaction.smart.script.trace.TracedResult import com.wavesplatform.transaction.{BlockchainUpdater, *} import com.wavesplatform.utils.{EthEncoding, SystemTime} -import com.wavesplatform.utx.UtxPoolImpl import com.wavesplatform.wallet.Wallet -import com.wavesplatform.{Application, TestValues, crypto, database} +import com.wavesplatform.{TestValues, crypto, database} import monix.execution.Scheduler.Implicits.global import org.iq80.leveldb.DB import org.scalatest.matchers.should.Matchers.* @@ -36,7 +37,13 @@ import scala.concurrent.duration.* import scala.util.Try import scala.util.control.NonFatal -case class Domain(db: DB, blockchainUpdater: BlockchainUpdaterImpl, levelDBWriter: LevelDBWriter, settings: WavesSettings) { +case class Domain( + db: DB, + blockchainUpdater: BlockchainUpdaterImpl, + levelDBWriter: LevelDBWriter, + settings: WavesSettings, + beforeSetPriorityDiffs: () => Unit = () => () +) { import Domain.* val blockchain: BlockchainUpdaterImpl = blockchainUpdater @@ -52,10 +59,15 @@ case class Domain(db: DB, blockchainUpdater: BlockchainUpdaterImpl, levelDBWrite def createDiffE(tx: Transaction): Either[ValidationError, Diff] = transactionDiffer(tx).resultE def createDiff(tx: Transaction): Diff = createDiffE(tx).explicitGet() - lazy val utxPool: UtxPoolImpl = - new UtxPoolImpl(SystemTime, blockchain, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable) + lazy val utxPool = + new TestUtxPool(SystemTime, blockchain, settings.utxSettings, settings.maxTxErrorLogSize, settings.minerSettings.enable, beforeSetPriorityDiffs) lazy val wallet: Wallet = Wallet(settings.walletSettings.copy(file = None)) + def blockchainWithDiscardedDiffs(): CompositeBlockchain = { + def bc = CompositeBlockchain(blockchain, utxPool.discardedMicrosDiff()) + utxPool.priorityPool.optimisticRead(bc)(_ => true) + } + object commonApi { /** @return @@ -64,7 +76,7 @@ case class Domain(db: DB, blockchainUpdater: BlockchainUpdaterImpl, levelDBWrite * [[com.wavesplatform.state.diffs.FeeValidation#getMinFee(com.wavesplatform.state.Blockchain, com.wavesplatform.transaction.Transaction)]] */ def calculateFee(tx: Transaction): (Asset, Long, Long) = - transactions.calculateFee(tx).explicitGet() + transactionsApi.calculateFee(tx).explicitGet() def calculateWavesFee(tx: Transaction): Long = { val (Waves, _, feeInWaves) = calculateFee(tx): @unchecked @@ -72,7 +84,7 @@ case class Domain(db: DB, blockchainUpdater: BlockchainUpdaterImpl, levelDBWrite } def transactionMeta(transactionId: ByteStr): TransactionMeta = - transactions + transactionsApi .transactionById(transactionId) .getOrElse(throw new NoSuchElementException(s"No meta for $transactionId")) @@ -83,16 +95,7 @@ case class Domain(db: DB, blockchainUpdater: BlockchainUpdaterImpl, levelDBWrite } def addressTransactions(address: Address): Seq[TransactionMeta] = - transactions.transactionsByAddress(address, None, Set.empty, None).toListL.runSyncUnsafe() - - lazy val transactions: CommonTransactionsApi = CommonTransactionsApi( - blockchainUpdater.bestLiquidDiff.map(diff => Height(blockchainUpdater.height) -> diff), - db, - blockchain, - utxPool, - tx => Future.successful(utxPool.putIfNew(tx)), - Application.loadBlockAt(db, blockchain) - ) + transactionsApi.transactionsByAddress(address, None, Set.empty, None).toListL.runSyncUnsafe() } def liquidState: Option[NgState] = { @@ -135,8 +138,11 @@ case class Domain(db: DB, blockchainUpdater: BlockchainUpdaterImpl, levelDBWrite .getOrElse(TestBlock.create(Nil)) } - def liquidDiff: Diff = - blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty) + def liquidDiff: Diff = { + def liquidDiff = blockchainUpdater.bestLiquidDiff.getOrElse(Diff()) + def totalDiff = liquidDiff.combineE(utxPool.discardedMicrosDiff()).explicitGet() + utxPool.priorityPool.optimisticRead(totalDiff)(_ => true) + } def microBlocks: Vector[MicroBlock] = blockchain.microblockIds.reverseIterator.flatMap(blockchain.microBlock).to(Vector) @@ -159,24 +165,28 @@ case class Domain(db: DB, blockchainUpdater: BlockchainUpdaterImpl, levelDBWrite def nftList(address: Address): Seq[(IssuedAsset, AssetDescription)] = db.withResource { resource => AddressPortfolio - .nftIterator(resource, address, blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), None, blockchainUpdater.assetDescription) + .nftIterator(resource, address, liquidDiff, None, blockchainUpdater.assetDescription) .toSeq } def addressTransactions(address: Address, from: Option[ByteStr] = None): Seq[(Height, Transaction)] = - AddressTransactions - .allAddressTransactions( - db, - blockchainUpdater.bestLiquidDiff.map(diff => Height(blockchainUpdater.height) -> diff), - address, - None, - Set.empty, - from - ) - .map { case (m, tx) => m.height -> tx } - .toSeq - - def portfolio(address: Address): Seq[(IssuedAsset, Long)] = Domain.portfolio(address, db, blockchainUpdater) + transactionsApi + .transactionsByAddress(address, None, Set(), from) + .map(meta => (meta.height, meta.transaction)) + .toListL + .runSyncUnsafe() + + def portfolio(address: Address): Seq[(IssuedAsset, Long)] = + db.withResource { resource => + AddressPortfolio + .assetBalanceIterator( + resource, + address, + liquidDiff, + id => blockchainUpdater.assetDescription(id).exists(!_.nft) + ) + .toSeq + } def appendAndAssertSucceed(txs: Transaction*): Block = { val block = createBlock(Block.PlainBlockVersion, txs) @@ -392,24 +402,24 @@ case class Domain(db: DB, blockchainUpdater: BlockchainUpdaterImpl, levelDBWrite } val transactionsApi: CommonTransactionsApi = CommonTransactionsApi( - blockchainUpdater.bestLiquidDiff.map(Height(blockchainUpdater.height) -> _), + Some(Height(blockchainUpdater.height) -> liquidDiff), db, - blockchain, + () => blockchainWithDiscardedDiffs(), utxPool, - _ => Future.successful(TracedResult(Right(true))), + tx => Future.successful(utxPool.putIfNew(tx)), h => blocksApi.blockAtHeight(h) ) val accountsApi: CommonAccountsApi = CommonAccountsApi( - () => blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), + () => liquidDiff, db, - blockchain + () => blockchainWithDiscardedDiffs() ) val assetsApi: CommonAssetsApi = CommonAssetsApi( - () => blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), + () => liquidDiff, db, - blockchain + () => blockchainWithDiscardedDiffs() ) } @@ -426,15 +436,4 @@ object Domain { bcu.processBlock(block, hitSource) } } - - def portfolio(address: Address, db: DB, blockchainUpdater: BlockchainUpdaterImpl): Seq[(IssuedAsset, Long)] = db.withResource { resource => - AddressPortfolio - .assetBalanceIterator( - resource, - address, - blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), - id => blockchainUpdater.assetDescription(id).exists(!_.nft) - ) - .toSeq - } } diff --git a/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala index 57c46bb9196..3c51e57d1b6 100644 --- a/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/AddressRouteSpec.scala @@ -56,7 +56,7 @@ class AddressRouteSpec extends RouteSpec("/addresses") with PathMockFactory with private val addressApiRoute: AddressApiRoute = AddressApiRoute( restAPISettings, wallet, - blockchain, + () => blockchain, utxPoolSynchronizer, new TestTime, Schedulers.timeBoundedFixedPool( @@ -80,7 +80,7 @@ class AddressRouteSpec extends RouteSpec("/addresses") with PathMockFactory with routePath("/balance/{address}/{confirmations}") in withDomain(balances = Seq(AddrWithBalance(TxHelpers.defaultAddress))) { d => val route = addressApiRoute - .copy(blockchain = d.blockchainUpdater, commonAccountsApi = CommonAccountsApi(() => d.liquidDiff, d.db, d.blockchainUpdater)) + .copy(blockchain = () => d.blockchainUpdater, commonAccountsApi = CommonAccountsApi(() => d.liquidDiff, d.db, () => d.blockchainUpdater)) .route val address = TxHelpers.signer(1).toAddress @@ -349,7 +349,7 @@ class AddressRouteSpec extends RouteSpec("/addresses") with PathMockFactory with routePath(s"/scriptInfo/ after ${BlockchainFeatures.SynchronousCalls}") in { val blockchain = stub[Blockchain]("blockchain") - val route = seal(addressApiRoute.copy(blockchain = blockchain).route) + val route = seal(addressApiRoute.copy(blockchain = () => blockchain).route) (() => blockchain.activatedFeatures).when().returning(Map(BlockchainFeatures.SynchronousCalls.id -> 0)) val script = ExprScript(TRUE).explicitGet() @@ -403,7 +403,7 @@ class AddressRouteSpec extends RouteSpec("/addresses") with PathMockFactory with val route = addressApiRoute - .copy(blockchain = d.blockchainUpdater, commonAccountsApi = CommonAccountsApi(() => d.liquidDiff, d.db, d.blockchainUpdater)) + .copy(blockchain = () => d.blockchainUpdater, commonAccountsApi = CommonAccountsApi(() => d.liquidDiff, d.db, () => d.blockchainUpdater)) .route val requestBody = Json.obj("keys" -> Seq("test")) @@ -451,7 +451,7 @@ class AddressRouteSpec extends RouteSpec("/addresses") with PathMockFactory with val route = addressApiRoute - .copy(blockchain = d.blockchainUpdater, commonAccountsApi = CommonAccountsApi(() => d.liquidDiff, d.db, d.blockchainUpdater)) + .copy(blockchain = () => d.blockchainUpdater, commonAccountsApi = CommonAccountsApi(() => d.liquidDiff, d.db, () => d.blockchainUpdater)) .route val maxLimitKeys = Seq.fill(addressApiRoute.settings.dataKeysRequestLimit)(key) diff --git a/node/src/test/scala/com/wavesplatform/http/AliasBroadcastRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AliasBroadcastRouteSpec.scala index 70bf49b7c2a..f73ccec3fed 100644 --- a/node/src/test/scala/com/wavesplatform/http/AliasBroadcastRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/AliasBroadcastRouteSpec.scala @@ -20,13 +20,7 @@ import scala.concurrent.duration.DurationInt class AliasBroadcastRouteSpec extends RouteSpec("/alias/broadcast/") with RequestGen with PathMockFactory with RestAPISettingsHelper { private[this] val utxPoolSynchronizer = DummyTransactionPublisher.rejecting(tx => TransactionValidationError(GenericError("foo"), tx)) - val route = AliasApiRoute( - restAPISettings, - stub[CommonTransactionsApi], - stub[Wallet], - utxPoolSynchronizer, - stub[Time], - stub[Blockchain], + val route = AliasApiRoute(restAPISettings, stub[CommonTransactionsApi], stub[Wallet], utxPoolSynchronizer, stub[Time], () => stub[Blockchain], new RouteTimeout(60.seconds)(Schedulers.fixedPool(1, "heavy-request-scheduler")) ).route diff --git a/node/src/test/scala/com/wavesplatform/http/AssetsBroadcastRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AssetsBroadcastRouteSpec.scala index f22f80cdd4e..9ce241eab4b 100644 --- a/node/src/test/scala/com/wavesplatform/http/AssetsBroadcastRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/AssetsBroadcastRouteSpec.scala @@ -30,7 +30,7 @@ class AssetsBroadcastRouteSpec extends RouteSpec("/assets/broadcast/") with Requ restAPISettings, stub[Wallet], DummyTransactionPublisher.rejecting(tx => TransactionValidationError(GenericError("foo"), tx)), - stub[Blockchain], + () => stub[Blockchain], stub[Time], stub[CommonAccountsApi], stub[CommonAssetsApi], @@ -175,7 +175,7 @@ class AssetsBroadcastRouteSpec extends RouteSpec("/assets/broadcast/") with Requ restAPISettings, stub[Wallet], DummyTransactionPublisher.accepting, - stub[Blockchain], + () => stub[Blockchain], stub[Time], stub[CommonAccountsApi], stub[CommonAssetsApi], diff --git a/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala index d3aac488cf0..b5a7b743225 100644 --- a/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/AssetsRouteSpec.scala @@ -51,7 +51,7 @@ class AssetsRouteSpec extends RouteSpec("/assets") with Eventually with RestAPIS restAPISettings, testWallet, DummyTransactionPublisher.accepting, - d.blockchain, + () => d.blockchain, TestTime(), d.accountsApi, d.assetsApi, diff --git a/node/src/test/scala/com/wavesplatform/http/LeaseBroadcastRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/LeaseBroadcastRouteSpec.scala index 7420b09eaea..1a7ca2e222b 100644 --- a/node/src/test/scala/com/wavesplatform/http/LeaseBroadcastRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/LeaseBroadcastRouteSpec.scala @@ -5,7 +5,6 @@ import com.wavesplatform.api.common.CommonAccountsApi import com.wavesplatform.api.http.ApiError.* import com.wavesplatform.api.http.RouteTimeout import com.wavesplatform.api.http.leasing.LeaseApiRoute -import com.wavesplatform.state.Blockchain import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError import com.wavesplatform.transaction.Transaction import com.wavesplatform.transaction.TxValidationError.GenericError @@ -22,10 +21,7 @@ import scala.concurrent.duration.* class LeaseBroadcastRouteSpec extends RouteSpec("/leasing/broadcast/") with RequestGen with PathMockFactory with RestAPISettingsHelper { private[this] val publisher = DummyTransactionPublisher.rejecting(t => TransactionValidationError(GenericError("foo"), t)) - private[this] val route = LeaseApiRoute( - restAPISettings, - stub[Wallet], - stub[Blockchain], + private[this] val route = LeaseApiRoute(restAPISettings, stub[Wallet], publisher, stub[Time], stub[CommonAccountsApi], diff --git a/node/src/test/scala/com/wavesplatform/http/LeaseRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/LeaseRouteSpec.scala index 945cc66b966..f5a52cddd20 100644 --- a/node/src/test/scala/com/wavesplatform/http/LeaseRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/LeaseRouteSpec.scala @@ -52,10 +52,9 @@ class LeaseRouteSpec LeaseApiRoute( restAPISettings, testWallet, - domain.blockchain, (_, _) => Future.successful(TracedResult(Right(true))), ntpTime, - CommonAccountsApi(() => domain.blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), domain.db, domain.blockchain), + CommonAccountsApi(() => domain.blockchainUpdater.bestLiquidDiff.getOrElse(Diff.empty), domain.db, () => domain.blockchain), new RouteTimeout(60.seconds)(Schedulers.fixedPool(1, "heavy-request-scheduler")) ) @@ -516,10 +515,7 @@ class LeaseRouteSpec val blockchain = stub[Blockchain] val commonApi = stub[CommonAccountsApi] - val route = LeaseApiRoute( - restAPISettings, - stub[Wallet], - blockchain, + val route = LeaseApiRoute(restAPISettings, stub[Wallet], stub[TransactionPublisher], SystemTime, commonApi, diff --git a/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala b/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala index 2c45e189413..2f6934de0ab 100644 --- a/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/ProtoVersionTransactionsSpec.scala @@ -58,11 +58,7 @@ class ProtoVersionTransactionsSpec extends RouteSpec("/transactions") with RestA private val transactionsApi = mock[CommonTransactionsApi] private val route: Route = - TransactionsApiRoute( - restAPISettings, - transactionsApi, - testWallet, - blockchain, + TransactionsApiRoute(restAPISettings, transactionsApi, testWallet, () => blockchain, () => utx.size, DummyTransactionPublisher.accepting, ntpTime, diff --git a/node/src/test/scala/com/wavesplatform/http/SpentComplexitySpec.scala b/node/src/test/scala/com/wavesplatform/http/SpentComplexitySpec.scala index e963925b250..953b3d9534b 100644 --- a/node/src/test/scala/com/wavesplatform/http/SpentComplexitySpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/SpentComplexitySpec.scala @@ -67,11 +67,7 @@ class SpentComplexitySpec private def route(d: Domain) = seal( - TransactionsApiRoute( - restAPISettings, - d.transactionsApi, - testWallet, - d.blockchain, + TransactionsApiRoute(restAPISettings, d.transactionsApi, testWallet, () => d.blockchain, () => 0, DummyTransactionPublisher.accepting, ntpTime, diff --git a/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala b/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala index b726301624b..b3c6b9f3c5a 100644 --- a/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/TransactionBroadcastSpec.scala @@ -39,7 +39,7 @@ class TransactionBroadcastSpec restAPISettings, stub[CommonTransactionsApi], stub[Wallet], - blockchain, + () => blockchain, mockFunction[Int], transactionPublisher, testTime, @@ -62,7 +62,7 @@ class TransactionBroadcastSpec val transactionPublisher = blockchain.stub.transactionPublisher(testTime) - val route = transactionsApiRoute.copy(blockchain = blockchain, transactionPublisher = transactionPublisher).route + val route = transactionsApiRoute.copy(blockchain = () => blockchain, transactionPublisher = transactionPublisher).route val transaction = TxHelpers.exchange( ethBuyOrder, @@ -273,7 +273,7 @@ class TransactionBroadcastSpec (blockchain.hasAccountScript _).when(*).returns(true) } val publisher = createTxPublisherStub(blockchain) - val route = transactionsApiRoute.copy(blockchain = blockchain, transactionPublisher = publisher).route + val route = transactionsApiRoute.copy(blockchain = () => blockchain, transactionPublisher = publisher).route Post(routePath("/broadcast?trace=true"), invoke.json()) ~> route ~> check { responseAs[JsObject] should matchJson( diff --git a/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala b/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala index 06104044544..cb66c2afce2 100644 --- a/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala +++ b/node/src/test/scala/com/wavesplatform/http/TransactionsRouteSpec.scala @@ -68,7 +68,7 @@ class TransactionsRouteSpec restAPISettings, addressTransactions, testWallet, - blockchain, + () => blockchain, utxPoolSize, utxPoolSynchronizer, testTime, @@ -123,11 +123,11 @@ class TransactionsRouteSpec seal( new TransactionsApiRoute( restAPISettings, - d.commonApi.transactions, + d.transactionsApi, testWallet, - d.blockchain, + () => d.blockchain, () => 0, - (t, _) => d.commonApi.transactions.broadcastTransaction(t), + (t, _) => d.transactionsApi.broadcastTransaction(t), ntpTime, new RouteTimeout(60.seconds)(Schedulers.fixedPool(1, "heavy-request-scheduler")) ).route @@ -398,7 +398,7 @@ class TransactionsRouteSpec ) ) - val route = seal(transactionsApiRoute.copy(blockchain = blockchain, commonApi = transactionsApi).route) + val route = seal(transactionsApiRoute.copy(blockchain = () => blockchain, commonApi = transactionsApi).route) Get(routePath(s"/info/${transaction.id()}")) ~> route ~> check { responseAs[JsObject] should matchJson(s"""{ | "type" : 18, @@ -455,7 +455,7 @@ class TransactionsRouteSpec ) ) - val route = seal(transactionsApiRoute.copy(blockchain = blockchain, commonApi = transactionsApi).route) + val route = seal(transactionsApiRoute.copy(blockchain = () => blockchain, commonApi = transactionsApi).route) Get(routePath(s"/info/${transaction.id()}")) ~> route ~> check { responseAs[JsObject] should matchJson(s"""{ | "type" : 18, @@ -516,7 +516,7 @@ class TransactionsRouteSpec ) ) - val route = transactionsApiRoute.copy(blockchain = blockchain, commonApi = transactionsApi).route + val route = transactionsApiRoute.copy(blockchain = () => blockchain, commonApi = transactionsApi).route Get(routePath(s"/info/${leaseCancel.id()}")) ~> route ~> check { val json = responseAs[JsObject] json shouldBe Json.parse(s"""{ diff --git a/node/src/test/scala/com/wavesplatform/mining/DiscardedMicroBlocksDiffTest.scala b/node/src/test/scala/com/wavesplatform/mining/DiscardedMicroBlocksDiffTest.scala new file mode 100644 index 00000000000..1f25b61dbc4 --- /dev/null +++ b/node/src/test/scala/com/wavesplatform/mining/DiscardedMicroBlocksDiffTest.scala @@ -0,0 +1,192 @@ +package com.wavesplatform.mining + +import com.wavesplatform.account.Alias +import com.wavesplatform.block.Block.ProtoBlockVersion +import com.wavesplatform.db.WithDomain +import com.wavesplatform.db.WithState.AddrWithBalance +import com.wavesplatform.history.Domain +import com.wavesplatform.lang.directives.values.V6 +import com.wavesplatform.lang.v1.compiler.TestCompiler +import com.wavesplatform.state.appender.BlockAppender +import com.wavesplatform.state.{EmptyDataEntry, IntegerDataEntry, StringDataEntry} +import com.wavesplatform.test.DomainPresets.RideV6 +import com.wavesplatform.test.{NumericExt, PropSpec, TestTime} +import com.wavesplatform.transaction.Asset.IssuedAsset +import com.wavesplatform.transaction.Transaction +import com.wavesplatform.transaction.TransactionType.Transfer +import com.wavesplatform.transaction.TxHelpers.* +import monix.execution.Scheduler +import monix.execution.Scheduler.Implicits.global +import org.scalatest.Assertion + +import java.util.concurrent.CountDownLatch +import scala.concurrent.duration.Duration.Inf +import scala.concurrent.{Await, Future} + +class DiscardedMicroBlocksDiffTest extends PropSpec with WithDomain { + property("interim waves balance") { + testInterimState( + transfer(amount = 123), + _.accountsApi.balance(secondAddress) shouldBe 123 + ) + } + + property("interim asset balance") { + val issueTx = issue() + val asset = IssuedAsset(issueTx.id()) + testInterimState( + transfer(amount = 123, asset = asset), + _.accountsApi.assetBalance(secondAddress, asset) shouldBe 123, + preconditions = Seq(issueTx) + ) + } + + property("interim account data") { + testInterimState( + dataEntry(defaultSigner, IntegerDataEntry("key", 1)), + _.accountsApi.data(defaultAddress, "key").map(_.value) shouldBe Some(1) + ) + } + + property("interim account data delete") { + testInterimState( + dataEntry(defaultSigner, EmptyDataEntry("key")), + _.accountsApi.data(defaultAddress, "key") shouldBe None, + preconditions = Seq(dataEntry(defaultSigner, StringDataEntry("key", "value"))) + ) + } + + property("interim account script") { + testInterimState( + setScript(defaultSigner, TestCompiler(V6).compileExpression("true")), + _.accountsApi.script(defaultAddress) should not be empty + ) + } + + property("interim active leases") { + testInterimState( + lease(), + _.accountsApi.activeLeases(defaultAddress).toListL.runSyncUnsafe() should not be empty + ) + } + + property("interim cancel leases") { + val leaseTx = lease() + testInterimState( + leaseCancel(leaseTx.id()), + _.accountsApi.activeLeases(defaultAddress).toListL.runSyncUnsafe() shouldBe empty, + preconditions = Seq(leaseTx) + ) + } + + property("interim transaction") { + val tx = transfer() + testInterimState( + tx, + { d => + d.transactionsApi.transactionsByAddress(defaultAddress, None, Set(Transfer)).toListL.runSyncUnsafe().map(_.transaction) shouldBe Seq(tx) + d.transactionsApi.transactionById(tx.id()).map(_.transaction) shouldBe Some(tx) + } + ) + } + + property("interim asset issue") { + val issueTx = issue() + val asset = IssuedAsset(issueTx.id()) + testInterimState( + issueTx, + _.assetsApi.description(asset) should not be empty + ) + } + + property("interim asset reissue") { + val issueTx = issue() + val asset = IssuedAsset(issueTx.id()) + testInterimState( + reissue(asset, amount = 1), + _.assetsApi.description(asset).get.totalVolume shouldBe issueTx.quantity.value + 1, + preconditions = Seq(issueTx) + ) + } + + property("interim asset burn") { + val issueTx = issue() + val asset = IssuedAsset(issueTx.id()) + testInterimState( + burn(asset, amount = 1), + _.assetsApi.description(asset).get.totalVolume shouldBe issueTx.quantity.value - 1, + preconditions = Seq(issueTx) + ) + } + + property("interim asset script") { + val exprTrue = TestCompiler(V6).compileExpression("true") + val exprFalse = TestCompiler(V6).compileExpression("false") + val issueTx = issue(script = Some(exprTrue)) + val asset = IssuedAsset(issueTx.id()) + testInterimState( + setAssetScript(asset = asset, script = exprFalse, fee = 1.waves), + _.assetsApi.description(asset).flatMap(_.script).map(_.script) shouldBe Some(exprFalse), + preconditions = Seq(issueTx) + ) + } + + property("interim alias") { + testInterimState( + createAlias("alias"), + { d => + d.accountsApi.resolveAlias(Alias('T', "alias")) shouldBe Right(defaultAddress) + d.transactionsApi.aliasesOfAddress(defaultAddress).toListL.runSyncUnsafe().map(_._2.aliasName) shouldBe Seq("alias") + } + ) + } + + property("interim sponsorship") { + val issueTx = issue() + val asset = IssuedAsset(issueTx.id()) + testInterimState( + sponsor(asset, Some(123)), + _.assetsApi.description(asset).map(_.sponsorship) shouldBe Some(123), + preconditions = Seq(issueTx) + ) + } + + property("interim sponsorship cancel") { + val issueTx = issue() + val asset = IssuedAsset(issueTx.id()) + val sponsorTx = sponsor(asset, Some(123)) + testInterimState( + sponsor(asset, None), + _.assetsApi.description(asset).map(_.sponsorship) shouldBe Some(0), + preconditions = Seq(issueTx, sponsorTx) + ) + } + + private def testInterimState(tx: Transaction, assert: Domain => Assertion, preconditions: Seq[Transaction] = Nil): Assertion = { + val waitInterimState = new CountDownLatch(1) + val endInterimState = new CountDownLatch(1) + withDomain( + RideV6, + AddrWithBalance.enoughBalances(defaultSigner), + beforeSetPriorityDiffs = { () => + waitInterimState.countDown() + endInterimState.await() + } + ) { d => + val previousBlockId = d.appendBlock(preconditions: _*).id() + d.appendMicroBlock(tx) + assert(d) + + val keyBlock = d.createBlock(ProtoBlockVersion, Nil, Some(previousBlockId)) + val appendKeyBlock = BlockAppender(d.blockchain, TestTime(), d.utxPool, d.posSelector, Scheduler.global, verify = false)(keyBlock).runToFuture + + waitInterimState.await() + val check = Future(assert(d)) + endInterimState.countDown() + + Await.result(check, Inf) + Await.result(appendKeyBlock, Inf) + assert(d) + } + } +} diff --git a/node/src/test/scala/com/wavesplatform/serialization/EvaluatedPBSerializationTest.scala b/node/src/test/scala/com/wavesplatform/serialization/EvaluatedPBSerializationTest.scala index f3d0ad8d824..0568f642328 100644 --- a/node/src/test/scala/com/wavesplatform/serialization/EvaluatedPBSerializationTest.scala +++ b/node/src/test/scala/com/wavesplatform/serialization/EvaluatedPBSerializationTest.scala @@ -114,7 +114,7 @@ class EvaluatedPBSerializationTest restAPISettings, d.transactionsApi, d.wallet, - d.blockchain, + () => d.blockchain, () => d.utxPool.size, (_, _) => Future.successful(TracedResult(Right(true))), ntpTime, diff --git a/node/src/test/scala/com/wavesplatform/state/RollbackSpec.scala b/node/src/test/scala/com/wavesplatform/state/RollbackSpec.scala index 168e898316d..d5bc1315dfc 100644 --- a/node/src/test/scala/com/wavesplatform/state/RollbackSpec.scala +++ b/node/src/test/scala/com/wavesplatform/state/RollbackSpec.scala @@ -374,7 +374,7 @@ class RollbackSpec extends FreeSpec with WithDomain { TestBlock.create( nextTs, genesisBlockId, - Seq(TxHelpers.dataEntry(sender, dataEntry)) + Seq(TxHelpers.dataEntry(sender, dataEntry, TxVersion.V1)) ) ) diff --git a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/BalancesV4Test.scala b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/BalancesV4Test.scala index 71798f7c5a2..91cf148610d 100644 --- a/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/BalancesV4Test.scala +++ b/node/src/test/scala/com/wavesplatform/state/diffs/smart/scenarios/BalancesV4Test.scala @@ -94,7 +94,7 @@ class BalancesV4Test extends PropSpec with WithState { rideV4Activated ) { case (d, s) => - val apiBalance = com.wavesplatform.api.common.CommonAccountsApi(() => d, db, s).balanceDetails(acc1.toAddress).explicitGet() + val apiBalance = com.wavesplatform.api.common.CommonAccountsApi(() => d, db, () => s).balanceDetails(acc1.toAddress).explicitGet() val data = d.accountData(dapp.toAddress) data.data("available") shouldBe IntegerDataEntry("available", apiBalance.available) apiBalance.available shouldBe 16 * Constants.UnitsInWave diff --git a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala index 5aeb7b0f5c3..dcd0e1db6fd 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/TxHelpers.scala @@ -138,8 +138,8 @@ object TxHelpers { .selfSigned(version, sender, asset, amount, reissuable = reissuable, fee, timestamp, chainId) .explicitGet() - def dataEntry(account: KeyPair, value: DataEntry[?]): DataTransaction = - DataTransaction.selfSigned(TxVersion.V1, account, Seq(value), TestValues.fee * 3, timestamp).explicitGet() + def dataEntry(account: KeyPair, value: DataEntry[?], version: TxVersion = TxVersion.V2): DataTransaction = + DataTransaction.selfSigned(version, account, Seq(value), TestValues.fee * 3, timestamp).explicitGet() def dataSingle(account: KeyPair = defaultSigner, key: String = "test", value: String = "test"): DataTransaction = data(account, Seq(StringDataEntry(key, value))) @@ -351,7 +351,7 @@ object TxHelpers { } def setAssetScript( - acc: KeyPair, + acc: KeyPair = defaultSigner, asset: IssuedAsset, script: Script, fee: Long = FeeConstants(TransactionType.SetAssetScript) * FeeUnit, diff --git a/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionStateChangesSpec.scala b/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionStateChangesSpec.scala index 95b44feecc8..e9e0e44c716 100644 --- a/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionStateChangesSpec.scala +++ b/node/src/test/scala/com/wavesplatform/transaction/smart/EthereumTransactionStateChangesSpec.scala @@ -77,7 +77,7 @@ class EthereumTransactionStateChangesSpec extends FlatSpec with WithDomain with d.appendBlock(invoke) d.liquidAndSolidAssert { () => - d.commonApi.transactions.transactionById(invoke.id()) match { + d.transactionsApi.transactionById(invoke.id()) match { case Some(meta: TransactionMeta.Ethereum) => assert(!meta.succeeded, "should fail") Json.toJson(meta.invokeScriptResult) should matchJson(""" @@ -130,7 +130,7 @@ class EthereumTransactionStateChangesSpec extends FlatSpec with WithDomain with d.liquidAndSolidAssert { () => d.blockchain.accountData(dApp.toAddress, "test") shouldBe Some(StringDataEntry("test", "foo")) - d.commonApi.transactions.transactionById(invoke.id()) match { + d.transactionsApi.transactionById(invoke.id()) match { case Some(meta: TransactionMeta.Ethereum) => assert(meta.succeeded, "should succeed") Json.toJson(meta.invokeScriptResult) should matchJson("""{ @@ -191,7 +191,7 @@ class EthereumTransactionStateChangesSpec extends FlatSpec with WithDomain with d.appendBlock(invoke) d.liquidAndSolidAssert { () => - d.commonApi.transactions.transactionById(invoke.id()) match { + d.transactionsApi.transactionById(invoke.id()) match { case Some(meta: TransactionMeta.Ethereum) => assert(!meta.succeeded, "should fail") Json.toJson(meta.invokeScriptResult) should matchJson(""" @@ -283,7 +283,7 @@ class EthereumTransactionStateChangesSpec extends FlatSpec with WithDomain with d.blockchain.accountData(dApp.toAddress, "test") shouldBe Some(StringDataEntry("test", "foo")) d.blockchain.accountData(nestedDApp.toAddress, "test1") shouldBe Some(StringDataEntry("test1", "bar")) - d.commonApi.transactions.transactionById(invoke.id()) match { + d.transactionsApi.transactionById(invoke.id()) match { case Some(meta: TransactionMeta.Ethereum) => assert(meta.succeeded, "should succeed") Json.toJson(meta.invokeScriptResult) should matchJson("""{