Skip to content

Commit

Permalink
NODE-2626 Corrected snapshot hash of LeaseState (#3909)
Browse files Browse the repository at this point in the history
  • Loading branch information
xrtm000 authored Nov 17, 2023
1 parent 9b4827e commit 261b759
Show file tree
Hide file tree
Showing 18 changed files with 195 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,15 @@ class AccountsApiGrpcImpl(commonApi: CommonAccountsApi)(implicit sc: Scheduler)

override def getScript(request: AccountRequest): Future[ScriptResponse] = Future {
commonApi.script(request.address.toAddress()) match {
case Some(desc) => ScriptResponse(PBTransactions.toPBScript(Some(desc.script)), desc.script.expr.toString, desc.verifierComplexity, desc.publicKey.toByteString)
case None => ScriptResponse()
case Some(desc) =>
ScriptResponse(
PBTransactions.toPBScript(Some(desc.script)),
desc.script.expr.toString,
desc.verifierComplexity,
desc.publicKey.toByteString
)
case None =>
ScriptResponse()
}
}

Expand Down
42 changes: 26 additions & 16 deletions grpc-server/src/main/scala/com/wavesplatform/events/events.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import com.wavesplatform.transaction.assets.exchange.ExchangeTransaction
import com.wavesplatform.transaction.lease.LeaseTransaction
import com.wavesplatform.transaction.smart.InvokeScriptTransaction
import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction}
import com.wavesplatform.transaction.{Asset, Authorized, CreateAliasTransaction, EthereumTransaction}
import com.wavesplatform.transaction.{Asset, Authorized, CreateAliasTransaction, EthereumTransaction, Transaction}

