Skip to content
Merged
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
50 changes: 23 additions & 27 deletions .github/workflows/check-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,6 @@ jobs:
env:
JAVA_OPTS: -Dfile.encoding=UTF-8
BRANCH_NAME: ${{ github.head_ref || github.ref_name }}
SBT_IT_TEST_THREADS: 2
services:
docker:
image: docker:latest
options: --privileged # Required for Docker-in-Docker
volumes:
- /var/run/docker.sock:/var/run/docker.sock
ports:
- 2375:2375
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
Expand All @@ -34,26 +25,9 @@ jobs:
java-version: '11'
cache: 'sbt'
- uses: foundry-rs/foundry-toolchain@v1
- name: Cache solc
id: cache-solc
uses: actions/cache@v4
with:
path: /home/runner/.solc
key: solc-v0.8.29
restore-keys: solc-
- name: Get solc
if: ${{ steps.cache-solc.outputs.cache-hit != 'true' }}
run: |
mkdir -p /home/runner/.solc/v0.8.29
wget https://binaries.soliditylang.org/linux-amd64/solc-linux-amd64-v0.8.29+commit.ab55807c -O /home/runner/.solc/v0.8.29/solc-static-linux
echo "/home/runner/.solc" >> $GITHUB_PATH
ln -sf /home/runner/.solc/v0.8.29/solc-static-linux /home/runner/.solc/solc
which solc || echo "Solc not found in PATH"
- uses: sbt/setup-sbt@v1
- name: Run tests
run: |
forge --version
sbt --no-colors --color=never --batch "scalafmtCheck;test;docker;consensus-client-it/test"
run: sbt -Dcc.it.max-parallel-suites=2 -Dlogback.test.level=OFF --batch "scalafmtCheck;test;docker;consensus-client-it/test"
env:
RUN_ID: ${{ github.head_ref }}-${{ github.run_number }}-${{ github.run_attempt }}
- uses: scacap/action-surefire-report@5609ce4db72c09db044803b344a8968fd1f315da
Expand All @@ -70,3 +44,25 @@ jobs:
path: consensus-client-it/target/test-logs
if-no-files-found: warn
retention-days: 14

local-network:
name: Run Local Network tests
timeout-minutes: 10
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '11'
cache: 'sbt'
- uses: foundry-rs/foundry-toolchain@v1
- uses: sbt/setup-sbt@v1
- run: |
sbt --batch docker
cd local-network
./restart.sh
env:
COMPOSE_PROFILES: tests
5 changes: 4 additions & 1 deletion .github/workflows/publish-docker-image.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ jobs:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: proudust/gh-describe@70f72d4f6304ea053cf5a3d71c36211d5acc0c73
id: ghd
- uses: actions/setup-java@v4
with:
distribution: 'temurin'
Expand All @@ -38,8 +40,9 @@ jobs:
echo type=sha
echo EOF
} >> "$GITHUB_OUTPUT"
echo "cc-version=$(echo ${{ steps.ghd.outputs.describe }} | cut -c 2-)" >> "$GITHUB_OUTPUT"
id: tag-list
- run: sbt --batch buildTarballsForDocker
- run: sbt -Dproject.version=${{ steps.tag-list.outputs.cc-version }} --batch buildTarballsForDocker
- uses: docker/login-action@v3
with:
registry: ghcr.io
Expand Down
2 changes: 1 addition & 1 deletion .sbtopts
Original file line number Diff line number Diff line change
@@ -1 +1 @@
-mem 2048
--mem 2048
2 changes: 1 addition & 1 deletion 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.3",
scalaVersion := "3.7.4",
organization := "network.units",
organizationName := "Units Network",
resolvers ++= Seq(Resolver.sonatypeCentralSnapshots, Resolver.mavenLocal),
Expand Down
34 changes: 28 additions & 6 deletions consensus-client-it/build.sbt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import com.github.sbt.git.SbtGit.git.gitCurrentBranch
import com.spotify.docker.client.DefaultDockerClient
import org.web3j.codegen.SolidityFunctionWrapperGenerator
import org.web3j.tx.Contract
import play.api.libs.json.Json
Expand All @@ -8,11 +9,13 @@ import java.io.FileInputStream
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import scala.sys.process.*
import scala.sys.props
import scala.util.control.NonFatal

