Skip to content

Commit

Permalink
Bind chain contract invocations to specific VRF values (#6)
Browse files Browse the repository at this point in the history
  • Loading branch information
phearnot authored Sep 2, 2024
1 parent 36801f1 commit 086c672
Show file tree
Hide file tree
Showing 4 changed files with 181 additions and 151 deletions.
77 changes: 24 additions & 53 deletions src/main/scala/units/ELUpdater.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,9 @@ import com.typesafe.scalalogging.StrictLogging
import com.wavesplatform.account.{Address, KeyPair}
import com.wavesplatform.common.merkle.Digest
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.crypto
import units.ELUpdater.State.*
import units.ELUpdater.State.ChainStatus.{FollowingChain, Mining, WaitForNewChain}
import units.client.L2BlockLike
import units.client.contract.*
import units.client.engine.EngineApiClient
import units.client.engine.EngineApiClient.PayloadId
import units.client.engine.model.*
import units.client.engine.model.Withdrawal.WithdrawalIndex
import units.client.http.EcApiClient
import units.client.http.model.EcBlock
import units.eth.{EmptyL2Block, EthAddress, EthereumConstants}
import units.network.BlocksObserverImpl.BlockWithChannel
import units.util.HexBytesConverter
import units.util.HexBytesConverter.toHexNoPrefix
import com.wavesplatform.lang.ValidationError
import com.wavesplatform.lang.v1.FunctionHeader
import com.wavesplatform.lang.v1.compiler.Terms.{CONST_LONG, CONST_STRING, FUNCTION_CALL}
import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL
import com.wavesplatform.network.ChannelGroupExt
import com.wavesplatform.state.Blockchain
import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit, ScriptExtraFee}
Expand All @@ -41,6 +25,20 @@ import io.netty.channel.group.DefaultChannelGroup
import monix.execution.cancelables.SerialCancelable
import monix.execution.{CancelableFuture, Scheduler}
import play.api.libs.json.*
import units.ELUpdater.State.*
import units.ELUpdater.State.ChainStatus.{FollowingChain, Mining, WaitForNewChain}
import units.client.L2BlockLike
import units.client.contract.*
import units.client.engine.EngineApiClient
import units.client.engine.EngineApiClient.PayloadId
import units.client.engine.model.*
import units.client.engine.model.Withdrawal.WithdrawalIndex
import units.client.http.EcApiClient
import units.client.http.model.EcBlock
import units.eth.{EmptyL2Block, EthAddress, EthereumConstants}
import units.network.BlocksObserverImpl.BlockWithChannel
import units.util.HexBytesConverter
import units.util.HexBytesConverter.toHexNoPrefix

