Skip to content

Commit

Permalink
Merge pull request #1037 from HorizenOfficial/as/EON_1857
Browse files Browse the repository at this point in the history
Fix for issue EON-1857
  • Loading branch information
paolocappelletti authored May 30, 2024
2 parents 5a88a9a + bd542ec commit 656ffca
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ interface ForgerStakesV2 {
Updates an existing forger.
A forger can be updated just once and only if rewardAddress == 0x000..00 and rewardShare == 0.
See above the registerForger command for the parameters meaning.
Note: 2 epochs should be gone by after the activation of the EON 1.4 fork
*/
function updateForger(bytes32 signPubKey, bytes32 vrf1, bytes1 vrf2, uint32 rewardShare,
address rewardAddress, bytes32 sign1_1, bytes32 sign1_2,
Expand Down
17 changes: 17 additions & 0 deletions qa/sc_evm_forger_v2_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,25 @@ def run_test(self):
reward_share_updated = 1
reward_address_updated = "0x1111111122222222333333334444444455555555"

# switch to the next epoch
generate_next_block(sc_node_1, "first node", force_switch_to_next_epoch=True)
self.sc_sync_all()

# negative tests
# ==============================================================================================================
# - try updating a forger before 2 epochs pass by after the fork activation
res = ac_updateForger(sc_node_1, block_sign_pub_key_1_2, vrf_pub_key_1_2,
reward_address=reward_address_updated, reward_share=reward_share_updated)
self.sc_sync_all()

assert_true('error' in res)
assert_equal('0204', res['error']['code'])
assert_true(' 2 epochs must go by before invoking this command' in res['error']['description'])

# switch to the next epoch for the second time, now it is ok invoking update forger cmd
generate_next_block(sc_node_1, "first node", force_switch_to_next_epoch=True)
self.sc_sync_all()

# - try updating a forger that does not exist
result = ac_updateForger(sc_node_1, block_sign_pub_key_genesis, vrf_pub_key_1_2,
reward_address=reward_address_updated, reward_share=reward_share_updated)
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/main/resources/account/api/accountApi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1449,7 +1449,7 @@ paths:
tags:
- transaction
summary: Update an existing forger
description: Update an existing forger. This action can be performed only for forgers with rewardShare = 0 and rewardAddress not set, and only to assign them a value.
description: Update an existing forger. This action can be performed only for forgers with rewardShare = 0 and rewardAddress not set, and only to assign them a value. Moreover, in order to execute this command, at least 2 epoch must be gone by after the EON 1.4 fork activation.
operationId: updateForger
requestBody:
content:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import io.horizen.account.node.{AccountNodeView, NodeAccountHistory, NodeAccount
import io.horizen.account.proof.SignatureSecp256k1
import io.horizen.account.proposition.AddressProposition
import io.horizen.account.secret.PrivateKeySecp256k1
import io.horizen.account.state.ForgerStakeV2MsgProcessor.{MAX_REWARD_SHARE, MIN_REGISTER_FORGER_STAKED_AMOUNT_IN_WEI}
import io.horizen.account.state.ForgerStakeV2MsgProcessor.{MAX_REWARD_SHARE, MIN_REGISTER_FORGER_STAKED_AMOUNT_IN_WEI, NUM_OF_EPOCHS_AFTER_FORK_ACTIVATION_FOR_UPDATE_FORGER}
import io.horizen.account.state.McAddrOwnershipMsgProcessor._
import io.horizen.account.state._
import io.horizen.account.state.nativescdata.forgerstakev2.RegisterOrUpdateForgerCmdInputDecoder.NULL_ADDRESS_WITH_PREFIX_HEX_STRING
Expand Down Expand Up @@ -666,66 +666,71 @@ case class AccountTransactionApiRoute(override val settings: RESTApiSettings,

val accountState = sidechainNodeView.getNodeState
val epochNumber = accountState.getConsensusEpochNumber.getOrElse(0)

if (!Version1_4_0Fork.get(epochNumber).active) {
ApiResponseUtil.toResponse(GenericTransactionError(s"Fork 1.4 is not active, can not invoke this command",
JOptional.empty()))
}
else if (!accountState.forgerStakesV2IsActive) {
ApiResponseUtil.toResponse(GenericTransactionError(s"Forger Stake Storage V2 is not active, can not invoke this command",
JOptional.empty()))
}
else if (operation == ForgerStakeV2MsgProcessor.UpdateForgerCmd &&
epochNumber < (Version1_4_0Fork.getActivationEpoch() + NUM_OF_EPOCHS_AFTER_FORK_ACTIVATION_FOR_UPDATE_FORGER) ) {
ApiResponseUtil.toResponse(GenericTransactionError(s"Fork 1.4 has been activated at epoch ${Version1_4_0Fork.getActivationEpoch()}, but $NUM_OF_EPOCHS_AFTER_FORK_ACTIVATION_FOR_UPDATE_FORGER epochs must go by before invoking this command (current epoch: $epochNumber)",
JOptional.empty()))
} else {
// default gas related params
val baseFee = sidechainNodeView.getNodeState.getNextBaseFee
var maxPriorityFeePerGas = BigInteger.valueOf(120)
var maxFeePerGas = BigInteger.TWO.multiply(baseFee).add(maxPriorityFeePerGas)
var gasLimit = BigInteger.valueOf(500000)

if (body.gasInfo.isDefined) {
maxFeePerGas = body.gasInfo.get.maxFeePerGas
maxPriorityFeePerGas = body.gasInfo.get.maxPriorityFeePerGas
gasLimit = body.gasInfo.get.gasLimit
}
// default gas related params
val baseFee = sidechainNodeView.getNodeState.getNextBaseFee
var maxPriorityFeePerGas = BigInteger.valueOf(120)
var maxFeePerGas = BigInteger.TWO.multiply(baseFee).add(maxPriorityFeePerGas)
var gasLimit = BigInteger.valueOf(500000)

if (body.gasInfo.isDefined) {
maxFeePerGas = body.gasInfo.get.maxFeePerGas
maxPriorityFeePerGas = body.gasInfo.get.maxPriorityFeePerGas
gasLimit = body.gasInfo.get.gasLimit
}

val txCost = valueInWei.add(maxFeePerGas.multiply(gasLimit))
val txCost = valueInWei.add(maxFeePerGas.multiply(gasLimit))

val secret = getFittingSecret(sidechainNodeView, None, txCost)
val secret = getFittingSecret(sidechainNodeView, None, txCost)

secret match {
case Some(secret) =>
val nonce = body.nonce.getOrElse(sidechainNodeView.getNodeState.getNonce(secret.publicImage.address))
secret match {
case Some(secret) =>
val nonce = body.nonce.getOrElse(sidechainNodeView.getNodeState.getNonce(secret.publicImage.address))

val blockSignPubKey = PublicKey25519PropositionSerializer.getSerializer.parseBytesAndCheck(BytesUtils.fromHexString(body.blockSignPubKey))
val vrfPubKey = VrfPublicKeySerializer.getSerializer.parseBytesAndCheck(BytesUtils.fromHexString(body.vrfPubKey))
val blockSignPubKey = PublicKey25519PropositionSerializer.getSerializer.parseBytesAndCheck(BytesUtils.fromHexString(body.blockSignPubKey))
val vrfPubKey = VrfPublicKeySerializer.getSerializer.parseBytesAndCheck(BytesUtils.fromHexString(body.vrfPubKey))

Try {
val msg = ForgerStakeV2MsgProcessor.getHashedMessageToSign(body.blockSignPubKey, body.vrfPubKey, rewardShare, rewardAddress)
val signatures = signMessageWithSecrets(sidechainNodeView, blockSignPubKey, vrfPubKey, msg)
Try {
val msg = ForgerStakeV2MsgProcessor.getHashedMessageToSign(body.blockSignPubKey, body.vrfPubKey, rewardShare, rewardAddress)
val signatures = signMessageWithSecrets(sidechainNodeView, blockSignPubKey, vrfPubKey, msg)

encodeRegisterOrUpdateForgerCmdRequest(operation, blockSignPubKey, vrfPubKey, rewardShare, new AddressProposition(hexStringToByteArray(rewardAddress)), signatures._1, signatures._2)
} match {
case Success(dataBytes) =>
encodeRegisterOrUpdateForgerCmdRequest(operation, blockSignPubKey, vrfPubKey, rewardShare, new AddressProposition(hexStringToByteArray(rewardAddress)), signatures._1, signatures._2)
} match {
case Success(dataBytes) =>

val tmpTx: EthereumTransaction = new EthereumTransaction(
params.chainId,
JOptional.of(new AddressProposition(FORGER_STAKE_V2_SMART_CONTRACT_ADDRESS)),
nonce,
gasLimit,
maxPriorityFeePerGas,
maxFeePerGas,
valueInWei,
dataBytes,
null
)
validateAndSendTransaction(signTransactionWithSecret(secret, tmpTx))
val tmpTx: EthereumTransaction = new EthereumTransaction(
params.chainId,
JOptional.of(new AddressProposition(FORGER_STAKE_V2_SMART_CONTRACT_ADDRESS)),
nonce,
gasLimit,
maxPriorityFeePerGas,
maxFeePerGas,
valueInWei,
dataBytes,
null
)
validateAndSendTransaction(signTransactionWithSecret(secret, tmpTx))

case Failure(exception) =>
ApiResponseUtil.toResponse(GenericTransactionError(s"Command $operation failed: ", JOptional.of(exception)))

case Failure(exception) =>
ApiResponseUtil.toResponse(GenericTransactionError(s"Command $operation failed: ", JOptional.of(exception)))
}

}
case None =>
ApiResponseUtil.toResponse(ErrorInsufficientBalance("No account with enough balance found", JOptional.empty()))

case None =>
ApiResponseUtil.toResponse(ErrorInsufficientBalance("No account with enough balance found", JOptional.empty()))
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,9 @@ object Version1_4_0Fork {
ForkManager.getOptionalSidechainFork[Version1_4_0Fork](epochNumber).getOrElse(DefaultFork)
}

def getActivationEpoch(): Int = {
ForkManager.getFirstActivationEpoch[Version1_4_0Fork]()
}

private val DefaultFork: Version1_4_0Fork = Version1_4_0Fork()
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ object ForgerStakeV2MsgProcessor extends NativeSmartContractWithFork with Forge

val MAX_REWARD_SHARE = 1000
val MIN_REGISTER_FORGER_STAKED_AMOUNT_IN_WEI: BigInteger = convertZenniesToWei(minForgerStake) // 10 Zen
val NUM_OF_EPOCHS_AFTER_FORK_ACTIVATION_FOR_UPDATE_FORGER: Int = 2

override val contractAddress: Address = FORGER_STAKE_V2_SMART_CONTRACT_ADDRESS
override val contractCode: Array[Byte] = Keccak256.hash("ForgerStakeV2SmartContractCode")
Expand All @@ -53,7 +54,7 @@ object ForgerStakeV2MsgProcessor extends NativeSmartContractWithFork with Forge
case RegisterForgerCmd =>
doRegisterForger(invocation, gasView, context)
case UpdateForgerCmd =>
doUpdateForger(invocation, gasView)
doUpdateForger(invocation, gasView, context)
case DelegateCmd =>
doDelegateCmd(invocation, gasView, context)
case WithdrawCmd =>
Expand Down Expand Up @@ -185,7 +186,7 @@ object ForgerStakeV2MsgProcessor extends NativeSmartContractWithFork with Forge
Array.emptyByteArray
}

def doUpdateForger(invocation: Invocation, gasView: BaseAccountStateView): Array[Byte] = {
def doUpdateForger(invocation: Invocation, gasView: BaseAccountStateView, context: ExecutionContext): Array[Byte] = {
requireIsNotPayable(invocation)
checkForgerStakesV2IsActive(gasView)

Expand Down
8 changes: 8 additions & 0 deletions sdk/src/main/scala/io/horizen/fork/ForkManager.scala
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,14 @@ object ForkManager {
findActiveFork(forksOfTypeT, consensusEpoch)
}

def getFirstActivationEpoch[T <: OptionalSidechainFork : Manifest](): Int = {
assertInitialized()
val forksOfTypeT = optionalSidechainForks.collect({ case (i, fork: T) => (i, fork) })
// head() throws an exception if the list is empty, which should not happen
val (activationEpoch, _) = forksOfTypeT.head
activationEpoch
}

def init(forkConfigurator: ForkConfigurator, networkName: String): Unit = {
if (initialized) throw new IllegalStateException("ForkManager is already initialized.")

Expand Down

0 comments on commit 656ffca

Please sign in to comment.