Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

NODE-2610 Affected addresses with zero balance diff #3872

Open
wants to merge 2 commits into
base: version-1.5.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ object NewTransactionInfo {
elidedAffectedAddresses(tx, blockchain) ++ maybeDApp
else
snapshot.balances.keySet.map(_._1) ++
snapshot.zeroBalanceAffected ++
snapshot.leaseBalances.keySet ++
snapshot.accountData.keySet ++
calledScripts ++
Expand Down
50 changes: 26 additions & 24 deletions node/src/main/scala/com/wavesplatform/state/StateSnapshot.scala
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ case class StateSnapshot(
accountData: Map[Address, Map[String, DataEntry[?]]] = Map(),
scriptResults: Map[ByteStr, InvokeScriptResult] = Map(),
ethereumTransactionMeta: Map[ByteStr, EthereumTransactionMeta] = Map(),
zeroBalanceAffected: Set[Address] = Set(),
scriptsComplexity: Long = 0
) {
import com.wavesplatform.protobuf.snapshot.TransactionStateSnapshot as S
Expand Down Expand Up @@ -106,7 +107,7 @@ case class StateSnapshot(
def addBalances(portfolios: Map[Address, Portfolio], blockchain: Blockchain): Either[String, StateSnapshot] =
StateSnapshot
.balances(portfolios, SnapshotBlockchain(blockchain, this))
.map(b => copy(balances = balances ++ b))
.map { case (a, b) => copy(zeroBalanceAffected = zeroBalanceAffected ++ a, balances = balances ++ b) }

def withTransaction(tx: NewTransactionInfo): StateSnapshot =
copy(transactions + (tx.transaction.id() -> tx))
Expand Down Expand Up @@ -270,55 +271,55 @@ object StateSnapshot {
): Either[ValidationError, StateSnapshot] = {
val r =
for {
b <- balances(portfolios, blockchain)
lb <- leaseBalances(portfolios, blockchain)
of <- this.orderFills(orderFills, blockchain)
(affected, balances) <- balances(portfolios, blockchain)
leaseBalances <- leaseBalances(portfolios, blockchain)
orderFills <- this.orderFills(orderFills, blockchain)
} yield StateSnapshot(
transactions,
b,
lb,
balances,
leaseBalances,
assetStatics(issuedAssets),
assetVolumes(blockchain, issuedAssets, updatedAssets),
assetNamesAndDescriptions(issuedAssets, updatedAssets),
assetScripts,
sponsorships.collect { case (asset, value: SponsorshipValue) => (asset, value) },
resolvedLeaseStates(blockchain, leaseStates, aliases),
aliases,
of,
orderFills,
accountScripts,
accountData,
scriptResults,
ethereumTransactionMeta,
affected,
scriptsComplexity
)
r.leftMap(GenericError(_))
}

// ignores lease balances from portfolios
private def balances(portfolios: Map[Address, Portfolio], blockchain: Blockchain): Either[String, VectorMap[(Address, Asset), Long]] =
private def balances(
portfolios: Map[Address, Portfolio],
blockchain: Blockchain
): Either[String, (Set[Address], VectorMap[(Address, Asset), Long])] =
flatTraverse(portfolios) { case (address, Portfolio(wavesAmount, _, assets)) =>
val assetBalancesE = flatTraverse(assets) {
flatTraverse(assets.asInstanceOf[VectorMap[Asset, Long]] + (Waves -> wavesAmount)) {
case (_, 0) =>
Right(VectorMap[(Address, Asset), Long]())
case (assetId, balance) =>
safeSum(blockchain.balance(address, assetId), balance, s"$address -> Asset balance")
.map(newBalance => VectorMap((address, assetId: Asset) -> newBalance))
Right((Set(address), VectorMap[(Address, Asset), Long]()))
case (asset, balance) =>
val error = if (asset == Waves) "Waves balance" else "Asset balance"
safeSum(blockchain.balance(address, asset), balance, s"$address -> $error")
.map(newBalance => (Set(), VectorMap((address, asset) -> newBalance)))
}
if (wavesAmount != 0)
for {
assetBalances <- assetBalancesE
newWavesBalance <- safeSum(blockchain.balance(address), wavesAmount, s"$address -> Waves balance")
} yield assetBalances + ((address, Waves) -> newWavesBalance)
else
assetBalancesE
}

private def flatTraverse[E, K1, V1, K2, V2](m: Map[K1, V1])(f: (K1, V1) => Either[E, VectorMap[K2, V2]]): Either[E, VectorMap[K2, V2]] =
m.foldLeft(VectorMap[K2, V2]().asRight[E]) {
private def flatTraverse[E, A, K1, V1, K2, V2](
m: Map[K1, V1]
)(f: (K1, V1) => Either[E, (Set[A], VectorMap[K2, V2])]): Either[E, (Set[A], VectorMap[K2, V2])] =
m.foldLeft((Set[A](), VectorMap[K2, V2]()).asRight[E]) {
case (e @ Left(_), _) =>
e
case (Right(acc), (k, v)) =>
f(k, v).map(acc ++ _)
case (Right((s, m)), (k, v)) =>
f(k, v).map { case (ns, nm) => (s ++ ns, m ++ nm) }
}

def ofLeaseBalances(balances: Map[Address, LeaseBalance], blockchain: Blockchain): Either[String, StateSnapshot] =
Expand Down Expand Up @@ -426,6 +427,7 @@ object StateSnapshot {
combineDataEntries(s1.accountData, s2.accountData),
s1.scriptResults |+| s2.scriptResults,
s1.ethereumTransactionMeta ++ s2.ethereumTransactionMeta,
s1.zeroBalanceAffected ++ s2.zeroBalanceAffected,
s1.scriptsComplexity + s2.scriptsComplexity
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,12 @@ import com.wavesplatform.test.*
import com.wavesplatform.test.DomainPresets.*
import com.wavesplatform.transaction.*
import com.wavesplatform.transaction.Asset.{IssuedAsset, Waves}
import com.wavesplatform.transaction.TxHelpers.*
import com.wavesplatform.transaction.TxValidationError.{AccountBalanceError, GenericError}
import com.wavesplatform.transaction.assets.IssueTransaction
import com.wavesplatform.transaction.assets.exchange.*
import com.wavesplatform.transaction.assets.exchange.OrderPriceMode.{AssetDecimals, FixedDecimals, Default as DefaultPriceMode}
import com.wavesplatform.transaction.assets.exchange.OrderType.{BUY, SELL}
import com.wavesplatform.transaction.smart.script.ScriptCompiler
import com.wavesplatform.transaction.transfer.MassTransferTransaction.ParsedTransfer
import com.wavesplatform.transaction.transfer.{MassTransferTransaction, TransferTransaction}
Expand Down Expand Up @@ -336,7 +338,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w
val totalPortfolioDiff: Portfolio = blockDiff.portfolios.values.fold(Portfolio())(_.combine(_).explicitGet())
totalPortfolioDiff.balance shouldBe 0
totalPortfolioDiff.effectiveBalance(false).explicitGet() shouldBe 0
totalPortfolioDiff.assets.values.toSet should (be (Set()) or be (Set(0)))
totalPortfolioDiff.assets.values.toSet should (be(Set()) or be(Set(0)))

blockDiff.portfolios(exchange.sender.toAddress).balance shouldBe exchange.buyMatcherFee + exchange.sellMatcherFee - exchange.fee.value
}
Expand Down Expand Up @@ -937,7 +939,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w
BlockchainFeatures.FairPoS -> 0
)

private val RideV6 = DomainPresets.RideV6.blockchainSettings.functionalitySettings
private val RideV6FS = DomainPresets.RideV6.blockchainSettings.functionalitySettings

private def createSettings(preActivatedFeatures: (BlockchainFeature, Int)*): FunctionalitySettings =
TestFunctionalitySettings.Enabled
Expand Down Expand Up @@ -997,7 +999,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w
} yield {
val (genesis, transfers, issueAndScripts, exchangeTx, _) = smartTradePreconditions(buyerScriptSrc, sellerScriptSrc, txScript)
val preconBlocks = Seq(TestBlock.create(Seq(genesis)), TestBlock.create(transfers), TestBlock.create(issueAndScripts))
assertLeft(preconBlocks, TestBlock.create(Seq(exchangeTx)), RideV6)("TransactionNotAllowedByScript")
assertLeft(preconBlocks, TestBlock.create(Seq(exchangeTx)), RideV6FS)("TransactionNotAllowedByScript")
}
}

Expand All @@ -1009,7 +1011,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w
} yield {
val (genesis, transfers, issueAndScripts, exchangeTx, _) = smartTradePreconditions(buyerScriptSrc, sellerScriptSrc, txScript)
val preconBlocks = Seq(TestBlock.create(Seq(genesis)), TestBlock.create(transfers), TestBlock.create(issueAndScripts))
assertLeft(preconBlocks, TestBlock.create(Seq(exchangeTx)), RideV6)("TransactionNotAllowedByScript")
assertLeft(preconBlocks, TestBlock.create(Seq(exchangeTx)), RideV6FS)("TransactionNotAllowedByScript")
}
}