import scala.annotation.tailrec
import scala.concurrent.duration.*
Expand Down Expand Up @@ -155,7 +153,7 @@ class ELUpdater(
// Removing here, because we have these transactions in PP after the onProcessBlock trigger
utx.getPriorityPool.foreach { pp =>
val staleTxs = pp.priorityTransactions.filter {
case tx: InvokeScriptTransaction => tx.dApp == contractAddress && ContractFunction.AllNames.contains(tx.funcCall.function.funcName)
case tx: InvokeScriptTransaction => tx.dApp == contractAddress
case _ => false
}

Expand All @@ -166,48 +164,22 @@ class ELUpdater(
}
}

private def callContract(
contractFunction: ContractFunction,
blockData: EcBlock,
transfersRootHash: Digest,
lastClToElTransferIndex: Long,
invoker: KeyPair
): Job[Unit] = {
private def callContract(fc: FUNCTION_CALL, blockData: EcBlock, invoker: KeyPair): Job[Unit] = {
val extraFee = if (blockchain.hasPaidVerifier(invoker.toAddress)) ScriptExtraFee else 0

val chainIdArg = contractFunction.chainIdOpt.map(CONST_LONG.apply).toList
val epochArg = contractFunction.epochOpt.map(x => CONST_LONG(x)).toList

val v2Args =
if (contractFunction.version >= ContractFunction.V2) List(CONST_STRING(toHexNoPrefix(transfersRootHash)).explicitGet())
else Nil

val v3Args =
if (contractFunction.version >= ContractFunction.V3) List(CONST_LONG(lastClToElTransferIndex))
else Nil

val args = chainIdArg ++ List(
CONST_STRING(toHexNoPrefix(blockData.hashByteStr.arr)).explicitGet(),
CONST_STRING(toHexNoPrefix(blockData.parentHashByteStr.arr)).explicitGet()
) ++ epochArg ++ v2Args ++ v3Args
val tx = InvokeScriptTransaction(
TxVersion.V2,
invoker.publicKey,
contractAddress,
Some(
FUNCTION_CALL(
FunctionHeader.User(contractFunction.name),
args
)
),
Some(fc),
Seq.empty,
TxPositiveAmount.unsafeFrom(FeeConstants(TransactionType.InvokeScript) * FeeUnit + extraFee),
Asset.Waves,
System.currentTimeMillis(),
time.correctedTime(),
Proofs.empty,
blockchain.settings.addressSchemeCharacter.toByte
).signWith(invoker.privateKey)
logger.info(s"Invoking $contractAddress '${contractFunction.name}' for block ${blockData.hash}->${blockData.parentHash}, txId=${tx.id()}")
logger.info(s"Invoking $contractAddress '${fc.function.funcName}' for block ${blockData.hash}->${blockData.parentHash}, txId=${tx.id()}")
cleanPriorityPool()

broadcastTx(tx).resultE match {
Expand Down Expand Up @@ -266,11 +238,10 @@ class ELUpdater(
)
ecBlock = newBlock.toEcBlock
transfersRootHash <- getElToClTransfersRootHash(ecBlock.hash, chainContractOptions.elBridgeAddress)
funcCall <- contractFunction.toFunctionCall(ecBlock.hash, transfersRootHash, m.lastClToElTransferIndex)
_ <- callContract(
contractFunction,
funcCall,
ecBlock,
transfersRootHash,
m.lastClToElTransferIndex,
m.keyPair
)
} yield ecBlock).fold(
Expand Down Expand Up @@ -407,7 +378,7 @@ class ELUpdater(
miningData.payloadId,
parentBlock.hash,
miningData.nextBlockUnixTs,
newState.options.startEpochChainFunction(epochInfo.number, nodeChainInfo.toOption),
newState.options.startEpochChainFunction(parentBlock.hash, epochInfo.hitSource, nodeChainInfo.toOption),
newState.options
)
)
Expand Down Expand Up @@ -460,7 +431,7 @@ class ELUpdater(
miningData.payloadId,
parentBlock.hash,
miningData.nextBlockUnixTs,
chainContractOptions.appendFunction,
chainContractOptions.appendFunction(parentBlock.hash),
chainContractOptions
)
)
Expand Down
30 changes: 16 additions & 14 deletions src/main/scala/units/client/contract/ChainContractOptions.scala
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
package units.client.contract

import com.wavesplatform.common.state.ByteStr
import units.BlockHash
import units.client.contract.ContractFunction.*
import units.eth.{EthAddress, Gwei}

/** @note
* Make sure you have an activation gap: a new feature should not be activated suddenly during nearest blocks.
*/
case class ChainContractOptions(miningReward: Gwei, elBridgeAddress: EthAddress) {
def startEpochChainFunction(epochNumber: Int, chainInfo: Option[ChainInfo]): ContractFunction = {
def startEpochChainFunction(reference: BlockHash, vrf: ByteStr, chainInfo: Option[ChainInfo]): ContractFunction = {
chainInfo match {
case Some(ci) => extendChainFunction(ci, epochNumber)
case _ => startAltChainFunction(epochNumber)
case Some(ci) => extendChainFunction(reference, vrf, ci)
case _ => startAltChainFunction(reference, vrf)
}
}

def appendFunction: ContractFunction =
AppendBlock(V3)
def appendFunction(reference: BlockHash): AppendBlock =
AppendBlock(reference)

private def extendChainFunction(chainInfo: ChainInfo, epochNumber: Int): ContractFunction =
if (chainInfo.isMain) extendMainChainFunction(epochNumber)
else extendAltChainFunction(chainInfo.id, epochNumber)
private def extendChainFunction(reference: BlockHash, vrf: ByteStr, chainInfo: ChainInfo): ContractFunction =
if (chainInfo.isMain) extendMainChainFunction(reference, vrf)
else extendAltChainFunction(reference, vrf, chainInfo.id)

private def extendMainChainFunction(epochNumber: Int): ContractFunction =
ExtendMainChain(epochNumber, V3)
private def extendMainChainFunction(reference: BlockHash, vrf: ByteStr): ContractFunction =
ExtendMainChain(reference, vrf)

private def extendAltChainFunction(chainId: Long, epochNumber: Int): ContractFunction =
ExtendAltChain(chainId, epochNumber, V3)
private def extendAltChainFunction(reference: BlockHash, vrf: ByteStr, chainId: Long): ContractFunction =
ExtendAltChain(reference, vrf, chainId)

private def startAltChainFunction(epochNumber: Int): ContractFunction =
StartAltChain(epochNumber, V3)
private def startAltChainFunction(reference: BlockHash, vrf: ByteStr): ContractFunction =
StartAltChain(reference, vrf)
}
66 changes: 26 additions & 40 deletions src/main/scala/units/client/contract/ContractFunction.scala
Original file line number Diff line number Diff line change
@@ -1,49 +1,35 @@
package units.client.contract

import cats.syntax.option.*
import units.client.contract.ContractFunction.*

sealed abstract class ContractFunction(
baseName: String,
val chainIdOpt: Option[Long] = none,
val epochOpt: Option[Int] = none
) {
def version: Version

val name: String = s"$baseName${version.mineFunctionsSuffix}"
import com.wavesplatform.common.merkle.Digest
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.lang.CommonError
import com.wavesplatform.lang.v1.FunctionHeader
import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BYTESTR, CONST_LONG, CONST_STRING, EVALUATED, FUNCTION_CALL}
import units.{BlockHash, ClientError, Job}
import units.util.HexBytesConverter.toHexNoPrefix
import cats.syntax.either.*

abstract class ContractFunction(name: String, reference: BlockHash, extraArgs: Either[CommonError, List[EVALUATED]]) {
def toFunctionCall(blockHash: BlockHash, transfersRootHash: Digest, lastClToElTransferIndex: Long): Job[FUNCTION_CALL] = (for {
hash <- CONST_STRING(blockHash)
ref <- CONST_STRING(reference)
trh <- CONST_STRING(toHexNoPrefix(transfersRootHash))
xtra <- extraArgs
} yield FUNCTION_CALL(
FunctionHeader.User(name),
List(hash, ref) ++ xtra ++ List(trh, CONST_LONG(lastClToElTransferIndex))
)).leftMap(e => ClientError(s"Error building function call for $name: $e"))
}

object ContractFunction {
val ExtendMainChainBaseName = "extendMainChain"
val AppendBlockBaseName = "appendBlock"
val ExtendAltChainBaseName = "extendAltChain"
val StartAltChainBaseName = "startAltChain"

case class ExtendMainChain(epoch: Int, version: Version) extends ContractFunction(ExtendMainChainBaseName, epochOpt = epoch.some)
case class AppendBlock(version: Version) extends ContractFunction(AppendBlockBaseName)
case class ExtendAltChain(chainId: Long, epoch: Int, version: Version) extends ContractFunction(ExtendAltChainBaseName, chainId.some, epoch.some)
case class StartAltChain(epoch: Int, version: Version) extends ContractFunction(StartAltChainBaseName, epochOpt = epoch.some)

val AllNames: List[String] = for {
function <- List(ExtendMainChainBaseName, AppendBlockBaseName, ExtendAltChainBaseName, StartAltChainBaseName)
version <- Version.All
} yield function + version.mineFunctionsSuffix

val StartAltChainNames = AllNames.filter(_.startsWith(StartAltChainBaseName))
case class ExtendMainChain(reference: BlockHash, vrf: ByteStr)
extends ContractFunction("extendMainChain_v4", reference, CONST_BYTESTR(vrf).map(v => List(v)))

sealed abstract class Version(protected val n: Int) extends Ordered[Version] with Product with Serializable {
val mineFunctionsSuffix: String = s"_v$n"
case class AppendBlock(reference: BlockHash) extends ContractFunction("appendBlock_v3", reference, Right(Nil))

override def compare(that: Version): Int = java.lang.Integer.compare(n, that.n)
override def canEqual(that: Any): Boolean = that match {
case _: Version => true
case _ => false
}
}
case class ExtendAltChain(reference: BlockHash, vrf: ByteStr, chainId: Long)
extends ContractFunction("extendAltChain_v4", reference, CONST_BYTESTR(vrf).map(v => List(v, CONST_LONG(chainId))))

case object V2 extends Version(2) // Supports EL to CL transfers
case object V3 extends Version(3) // Supports CL to EL transfers
object Version {
val All = List(V2, V3)
}
case class StartAltChain(reference: BlockHash, vrf: ByteStr)
extends ContractFunction("startAltChain_v4", reference, CONST_BYTESTR(vrf).map(v => List(v)))
}
Loading

0 comments on commit 086c672

Please sign in to comment.