Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
ab9939f
Rebuild on main
vladimirlogachev Jul 8, 2025
26244d2
Fix MultipleTransfersViaDepositsTestSuite
vladimirlogachev Jul 8, 2025
15cf88a
Combine two features into a single feature
vladimirlogachev Jul 8, 2025
d0f8a6a
Fix "Enough C2E transfers" test case
vladimirlogachev Jul 8, 2025
92ec9d2
Add condition for the `from` field vaidation in validateC2EAssetTransfer
vladimirlogachev Jul 8, 2025
c8383b8
Refactor `requireTransfer` to use extractors
vladimirlogachev Jul 8, 2025
4635bc4
Fix unit test
vladimirlogachev Jul 9, 2025
28211b6
Clean up
vladimirlogachev Jul 9, 2025
92ef93f
Remove limitation on native transfers number
vladimirlogachev Jul 9, 2025
2ebbdba
Update comments
vladimirlogachev Jul 9, 2025
75481bb
Add more logs
vladimirlogachev Jul 10, 2025
6b84915
Reorder `depositedTransactions` in `updateHeadAndStartBuildingPayload`
vladimirlogachev Jul 10, 2025
22c6814
Fix a bug with `MaxWithdrawals` usage
vladimirlogachev Jul 14, 2025
2961a88
Add ManyTransfersTestSuite
vladimirlogachev Jul 15, 2025
d286e36
Clean up tests
vladimirlogachev Jul 17, 2025
e39b09e
Apply fix for transfers order
vladimirlogachev Jul 17, 2025
4420057
Add more cases to C2ETransfersTestSuite
vladimirlogachev Jul 17, 2025
7568708
Add one more test case
vladimirlogachev Jul 17, 2025
ed9f26f
Add more test cases
vladimirlogachev Jul 17, 2025
171d430
Fix strict transfers unit test
vladimirlogachev Jul 17, 2025
d80738b
Update consensus-client-it/src/test/scala/units/ManyTransfersTestSuit…
vladimirlogachev Jul 18, 2025
aeeb38f
Update consensus-client-it/src/test/scala/units/ManyTransfersTestSuit…
vladimirlogachev Jul 18, 2025
4d3870d
Update src/main/scala/units/client/contract/ChainContractClient.scala
vladimirlogachev Jul 18, 2025
6bf5293
Fix according to the review
vladimirlogachev Jul 18, 2025
8ceebd7
Simplify `updateHeadAndStartBuildingPayload`
vladimirlogachev Jul 18, 2025
c0a0c95
SImplify ManyTransfersTestSuite
vladimirlogachev Jul 18, 2025
6f6e79a
Fix formatting
vladimirlogachev Jul 18, 2025
0a410a2
Simplify `depositedTransactions` calculation
vladimirlogachev Jul 18, 2025
d261096
Fix error message to match the validation
vladimirlogachev Jul 25, 2025
fc250ca
Merge branch 'main' into native-token-transfers-deposits-2
vladimirlogachev Aug 7, 2025
5ea1249
Merge branch 'main' into native-token-transfers-deposits-2
vladimirlogachev Aug 11, 2025
af61bb2
Merge branch 'main' into native-token-transfers-deposits-2
vladimirlogachev Aug 14, 2025
53b092a
Adjust the check
vladimirlogachev Aug 14, 2025
951a09e
Overrride `equals` for DepositedTransaction, add unit test
vladimirlogachev Aug 15, 2025
63d252f
Fix validation for strict transfers enabled
vladimirlogachev Aug 25, 2025
9e8838a
Remove logs
vladimirlogachev Aug 25, 2025
a4ddabc
Fix a bug in validation
vladimirlogachev Aug 25, 2025
ff1fe63
Fix a bug with indices, unify range creation
vladimirlogachev Aug 25, 2025
fa810e1
Extract `prepareTransactions` method
vladimirlogachev Aug 26, 2025
2d2fb5c
Merge remote-tracking branch 'origin/main' into native-token-transfer…
vladimirlogachev Sep 11, 2025
5ab6b87
Add ec2 to BaseDockerTestSuite
vladimirlogachev Sep 11, 2025
91feaa0
Add BlockValidationTestSuite (WIP)
vladimirlogachev Sep 24, 2025
b1aa2b9
Remove ec2
vladimirlogachev Sep 24, 2025
0b9b5c1
Add `enableMining` flag to WavesNodeContainer
vladimirlogachev Sep 24, 2025
dd2f92b
Override `waves1` in BlockValidationTestSuite
vladimirlogachev Sep 24, 2025
32a9a59
Add 2 more EL miners
vladimirlogachev Sep 25, 2025
e7eb0dc
WIP
vladimirlogachev Oct 1, 2025
6bea7eb
Register the EL block on chain contract
vladimirlogachev Oct 2, 2025
f915bd2
Fix vrf calculation
vladimirlogachev Oct 2, 2025
0f9e562
Fix assertions
vladimirlogachev Oct 2, 2025
52fe888
Add assertion: Block exists on EC1
vladimirlogachev Oct 2, 2025
fcdf650
Update comments
vladimirlogachev Oct 2, 2025
db8436c
Remove block validation test cases from C2ETransfersTestSuite
vladimirlogachev Oct 2, 2025
dc5191c
Refactor to use `Handshake.encode` method
vladimirlogachev Oct 2, 2025
fe60889
Refactor to use netty.bootstrap
vladimirlogachev Oct 2, 2025
dd0e912
Simplify BlockValidationTestSuite
vladimirlogachev Oct 2, 2025
20a9cb2
Simplify TestNetworkClient
vladimirlogachev Oct 2, 2025
8c0a3ce
Simplify TestNetworkClient
vladimirlogachev Oct 2, 2025
815add9
Adjust wording
vladimirlogachev Oct 8, 2025
74ca44b
Add successful case to BlockValidationTestSuite
vladimirlogachev Oct 8, 2025
8c0c802
WIP
vladimirlogachev Oct 13, 2025
bface09
Add log
vladimirlogachev Oct 13, 2025
ccfc9da
WIP
vladimirlogachev Oct 13, 2025
debad71
Fix successful case
vladimirlogachev Oct 14, 2025
da42eb2
SImplify BlockValidationTestSuite
vladimirlogachev Oct 14, 2025
fc03b1e
Speed up test
vladimirlogachev Oct 14, 2025
fbd475c
Extract BaseBlockValidationSuite
vladimirlogachev Oct 14, 2025
7970696
Refactor BlockValidationTestSuite
vladimirlogachev Oct 14, 2025
637fc13
Add more test cases
vladimirlogachev Oct 14, 2025
c1c35db
Remove logs
vladimirlogachev Oct 15, 2025
373f8a8
Add type signatures
vladimirlogachev Oct 15, 2025
05d037b
Add block validation test for asset transfers (WIP)
vladimirlogachev Oct 15, 2025
7709568
Fix amount in deposited transaction
vladimirlogachev Oct 15, 2025
aa43240
Fix successful case
vladimirlogachev Oct 15, 2025
9bd134c
Adjust BaseBlockValidationSuite for asset transfers
vladimirlogachev Oct 15, 2025
95aca82
Add more test cases
vladimirlogachev Oct 15, 2025
c1b0970
Add more test cases
vladimirlogachev Oct 15, 2025
a370d4d
Update src/main/scala/units/ELUpdater.scala
vladimirlogachev Oct 16, 2025
f9708c9
Update consensus-client-it/src/test/scala/units/docker/WavesNodeConta…
vladimirlogachev Oct 16, 2025
619dabb
Update consensus-client-it/src/test/scala/units/docker/WavesNodeConta…
vladimirlogachev Oct 16, 2025
45f20ee
Update consensus-client-it/src/test/scala/units/docker/WavesNodeConta…
vladimirlogachev Oct 16, 2025
501cc78
Update consensus-client-it/src/test/scala/units/docker/WavesNodeConta…
vladimirlogachev Oct 16, 2025
a3d4cec
Reuse NetworkClient
phearnot Oct 16, 2025
b915893
Rename fields
vladimirlogachev Oct 16, 2025
ceccb4b
Update consensus-client-it/src/test/scala/units/TestNetworkClient.scala
vladimirlogachev Oct 16, 2025
42e6c73
Refactor to use WavesNodeContainer in TestNetworkClient
vladimirlogachev Oct 16, 2025
34bde39
Fix a bug in TestNetworkClient
vladimirlogachev Oct 16, 2025
6877173
Extract BaseBlockValidationSuite
vladimirlogachev Oct 16, 2025
8018bf6
Extract test suites to separate files
vladimirlogachev Oct 16, 2025
50e71fd
Fix ELUpdater formatting
vladimirlogachev Oct 16, 2025
da9ac60
Use calculateRandao from ELUpdater
vladimirlogachev Oct 17, 2025
26c6425
Remove `correctedTime` from BaseBlockValidationSuite
vladimirlogachev Oct 17, 2025
ace6fd2
Move getLastWithdrawalIndex to EngineApiClient extension
vladimirlogachev Oct 17, 2025
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
6 changes: 3 additions & 3 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ git.uncommittedSignifier := Some("DIRTY")
inScope(Global)(
Seq(
onChangedBuildSource := ReloadOnSourceChanges,
scalaVersion := "3.7.2",
scalaVersion := "3.7.3",
organization := "network.units",
organizationName := "Units Network",
resolvers ++= Seq(Resolver.sonatypeCentralSnapshots, Resolver.mavenLocal),
Expand All @@ -33,14 +33,14 @@ name := "consensus-client"
maintainer := "Units Network Team"

libraryDependencies ++= {
val node = "1.5.11"
val node = "1.5.12-SNAPSHOT"
val sttpVersion = "3.11.0"
Seq(
"com.wavesplatform" % "node-testkit" % node % Test,
"com.wavesplatform" % "node" % node % Provided,
"com.softwaremill.sttp.client3" %% "core" % sttpVersion,
"com.softwaremill.sttp.client3" %% "play-json" % sttpVersion,
"com.github.jwt-scala" %% "jwt-play-json" % "11.0.2",
"com.github.jwt-scala" %% "jwt-play-json" % "11.0.3",
"org.web3j" % "core" % "4.9.8"
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@ import com.wavesplatform.api.http.`X-Api-Key`
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.state.DataEntry.Format
import com.wavesplatform.state.{DataEntry, EmptyDataEntry, Height}
import com.wavesplatform.transaction.Asset
import com.wavesplatform.transaction.Asset.IssuedAsset
import com.wavesplatform.transaction.Transaction
import com.wavesplatform.transaction.{Asset, Transaction}
import com.wavesplatform.utils.ScorexLogging
import org.scalatest.matchers.should.Matchers
import play.api.libs.json.*
Expand Down Expand Up @@ -159,12 +158,12 @@ class NodeHttpApi(apiUri: Uri, backend: SttpBackend[Identity, ?], apiKeyValue: S
}
}
}

def balance(address: Address, asset: Asset)(implicit loggingOptions: LoggingOptions = LoggingOptions()): Long = {
if (loggingOptions.logCall) log.debug(s"${loggingOptions.prefix} balance($address, $asset)")
basicRequest
.get(asset match {
case Asset.Waves => uri"$apiUri/addresses/balance/$address"
case Asset.Waves => uri"$apiUri/addresses/balance/$address"
case IssuedAsset(id) => uri"$apiUri/assets/balance/$address/$id"
})
.response(asJson[BalanceResponse])
Expand Down
3 changes: 3 additions & 0 deletions consensus-client-it/src/test/scala/units/Accounts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ trait Accounts {
val miner21Account = mkKeyPair("devnet-2", 0)
val miner21RewardAddress = EthAddress.unsafeFrom("0xcf0b9e13fdd593f4ca26d36afcaa44dd3fdccbed")

val miner31Account = mkKeyPair("devnet-3", 0)
val miner31RewardAddress = EthAddress.unsafeFrom("0xf1FE6d7bfebead68A8C06cCcee97B61d7DAA0338")

val clRichAccount1 = mkKeyPair("devnet rich", 0)
val clRichAccount2 = mkKeyPair("devnet rich", 1)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
package units

import com.wavesplatform.*
import com.wavesplatform.account.*
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2.explicitGet
import com.wavesplatform.crypto.Keccak256
import com.wavesplatform.state.{Height, IntegerDataEntry}
import com.wavesplatform.transaction.Asset.IssuedAsset
import com.wavesplatform.transaction.smart.InvokeScriptTransaction
import com.wavesplatform.transaction.{Asset, TxHelpers}
import monix.execution.atomic.AtomicInt
import org.web3j.protocol.core.DefaultBlockParameterName
import org.web3j.tx.RawTransactionManager
import org.web3j.tx.gas.DefaultGasProvider
import play.api.libs.json.*
import units.{BlockHash, ELUpdater}
import units.client.engine.model.{EcBlock, Withdrawal}
import units.docker.EcContainer
import units.el.*
import units.eth.{EmptyL2Block, EthAddress, EthereumConstants}
import units.util.{BlockToPayloadMapper, HexBytesConverter}

import scala.concurrent.duration.DurationInt
import scala.jdk.OptionConverters.RichOptional

trait BaseBlockValidationSuite extends BaseDockerTestSuite {
protected val setupMiner: SeedKeyPair = miner11Account // Leaves after setting up the contracts
protected val actingMiner: SeedKeyPair = miner12Account
protected val actingMinerRewardAddress: EthAddress = miner12RewardAddress

// Note: additional miners are needed to avoid the actingMiner having majority of the stake
protected val additionalMiner1: SeedKeyPair = miner21Account
protected val additionalMiner1RewardAddress: EthAddress = miner21RewardAddress
protected val additionalMiner2: SeedKeyPair = miner31Account
protected val additionalMiner2RewardAddress: EthAddress = miner31RewardAddress

// transfers
protected val clSender: SeedKeyPair = clRichAccount1
protected val elRecipient: EthAddress = elRichAddress1

// native transfers
protected val userNativeTokenAmount = 1
protected val clNativeTokenAmount: Long = UnitsConvert.toUnitsInWaves(userNativeTokenAmount)
protected val elNativeTokenAmount: BigInt = UnitsConvert.toWei(userNativeTokenAmount)

// asset transfers
protected val gasProvider = new DefaultGasProvider
protected lazy val txnManager = new RawTransactionManager(ec1.web3j, elRichAccount1, EcContainer.ChainId, 20, 2000)
protected lazy val terc20 = new Erc20Client(ec1.web3j, TErc20Address, txnManager, gasProvider)
protected val issueAssetDecimals: Byte = 8.toByte
protected lazy val issueAsset: IssuedAsset = chainContract.getRegisteredAsset(1) match {
case ia: IssuedAsset => ia
case _ => fail("Expected issued asset")
}
protected val userAssetTokenAmount = 1
protected val clAssetTokenAmount: Long = UnitsConvert.toWavesAtomic(userAssetTokenAmount, issueAssetDecimals)
protected val elAssetTokenAmount: BigInt = UnitsConvert.toAtomic(userAssetTokenAmount, TErc20Decimals)

protected final def mkRewardWithdrawal(elParentBlock: EcBlock): Withdrawal = {
val chainContractOptions = chainContract.getOptions

val elWithdrawalIndexBefore = (elParentBlock.withdrawals.lastOption.map(_.index) match {
case Some(r) => Right(r)
case None =>
if (elParentBlock.height - 1 <= EthereumConstants.GenesisBlockHeight) Right(-1L)
else ec1.engineApi.getLastWithdrawalIndex(elParentBlock.parentHash)
}).explicitGet()
Withdrawal(elWithdrawalIndexBefore + 1, elParentBlock.minerRewardL2Address, chainContractOptions.miningReward)
}

protected final def mkSimulatedBlock(
elParentBlock: EcBlock,
withdrawals: Seq[Withdrawal],
depositedTransactions: Seq[DepositedTransaction]
): (JsObject, String, ByteStr) = {
step("Building a simulated block")
val feeRecipient = actingMinerRewardAddress

val currentUnixTs = System.currentTimeMillis() / 1000
val blockDelay = 6
val nextBlockUnixTs = (elParentBlock.timestamp + blockDelay).max(currentUnixTs)

val currentEpochHeader = waves1.api.blockHeader(waves1.api.height()).value
val hitSource = ByteStr.decodeBase58(currentEpochHeader.VRF).get
val prevRandao = ELUpdater.calculateRandao(hitSource, elParentBlock.hash)

val txHashes = depositedTransactions.map(t => HexBytesConverter.toHex(Keccak256.hash(HexBytesConverter.toBytes(t.toHex)))).mkString(", ")
log.debug(s"Deposited transactions hashes: $txHashes")

val simulatedBlock: JsObject = ec1.engineApi
.simulate(
EmptyL2Block.mkSimulateCall(elParentBlock, feeRecipient, nextBlockUnixTs, prevRandao, withdrawals, depositedTransactions),
elParentBlock.hash
)
.explicitGet()
.head

val payload = BlockToPayloadMapper.toPayloadJson(
simulatedBlock,
Json.obj(
"transactions" -> depositedTransactions.map(_.toHex),
"withdrawals" -> Json.toJson(withdrawals)
)
)

val simulatedBlockHash: String = (simulatedBlock \ "hash").as[String]

(payload, simulatedBlockHash, hitSource)
}

protected def deployContractsAndActivateTransferFeatures(): Unit = {
deploySolidityContracts()

step("Enable token transfers")
val activationEpoch = waves1.api.height() + 1
waves1.api.broadcastAndWait(
ChainContract.enableTokenTransfersWithWaves(
StandardBridgeAddress,
WWavesAddress,
activationEpoch = activationEpoch
)
)

step("Set strict C2E transfers feature activation epoch")
waves1.api.broadcastAndWait(
TxHelpers.dataEntry(
chainContractAccount,
IntegerDataEntry("strictC2ETransfersActivationEpoch", activationEpoch)
)
)

step("Wait for features activation")
waves1.api.waitForHeight(activationEpoch)
}

protected def transferNativeTokenToClSender(): Unit = {
step("Prepare: issue tokens on chain contract and transfer to a user")
waves1.api.broadcastAndWait(
TxHelpers.reissue(
asset = chainContract.nativeTokenId,
sender = chainContractAccount,
amount = clNativeTokenAmount
)
)
waves1.api.broadcastAndWait(
TxHelpers.transfer(
from = chainContractAccount,
to = clSender.toAddress,
amount = clNativeTokenAmount,
asset = chainContract.nativeTokenId
)
)
}

protected def transferAssetTokenToClSender(): Unit = {
step("Register asset")
waves1.api.broadcastAndWait(ChainContract.issueAndRegister(TErc20Address, TErc20Decimals, "TERC20", "Test ERC20 token", issueAssetDecimals))

eventually {
standardBridge.isRegistered(TErc20Address, ignoreExceptions = true) shouldBe true
}

step("Transfer asset from EL to CL")

val currNonce =
AtomicInt(ec1.web3j.ethGetTransactionCount(elRichAddress1.hex, DefaultBlockParameterName.PENDING).send().getTransactionCount.intValueExact())
def nextNonce: Int = currNonce.getAndIncrement()

waitFor(terc20.sendApprove(StandardBridgeAddress, elAssetTokenAmount, nextNonce))

val e2cIssuedTxn = standardBridge.sendBridgeErc20(elRichAccount1, TErc20Address, clSender.toAddress, elAssetTokenAmount, nextNonce)

chainContract.waitForEpoch(waves1.api.height() + 1)
val e2cReceipt =
eventually {
val hash = e2cIssuedTxn.getTransactionHash
withClue(s"$hash: ") {
ec1.web3j.ethGetTransactionReceipt(hash).send().getTransactionReceipt.toScala.value
}
}

val e2cBlockHash = BlockHash(e2cReceipt.getBlockHash)

val e2cLogsInBlock = ec1.engineApi
.getLogs(e2cBlockHash, List(NativeBridgeAddress, StandardBridgeAddress), Nil)
.explicitGet()
.filter(_.topics.intersect(E2CTopics).nonEmpty)

val e2cBlockConfirmationHeight = eventually {
chainContract.getBlock(e2cBlockHash).value.height
}

step(s"Wait for block $e2cBlockHash ($e2cBlockConfirmationHeight) finalization")
eventually {
val currFinalizedHeight = chainContract.getFinalizedBlock.height
step(s"Current finalized height: $currFinalizedHeight")
currFinalizedHeight should be >= e2cBlockConfirmationHeight
}

step("Broadcast withdrawAsset transactions")
waves1.api.broadcastAndWait(
ChainContract.withdrawAsset(
sender = clSender,
blockHash = e2cBlockHash,
merkleProof = BridgeMerkleTree.mkTransferProofs(e2cLogsInBlock, 0).explicitGet().reverse,
transferIndexInBlock = 0,
amount = UnitsConvert.toWavesAtomic(userAssetTokenAmount, issueAssetDecimals),
asset = issueAsset
)
)
}

protected def leaveSetupMinerAndJoinOthers(): Unit = {
log.debug(s"setupMiner: ${setupMiner.toAddress}")
log.debug(s"actingMiner: ${actingMiner.toAddress}")
log.debug(s"additionalMiner1: ${additionalMiner1.toAddress}")
log.debug(s"additionalMiner2: ${additionalMiner2.toAddress}")

step(s"additionalMiner1 join")
waves1.api.broadcastAndWait(
ChainContract.join(
minerAccount = additionalMiner1,
elRewardAddress = additionalMiner1RewardAddress
)
)

step(s"Wait additionalMiner1 epoch")
chainContract.waitForMinerEpoch(additionalMiner1)

step(s"setupMiner leave")
eventually(interval(500 millis)) {
waves1.api.broadcastAndWait(ChainContract.leave(setupMiner))
}

step(s"additionalMiner2 join")
waves1.api.broadcastAndWait(
ChainContract.join(
minerAccount = additionalMiner2,
elRewardAddress = additionalMiner2RewardAddress
)
)

step(s"actingMiner join")
waves1.api.broadcastAndWait(
ChainContract.join(
minerAccount = actingMiner,
elRewardAddress = actingMinerRewardAddress
)
)

step(s"Wait actingMiner epoch")
chainContract.waitForMinerEpoch(actingMiner)
}

protected def setupForNativeTokenTransfer(): Unit = {
super.beforeAll()
deployContractsAndActivateTransferFeatures()
transferNativeTokenToClSender()
leaveSetupMinerAndJoinOthers()
}

protected def setupForAssetTokenTransfer(): Unit = {
super.beforeAll()
deployContractsAndActivateTransferFeatures()
transferAssetTokenToClSender()
leaveSetupMinerAndJoinOthers()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,13 @@ trait BaseDockerTestSuite

private implicit val httpClientBackend: SttpBackend[Identity, Any] = new LoggingBackend(HttpClientSyncBackend())

protected lazy val ec1: EcContainer =
new OpGethContainer(network, 1, Networks.ipForNode(2) /* ipForNode(1) is assigned to Ryuk */ )
/*
* ipForNode(1) -> Ryuk
* ipForNode(2) -> ec1
* ipForNode(3) -> waves1
*/

protected lazy val ec1: EcContainer = new OpGethContainer(network, 1, Networks.ipForNode(2))

protected lazy val waves1: WavesNodeContainer = new WavesNodeContainer(
network = network,
Expand Down
Loading