Skip to content

Commit

Permalink
L2-383
Browse files Browse the repository at this point in the history
  • Loading branch information
vsuharnikov committed Nov 6, 2024
1 parent 6091958 commit 7cdaa71
Show file tree
Hide file tree
Showing 16 changed files with 158 additions and 28 deletions.
2 changes: 1 addition & 1 deletion consensus-client-it/build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import java.time.format.DateTimeFormatter
description := "Consensus client integration tests"

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

Expand Down
2 changes: 2 additions & 0 deletions consensus-client-it/src/test/resources/logback-test.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<shutdownHook/>

<property name="pattern" value="%date{HH:mm:ss.SSS,UTC} %-5level [%.10thread] %logger{26} - %msg%n%rEx"/>

<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class LoggingBackend[F[_], P](delegate: SttpBackend[F, P]) extends DelegateSttpB

l.filter(_.logRequest).foreach { l =>
var logStr = s"${l.prefix} ${request.method} ${request.uri}"
if (l.logResponseBody) logStr += s": body=${request.body.show}"
if (l.logRequestBody) logStr += s": body=${request.body.show}"
log.debug(logStr)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ import scala.util.chaining.scalaUtilChainingOps
class NodeHttpApi(apiUri: Uri, backend: SttpBackend[Identity, ?], averageBlockDelay: FiniteDuration) extends HasRetry with ScorexLogging {
protected override implicit val patienceConfig: PatienceConfig = PatienceConfig(timeout = averageBlockDelay, interval = 1.second)

def blockHeader(atHeight: Int): Option[BlockHeaderResponse] = {
val loggingOptions: LoggingOptions = LoggingOptions()
log.debug(s"${loggingOptions.prefix} blockHeader($atHeight)")
basicRequest
.get(uri"$apiUri/blocks/headers/at/$atHeight")
.response(asJson[BlockHeaderResponse])
.tag(LoggingOptionsTag, loggingOptions)
.send(backend)
.body match {
case Left(HttpError(_, StatusCode.NotFound)) => none
case Left(HttpError(body, statusCode)) => throw new RuntimeException(s"Server returned error $body with status ${statusCode.code}")
case Left(DeserializationException(body, error)) => throw new RuntimeException(s"failed to parse response $body: $error")
case Right(r) => r.some
}
}

def waitForHeight(atLeast: Int): Height = {
val loggingOptions: LoggingOptions = LoggingOptions()
log.debug(s"${loggingOptions.prefix} waitForHeight($atLeast)")
Expand Down Expand Up @@ -108,7 +124,7 @@ class NodeHttpApi(apiUri: Uri, backend: SttpBackend[Identity, ?], averageBlockDe
case Right(r) => r.some
}

def getDataByKey(address: Address, key: String)(implicit loggingOptions: LoggingOptions = LoggingOptions()): Option[DataEntry[?]] = {
def dataByKey(address: Address, key: String)(implicit loggingOptions: LoggingOptions = LoggingOptions()): Option[DataEntry[?]] = {
log.debug(s"${loggingOptions.prefix} getDataByKey($address, $key)")
basicRequest
.get(uri"$apiUri/addresses/data/$address/$key")
Expand Down Expand Up @@ -179,8 +195,24 @@ class NodeHttpApi(apiUri: Uri, backend: SttpBackend[Identity, ?], averageBlockDe
case Right(r) => r.peers.length
}

def evaluateExpr(address: Address, expr: String): JsObject = {
implicit val loggingOptions: LoggingOptions = LoggingOptions()
log.debug(s"${loggingOptions.prefix} evaluateExpr($address, '$expr')")
basicRequest
.post(uri"$apiUri/utils/script/evaluate/$address")
.body(Json.obj("expr" -> expr))
.response(asJson[JsObject])
.tag(LoggingOptionsTag, loggingOptions)
.send(backend)
.body match {
case Left(e) => throw new RuntimeException(e)
case Right(r) => r
}
}

def createWalletAddress(): Unit = {
implicit val loggingOptions: LoggingOptions = LoggingOptions()
log.debug(s"${loggingOptions.prefix} createWalletAddress")
basicRequest
.post(uri"$apiUri/addresses")
.header(`X-Api-Key`.name, ApiKeyValue)
Expand All @@ -191,6 +223,7 @@ class NodeHttpApi(apiUri: Uri, backend: SttpBackend[Identity, ?], averageBlockDe

def rollback(to: Height): Unit = {
implicit val loggingOptions: LoggingOptions = LoggingOptions()
log.debug(s"${loggingOptions.prefix} rollback($to)")
basicRequest
.post(uri"$apiUri/debug/rollback")
.header(`X-Api-Key`.name, ApiKeyValue)
Expand All @@ -217,6 +250,11 @@ class NodeHttpApi(apiUri: Uri, backend: SttpBackend[Identity, ?], averageBlockDe
object NodeHttpApi {
val ApiKeyValue = "testapi"

case class BlockHeaderResponse(VRF: String)
object BlockHeaderResponse {
implicit val blockHeaderResponseFormat: OFormat[BlockHeaderResponse] = Json.format[BlockHeaderResponse]
}

case class HeightResponse(height: Height)
object HeightResponse {
implicit val heightResponseFormat: OFormat[HeightResponse] = Json.format[HeightResponse]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package units

import com.wavesplatform.account.KeyPair
import com.wavesplatform.common.state.ByteStr
import units.client.contract.HasConsensusLayerDappTxHelpers.EmptyE2CTransfersRootHashHex
import units.docker.WavesNodeContainer

class AlternativeChainTestSuite extends OneNodeTestSuite with OneNodeTestSuite.OneMiner {
"L2-383 Start an alternative chain after not getting an EL-block" in {
step("EL miner #2 join")
waves1.api.broadcastAndWait(
chainContract.join(
minerAccount = miner21Account,
elRewardAddress = miner21RewardAddress
)
)

step("Wait miner #2 epoch")
waitMinerEpoch(miner21Account)

step("Issue miner #2 block confirmation")
val lastContractBlock = waves1.chainContract.getLastBlockMeta(0).getOrElse(fail("Can't get last block"))
val lastWavesBlock = waves1.api.blockHeader(waves1.api.height).getOrElse(fail("Can't get current block header"))
waves1.api.broadcastAndWait(
chainContract.extendMainChain(
minerAccount = miner21Account,
blockHash = BlockHash("0x0000000000000000000000000000000000000000000000000000000000000001"),
parentBlockHash = lastContractBlock.hash,
e2cTransfersRootHashHex = EmptyE2CTransfersRootHashHex,
lastC2ETransferIndex = -1,
vrf = ByteStr.decodeBase58(lastWavesBlock.VRF).get
)
)

step("Wait miner #1 epoch")
waitMinerEpoch(miner11Account)

step("Checking an alternative chain started")
retry {
waves1.chainContract.getChainInfo(1L).getOrElse(fail("Can't get an alternative chain info"))
}
}

private def waitMinerEpoch(minerAccount: KeyPair): Unit = {
val expectedGenerator = minerAccount.toAddress
retry {
val actualGenerator = waves1.chainContract.computedGenerator
if (actualGenerator != expectedGenerator) fail(s"Expected $expectedGenerator generator, got $actualGenerator")
}(patienceConfig.copy(timeout = WavesNodeContainer.AverageBlockDelay * 5))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ import monix.execution.atomic.AtomicBoolean
import org.scalatest.freespec.AnyFreeSpec
import org.scalatest.matchers.should.Matchers
import org.scalatest.{BeforeAndAfterAll, EitherValues, OptionValues}
import units.BaseItTestSuite.generateWavesGenesisConfig
import units.BaseDockerTestSuite.generateWavesGenesisConfig
import units.client.contract.HasConsensusLayerDappTxHelpers
import units.docker.BaseContainer.{ConfigsDir, DefaultLogsDir}
import units.docker.Networks
import units.eth.Gwei
import units.test.TestEnvironment.*
import units.test.{CustomMatchers, HasRetry}

import java.io.PrintStream
import java.nio.file.{Files, Path}
import scala.concurrent.duration.DurationInt

trait BaseItTestSuite
trait BaseDockerTestSuite
extends AnyFreeSpec
with ScorexLogging
with BeforeAndAfterAll
Expand All @@ -44,7 +44,7 @@ trait BaseItTestSuite
protected def setupNetwork(): Unit

override def beforeAll(): Unit = {
BaseItTestSuite.init()
BaseDockerTestSuite.init()
super.beforeAll()
log.debug(s"Docker network name: ${network.getName}, id: ${network.getId}") // Force create network

Expand All @@ -59,7 +59,7 @@ trait BaseItTestSuite
}
}

object BaseItTestSuite {
object BaseDockerTestSuite {
private val initialized = AtomicBoolean(false)

def init(): Unit =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.wavesplatform.common.utils.EitherExt2
import units.client.engine.model.BlockNumber
import units.docker.{EcContainer, Networks, WavesNodeContainer}

trait OneNodeTestSuite extends BaseItTestSuite {
trait OneNodeTestSuite extends BaseDockerTestSuite {
protected lazy val ec1: EcContainer = new EcContainer(
network = network,
number = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package units
import org.scalatest.{Args, Status, SuiteMixin}

trait ReportingTestName extends SuiteMixin {
self: BaseItTestSuite =>
self: BaseDockerTestSuite =>

abstract override protected def runTest(testName: String, args: Args): Status = {
testStep(s"Test '$testName' started")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.wavesplatform.common.utils.EitherExt2
import units.client.engine.model.BlockNumber
import units.docker.{EcContainer, Networks, WavesNodeContainer}

trait TwoNodesTestSuite extends BaseItTestSuite {
trait TwoNodesTestSuite extends BaseDockerTestSuite {
protected lazy val ec1: EcContainer = new EcContainer(
network = network,
number = 1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,19 @@ import units.client.contract.{ChainContractClient, ContractBlock}
import scala.annotation.tailrec

class HttpChainContractClient(api: NodeHttpApi, override val contract: Address) extends ChainContractClient {
override def extractData(key: String): Option[DataEntry[?]] = api.getDataByKey(contract, key)
override def extractData(key: String): Option[DataEntry[?]] = api.dataByKey(contract, key)

lazy val token: IssuedAsset = IssuedAsset(ByteStr.decodeBase58(getStringData("tokenId").getOrElse(fail("Call setup first"))).get)

def computedGenerator: Address = {
val rawResult = api.evaluateExpr(contract, "computedGenerator").result
val rawAddress = (rawResult \ "result" \ "value").as[String]
Address.fromString(rawAddress) match {
case Left(e) => fail(s"Can't parse computedGenerator address: $rawAddress. Reason: $e")
case Right(r) => r
}
}

def getEpochFirstBlock(epochNumber: Int): Option[ContractBlock] =
getEpochMeta(epochNumber).flatMap { epochData =>
getEpochFirstBlock(epochData.lastBlockHash)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import com.wavesplatform.utils.LoggerFacade
import org.slf4j.LoggerFactory
import org.testcontainers.containers.wait.strategy.DockerHealthcheckWaitStrategy

import java.nio.file.{Files, Path}

abstract class BaseContainer(val hostName: String) {
protected lazy val log = LoggerFacade(LoggerFactory.getLogger(s"${getClass.getSimpleName}.$hostName"))

Expand All @@ -25,11 +23,3 @@ abstract class BaseContainer(val hostName: String) {

def logPorts(): Unit
}

object BaseContainer {
val ConfigsDir: Path = Path.of(System.getProperty("cc.it.configs.dir"))
val DefaultLogsDir: Path = Path.of(System.getProperty("cc.it.logs.dir"))
Files.createDirectories(DefaultLogsDir)

val WavesDockerImage: String = System.getProperty("cc.it.docker.image")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package units.docker

import org.testcontainers.utility.DockerImageName.parse
import units.test.TestEnvironment.WavesDockerImage

object DockerImages {
val WavesNode = parse(WavesDockerImage)
val ExecutionClient = parse("hyperledger/besu:latest")
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,16 @@ import com.typesafe.config.{ConfigFactory, ConfigValueFactory}
import net.ceedubs.ficus.Ficus.toFicusConfig
import org.testcontainers.containers.BindMode
import org.testcontainers.containers.Network.NetworkImpl
import org.testcontainers.utility.DockerImageName
import org.web3j.protocol.Web3j
import org.web3j.protocol.http.HttpService
import sttp.client3.HttpClientSyncBackend
import units.ClientConfig
import units.client.engine.{HttpEngineApiClient, LoggedEngineApiClient}
import units.docker.BaseContainer.{ConfigsDir, DefaultLogsDir}
import units.docker.EcContainer.{EnginePort, RpcPort, mkConfig}
import units.el.ElBridgeClient
import units.eth.EthAddress
import units.http.OkHttpLogger
import units.test.TestEnvironment.*

import java.io.File
import java.nio.charset.StandardCharsets
Expand All @@ -25,7 +24,7 @@ class EcContainer(network: NetworkImpl, number: Int, ip: String) extends BaseCon
private val logFile = new File(s"$DefaultLogsDir/besu-$number.log")
Files.touch(logFile)

protected override val container = new GenericContainer(DockerImageName.parse("hyperledger/besu:latest"))
protected override val container = new GenericContainer(DockerImages.ExecutionClient)
.withNetwork(network)
.withExposedPorts(RpcPort, EnginePort)
.withEnv(EcContainer.peersEnv, EcContainer.peersVal.mkString(","))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@ import com.wavesplatform.common.utils.Base58
import com.wavesplatform.crypto
import org.testcontainers.containers.BindMode
import org.testcontainers.containers.Network.NetworkImpl
import org.testcontainers.utility.DockerImageName
import sttp.client3.{HttpClientSyncBackend, UriContext}
import units.client.HttpChainContractClient
import units.docker.BaseContainer.*
import units.docker.WavesNodeContainer.*
import units.test.TestEnvironment.*

import java.io.File
import java.nio.charset.StandardCharsets
Expand All @@ -34,7 +33,7 @@ class WavesNodeContainer(
private val logFile = new File(s"$DefaultLogsDir/waves-$number.log")
Files.touch(logFile)

protected override val container = new GenericContainer(DockerImageName.parse(WavesDockerImage))
protected override val container = new GenericContainer(DockerImages.WavesNode)
.withNetwork(network)
.withExposedPorts(ApiPort)
.withEnv(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package units.test

import java.nio.file.{Files, Path}

object TestEnvironment {
val ConfigsDir: Path = Path.of(System.getProperty("cc.it.configs.dir"))
val DefaultLogsDir: Path = Path.of(System.getProperty("cc.it.logs.dir"))
Files.createDirectories(DefaultLogsDir)

val WavesDockerImage: String = System.getProperty("cc.it.docker.image")
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,28 @@ trait HasConsensusLayerDappTxHelpers {
fee = extendMainChainFee
)

def extendMainChain(
minerAccount: KeyPair,
blockHash: BlockHash,
parentBlockHash: BlockHash,
e2cTransfersRootHashHex: String,
lastC2ETransferIndex: Long,
vrf: ByteStr
): InvokeScriptTransaction =
TxHelpers.invoke(
invoker = minerAccount,
dApp = chainContractAddress,
func = "extendMainChain".some,
args = List(
Terms.CONST_STRING(blockHash.drop(2)).explicitGet(),
Terms.CONST_STRING(parentBlockHash.drop(2)).explicitGet(),
Terms.CONST_BYTESTR(vrf).explicitGet(),
Terms.CONST_STRING(e2cTransfersRootHashHex.drop(2)).explicitGet(),
Terms.CONST_LONG(lastC2ETransferIndex)
),
fee = extendMainChainFee
)

def appendBlock(
minerAccount: KeyPair,
block: L2BlockLike,
Expand Down

0 comments on commit 7cdaa71

Please sign in to comment.