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

Chain contract registry #29

Merged
merged 4 commits into from
Dec 11, 2024
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
18 changes: 11 additions & 7 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,17 @@ inScope(Global)(
name := "consensus-client"
maintainer := "Units Network Team"

libraryDependencies ++= Seq(
"com.wavesplatform" % "node-testkit" % "1.5.8" % Test,
"com.wavesplatform" % "node" % "1.5.8" % Provided,
"com.softwaremill.sttp.client3" % "core_2.13" % "3.10.1",
"com.softwaremill.sttp.client3" %% "play-json" % "3.10.1",
"com.github.jwt-scala" %% "jwt-play-json" % "10.0.1"
)
libraryDependencies ++= {
val node = "1.5-SNAPSHOT"
val sttp = "3.10.1"
Seq(
"com.wavesplatform" % "node-testkit" % node % Test,
"com.wavesplatform" % "node" % node % Provided,
"com.softwaremill.sttp.client3" % "core_2.13" % sttp,
"com.softwaremill.sttp.client3" %% "play-json" % sttp,
"com.github.jwt-scala" %% "jwt-play-json" % "10.0.1"
)
}

Compile / packageDoc / publishArtifact := false

Expand Down
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
1 change: 1 addition & 0 deletions consensus-client-it/src/test/scala/units/Accounts.scala
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import units.eth.EthAddress
import java.nio.charset.StandardCharsets

trait Accounts {
val chainRegistryAccount: KeyPair = mkKeyPair("devnet registry", 0)
val chainContractAccount: KeyPair = mkKeyPair("devnet cc", 0)

val miner11Account = mkKeyPair("devnet-1", 0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,9 @@ trait BaseDockerTestSuite
}

protected def setupChain(): Unit = {
log.info("Approve chain on registry")
waves1.api.broadcast(ChainRegistry.approve())

log.info("Set script")
waves1.api.broadcastAndWait(ChainContract.setScript())

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

class RegistryDockerTestSuite extends BaseDockerTestSuite {
"Approved, rejected, approved - mining works when approved" in {
step(s"Wait miner 1 (${miner11Account.toAddress}) forge at least one block")
chainContract.waitForHeight(1L)

step("Broadcast a reject transaction")
val rejectTxnHeight = waves1.api.broadcastAndWait(ChainRegistry.reject()).height

step("Wait a rejection height")
waves1.api.waitForHeight(rejectTxnHeight + 1)
val lastElBlock1 = chainContract.getLastBlockMeta(0L).value

step("Expect no mining")
waves1.api.waitForHeight(rejectTxnHeight + 2)

val lastElBlock2 = chainContract.getLastBlockMeta(0L).value
withClue("Same block - no mining: ") {
lastElBlock2.hash shouldBe lastElBlock1.hash
}

val approveTxnHeight = waves1.api.broadcastAndWait(ChainRegistry.approve()).height
step("Wait an approval height")
waves1.api.waitForHeight(approveTxnHeight + 1)

step("Mining working")
chainContract.waitForHeight(lastElBlock2.height + 1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class WavesNodeContainer(
"NODE_NUMBER" -> s"$number",
"WAVES_WALLET_SEED" -> Base58.encode(baseSeed.getBytes(StandardCharsets.UTF_8)),
"JAVA_OPTS" -> List(
"-Dwaves.miner.quorum=0",
s"-Dunits.defaults.chain-contract=$chainContractAddress",
s"-Dunits.defaults.execution-client-address=$ecEngineApiUrl",
"-Dlogback.file.level=TRACE",
Expand Down
2 changes: 1 addition & 1 deletion docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
ARG baseImage=ghcr.io/wavesplatform/waves:1.5.8-1
ARG baseImage=ghcr.io/wavesplatform/waves:units-registry
FROM $baseImage
COPY target /tmp/
RUN tar zxvf /tmp/consensus-client.tgz -C $WAVES_INSTALL_PATH --strip-components=1
13 changes: 11 additions & 2 deletions local-network/configs/wavesnode/genesis-template.conf
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ genesis-generator {
# Private key: 5r7KpS3MNXxRcz9jMbpjZKhaZVxDxF74H4VLW6ydi4aT
# Public key: 2JYMTjUK7tC8NQi6TD6oWgy41YbrnXuoLzZydrFKTKt6
# Address: 3FSgXpgbT6m1speWgVx3cVxAZKmdr4barHU
{ seed-text = "devnet-1", nonce = 1, amount = 998036000000000 }
{ seed-text = "devnet-1", nonce = 1, amount = 900000000000000 }

# Miner on wavesnode-2
# Seed text: devnet-2
Expand All @@ -33,7 +33,7 @@ genesis-generator {
# Private key: F17jrRWmrR7en6eY6ptvnUTNqc9W8RSP34hpX7bNhbht
# Public key: FuibB1rJ1uHVvvY6FNmhLqnbvQBNtziWbh7zh5c1CQCG
# Address: 3FSrRN8X7cDsLyYTScS8Yf8KSwZgJBwf1jU
{ seed-text = "devnet-2", nonce = 0, amount = 998036000000000 }
{ seed-text = "devnet-2", nonce = 0, amount = 800900102030000 }

# Additional addresses

Expand Down Expand Up @@ -62,6 +62,15 @@ genesis-generator {
# Address: 3FVp6fUSmVehs7Q3uJBXe5kWtpz57f7azzA
{ seed-text = "devnet dao", nonce = 0, amount = 100000000 }

# Chain registry
# Seed text: devnet registry
# Nonce: 0
# Seed: 3oujRTe9jVvnMHcKe9vGG
# Private key: 3kqzGQiNy5qTKEGDzCxh6Kk8LLXW5Lh22pLmZvFNqDm1
# Public key: HRHrDistEQkJJ7dBxBBzxfSsbN8oEF8xt3FeUHLNBYfX
# Address: 3FibjK5ZAXFfJf9J3gDsheia88Vz2itLPu1
{ seed-text = "devnet registry", nonce = 0, amount = 100000000 }

# Chain contract
# Seed text: devnet cc
# Nonce: 0
Expand Down
7 changes: 6 additions & 1 deletion local-network/configs/wavesnode/waves.conf
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,17 @@ waves {
dao-address = "3FVGizGxjdi8m4e8WKjbwtzg4rdiiUGkLCu"
xtn-buyback-address = "3FZQMZsyypqDUk2r5katP1AUChe7Uzc9dC4"
xtn-buyback-reward-period = 20
units-registry-address = "3FibjK5ZAXFfJf9J3gDsheia88Vz2itLPu1"
}

rewards {
term = 100000
term-after-capped-reward-feature = 5
initial = 600000000
min-increment = 50000000
voting-interval = 1
}

include "genesis.conf"
include "/etc/it/genesis.conf"
}
Expand All @@ -59,7 +62,7 @@ waves {
micro-block-interval = 200ms
max-transactions-in-micro-block = 500
min-micro-block-age = 0ms
quorum = 0
quorum = 1 # Solves a simultaneous mining on start
interval-after-last-block-then-generation-is-allowed = 120d
}

Expand Down Expand Up @@ -112,6 +115,8 @@ waves {
minimum-peers = 0
}

ntp-server = "47.91.111.180" # TODO: set to null after https://github.com/wavesplatform/Waves/pull/3978

# Nodes synchronization settings
synchronization {
# Timeout to receive all requested blocks
Expand Down
6 changes: 6 additions & 0 deletions local-network/deploy/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,14 @@
log.info(f"Wait for {min_peers} peers, now: {r}")
sleep(2)

log.info(f"Registry address: {network.cl_registry.oracleAddress}")
log.info(f"Chain contract address: {network.cl_chain_contract.oracleAddress}")

log.info("Approve chain contract on registry")
network.cl_registry.storeData(
f"unit_{network.cl_chain_contract.oracleAddress}_approved", "boolean", True
)

script_info = network.cl_chain_contract.oracleAcc.scriptInfo()
if script_info["script"] is None:
log.info("Set chain contract script")
Expand Down
6 changes: 5 additions & 1 deletion local-network/deploy/local/network.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ class Miner:

class ExtendedNetwork(Network):
@cached_property
def cl_dao(self) -> ChainContract:
def cl_dao(self) -> pw.Address:
return pw.Address(seed="devnet dao", nonce=0)

@cached_property
def cl_registry(self) -> pw.Oracle:
return pw.Oracle(seed="devnet registry")

@cached_property
def cl_chain_contract(self) -> ChainContract:
return ChainContract(seed="devnet cc", nonce=0)
Expand Down
7 changes: 0 additions & 7 deletions src/main/scala/units/ClientConfig.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package units
import com.wavesplatform.account.Address
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.settings.*
import net.ceedubs.ficus.Ficus.*
import net.ceedubs.ficus.readers.ArbitraryTypeReader.arbitraryTypeValueReader
import net.ceedubs.ficus.readers.{Generated, ValueReader}
import units.client.JsonRpcClient

import scala.concurrent.duration.FiniteDuration
Expand All @@ -29,7 +26,3 @@ case class ClientConfig(
apiRequestRetryWaitTime = apiRequestRetryWaitTime
)
}

object ClientConfig {
implicit val valueReader: Generated[ValueReader[ClientConfig]] = arbitraryTypeValueReader
}
13 changes: 10 additions & 3 deletions src/main/scala/units/ConsensusClient.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package units

import com.typesafe.config.Config
import com.typesafe.scalalogging.StrictLogging
import com.wavesplatform.block.{Block, MicroBlock}
import com.wavesplatform.common.state.ByteStr
import com.wavesplatform.common.utils.EitherExt2
import com.wavesplatform.events.BlockchainUpdateTriggers
import com.wavesplatform.extensions.{Extension, Context as ExtensionContext}
import com.wavesplatform.state.{Blockchain, StateSnapshot}
import io.netty.channel.group.DefaultChannelGroup
import monix.execution.{CancelableFuture, Scheduler}
import net.ceedubs.ficus.Ficus.*
import pureconfig.ConfigSource
import pureconfig.generic.auto.*
import units.ConsensusClient.ChainHandler
import units.client.engine.EngineApiClient
import units.network.*
Expand All @@ -28,15 +31,18 @@ class ConsensusClient(context: ExtensionContext) extends StrictLogging with Exte
private val chainHandlers: Seq[ChainHandler] = {
val defaultConfig = context.settings.config.getConfig("units.defaults")

def load(cfg: Config): ClientConfig =
ConfigSource.fromConfig(cfg.withFallback(defaultConfig).resolve()).loadOrThrow[ClientConfig]

val legacyChainConfig =
Try(context.settings.config.getConfig("waves.l2")).toOption.map(_.withFallback(defaultConfig).as[ClientConfig]).tapEach { _ =>
Try(context.settings.config.getConfig("waves.l2")).toOption.map(load).tapEach { _ =>
logger.info("Consensus client settings at waves.l2 path have been deprecated, please update your config file")
}

val newChainConfigs = context.settings.config
.getConfigList("units.chains")
.asScala
.map(cfg => cfg.withFallback(defaultConfig).resolve().as[ClientConfig])
.map(load)

val allChainConfigs = legacyChainConfig ++ newChainConfigs

Expand Down Expand Up @@ -93,6 +99,7 @@ object ConsensusClient {
config,
context.time,
context.wallet,
context.settings.blockchainSettings.functionalitySettings.unitsRegistryAddressParsed.explicitGet(),
blockObserver.loadBlock,
context.broadcastTransaction,
eluScheduler,
Expand Down
57 changes: 32 additions & 25 deletions src/main/scala/units/ELUpdater.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import com.wavesplatform.crypto
import com.wavesplatform.lang.ValidationError
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}
import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError
import com.wavesplatform.state.{Blockchain, BooleanDataEntry}
import com.wavesplatform.transaction.TxValidationError.InvokeRejectError
import com.wavesplatform.transaction.smart.InvokeScriptTransaction
import com.wavesplatform.transaction.smart.script.trace.TracedResult
Expand Down Expand Up @@ -50,6 +50,7 @@ class ELUpdater(
config: ClientConfig,
time: Time,
wallet: Wallet,
registryAddress: Option[Address],
requestBlockFromPeers: BlockHash => CancelableFuture[BlockWithChannel],
broadcastTx: Transaction => TracedResult[ValidationError, Boolean],
scheduler: Scheduler,
Expand Down Expand Up @@ -147,23 +148,6 @@ class ELUpdater(
}
}

private def cleanPriorityPool(): Unit = {
// A transaction moves to priority pool when a new key block references one of the previous micro blocks.
// When we add a new fresh transaction (extendMainChain) to UTX, it is validated against a stale transaction changes.
// 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
case _ => false
}

if (staleTxs.nonEmpty) {
logger.debug(s"Removing stale transactions: ${staleTxs.map(_.id()).mkString(", ")}")
utx.removeAll(staleTxs)
}
}
}

private def callContract(fc: FUNCTION_CALL, blockData: EcBlock, invoker: KeyPair): JobResult[Unit] = {
val extraFee = if (blockchain.hasPaidVerifier(invoker.toAddress)) ScriptExtraFee else 0

Expand All @@ -180,7 +164,6 @@ class ELUpdater(
blockchain.settings.addressSchemeCharacter.toByte
).signWith(invoker.privateKey)
logger.info(s"Invoking $contractAddress '${fc.function.funcName}' for block ${blockData.hash}->${blockData.parentHash}, txId=${tx.id()}")
cleanPriorityPool()

broadcastTx(tx).resultE match {
case Right(true) => Either.unit
Expand Down Expand Up @@ -250,7 +233,7 @@ class ELUpdater(
)
}
case Working(_, _, _, _, _, _: Mining | _: FollowingChain, _, _) =>
// a new epoch started and we trying to apply a previous epoch payload:
// a new epoch started, and we're trying to apply a previous epoch payload:
// Mining - we mine again
// FollowingChain - we validate
case other => logger.debug(s"Unexpected state $other attempting to finish building $payloadId")
Expand Down Expand Up @@ -485,10 +468,32 @@ class ELUpdater(
}

private def handleConsensusLayerChanged(): Unit = {
state match {
case Starting => updateStartingState()
case w: Working[ChainStatus] => updateWorkingState(w)
case other => logger.debug(s"Unprocessed state: $other")
def stopMining(): Unit = setState("26", Starting)

isChainEnabled match {
case Left(e) =>
logger.warn(s"$contractAddress chain is disabled: $e")
stopMining()

case Right(false) =>
logger.warn(s"$contractAddress chain is disabled")
stopMining()

case Right(true) =>
state match {
case Starting => updateStartingState()
case w: Working[ChainStatus] => updateWorkingState(w)
case other => logger.debug(s"Unprocessed state: $other")
}
}
}

private def isChainEnabled: Either[String, Boolean] = registryAddress.fold(true.asRight[String]) { registryAddress =>
val key = registryKey(contractAddress)
blockchain.accountData(registryAddress, key) match {
case Some(BooleanDataEntry(_, isEnabled)) => isEnabled.asRight
case None => false.asRight
case Some(x) => s"Expected '$key' to be a boolean, got: $x".asLeft
}
}

Expand Down Expand Up @@ -1572,7 +1577,7 @@ object ELUpdater {

case class ChainSwitchInfo(prevChainId: Long, referenceBlock: ContractBlock)

/** We haven't received a EC-block {@link missedBlock} of a previous epoch when started a mining on a new epoch. We can return to the main chain, if
/** We haven't received an EC-block [[missedBlock]] of a previous epoch when started a mining on a new epoch. We can return to the main chain, if we
* get a missed EC-block.
*/
case class ReturnToMainChainInfo(missedBlock: ContractBlock, missedBlockParent: EcBlock, chainId: Long)
Expand All @@ -1599,4 +1604,6 @@ object ELUpdater {
val msg = hitSource.arr ++ HexBytesConverter.toBytes(parentHash)
HexBytesConverter.toHex(crypto.secureHash(msg))
}

def registryKey(chainContract: Address): String = s"unit_${chainContract}_approved"
}
7 changes: 3 additions & 4 deletions src/main/scala/units/client/JsonRpcClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@ package units.client

import cats.Id
import cats.syntax.either.*
import net.ceedubs.ficus.Ficus.*
import net.ceedubs.ficus.readers.ArbitraryTypeReader.arbitraryTypeValueReader
import net.ceedubs.ficus.readers.{Generated, ValueReader}
import play.api.libs.json.{JsError, JsValue, Reads, Writes}
import pureconfig.*
import pureconfig.generic.semiauto.*
import sttp.client3.*
import sttp.client3.playJson.*
import units.ClientError
Expand Down Expand Up @@ -79,7 +78,7 @@ object JsonRpcClient {
}

object Config {
implicit val configValueReader: Generated[ValueReader[Config]] = arbitraryTypeValueReader
implicit val configValueReader: ConfigReader[Config] = deriveReader
}

def newRequestId: Int = ThreadLocalRandom.current().nextInt(10000, 100000)
Expand Down
Loading
Loading