Skip to content

Commit c2c9606

Browse files
committed
Store block delay on chain contract
1 parent 9b64d2e commit c2c9606

File tree

14 files changed

+100
-97
lines changed

14 files changed

+100
-97
lines changed

consensus-client-it/src/test/scala/units/BaseBlockValidationSuite.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ trait BaseBlockValidationSuite extends BaseDockerTestSuite {
6666
if (elParentBlock.height - 1 <= EthereumConstants.GenesisBlockHeight) Right(-1L)
6767
else ec1.engineApi.getLastWithdrawalIndex(elParentBlock.parentHash)
6868
}).explicitGet()
69-
Withdrawal(elWithdrawalIndexBefore + 1, elParentBlock.minerRewardL2Address, chainContractOptions.miningReward)
69+
Withdrawal(elWithdrawalIndexBefore + 1, elParentBlock.minerRewardL2Address, chainContractOptions.miningReward.newValue)
7070
}
7171

7272
protected final def mkSimulatedBlock(

consensus-client-it/src/test/scala/units/BaseDockerTestSuite.scala

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,7 @@ trait BaseDockerTestSuite
9494
step("Setup chain contract")
9595
val genesisBlock = ec1.engineApi.getBlockByNumber(BlockNumber.Number(0)).explicitGet().getOrElse(fail("No EL genesis block"))
9696
waves1.api.broadcastAndWait(
97-
ChainContract.setup(
98-
genesisBlock = genesisBlock,
99-
elMinerReward = rewardAmount.amount.longValue(),
100-
daoAddress = None,
101-
daoReward = 0,
102-
invoker = chainContractAccount
103-
)
97+
ChainContract.setup(genesisBlock, rewardAmount.amount.longValue(), None, 0, 2, invoker = chainContractAccount)
10498
)
10599
log.info(s"Native token id: ${chainContract.nativeTokenId}")
106100

contracts/waves/src/main.ride

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1082,7 +1082,7 @@ func enableTokenTransfers(standardBridgeAddress: String, wavesERC20AddressHex: S
10821082

10831083
# genesisBlockHashHex without 0x
10841084
@Callable(i)
1085-
func setup(genesisBlockHashHex: String, minerRewardInGwei: Int, daoAddress: String, daoReward: Int) = {
1085+
func setup(genesisBlockHashHex: String, minerRewardInGwei: Int, daoAddress: String, daoReward: Int, blockDelayInSeconds: Int) = {
10861086
if (isContractSetup()) then throw("The contract has been already set up")
10871087
else if (i.originCaller != this) then throw("Only owner of chain contract can do this")
10881088
else if (minerRewardInGwei < 0) then throw("The miner reward must be nonnegative")
@@ -1122,7 +1122,8 @@ func setup(genesisBlockHashHex: String, minerRewardInGwei: Int, daoAddress: Stri
11221122
StringEntry(finalizedBlockKey, genesisBlockHashHex),
11231123
issue,
11241124
StringEntry(tokenIdKey, tokenId.toBase58String()),
1125-
StringEntry(elNativeBridgeAddressKey, "0x0000000000000000000000000000000000006a7e")
1125+
StringEntry(elNativeBridgeAddressKey, "0x0000000000000000000000000000000000006a7e"),
1126+
IntegerEntry("blockDelay", blockDelayInSeconds)
11261127
] ++ daoEntries
11271128
}
11281129
}

src/main/scala/units/ClientConfig.scala

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@ case class ClientConfig(
1414
executionClientAddress: String,
1515
apiRequestRetries: Int,
1616
apiRequestRetryWaitTime: FiniteDuration,
17-
blockDelay: FiniteDuration,
1817
firstBlockMinDelay: FiniteDuration,
1918
blockSyncRequestTimeout: FiniteDuration,
2019
network: NetworkSettings,

src/main/scala/units/ELUpdater.scala

Lines changed: 30 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import com.wavesplatform.network.ChannelGroupExt
1212
import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit, ScriptExtraFee}
1313
import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError
1414
import com.wavesplatform.state.{Blockchain, BooleanDataEntry}
15+
import com.wavesplatform.transaction.*
1516
import com.wavesplatform.transaction.TxValidationError.InvokeRejectError
1617
import com.wavesplatform.transaction.smart.InvokeScriptTransaction
1718
import com.wavesplatform.transaction.smart.script.trace.TracedResult
18-
import com.wavesplatform.transaction.*
1919
import com.wavesplatform.utils.{Time, UnsupportedFeature, forceStopApplication}
2020
import com.wavesplatform.wallet.Wallet
2121
import io.netty.channel.Channel
@@ -189,7 +189,7 @@ class ELUpdater(
189189
val startC2ETransferIndex = lastC2ETransferIndex + 1
190190

191191
val rewardWithdrawal = prevEpochMinerRewardAddress
192-
.map(Withdrawal(startElWithdrawalIndex, _, chainContractOptions.miningReward))
192+
.map(Withdrawal(startElWithdrawalIndex, _, chainContractOptions.miningReward.valueAtEpoch(epochInfo.number)))
193193
.toVector
194194

195195
val strictC2ETransfersActivated = epochInfo.number >= chainContractClient.getStrictC2ETransfersActivationEpoch
@@ -301,7 +301,7 @@ class ELUpdater(
301301
lastEcBlock <- engineApiClient.getLastExecutionBlock()
302302
willSimulateBlock = lastEcBlock.hash != parentBlock.hash
303303
currentUnixTs = time.correctedTime() / 1000
304-
nextBlockUnixTs = (parentBlock.timestamp + config.blockDelay.toSeconds).max(
304+
nextBlockUnixTs = (parentBlock.timestamp + prevState.options.blockDelayInSeconds.valueAtEpoch(epochInfo.number)).max(
305305
currentUnixTs +
306306
// We don't collect transactions for simulated payload, thus we don't need to wait for firstBlockMinDelay
307307
(if (willSimulateBlock) 0 else config.firstBlockMinDelay.toSeconds)
@@ -390,8 +390,9 @@ class ELUpdater(
390390
getAndApplyPayloadResult match {
391391
case Left(err) => logger.error(s"Failed to forge block at epoch ${epochInfo.number}: ${err.message}")
392392
case Right(networkBlock) =>
393-
val ecBlock = networkBlock.toEcBlock
394-
val nextBlockUnixTs = (ecBlock.timestamp + config.blockDelay.toSeconds).max(time.correctedTime() / 1000)
393+
val ecBlock = networkBlock.toEcBlock
394+
val nextBlockUnixTs =
395+
(ecBlock.timestamp + chainContractOptions.blockDelayInSeconds.valueAtEpoch(epochInfo.number)).max(time.correctedTime() / 1000)
395396
val nextMiningDataE = updateHeadAndStartBuildingPayload(
396397
epochInfo,
397398
ecBlock,
@@ -1004,28 +1005,23 @@ class ELUpdater(
10041005
}
10051006

10061007
// Of a current epoch miner
1007-
private def validateBlockSignature(block: NetworkL2Block, epochInfo: Option[EpochInfo]): JobResult[Unit] = {
1008-
epochInfo match {
1009-
case Some(epochMeta) =>
1010-
for {
1011-
_ <- Either.raiseUnless(block.minerRewardL2Address == epochMeta.rewardAddress) {
1012-
ClientError(s"block miner ${block.minerRewardL2Address} doesn't equal to ${epochMeta.rewardAddress}")
1013-
}
1014-
signature <- Either.fromOption(block.signature, ClientError(s"signature not found"))
1015-
publicKey <- Either.fromOption(
1016-
chainContractClient.getMinerPublicKey(block.minerRewardL2Address),
1017-
ClientError(s"public key for block miner ${block.minerRewardL2Address} not found")
1018-
)
1019-
_ <- Either.raiseUnless(crypto.verify(signature, Json.toBytes(block.payload), publicKey, checkWeakPk = true)) {
1020-
ClientError(s"invalid signature")
1021-
}
1022-
} yield ()
1023-
case _ => Either.unit
1024-
}
1025-
}
1008+
private def validateBlockSignature(block: NetworkL2Block, epochInfo: EpochInfo): JobResult[Unit] =
1009+
for {
1010+
_ <- Either.raiseUnless(block.minerRewardL2Address == epochInfo.rewardAddress) {
1011+
ClientError(s"block miner ${block.minerRewardL2Address} doesn't equal to ${epochInfo.rewardAddress}")
1012+
}
1013+
signature <- Either.fromOption(block.signature, ClientError(s"signature not found"))
1014+
publicKey <- Either.fromOption(
1015+
chainContractClient.getMinerPublicKey(block.minerRewardL2Address),
1016+
ClientError(s"public key for block miner ${block.minerRewardL2Address} not found")
1017+
)
1018+
_ <- Either.raiseUnless(crypto.verify(signature, Json.toBytes(block.payload), publicKey, checkWeakPk = true)) {
1019+
ClientError(s"invalid signature")
1020+
}
1021+
} yield ()
10261022

1027-
private def validateTimestamp(newNetworkBlock: NetworkL2Block, parentEcBlock: EcBlock): JobResult[Unit] = {
1028-
val minAppendTs = parentEcBlock.timestamp + config.blockDelay.toSeconds
1023+
private def validateTimestamp(newNetworkBlock: NetworkL2Block, parentEcBlock: EcBlock, epoch: Int): JobResult[Unit] = {
1024+
val minAppendTs = parentEcBlock.timestamp + chainContractClient.getOptions.blockDelayInSeconds.valueAtEpoch(epoch)
10291025
Either.raiseUnless(newNetworkBlock.timestamp >= minAppendTs) {
10301026
ClientError(
10311027
s"timestamp (${newNetworkBlock.timestamp}) of appended block must be greater or equal $minAppendTs, " +
@@ -1034,11 +1030,6 @@ class ELUpdater(
10341030
}
10351031
}
10361032

1037-
private def preValidateBlock(networkBlock: NetworkL2Block, parentBlock: EcBlock, epochInfo: Option[EpochInfo]): JobResult[Unit] = for {
1038-
_ <- validateTimestamp(networkBlock, parentBlock)
1039-
_ <- validateBlockSignature(networkBlock, epochInfo)
1040-
} yield ()
1041-
10421033
private def getAltChainReferenceBlock(nodeChainInfo: ChainInfo, lastContractBlock: ContractBlock): JobResult[ContractBlock] = {
10431034
if (nodeChainInfo.isMain) {
10441035
for {
@@ -1104,9 +1095,8 @@ class ELUpdater(
11041095
processInvalidBlock(contractBlock, prevState, Some(nodeChainInfo))
11051096
}
11061097
case contractBlock =>
1107-
// We should check block signature based on epochInfo if block is not at contract yet
1108-
val epochInfo = Option.when(contractBlock.isEmpty)(prevState.epochInfo)
1109-
applyBlock(networkBlock, parentBlock, epochInfo) match {
1098+
(if (contractBlock.isEmpty) validateBlockSignature(networkBlock, prevState.epochInfo) else ().asRight[ClientError])
1099+
.flatMap(_ => applyBlock(networkBlock, parentBlock, prevState.epochInfo)) match {
11101100
case Right(_) =>
11111101
logger.debug(s"Block ${networkBlock.hash} successfully applied")
11121102
broadcastAndConfirmBlock(networkBlock, ch, prevState, nodeChainInfo, returnToMainChainInfo)
@@ -1577,13 +1567,13 @@ class ELUpdater(
15771567
): JobResult[Working[ChainStatus]] = {
15781568
logger.trace(s"Trying to apply and do a full validation of block ${networkBlock.hash}")
15791569
for {
1580-
_ <- applyBlock(networkBlock, parentBlock, epochInfo = None) // epochInfo is empty, because we don't need to validate a block signature
1570+
_ <- applyBlock(networkBlock, parentBlock, prevState.epochInfo)
15811571
updatedState <- validateAppliedBlock(contractBlock, networkBlock.toEcBlock, prevState)
15821572
} yield updatedState
15831573
}
15841574

1585-
private def applyBlock(networkBlock: NetworkL2Block, parentBlock: EcBlock, epochInfo: Option[EpochInfo]): JobResult[Unit] = for {
1586-
_ <- preValidateBlock(networkBlock, parentBlock, epochInfo)
1575+
private def applyBlock(networkBlock: NetworkL2Block, parentBlock: EcBlock, epochInfo: EpochInfo): JobResult[Unit] = for {
1576+
_ <- validateTimestamp(networkBlock, parentBlock, epochInfo.number)
15871577
_ <- engineApiClient.newPayload(networkBlock.payload)
15881578
} yield ()
15891579

@@ -1611,7 +1601,9 @@ class ELUpdater(
16111601
_ <- validateE2CTransfers(contractBlock, ecBlockLogs)
16121602
_ <- validateAssetRegistryUpdate(ecBlockLogs, contractBlock, parentContractBlock, prevState.options)
16131603
_ <- validateRandao(ecBlock, contractBlock.epoch)
1614-
miningReward = getMinerRewardAddress(contractBlock, parentContractBlock).map(MiningReward(_, prevState.options.miningReward))
1604+
miningReward = getMinerRewardAddress(contractBlock, parentContractBlock).map(
1605+
MiningReward(_, prevState.options.miningReward.valueAtEpoch(contractBlock.epoch))
1606+
)
16151607
updatedLastElWithdrawalIndex <- validateC2E(
16161608
contractBlock,
16171609
ecBlock,

src/main/scala/units/client/contract/ChainContractClient.scala

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -212,17 +212,34 @@ trait ChainContractClient {
212212
.orElse(getBinaryData(s"miner${rewardAddress.hexNoPrefix}PK"))
213213
.map(PublicKey(_))
214214

215-
def getOptions: ChainContractOptions = ChainContractOptions(
216-
miningReward = getLongData("minerReward")
217-
.map(Gwei.ofRawGwei)
218-
.getOrElse(throw new IllegalStateException("minerReward is empty on contract")),
219-
elNativeBridgeAddress = getStringData("elBridgeAddress")
220-
.map(EthAddress.unsafeFrom)
221-
.getOrElse(throw new IllegalStateException("elBridgeAddress is empty on contract")),
222-
elStandardBridgeAddress = getStringData("elStandardBridgeAddress")
223-
.map(EthAddress.unsafeFrom),
224-
assetTransfersActivationEpoch = getAssetTransfersActivationEpoch
225-
)
215+
def getOptions: ChainContractOptions = {
216+
val minerReward = extractData("minerReward") match {
217+
case Some(IntegerDataEntry(value = reward)) => ValueAtEpoch(Gwei.ofRawGwei(0), Gwei.ofRawGwei(reward), 1)
218+
case Some(StringDataEntry(value = rewards)) =>
219+
val Array(oldReward, newReward, changeEpoch) = rewards.split(Sep)
220+
ValueAtEpoch(Gwei.ofRawGwei(oldReward.toLong), Gwei.ofRawGwei(newReward.toLong), changeEpoch.toInt)
221+
case _ => throw new IllegalStateException("minerReward is empty on contract")
222+
}
223+
224+
val blockDelay = extractData("blockDelay") match {
225+
case Some(IntegerDataEntry(value = delay)) => ValueAtEpoch(0, BigInt(delay).bigInteger.intValueExact(), 1)
226+
case Some(StringDataEntry(value = delays)) =>
227+
val Array(oldDelay, newDelay, changeEpoch) = delays.split(Sep)
228+
ValueAtEpoch(oldDelay.toInt, newDelay.toInt, changeEpoch.toInt)
229+
case _ => throw new IllegalStateException("blockDelay is empty on contract")
230+
}
231+
232+
ChainContractOptions(
233+
minerReward,
234+
getStringData("elBridgeAddress")
235+
.map(EthAddress.unsafeFrom)
236+
.getOrElse(throw new IllegalStateException("elBridgeAddress is empty on contract")),
237+
getStringData("elStandardBridgeAddress")
238+
.map(EthAddress.unsafeFrom),
239+
getAssetTransfersActivationEpoch,
240+
blockDelay
241+
)
242+
}
226243

227244
private def getAssetTransfersActivationEpoch: Long = getLongData("assetTransfersActivationEpoch").getOrElse(Long.MaxValue)
228245

src/main/scala/units/client/contract/ChainContractOptions.scala

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,12 @@ import units.BlockHash
55
import units.client.contract.ContractFunction.*
66
import units.eth.{EthAddress, Gwei}
77

8-
/** @note
9-
* Make sure you have an activation gap: a new feature should not be activated suddenly during nearest blocks.
10-
*/
118
case class ChainContractOptions(
12-
miningReward: Gwei,
9+
miningReward: ValueAtEpoch[Gwei],
1310
elNativeBridgeAddress: EthAddress,
1411
elStandardBridgeAddress: Option[EthAddress],
15-
assetTransfersActivationEpoch: Long
12+
assetTransfersActivationEpoch: Long,
13+
blockDelayInSeconds: ValueAtEpoch[Int]
1614
) {
1715
def bridgeAddresses(epoch: Int): List[EthAddress] = {
1816
val before = List(elNativeBridgeAddress)
@@ -35,3 +33,7 @@ case class ChainContractOptions(
3533

3634
private def versionOf(epoch: Int): Int = if (epoch < assetTransfersActivationEpoch) 1 else 2
3735
}
36+
37+
case class ValueAtEpoch[A](oldValue: A, newValue: A, changeAtEpoch: Int) {
38+
def valueAtEpoch(epoch: Int): A = if epoch < changeAtEpoch then oldValue else newValue
39+
}

src/main/scala/units/eth/Gwei.scala

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,14 @@ import play.api.libs.json.Format
55

66
import java.math.BigInteger
77

8-
class Gwei private (val amount: BigInteger) {
9-
def toHex: String = Numeric.toHexStringWithPrefix(amount)
10-
11-
override def hashCode(): Int = amount.hashCode()
12-
override def equals(that: Any): Boolean = that match {
13-
case that: Gwei => this.amount.compareTo(that.amount) == 0
14-
case _ => false
15-
}
16-
override def toString: String = s"${amount.toString} Gwei"
17-
}
8+
opaque type Gwei = BigInteger
189

1910
object Gwei {
20-
implicit val gweiFormat: Format[Gwei] = implicitly[Format[String]].bimap(ofRawGwei, _.toHex)
11+
implicit val gweiFormat: Format[Gwei] = implicitly[Format[String]].bimap(ofRawGwei, Numeric.toHexStringWithPrefix)
12+
13+
extension (g: Gwei) def amount: BigInteger = g
2114

2215
def ofRawGwei(x: Long): Gwei = ofRawGwei(BigInteger.valueOf(x))
23-
def ofRawGwei(x: BigInteger): Gwei = new Gwei(x)
16+
def ofRawGwei(x: BigInteger): Gwei = x
2417
def ofRawGwei(hex: String): Gwei = Gwei.ofRawGwei(Numeric.toBigInt(hex))
2518
}

src/test/scala/units/BaseTestSuite.scala

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import units.util.HexBytesConverter
2323

2424
import java.nio.charset.StandardCharsets
2525
import java.util.concurrent.ThreadLocalRandom
26+
import scala.concurrent.duration.DurationInt
2627

2728
trait BaseTestSuite
2829
extends AnyFreeSpec
@@ -49,7 +50,8 @@ trait BaseTestSuite
4950
d.ecGenesisBlock,
5051
ElMinerDefaultReward.amount.longValue(),
5152
defaultSettings.daoRewardAccount.map(_.toAddress),
52-
defaultSettings.daoRewardAmount
53+
defaultSettings.daoRewardAmount,
54+
defaultSettings.blockDelayInSeconds
5355
),
5456
if (settings.registerWwavesToken)
5557
d.ChainContract.enableTokenTransfersWithWaves(
@@ -82,14 +84,15 @@ trait BaseTestSuite
8284

8385
try {
8486
d = new ExtensionDomain(
85-
rdb = new RDB(rdb.db, rdb.txMetaHandle, rdb.txHandle, rdb.txSnapshotHandle, rdb.apiHandle, Seq.empty),
86-
blockchainUpdater = bcu,
87-
rocksDBWriter = blockchain,
88-
settings = settings.wavesSettings,
89-
chainRegistryAccount = chainRegistryAccount,
90-
nativeBridgeAddress = NativeBridgeAddress,
91-
standardBridgeAddress = StandardBridgeAddress,
92-
elMinerDefaultReward = ElMinerDefaultReward
87+
new RDB(rdb.db, rdb.txMetaHandle, rdb.txHandle, rdb.txSnapshotHandle, rdb.apiHandle, Seq.empty),
88+
bcu,
89+
blockchain,
90+
settings.wavesSettings,
91+
chainRegistryAccount,
92+
NativeBridgeAddress,
93+
StandardBridgeAddress,
94+
ElMinerDefaultReward,
95+
defaultSettings.blockDelayInSeconds.seconds
9396
)
9497

9598
d.wallet.generateNewAccounts(2) // Enough for now

src/test/scala/units/ClientConfigTestSuite.scala

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,6 @@ class ClientConfigTestSuite extends FlatSpec {
6060
| api-request-retries = 2
6161
| api-request-retry-wait-time = 2s
6262
|
63-
| block-delay = 6s
6463
| first-block-min-delay = 1s
6564
| block-sync-request-timeout = 500ms
6665
|
@@ -82,7 +81,6 @@ class ClientConfigTestSuite extends FlatSpec {
8281
clientConfig.executionClientAddress shouldBe "http://ec-1:8551"
8382
clientConfig.apiRequestRetries shouldBe 2
8483
clientConfig.apiRequestRetryWaitTime shouldBe 2.seconds
85-
clientConfig.blockDelay shouldBe 6.seconds
8684
clientConfig.firstBlockMinDelay shouldBe 1.second
8785
clientConfig.blockSyncRequestTimeout shouldBe 500.millis
8886
clientConfig.miningEnable shouldBe true
@@ -124,7 +122,6 @@ class ClientConfigTestSuite extends FlatSpec {
124122
clientConfig.executionClientAddress shouldBe "http://ec-1:8551"
125123
clientConfig.apiRequestRetries shouldBe 2
126124
clientConfig.apiRequestRetryWaitTime shouldBe 2.seconds
127-
clientConfig.blockDelay shouldBe 6.seconds
128125
clientConfig.blockSyncRequestTimeout shouldBe 500.millis
129126
clientConfig.miningEnable shouldBe true
130127
clientConfig.privateKeys shouldBe Seq.empty

0 commit comments

Comments
 (0)