Expand All @@ -1021,7 +1023,7 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w
} yield {
val (genesis, transfers, issueAndScripts, exchangeTx, _) = smartTradePreconditions(buyerScriptSrc, sellerScriptSrc, txScript)
val preconBlocks = Seq(TestBlock.create(Seq(genesis)), TestBlock.create(transfers), TestBlock.create(issueAndScripts))
assertLeft(preconBlocks, TestBlock.create(Seq(exchangeTx)), RideV6)("TransactionNotAllowedByScript")
assertLeft(preconBlocks, TestBlock.create(Seq(exchangeTx)), RideV6FS)("TransactionNotAllowedByScript")
}
}

Expand Down Expand Up @@ -2022,6 +2024,21 @@ class ExchangeTransactionDiffTest extends PropSpec with Inside with WithDomain w
}
}

property("tx belongs to matcher on zero balance diff") {
val matcher = signer(2)
withDomain(RideV6, Seq(AddrWithBalance(matcher.toAddress))) { d =>
val issueTx = issue()
val asset = IssuedAsset(issueTx.id())
val order1 = order(BUY, Waves, asset, fee = TestValues.fee / 2, matcher = matcher)
val order2 = order(SELL, Waves, asset, fee = TestValues.fee / 2, matcher = matcher)
val exchange = exchangeFromOrders(order1, order2, matcher = matcher, TestValues.fee)
d.appendBlock(issueTx)
d.appendBlock(exchange)
d.liquidDiff.portfolios.get(matcher.toAddress) shouldBe None
d.liquidDiff.transaction(exchange.id()).get.affected shouldBe Set(matcher.toAddress, defaultAddress)
}
}

def script(caseType: String, v: Boolean, complex: Boolean = false): Seq[String] = Seq(true, false).map { full =>
val expr =
s"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,4 +62,23 @@ class InvokeAffectedAddressTest extends PropSpec with WithDomain {
}
}
}

property("tx belongs to invoker on zero balance diff") {
def dApp(fee: Long) =
TestCompiler(V5).compileContract(
s"""
| @Callable(i)
| func default() = [ScriptTransfer(i.caller, $fee, unit)]
""".stripMargin
)

val invoker = signer(2)
withDomain(RideV5, AddrWithBalance.enoughBalances(secondSigner, invoker)) { d =>
val invokeTx = invoke(invoker = invoker)
d.appendBlock(setScript(secondSigner, dApp(invokeTx.fee.value)))
d.appendAndAssertSucceed(invokeTx)
d.liquidDiff.portfolios.get(invoker.toAddress) shouldBe None
d.liquidDiff.transaction(invokeTx.id()).get.affected shouldBe Set(invoker.toAddress, secondAddress)
}
}
}