description := "Consensus client integration tests"

libraryDependencies ++= Seq(
"org.testcontainers" % "testcontainers" % "1.20.4",
"org.testcontainers" % "testcontainers" % "2.0.1",
"org.web3j" % "core" % "4.9.8"
).map(_ % Test)

Expand Down Expand Up @@ -57,11 +60,29 @@ Test / sourceGenerators += Def.task {

val logsDirectory = taskKey[File]("The directory for logs") // Task to evaluate and recreate the logs directory every time

Global / concurrentRestrictions := {
val threadNumber = Option(System.getenv("SBT_IT_TEST_THREADS")).fold(1)(_.toInt)
Seq(Tags.limit(Tags.ForkedTestGroup, threadNumber))
}
Global / concurrentRestrictions := Seq(
Tags.limit(
Tags.ForkedTestGroup,
Option(Integer.getInteger("cc.it.max-parallel-suites"))
.getOrElse[Integer] {
try {
val docker = DefaultDockerClient.fromEnv().build()
try {
val dockerCpu: Int = docker.info().cpus()
sLog.value.info(s"Docker CPU count: $dockerCpu")
dockerCpu * 2
} finally docker.close()
} catch {
case NonFatal(_) =>
sLog.value.warn(s"Could not connect to Docker, is the daemon running?")
sLog.value.info(s"System CPU count: ${EvaluateTask.SystemProcessors}")
EvaluateTask.SystemProcessors
}
}
)
)

val LogbackTestLevel = "logback.test.level"
inConfig(Test)(
Seq(
logsDirectory := {
Expand All @@ -74,7 +95,8 @@ inConfig(Test)(
s"-Dlogback.configurationFile=${(Test / resourceDirectory).value}/logback-test.xml", // Fixes a logback blaming for multiple configs
s"-Dcc.it.configs.dir=${baseDirectory.value.getParent}/local-network/configs",
s"-Dcc.it.docker.image=consensus-client:${gitCurrentBranch.value}",
s"-Dcc.it.contracts.dir=${baseDirectory.value / ".." / "contracts" / "eth"}"
s"-Dcc.it.contracts.dir=${baseDirectory.value / ".." / "contracts" / "eth"}",
s"-D$LogbackTestLevel=${props.getOrElse(LogbackTestLevel, "TRACE")}"
),
testOptions += Tests.Argument(TestFrameworks.ScalaTest, "-fFWD", ((Test / logsDirectory).value / "summary.log").toString),
fork := true,
Expand Down
2 changes: 1 addition & 1 deletion consensus-client-it/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>DEBUG</level>
<level>${logback.test.level:-DEBUG}</level>
</filter>
<encoder>
<pattern>${pattern}</pattern>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class NodeHttpApi(apiUri: Uri, backend: SttpBackend[Identity, ?], apiKeyValue: S
extends IntegrationTestEventually
with Matchers
with ScorexLogging {
def blockHeader(atHeight: Int)(implicit loggingOptions: LoggingOptions = LoggingOptions()): Option[BlockHeaderResponse] = {
def blockHeader(atHeight: Height)(implicit loggingOptions: LoggingOptions = LoggingOptions()): Option[BlockHeaderResponse] = {
if (loggingOptions.logRequest) log.debug(s"${loggingOptions.prefix} blockHeader($atHeight)")
basicRequest
.get(uri"$apiUri/blocks/headers/at/$atHeight")
Expand All @@ -45,7 +45,7 @@ class NodeHttpApi(apiUri: Uri, backend: SttpBackend[Identity, ?], apiKeyValue: S
}
}

def waitForHeight(atLeast: Int)(implicit loggingOptions: LoggingOptions = LoggingOptions()): Height = {
def waitForHeight(atLeast: Height)(implicit loggingOptions: LoggingOptions = LoggingOptions()): Height = {
if (loggingOptions.logCall) log.debug(s"${loggingOptions.prefix} waitForHeight($atLeast)")
val subsequentLoggingOptions = loggingOptions.copy(logCall = false, logResult = false, logRequest = false)
val currHeight = height()(using subsequentLoggingOptions)
Expand Down Expand Up @@ -258,7 +258,8 @@ object NodeHttpApi {

case class HeightResponse(height: Height)
object HeightResponse {
implicit val heightResponseFormat: OFormat[HeightResponse] = Json.format[HeightResponse]
given Reads[Height] = Reads.IntReads.map(Height.apply)
given OFormat[HeightResponse] = Json.format[HeightResponse]
}

case class BroadcastResponse(id: String)
Expand All @@ -268,7 +269,8 @@ object NodeHttpApi {

case class TransactionInfoResponse(height: Height, applicationStatus: String)
object TransactionInfoResponse {
implicit val transactionInfoResponseFormat: OFormat[TransactionInfoResponse] = Json.format[TransactionInfoResponse]
given Reads[Height] = Reads.IntReads.map(Height.apply)
given OFormat[TransactionInfoResponse] = Json.format[TransactionInfoResponse]
}

case class BalanceResponse(balance: Long)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
package units

import com.wavesplatform.state.Height

class AssetRegistryTestSuite extends BaseDockerTestSuite {
private val clRecipient = clRichAccount1
private val elSender = elRichAccount1

private val userAmount = 1
private val elAmount = UnitsConvert.toAtomic(userAmount, 18)

private var activationEpoch = 0
private var activationEpoch = Height(0)

"WAVES and issued asset are not registered before activation" in {
standardBridge.isRegistered(TErc20Address, ignoreExceptions = true) shouldBe false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ trait BaseDockerTestSuite

protected lazy val wavesGenesisConfigPath = generateWavesGenesisConfig()

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

/*
* ipForNode(1) -> Ryuk
Expand Down Expand Up @@ -94,13 +94,7 @@ trait BaseDockerTestSuite
step("Setup chain contract")
val genesisBlock = ec1.engineApi.getBlockByNumber(BlockNumber.Number(0)).explicitGet().getOrElse(fail("No EL genesis block"))
waves1.api.broadcastAndWait(
ChainContract.setup(
genesisBlock = genesisBlock,
elMinerReward = rewardAmount.amount.longValue(),
daoAddress = None,
daoReward = 0,
invoker = chainContractAccount
)
ChainContract.setup(genesisBlock, rewardAmount.amount.longValue(), None, 0, 2, invoker = chainContractAccount)
)
log.info(s"Native token id: ${chainContract.nativeTokenId}")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ class C2ENativeTokenTransfersViaDepositsTestSuite extends BaseDockerTestSuite {
waves1.api.broadcastAndWait(
TxHelpers.dataEntry(
chainContractAccount,
IntegerDataEntry("strictC2ETransfersActivationEpoch", activationEpoch)
IntegerDataEntry("strictC2ETransfersActivationEpoch", activationEpoch.toInt)
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import com.wavesplatform.state.{Height, IntegerDataEntry, StringDataEntry}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.transaction.TxHelpers

import scala.concurrent.duration.*
import org.scalatest.concurrent.PatienceConfiguration.*

class EmptyEpochMinerEvictionTestSuite extends BaseDockerTestSuite {
private val reporter = miner11Account
private val idleMiner = miner21Account
Expand All @@ -27,22 +30,18 @@ class EmptyEpochMinerEvictionTestSuite extends BaseDockerTestSuite {
waves1.api.dataByKey(chainContractAddress, "allMiners").value shouldBe
StringDataEntry("allMiners", s"${idleMiner.toAddress},${reporter.toAddress}")

val lastReportedHeight = Range
.inclusive(1, maxSkippedEpochCount)
.foldLeft(Height(0))((prevReportedHeight, _) => {

// Wait for another idle miner epoch
waves1.api.waitForHeight(prevReportedHeight + 1)
chainContract.waitForMinerEpoch(idleMiner)
val lastWavesBlock = waves1.api.blockHeader(waves1.api.height()).value
val vrf = ByteStr.decodeBase58(lastWavesBlock.VRF).get
// Report empty epoch
val reportResult = waves1.api.broadcastAndWait(ChainContract.reportEmptyEpoch(reporter, vrf))
reportResult.height
})

// Assertion: idle miner has been evicted
waves1.api.dataByKey(chainContractAddress, "allMiners").value shouldBe StringDataEntry("allMiners", s"${reporter.toAddress}")
val lastReportedHeight = eventually(Timeout(1.minute), Interval(2.seconds)) {
chainContract.waitForMinerEpoch(idleMiner)

val lastWavesBlock = waves1.api.blockHeader(waves1.api.height()).value

val vrf = ByteStr.decodeBase58(lastWavesBlock.VRF).get
// Report empty epoch
val h = waves1.api.broadcastAndWait(ChainContract.reportEmptyEpoch(reporter, vrf)).height
// Assertion: idle miner has been evicted
waves1.api.dataByKey(chainContractAddress, "allMiners").value shouldBe StringDataEntry("allMiners", s"${reporter.toAddress}")
h
}

// Assertion: reporter started mining on the same epoch, in which the previous miner has been evicted
val epochMeta = eventually {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ class ManyTransfersTestSuite extends BaseDockerTestSuite {
waves1.api.broadcastAndWait(
TxHelpers.dataEntry(
chainContractAccount,
IntegerDataEntry("strictC2ETransfersActivationEpoch", activationEpoch)
IntegerDataEntry("strictC2ETransfersActivationEpoch", activationEpoch.toInt)
)
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ class MultipleTransfersViaDepositsTestSuite extends BaseDockerTestSuite {
waves1.api.broadcastAndWait(
TxHelpers.dataEntry(
chainContractAccount,
IntegerDataEntry("strictC2ETransfersActivationEpoch", activationEpoch)
IntegerDataEntry("strictC2ETransfersActivationEpoch", activationEpoch.toInt)
)
)

Expand Down
34 changes: 30 additions & 4 deletions consensus-client-it/src/test/scala/units/SyncingTestSuite.scala
Original file line number Diff line number Diff line change
@@ -1,20 +1,44 @@
package units

import com.wavesplatform.state.Height
import com.wavesplatform.state.{Height, StringDataEntry}
import com.wavesplatform.utils.EthEncoding
import org.web3j.crypto.{RawTransaction, TransactionEncoder}
import org.web3j.protocol.core.methods.response.{EthSendTransaction, TransactionReceipt}
import org.web3j.tx.gas.DefaultGasProvider
import org.web3j.utils.Convert
import units.docker.EcContainer
import units.docker.{EcContainer, Networks, WavesNodeContainer}

import java.math.BigInteger
import scala.jdk.OptionConverters.RichOptional
import scala.concurrent.duration.DurationInt
import org.scalatest.concurrent.PatienceConfiguration.*

class SyncingTestSuite extends BaseDockerTestSuite {
private val elSender = elRichAccount1
private val amount = Convert.toWei("1", Convert.Unit.ETHER).toBigInteger

override protected lazy val waves1: WavesNodeContainer = new WavesNodeContainer(
network = network,
number = 1,
ip = Networks.ipForNode(3),
baseSeed = "devnet-2",
chainContractAddress = chainContractAddress,
ecEngineApiUrl = ec1.engineApiDockerUrl,
genesisConfigPath = wavesGenesisConfigPath
)

override def beforeAll(): Unit = {
super.beforeAll()
waves1.api.broadcast(ChainContract.join(miner21Account, miner21RewardAddress))

eventually {
waves1.api.dataByKey(chainContractAddress, "allMiners") match {
case Some(StringDataEntry(_, value)) => value.split(",").length shouldBe 2
case _ => fail("not all miners have joined")
}
}
}

"L2-381 EL transactions appear after rollback" in {
step("Send transaction 1")
val txn1Result = sendTxn(0)
Expand All @@ -39,10 +63,12 @@ class SyncingTestSuite extends BaseDockerTestSuite {

step("Rollback CL")
val elWaitHeight = ec1.web3j.ethBlockNumber().send().getBlockNumber.intValueExact() + 1
waves1.api.rollback(Height(contractBlock.epoch - 1))
waves1.api.rollback(contractBlock.epoch - 1)

step("Wait for EL blocks")
while (ec1.web3j.ethBlockNumber().send().getBlockNumber.intValueExact() < elWaitHeight) Thread.sleep(3000)
eventually(Timeout(2.minutes), Interval(10.seconds)) {
ec1.web3j.ethBlockNumber().send().getBlockNumber.intValueExact() should be >= elWaitHeight
}

step("Waiting transactions 2 and 3 on EL")
val txn2ReceiptAfterRb = waitForTxn(txn2Result)
Expand Down
Loading