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

Support op-geth as execution client #30

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
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 @@ -52,7 +52,7 @@ class NodeHttpApi(apiUri: Uri, backend: SttpBackend[Identity, ?], apiKeyValue: S
if (currHeight >= atLeast) currHeight
else {
Thread.sleep(patienceConfig.interval.toMillis)
val waitBlocks = (atLeast - currHeight).min(1)
val waitBlocks = (atLeast - currHeight).max(1)
eventually(timeout(MaxBlockDelay * waitBlocks)) {
val h = height()(subsequentLoggingOptions)
h should be >= atLeast
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ trait BaseDockerTestSuite
private implicit val httpClientBackend: SttpBackend[Identity, Any] = new LoggingBackend(HttpClientSyncBackend())

protected lazy val ec1: EcContainer = {
val constructor = TestEnvironment.ExecutionClient match {
case "besu" => new BesuContainer(_, _, _)
case "geth" => new GethContainer(_, _, _)
case x => throw new RuntimeException(s"Unknown execution client: $x. Only 'geth' or 'besu' supported")
val constructor = TestEnvironment.ExecutionClient.getOrElse("op-geth") match {
case "besu" => new BesuContainer(_, _, _)
case "geth" => new GethContainer(_, _, _)
case "op-geth" => new OpGethContainer(_, _, _)
}

constructor(network, 1, Networks.ipForNode(2) /* ipForNode(1) is assigned to Ryuk */ )
Expand Down
161 changes: 80 additions & 81 deletions consensus-client-it/src/test/scala/units/BridgeE2CTestSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,124 +4,123 @@ import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.utils.EthEncoding
import org.web3j.protocol.core.DefaultBlockParameterName
import org.web3j.protocol.core.methods.response.TransactionReceipt
import org.web3j.protocol.exceptions.TransactionException
import org.web3j.utils.Convert
import units.el.ElBridgeClient

import scala.jdk.OptionConverters.RichOptional

class BridgeE2CTestSuite extends BaseDockerTestSuite {
private val elSender = elRichAccount1
private val clRecipient = clRichAccount1
private val userAmount = 1
private val wavesAmount = UnitsConvert.toWavesAmount(userAmount)

private def sendNative(amount: BigInt = UnitsConvert.toWei(userAmount)): TransactionReceipt =
elBridge.sendNative(elSender, clRecipient.toAddress, amount)

private val tenGwei = BigInt(Convert.toWei("10", Convert.Unit.GWEI).toBigIntegerExact)

"Negative" - {
def sendNativeInvalid(amount: BigInt): TransactionException =
try {
sendNative(amount)
fail(s"Expected sendNative($amount) to fail")
} catch {
case e: TransactionException => e
}

"L2-264 Amount should % 10 Gwei" in {
val e = sendNativeInvalid(tenGwei + 1)
val encodedRevertReason = e.getTransactionReceipt.get().getRevertReason
val revertReason = ElBridgeClient.decodeRevertReason(encodedRevertReason)
revertReason shouldBe "Sent value 10000000001 must be a multiple of 10000000000"
def test(amount: BigInt, expectedError: String): Unit = {
val e = elBridge.callRevertedSendNative(elSender, clRecipient.toAddress, amount)
e should include(expectedError)
}

"L2-264 Amount should % 10 Gwei" in test(tenGwei + 1, "Sent value 10000000001 must be a multiple of 10000000000")

"L2-265 Amount should be between 10 and MAX_AMOUNT_IN_WEI Gwei" in {
withClue("1. Less than 10 Gwei: ") {
val e = sendNativeInvalid(1)
val encodedRevertReason = e.getTransactionReceipt.get().getRevertReason
val revertReason = ElBridgeClient.decodeRevertReason(encodedRevertReason)
revertReason shouldBe "Sent value 1 must be greater or equal to 10000000000"
test(1, "Sent value 1 must be greater or equal to 10000000000")
}

withClue("2. More than MAX_AMOUNT_IN_WEI: ") {
val maxAmountInWei = BigInt(Long.MaxValue) * tenGwei
val biggerAmount = (maxAmountInWei / tenGwei + 1) * tenGwei
val e = sendNativeInvalid(biggerAmount)
val encodedRevertReason = e.getTransactionReceipt.get().getRevertReason
val revertReason = ElBridgeClient.decodeRevertReason(encodedRevertReason)
revertReason shouldBe s"Sent value $biggerAmount must be less or equal to $maxAmountInWei"
val maxAmountInWei = BigInt(Long.MaxValue) * tenGwei
val biggerAmount = (maxAmountInWei / tenGwei + 1) * tenGwei
test(biggerAmount, s"Sent value $biggerAmount must be less or equal to $maxAmountInWei")
}
}
}

"L2-325 Sent tokens burned" in {
def burnedTokens = ec1.web3j.ethGetBalance(ElBridgeClient.BurnAddress.hex, DefaultBlockParameterName.LATEST).send().getBalance
val burnedTokensBefore = BigInt(burnedTokens)
"Positive" - {
def sendNative(amount: BigInt = UnitsConvert.toWei(userAmount)): TransactionReceipt = {
val txnResult = elBridge.sendSendNative(elSender, clRecipient.toAddress, amount)

val transferAmount = tenGwei
sendNative(transferAmount)
val burnedTokensAfter = BigInt(burnedTokens)
// To overcome a failed block confirmation in a new epoch issue
chainContract.waitForHeight(ec1.web3j.ethBlockNumber().send().getBlockNumber.longValueExact() + 2)

burnedTokensAfter shouldBe (transferAmount + burnedTokensBefore)
}
eventually {
val r = ec1.web3j.ethGetTransactionReceipt(txnResult.getTransactionHash).send().getTransactionReceipt.toScala.value
if (!r.isStatusOK) fail(s"Expected successful sendNative, got: ${ElBridgeClient.decodeRevertReason(r.getRevertReason)}")
r
}
}

"L2-325 Sent tokens burned" in {
def burnedTokens = ec1.web3j.ethGetBalance(ElBridgeClient.BurnAddress.hex, DefaultBlockParameterName.LATEST).send().getBalance
val burnedTokensBefore = BigInt(burnedTokens)

"L2-379 Checking balances in EL->CL transfers" in {
step("Broadcast Bridge.sendNative transaction")
def bridgeBalance = ec1.web3j.ethGetBalance(elBridgeAddress.hex, DefaultBlockParameterName.LATEST).send().getBalance
val bridgeBalanceBefore = bridgeBalance
val sendTxnReceipt = sendNative()
val transferAmount = tenGwei
sendNative(transferAmount)
val burnedTokensAfter = BigInt(burnedTokens)

withClue("1. The balance of Bridge contract wasn't changed: ") {
val bridgeBalanceAfter = bridgeBalance
bridgeBalanceAfter shouldBe bridgeBalanceBefore
burnedTokensAfter shouldBe (transferAmount + burnedTokensBefore)
}

val blockHash = BlockHash(sendTxnReceipt.getBlockHash)
step(s"Block with transaction: $blockHash")
"L2-379 Checking balances in EL->CL transfers" in {
step("Broadcast Bridge.sendNative transaction")
def bridgeBalance = ec1.web3j.ethGetBalance(elBridgeAddress.hex, DefaultBlockParameterName.LATEST).send().getBalance
val bridgeBalanceBefore = bridgeBalance
val sendTxnReceipt = sendNative()

val logsInBlock = ec1.engineApi.getLogs(blockHash, elBridgeAddress, Bridge.ElSentNativeEventTopic).explicitGet()
withClue("1. The balance of Bridge contract wasn't changed: ") {
val bridgeBalanceAfter = bridgeBalance
bridgeBalanceAfter shouldBe bridgeBalanceBefore
}

val transferEvents = logsInBlock.map { x =>
Bridge.ElSentNativeEvent.decodeArgs(x.data).explicitGet()
}
step(s"Transfer events: ${transferEvents.mkString(", ")}")
val blockHash = BlockHash(sendTxnReceipt.getBlockHash)
step(s"Block with transaction: $blockHash")

val sendTxnLogIndex = logsInBlock.indexWhere(_.transactionHash == sendTxnReceipt.getTransactionHash)
val transferProofs = Bridge.mkTransferProofs(transferEvents, sendTxnLogIndex).reverse
val logsInBlock = ec1.engineApi.getLogs(blockHash, elBridgeAddress, Bridge.ElSentNativeEventTopic).explicitGet()

step(s"Wait block $blockHash on contract")
val blockConfirmationHeight = eventually {
chainContract.getBlock(blockHash).value.height
}
val transferEvents = logsInBlock.map { x =>
Bridge.ElSentNativeEvent.decodeArgs(x.data).explicitGet()
}
step(s"Transfer events: ${transferEvents.mkString(", ")}")

step(s"Wait block $blockHash ($blockConfirmationHeight) finalization")
eventually {
val currFinalizedHeight = chainContract.getFinalizedBlock.height
step(s"Current finalized height: $currFinalizedHeight")
currFinalizedHeight should be >= blockConfirmationHeight
}
val sendTxnLogIndex = logsInBlock.indexWhere(_.transactionHash == sendTxnReceipt.getTransactionHash)
val transferProofs = Bridge.mkTransferProofs(transferEvents, sendTxnLogIndex).reverse

step(s"Wait block $blockHash on contract")
val blockConfirmationHeight = eventually {
chainContract.getBlock(blockHash).value.height
}

withClue("3. Tokens received: ") {
step(
s"Broadcast withdraw transaction: transferIndexInBlock=$sendTxnLogIndex, amount=$wavesAmount, " +
s"merkleProof={${transferProofs.map(EthEncoding.toHexString).mkString(",")}}"
)

def receiverBalance: Long = waves1.api.balance(clRecipient.toAddress, chainContract.token)
val receiverBalanceBefore = receiverBalance

waves1.api.broadcastAndWait(
ChainContract.withdraw(
sender = clRecipient,
blockHash = BlockHash(sendTxnReceipt.getBlockHash),
merkleProof = transferProofs,
transferIndexInBlock = sendTxnLogIndex,
amount = wavesAmount
step(s"Wait block $blockHash ($blockConfirmationHeight) finalization")
eventually {
val currFinalizedHeight = chainContract.getFinalizedBlock.height
step(s"Current finalized height: $currFinalizedHeight")
currFinalizedHeight should be >= blockConfirmationHeight
}

withClue("3. Tokens received: ") {
step(
s"Broadcast withdraw transaction: transferIndexInBlock=$sendTxnLogIndex, amount=$wavesAmount, " +
s"merkleProof={${transferProofs.map(EthEncoding.toHexString).mkString(",")}}"
)
)

val balanceAfter = receiverBalance
balanceAfter shouldBe (receiverBalanceBefore + wavesAmount)
def receiverBalance: Long = waves1.api.balance(clRecipient.toAddress, chainContract.token)
val receiverBalanceBefore = receiverBalance

waves1.api.broadcastAndWait(
ChainContract.withdraw(
sender = clRecipient,
blockHash = BlockHash(sendTxnReceipt.getBlockHash),
merkleProof = transferProofs,
transferIndexInBlock = sendTxnLogIndex,
amount = wavesAmount
)
)

val balanceAfter = receiverBalance
balanceAfter shouldBe (receiverBalanceBefore + wavesAmount)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ class SyncingTestSuite extends BaseDockerTestSuite {
private val elSender = elRichAccount1
private val amount = Convert.toWei("1", Convert.Unit.ETHER).toBigInteger

// TODO: Will be removed after fixing geth issues
"L2-381 EL transactions appear after rollback" ignore {
"L2-381 EL transactions appear after rollback" in {
step("Send transaction 1")
val txn1Result = sendTxn(0)
waitForTxn(txn1Result)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import org.testcontainers.utility.DockerImageName.parse
import units.test.TestEnvironment.WavesDockerImage

object DockerImages {
val WavesNode = parse(WavesDockerImage)
val BesuExecutionClient = parse("hyperledger/besu:latest")
val GethExecutionClient = parse("ethereum/client-go:stable")
val WavesNode = parse(WavesDockerImage)
val BesuExecutionClient = parse("hyperledger/besu:latest")
val GethExecutionClient = parse("ethereum/client-go:stable")
val OpGethExecutionClient = parse("us-docker.pkg.dev/oplabs-tools-artifacts/images/op-geth:v1.101411.2")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package units.docker

import okhttp3.Interceptor
import org.testcontainers.containers.BindMode
import org.testcontainers.containers.Network.NetworkImpl
import org.web3j.protocol.Web3j
import org.web3j.protocol.http.HttpService
import pdi.jwt.{JwtAlgorithm, JwtClaim, JwtJson}
import sttp.client3.{Identity, SttpBackend}
import units.client.JwtAuthenticationBackend
import units.client.engine.{EngineApiClient, HttpEngineApiClient, LoggedEngineApiClient}
import units.docker.EcContainer.{EnginePort, RpcPort}
import units.http.OkHttpLogger
import units.test.TestEnvironment.ConfigsDir

import java.time.Clock
import scala.io.Source

class OpGethContainer(network: NetworkImpl, number: Int, ip: String)(implicit httpClientBackend: SttpBackend[Identity, Any])
extends EcContainer(number) {
protected override val container = new GenericContainer(DockerImages.OpGethExecutionClient)
.withNetwork(network)
.withExposedPorts(RpcPort, EnginePort)
.withFileSystemBind(s"$ConfigsDir/ec-common/genesis.json", "/tmp/genesis.json", BindMode.READ_ONLY)
.withFileSystemBind(s"$ConfigsDir/op-geth/run-op-geth.sh", "/tmp/run.sh", BindMode.READ_ONLY)
.withFileSystemBind(s"$ConfigsDir/ec-common/p2p-key-$number.hex", "/etc/secrets/p2p-key", BindMode.READ_ONLY)
.withFileSystemBind(s"$ConfigsDir/ec-common/jwt-secret-$number.hex", "/etc/secrets/jwtsecret", BindMode.READ_ONLY)
.withFileSystemBind(s"$logFile", "/root/logs/log", BindMode.READ_WRITE)
.withCreateContainerCmdModifier { cmd =>
cmd
.withName(s"${network.getName}-$hostName")
.withHostName(hostName)
.withIpv4Address(ip)
.withEntrypoint("/tmp/run.sh")
.withStopTimeout(5)
}

lazy val jwtSecretKey = {
val src = Source.fromFile(s"$ConfigsDir/ec-common/jwt-secret-$number.hex")
try src.getLines().next()
finally src.close()
}

override lazy val engineApi: EngineApiClient = new LoggedEngineApiClient(
new HttpEngineApiClient(
engineApiConfig,
new JwtAuthenticationBackend(jwtSecretKey, httpClientBackend)
)
)

override lazy val web3j = Web3j.build(
new HttpService(
s"http://${container.getHost}:$rpcPort",
HttpService.getOkHttpClientBuilder
.addInterceptor { (chain: Interceptor.Chain) =>
val orig = chain.request()
val jwtToken = JwtJson.encode(JwtClaim().issuedNow(Clock.systemUTC), jwtSecretKey, JwtAlgorithm.HS256)
val request = orig
.newBuilder()
.header("Authorization", s"Bearer $jwtToken")
.build()

chain.proceed(request)
}
.addInterceptor(OkHttpLogger)
.build()
)
)
}
Loading
Loading