import scala.collection.mutable
import scala.collection.mutable.ArrayBuffer
Expand Down Expand Up @@ -389,7 +389,11 @@ object StateUpdate {
private lazy val WavesAlias = Alias.fromString("alias:W:waves", Some('W'.toByte)).explicitGet()
private lazy val WavesAddress = Address.fromString("3PGd1eQR8EhLkSogpmu9Ne7hSH1rQ5ALihd", Some('W'.toByte)).explicitGet()

def atomic(blockchainBeforeWithMinerReward: Blockchain, snapshot: StateSnapshot): StateUpdate = {
def atomic(
blockchainBeforeWithMinerReward: Blockchain,
snapshot: StateSnapshot,
txWithLeases: Iterable[(Transaction, Map[ByteStr, LeaseSnapshot])]
): StateUpdate = {
val blockchain = blockchainBeforeWithMinerReward
val blockchainAfter = SnapshotBlockchain(blockchain, snapshot)

Expand Down Expand Up @@ -426,18 +430,20 @@ object StateUpdate {
assetAfter = blockchainAfter.assetDescription(asset)
} yield AssetStateUpdate(asset.id, assetBefore, assetAfter)

val updatedLeases = snapshot.leaseStates.map { case (leaseId, newState) =>
LeaseUpdate(
leaseId,
if (newState.isActive) LeaseStatus.Active else LeaseStatus.Inactive,
newState.amount,
newState.sender,
newState.recipient match {
case `WavesAlias` => WavesAddress
case other => blockchainAfter.resolveAlias(other).explicitGet()
},
newState.sourceId
)
val updatedLeases = txWithLeases.flatMap { case (sourceTxId, leases) =>
leases.map { case (leaseId, newState) =>
LeaseUpdate(
leaseId,
if (newState.isActive) LeaseStatus.Active else LeaseStatus.Inactive,
newState.amount,
newState.sender,
newState.recipient match {
case `WavesAlias` => WavesAddress
case other => blockchainAfter.resolveAlias(other).explicitGet()
},
newState.toDetails(blockchain, Some(sourceTxId), blockchain.leaseDetails(leaseId)).sourceId
)
}
}.toVector

val updatedScripts = snapshot.accountScriptsByAddress.map { case (address, newScript) =>
Expand Down Expand Up @@ -546,13 +552,17 @@ object StateUpdate {
val accBlockchain = SnapshotBlockchain(blockchainBeforeWithReward, accSnapshot)
(
accSnapshot |+| txInfo.snapshot,
updates :+ atomic(accBlockchain, txInfo.snapshot)
updates :+ atomic(accBlockchain, txInfo.snapshot, Seq((txInfo.transaction, txInfo.snapshot.leaseStates)))
)
}
val blockchainAfter = SnapshotBlockchain(blockchainBeforeWithReward, totalSnapshot)
val metadata = transactionsMetadata(blockchainAfter, totalSnapshot)
val refAssets = referencedAssets(blockchainAfter, txsStateUpdates)
val keyBlockUpdate = atomic(blockchainBeforeWithReward, keyBlockSnapshot)
val keyBlockUpdate = atomic(
blockchainBeforeWithReward,
keyBlockSnapshot,
keyBlockSnapshot.transactions.map { case (_, txInfo) => (txInfo.transaction, txInfo.snapshot.leaseStates) }
)
(keyBlockUpdate, txsStateUpdates, metadata, refAssets)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,18 @@ import com.wavesplatform.lang.v1.compiler.Terms.{ARR, CONST_BOOLEAN, CONST_BYTES
import com.wavesplatform.lang.v1.serialization.SerdeV1
import com.wavesplatform.protobuf.transaction.PBAmounts
import com.wavesplatform.state.InvokeScriptResult.{AttachedPayment, Burn, Call, ErrorMessage, Invocation, Issue, Lease, LeaseCancel, Reissue, SponsorFee}
import com.wavesplatform.state.{Blockchain, DataEntry, InvokeScriptResult, TxMeta}
import com.wavesplatform.state.reader.LeaseDetails
import com.wavesplatform.state.{Blockchain, DataEntry, InvokeScriptResult, TxMeta}
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.{Asset, PBSince, Transaction}
import com.wavesplatform.transaction.lease.{LeaseCancelTransaction, LeaseTransaction}
import com.wavesplatform.transaction.serialization.impl.InvokeScriptTxSerializer
import com.wavesplatform.transaction.smart.InvokeScriptTransaction
import com.wavesplatform.transaction.smart.InvokeScriptTransaction.Payment
import com.wavesplatform.transaction.transfer.MassTransferTransaction
import com.wavesplatform.transaction.{Asset, PBSince, Transaction}
import com.wavesplatform.utils.EthEncoding
import play.api.libs.json.*
import play.api.libs.json.JsonConfiguration.Aux
import play.api.libs.json.{JsArray, JsBoolean, JsNumber, JsObject, JsString, JsValue, Json, JsonConfiguration, OWrites, OptionHandlers}

final case class TransactionJsonSerializer(blockchain: Blockchain, commonApi: CommonTransactionsApi) {

Expand Down Expand Up @@ -525,6 +525,6 @@ object TransactionJsonSerializer {
object LeaseRef {
import com.wavesplatform.utils.byteStrFormat
implicit val config: Aux[Json.MacroOptions] = JsonConfiguration(optionHandlers = OptionHandlers.WritesNull)
implicit val jsonWrites: OWrites[LeaseRef] = Json.writes[LeaseRef]
implicit val jsonWrites: OWrites[LeaseRef] = Json.writes[LeaseRef]
}
}
14 changes: 10 additions & 4 deletions node/src/main/scala/com/wavesplatform/database/RocksDBWriter.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import com.google.common.hash.{BloomFilter, Funnels}
import com.google.common.primitives.Ints
import com.wavesplatform.account.{Address, Alias}
import com.wavesplatform.api.common.WavesBalanceIterator
import com.wavesplatform.block.BlockSnapshot
import com.wavesplatform.block.Block.BlockId
import com.wavesplatform.block.BlockSnapshot
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.database
Expand All @@ -17,8 +17,7 @@ import com.wavesplatform.database.protobuf.{StaticAssetInfo, TransactionMeta, Bl
import com.wavesplatform.features.BlockchainFeatures
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.protobuf.block.PBBlocks
import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot
import com.wavesplatform.protobuf.snapshot.TransactionStatus as PBStatus
import com.wavesplatform.protobuf.snapshot.{TransactionStateSnapshot, TransactionStatus as PBStatus}
import com.wavesplatform.protobuf.{ByteStrExt, ByteStringExt}
import com.wavesplatform.settings.{BlockchainSettings, DBSettings}
import com.wavesplatform.state.*
Expand Down Expand Up @@ -485,7 +484,14 @@ class RocksDBWriter(
expiredKeys ++= updateHistory(rw, Keys.assetDetailsHistory(asset), threshold, Keys.assetDetails(asset))
}

for ((id, details) <- snapshot.leaseStates) {
val txLeases = for {
(_, txInfo) <- snapshot.transactions
(id, lease) <- txInfo.snapshot.leaseStates
} yield (Some(txInfo.transaction), id, lease)
val txLeaseIdSet = txLeases.map(_._2).toSet
val allLeases = txLeases ++ snapshot.leaseStates.collect { case (id, lease) if !txLeaseIdSet.contains(id) => (None, id, lease) }
allLeases.foreach { case (txOpt, id, lease) =>
val details = lease.toDetails(this, txOpt, leaseDetails(id))
rw.put(Keys.leaseDetails(id)(height), Some(Right(details)))
expiredKeys ++= updateHistory(rw, Keys.leaseDetailsHistory(id), threshold, Keys.leaseDetails(id))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -437,14 +437,13 @@ class BlockchainUpdaterImpl(
val snapshotsById =
for {
lt <- leaseTransactions
ltMeta <- transactionMeta(lt.id()).toSeq
recipient <- rocksdb.resolveAlias(lt.recipient).toSeq
portfolios = Map(
lt.sender.toAddress -> Portfolio(0, LeaseBalance(0, -lt.amount.value)),
recipient -> Portfolio(0, LeaseBalance(-lt.amount.value, 0))
)
leaseStates = Map(
lt.id() -> LeaseDetails(lt.sender, lt.recipient, lt.amount.value, LeaseDetails.Status.Expired(height), lt.id(), ltMeta.height)
lt.id() -> LeaseSnapshot(lt.sender, lt.recipient, lt.amount.value, LeaseDetails.Status.Expired(height))
)
snapshot = StateSnapshot.build(rocksdb, portfolios, leaseStates = leaseStates).explicitGet()
} yield lt.id() -> snapshot
Expand Down
30 changes: 30 additions & 0 deletions node/src/main/scala/com/wavesplatform/state/LeaseSnapshot.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.wavesplatform.state
import com.wavesplatform.account.{AddressOrAlias, PublicKey}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.state.reader.LeaseDetails
import com.wavesplatform.transaction.Transaction
import com.wavesplatform.transaction.lease.LeaseCancelTransaction

import scala.util.Try

case class LeaseSnapshot(sender: PublicKey, recipient: AddressOrAlias, amount: Long, status: LeaseDetails.Status) {
val isActive: Boolean = status == LeaseDetails.Status.Active

def toDetails(blockchain: Blockchain, txOpt: Option[Transaction], innerDetails: => Option[LeaseDetails]): LeaseDetails = {
def height(id: ByteStr) = blockchain.transactionMeta(id).map(_.height).getOrElse(blockchain.height)
val (sourceId, sourceHeight) = txOpt match {
case Some(c: LeaseCancelTransaction) => (c.leaseId, height(c.leaseId))
case Some(i) if isActive => (i.id(), blockchain.height) // produced by lease or invoke (including eth)
case Some(i) if !isActive && innerDetails.isEmpty => (i.id(), blockchain.height) // produced and cancelled by the same invoke
case _ =>
def id = innerDetails.get.sourceId // cancelled by invoke and produced by other transaction from the state
Try((id, height(id))).getOrElse((ByteStr.empty, 0))
}
LeaseDetails(sender, recipient, amount, status, sourceId, sourceHeight)
}
}

object LeaseSnapshot {
def fromDetails(details: LeaseDetails): LeaseSnapshot =
LeaseSnapshot(details.sender, details.recipient, details.amount, details.status)
}
70 changes: 31 additions & 39 deletions node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ import cats.data.Ior
import cats.implicits.{catsSyntaxEitherId, catsSyntaxSemigroup, toBifunctorOps, toTraverseOps}
import cats.kernel.Monoid
import com.google.protobuf.ByteString
import com.wavesplatform.account.{Address, AddressScheme, Alias, PublicKey}
import com.wavesplatform.account.{Address, Alias, PublicKey}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.crypto.KeyLength
import com.wavesplatform.database.protobuf.EthereumTransactionMeta
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.lang.script.ScriptReader
import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot
import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot.AssetStatic
import com.wavesplatform.protobuf.transaction.{PBAmounts, PBRecipients, PBTransactions}
import com.wavesplatform.protobuf.transaction.{PBAmounts, PBTransactions}
import com.wavesplatform.protobuf.{AddressExt, ByteStrExt, ByteStringExt}
import com.wavesplatform.state.reader.LeaseDetails.Status
import com.wavesplatform.state.reader.{LeaseDetails, SnapshotBlockchain}
Expand All @@ -30,7 +31,7 @@ case class StateSnapshot(
assetNamesAndDescriptions: Map[IssuedAsset, AssetInfo] = Map(),
assetScripts: Map[IssuedAsset, AssetScriptInfo] = Map(),
sponsorships: Map[IssuedAsset, SponsorshipValue] = Map(),
leaseStates: Map[ByteStr, LeaseDetails] = Map(),
leaseStates: Map[ByteStr, LeaseSnapshot] = Map(),
aliases: Map[Alias, Address] = Map(),
orderFills: Map[ByteStr, VolumeAndFee] = Map(),
accountScripts: Map[PublicKey, Option[AccountScriptInfo]] = Map(),
Expand Down Expand Up @@ -63,24 +64,14 @@ case class StateSnapshot(
orderFills.map { case (orderId, VolumeAndFee(volume, fee)) =>
S.OrderFill(orderId.toByteString, volume, fee)
}.toSeq,
leaseStates.map { case (leaseId, LeaseDetails(sender, recipient, amount, status, sourceId, height)) =>
leaseStates.map { case (leaseId, LeaseSnapshot(sender, recipient, amount, status)) =>
val pbStatus = status match {
case Status.Active =>
S.LeaseState.Status.Active(S.LeaseState.Active())
case Status.Cancelled(cancelHeight, txId) =>
S.LeaseState.Status.Cancelled(S.LeaseState.Cancelled(cancelHeight, txId.fold(ByteString.EMPTY)(_.toByteString)))
case Status.Expired(expiredHeight) =>
S.LeaseState.Status.Cancelled(S.LeaseState.Cancelled(expiredHeight))
S.LeaseState.Status.Active(S.LeaseState.Active(amount, sender.toByteString, recipient.asInstanceOf[Address].toByteString))
case _: Status.Cancelled | _: Status.Expired =>
S.LeaseState.Status.Cancelled(S.LeaseState.Cancelled())
}
S.LeaseState(
leaseId.toByteString,
pbStatus,
amount,
sender.toByteString,
ByteString.copyFrom(recipient.asInstanceOf[Address].bytes),
sourceId.toByteString,
height
)
S.LeaseState(leaseId.toByteString, pbStatus)
}.toSeq,
accountScripts.map { case (publicKey, scriptOpt) =>
scriptOpt.fold(
Expand Down Expand Up @@ -171,24 +162,25 @@ object StateSnapshot {
.map(s => s.assetId.toIssuedAssetId -> SponsorshipValue(s.minFee))
.toMap

val leaseStates: Map[ByteStr, LeaseDetails] =
pbSnapshot.leaseStates
.map(ls =>
ls.leaseId.toByteStr -> LeaseDetails(
ls.sender.toPublicKey,
PBRecipients.toAddress(ls.recipient.toByteArray, AddressScheme.current.chainId).explicitGet(),
ls.amount,
ls.status match {
case TransactionStateSnapshot.LeaseState.Status.Cancelled(c) =>
LeaseDetails.Status.Cancelled(c.height, if (c.transactionId.isEmpty) None else Some(c.transactionId.toByteStr))
case _ =>
LeaseDetails.Status.Active
},
ls.originTransactionId.toByteStr,
ls.height
)
)
.toMap
val leaseStates: Map[ByteStr, LeaseSnapshot] =
pbSnapshot.leaseStates.map { ls =>
ls.status match {
case TransactionStateSnapshot.LeaseState.Status.Active(value) =>
ls.leaseId.toByteStr -> LeaseSnapshot(
value.sender.toPublicKey,
value.recipient.toAddress(),
value.amount,
LeaseDetails.Status.Active
)
case _: TransactionStateSnapshot.LeaseState.Status.Cancelled | TransactionStateSnapshot.LeaseState.Status.Empty =>
ls.leaseId.toByteStr -> LeaseSnapshot(
PublicKey(ByteStr.fill(KeyLength)(0)),
Address(Array.fill(Address.HashLength)(0)),
0,
LeaseDetails.Status.Cancelled(0, None)
)
}
}.toMap

val aliases: Map[Alias, Address] =
pbSnapshot.aliases
Expand Down Expand Up @@ -254,7 +246,7 @@ object StateSnapshot {
updatedAssets: Map[IssuedAsset, Ior[AssetInfo, AssetVolumeInfo]] = Map(),
assetScripts: Map[IssuedAsset, AssetScriptInfo] = Map(),
sponsorships: Map[IssuedAsset, Sponsorship] = Map(),
leaseStates: Map[ByteStr, LeaseDetails] = Map(),
leaseStates: Map[ByteStr, LeaseSnapshot] = Map(),
aliases: Map[Alias, Address] = Map(),
accountData: Map[Address, Map[String, DataEntry[?]]] = Map(),
accountScripts: Map[PublicKey, Option[AccountScriptInfo]] = Map(),
Expand Down Expand Up @@ -381,9 +373,9 @@ object StateSnapshot {

private def resolvedLeaseStates(
blockchain: Blockchain,
leaseStates: Map[ByteStr, LeaseDetails],
leaseStates: Map[ByteStr, LeaseSnapshot],
aliases: Map[Alias, Address]
): Map[ByteStr, LeaseDetails] =
): Map[ByteStr, LeaseSnapshot] =
leaseStates.view
.mapValues(details =>
details.copy(recipient = details.recipient match {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,17 @@ object TxStateSnapshotHashBuilder {
} addEntry(KeyType.AssetScript, asset.id.arr)(scriptInfo.script.bytes().arr)

snapshot.leaseStates.foreach { case (leaseId, details) =>
addEntry(KeyType.LeaseStatus, leaseId.arr)(booleanToBytes(details.isActive))
if (details.isActive)
addEntry(KeyType.LeaseStatus, leaseId.arr)(
booleanToBytes(true),
details.sender.arr,
details.recipient.bytes,
Longs.toByteArray(details.amount)
)
else
addEntry(KeyType.LeaseStatus, leaseId.arr)(
booleanToBytes(false)
)
}

snapshot.sponsorships.foreach { case (asset, sponsorship) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import com.wavesplatform.lang.v1.estimator.v2.ScriptEstimatorV2
import com.wavesplatform.lang.v1.estimator.{ScriptEstimator, ScriptEstimatorV1}
import com.wavesplatform.lang.v1.traits.domain.*
import com.wavesplatform.state.reader.LeaseDetails
import com.wavesplatform.state.{AssetVolumeInfo, Blockchain, LeaseBalance, Portfolio, SponsorshipValue, StateSnapshot}
import com.wavesplatform.state.{AssetVolumeInfo, Blockchain, LeaseBalance, LeaseSnapshot, Portfolio, SponsorshipValue, StateSnapshot}
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.TxValidationError.GenericError

Expand Down Expand Up @@ -166,7 +166,7 @@ object DiffsCommon {
senderAddress -> Portfolio(-fee, LeaseBalance(0, amount)),
recipientAddress -> Portfolio(0, LeaseBalance(amount, 0))
)
details = LeaseDetails(sender, recipient, amount, LeaseDetails.Status.Active, txId, blockchain.height)
details = LeaseSnapshot(sender, recipient, amount, LeaseDetails.Status.Active)
snapshot <- StateSnapshot.build(
blockchain,
portfolios = portfolioDiff,
Expand Down Expand Up @@ -207,7 +207,7 @@ object DiffsCommon {
snapshot <- StateSnapshot.build(
blockchain,
portfolios = portfolios,
leaseStates = Map(leaseId -> actionInfo)
leaseStates = Map(leaseId -> LeaseSnapshot.fromDetails(actionInfo))
)
} yield snapshot
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@ import com.wavesplatform.account.{Address, PublicKey}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.*
import com.wavesplatform.state.reader.LeaseDetails
import com.wavesplatform.state.{Blockchain, LeaseBalance, StateSnapshot}
import com.wavesplatform.state.{Blockchain, LeaseBalance, LeaseSnapshot, StateSnapshot}
import play.api.libs.json.{Json, OFormat}

case object CancelAllLeases extends PatchAtHeight('W' -> 462000, 'T' -> 51500) {
private[patch] case class LeaseData(senderPublicKey: String, amount: Long, recipient: String, id: String)

private[patch] case class CancelledLeases(balances: Map[Address, LeaseBalance], cancelledLeases: Seq[LeaseData]) {
private[this] val height: Int = patchHeight.getOrElse(0)
val leaseStates: Map[ByteStr, LeaseDetails] = cancelledLeases.map { data =>
val leaseStates: Map[ByteStr, LeaseSnapshot] = cancelledLeases.map { data =>
val sender = PublicKey(ByteStr.decodeBase58(data.senderPublicKey).get)
val recipient = Address.fromString(data.recipient).explicitGet()
val id = ByteStr.decodeBase58(data.id).get
(id, LeaseDetails(sender, recipient, data.amount, status = LeaseDetails.Status.Expired(height), id, height))
(id, LeaseSnapshot(sender, recipient, data.amount, status = LeaseDetails.Status.Expired(height)))
}.toMap
}

Expand Down
Loading

0 comments on commit 261b759

Please sign in to comment.