Skip to content

Commit

Permalink
Merge pull request #1051 from HorizenOfficial/as/stop_bt
Browse files Browse the repository at this point in the history
Added new fork for EON 1.5 disabling of backward transfers
  • Loading branch information
paolocappelletti authored Jul 5, 2024
2 parents b048948 + 98a15ca commit 9fda215
Show file tree
Hide file tree
Showing 22 changed files with 214 additions and 67 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

public class AppForkConfigurator extends ForkConfigurator {
@Override
public SidechainForkConsensusEpoch fork1activation() {
public SidechainForkConsensusEpoch forkActivation() {
return new SidechainForkConsensusEpoch(0, 0, 0);
}

Expand Down Expand Up @@ -77,6 +77,10 @@ public List<Pair<SidechainForkConsensusEpoch, OptionalSidechainFork>> getOptiona
new Pair<>(
new SidechainForkConsensusEpoch(80, 80, 80),
new Version1_4_0Fork(true)
),
new Pair<>(
new SidechainForkConsensusEpoch(90, 90, 90),
new Version1_5_0Fork(true)
)
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

public class AppForkConfiguratorAllEnabledFromEpoch2 extends ForkConfigurator {
@Override
public SidechainForkConsensusEpoch fork1activation() {
public SidechainForkConsensusEpoch forkActivation() {
return new SidechainForkConsensusEpoch(0, 0, 0);
}

Expand Down Expand Up @@ -60,6 +60,11 @@ public List<Pair<SidechainForkConsensusEpoch, OptionalSidechainFork>> getOptiona
new SidechainForkConsensusEpoch(2, 2, 2),
new Version1_4_0Fork(true)
)
// This is not included because this fork disables backward transfers
/* , new Pair<>(
new SidechainForkConsensusEpoch(2, 2, 2),
new Version1_5_0Fork(true)
)*/
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

public class AppForkConfigurator extends ForkConfigurator {
@Override
public SidechainForkConsensusEpoch fork1activation() {
public SidechainForkConsensusEpoch forkActivation() {
return new SidechainForkConsensusEpoch(3, 3, 3);
}
}
2 changes: 2 additions & 0 deletions qa/SidechainTestFramework/account/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ def convertZenniesToZen(valueInZennies):
VERSION_1_3_FORK_EPOCH = 70
# The activation epoch for features released in v1.4, as coded in the sdk
VERSION_1_4_FORK_EPOCH = 80
# The activation epoch for features released in v1.5, as coded in the sdk
VERSION_1_5_FORK_EPOCH = 90

# Block gas limit
BLOCK_GAS_LIMIT = 30000000
Expand Down
60 changes: 54 additions & 6 deletions qa/sc_evm_backward_transfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
import logging
import pprint
import time
from _decimal import Decimal

import base58
from _decimal import Decimal
from eth_abi import decode
from eth_utils import add_0x_prefix, encode_hex, event_signature_to_log_topic, remove_0x_prefix

Expand All @@ -18,7 +18,8 @@
from SidechainTestFramework.account.simple_proxy_contract import SimpleProxyContract
from SidechainTestFramework.account.utils import (computeForgedTxFee,
convertZenToZennies, convertZenniesToWei,
WITHDRAWAL_REQ_SMART_CONTRACT_ADDRESS, INTEROPERABILITY_FORK_EPOCH)
WITHDRAWAL_REQ_SMART_CONTRACT_ADDRESS, INTEROPERABILITY_FORK_EPOCH,
VERSION_1_5_FORK_EPOCH)
from SidechainTestFramework.sc_forging_util import check_mcreference_presence, check_mcreferencedata_presence
from SidechainTestFramework.scutil import (
generate_next_block, generate_next_blocks, EVM_APP_SLOT_TIME
Expand Down Expand Up @@ -60,6 +61,7 @@
and then to MC/SC blocks.
- verify epoch 1 certificate, verify backward transfers list
- interoperability tests
- Verify that after reaching fork 1.5 backward transfers are disabled, bith via http api and via native contract
"""


Expand Down Expand Up @@ -88,6 +90,18 @@ def __init__(self):
super().__init__(block_timestamp_rewind=1500 * EVM_APP_SLOT_TIME * INTEROPERABILITY_FORK_EPOCH,
withdrawalEpochLength=10)

def advance_to_epoch(self, epoch_number: int):
sc_node = self.sc_nodes[0]
forging_info = sc_node.block_forgingInfo()
current_epoch = forging_info["result"]["bestBlockEpochNumber"]
# make sure we are not already passed the desired epoch
assert_false(current_epoch > epoch_number, "unexpected epoch number")
while current_epoch < epoch_number:
generate_next_block(sc_node, "first node", force_switch_to_next_epoch=True)
self.sc_sync_all()
forging_info = sc_node.block_forgingInfo()
current_epoch = forging_info["result"]["bestBlockEpochNumber"]

def run_test(self):
time.sleep(0.1)

Expand Down Expand Up @@ -394,8 +408,8 @@ def run_test(self):
native_contract = SmartContract("WithdrawalRequests")

# Test before interoperability fork
method = "getBackwardTransfers(uint32)"
native_input = format_eoa(native_contract.raw_encode_call(method, current_epoch_number))
method_get = "getBackwardTransfers(uint32)"
native_input = format_eoa(native_contract.raw_encode_call(method_get, current_epoch_number))
if self.options.all_forks is False:
try:
proxy_contract.do_static_call(evm_address_interop, 1, WITHDRAWAL_REQ_SMART_CONTRACT_ADDRESS, native_input)
Expand Down Expand Up @@ -456,8 +470,8 @@ def run_test(self):
generate_next_block(sc_node, "first node")

# Create a transaction requesting a withdrawal request using the proxy smart contract
method = "backwardTransfer(bytes20)"
native_input = format_eoa(native_contract.raw_encode_call(method, hex_str_to_bytes(mc_address1_pk)))
method_bwt = "backwardTransfer(bytes20)"
native_input = format_eoa(native_contract.raw_encode_call(method_bwt, hex_str_to_bytes(mc_address1_pk)))

bt_amount_in_zennies = 100
bt_amount_in_wei = convertZenniesToWei(bt_amount_in_zennies)
Expand Down Expand Up @@ -512,6 +526,40 @@ def run_test(self):
gas_used_tracer = int(trace_result['gasUsed'], 16)
assert_true(gas_used == gas_used_tracer, "Wrong gas")

# Create a transaction requesting a withdrawal request via smart contract call
tx_hash = native_contract.call_function(sc_node, method_bwt, hex_str_to_bytes(mc_address1_pk),
fromAddress=evm_address_interop, gasLimit=230000,
toAddress=WITHDRAWAL_REQ_SMART_CONTRACT_ADDRESS,
value=convertZenniesToWei(sc_bt_amount_in_zennies_2))

# Check the receipt
tx_receipt = generate_block_and_get_tx_receipt(sc_node, tx_hash)['result']
assert_equal('0x1', tx_receipt['status'], 'Transaction should fail')

# try doing the same but reach the fork first, and check we can not do a backward transfer anymore
self.advance_to_epoch(VERSION_1_5_FORK_EPOCH)

try:
withdrawcoins(sc_node, mc_address2, sc_bt_amount_in_zennies_2)
fail("call should fail after fork 1.5 activation")
except Exception as err:
print("Expected exception thrown: {}".format(err))
assert_true("Fork 1.5 is active" in str(err))
# 'Something went wrong, see {\'error\': {\'code\': \'0204\', \'description\': \'Fork 1.5 is active, can not invoke this command\', \'detail\': \'\'}}'

# Create a transaction requesting a withdrawal request using the native smart contract
tx_hash = native_contract.call_function(sc_node, method_bwt, hex_str_to_bytes(mc_address1_pk),
fromAddress=evm_address_interop, gasLimit=230000,
toAddress=WITHDRAWAL_REQ_SMART_CONTRACT_ADDRESS,
value=convertZenniesToWei(sc_bt_amount_in_zennies_2))

# Check the receipt
tx_receipt = generate_block_and_get_tx_receipt(sc_node, tx_hash)['result']
assert_equal('0x0', tx_receipt['status'], 'Transaction should fail')

res = sc_node.rpc_debug_traceTransaction(tx_hash, {"tracer": "callTracer"})['result']
assert_true("fork 1.5 active" in str(res['error']))


if __name__ == "__main__":
SCEvmBackwardTransfer().main()
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import io.horizen.account.api.http.route.AccountTransactionRestScheme._
import io.horizen.account.block.{AccountBlock, AccountBlockHeader}
import io.horizen.account.chain.AccountFeePaymentsInfo
import io.horizen.account.companion.SidechainAccountTransactionsCompanion
import io.horizen.account.fork.{Version1_3_0Fork, Version1_4_0Fork}
import io.horizen.account.fork.{Version1_3_0Fork, Version1_4_0Fork, Version1_5_0Fork}
import io.horizen.account.node.{AccountNodeView, NodeAccountHistory, NodeAccountMemoryPool, NodeAccountState}
import io.horizen.account.proof.SignatureSecp256k1
import io.horizen.account.proposition.AddressProposition
Expand Down Expand Up @@ -846,47 +846,58 @@ case class AccountTransactionApiRoute(override val settings: RESTApiSettings,
entity(as[ReqWithdrawCoins]) { body =>
// lock the view and try to create CoreTransaction
applyOnNodeView { sidechainNodeView =>
val valueInWei = ZenWeiConverter.convertZenniesToWei(body.withdrawalRequest.value)
val gasInfo = body.gasInfo

// 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)
val accountState = sidechainNodeView.getNodeState
val epochNumber = accountState.getConsensusEpochNumber.getOrElse(0)

if (gasInfo.isDefined) {
maxFeePerGas = gasInfo.get.maxFeePerGas
maxPriorityFeePerGas = gasInfo.get.maxPriorityFeePerGas
gasLimit = gasInfo.get.gasLimit
}
if (Version1_5_0Fork.get(epochNumber).active) {
ApiResponseUtil.toResponse(GenericTransactionError(s"Fork 1.5 is active, can not invoke this command",
JOptional.empty()))
} else {
val valueInWei = ZenWeiConverter.convertZenniesToWei(body.withdrawalRequest.value)
val gasInfo = body.gasInfo

val txCost = valueInWei.add(maxFeePerGas.multiply(gasLimit))
val secret = getFittingSecret(sidechainNodeView, None, txCost)
secret match {
case Some(secret) =>
val dataBytes = encodeAddNewWithdrawalRequestCmd(body.withdrawalRequest)
dataBytes match {
case Success(data) =>
val nonce = body.nonce.getOrElse(sidechainNodeView.getNodeState.getNonce(secret.publicImage.address))
val tmpTx: EthereumTransaction = new EthereumTransaction(
params.chainId,
JOptional.of(new AddressProposition(WithdrawalMsgProcessor.contractAddress)),
nonce,
gasLimit,
maxPriorityFeePerGas,
maxFeePerGas,
valueInWei,
data,
null
)
validateAndSendTransaction(signTransactionWithSecret(secret, tmpTx))
case Failure(exc) =>
ApiResponseUtil.toResponse(ErrorInvalidMcAddress(s"Invalid Mc address ${body.withdrawalRequest.mainchainAddress}", JOptional.of(exc)))
}
case None =>
ApiResponseUtil.toResponse(ErrorInsufficientBalance("No account with enough balance found", JOptional.empty()))
// 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 (gasInfo.isDefined) {
maxFeePerGas = gasInfo.get.maxFeePerGas
maxPriorityFeePerGas = gasInfo.get.maxPriorityFeePerGas
gasLimit = gasInfo.get.gasLimit
}

val txCost = valueInWei.add(maxFeePerGas.multiply(gasLimit))
val secret = getFittingSecret(sidechainNodeView, None, txCost)
secret match {
case Some(secret) =>
val dataBytes = encodeAddNewWithdrawalRequestCmd(body.withdrawalRequest)
dataBytes match {
case Success(data) =>
val nonce = body.nonce.getOrElse(sidechainNodeView.getNodeState.getNonce(secret.publicImage.address))
val tmpTx: EthereumTransaction = new EthereumTransaction(
params.chainId,
JOptional.of(new AddressProposition(WithdrawalMsgProcessor.contractAddress)),
nonce,
gasLimit,
maxPriorityFeePerGas,
maxFeePerGas,
valueInWei,
data,
null
)
validateAndSendTransaction(signTransactionWithSecret(secret, tmpTx))
case Failure(exc) =>
ApiResponseUtil.toResponse(ErrorInvalidMcAddress(s"Invalid Mc address ${body.withdrawalRequest.mainchainAddress}", JOptional.of(exc)))
}
case None =>
ApiResponseUtil.toResponse(ErrorInsufficientBalance("No account with enough balance found", JOptional.empty()))
}
}


}
}
}
Expand Down
23 changes: 23 additions & 0 deletions sdk/src/main/scala/io/horizen/account/fork/Version1_5_0Fork.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.horizen.account.fork

import io.horizen.fork.{ForkManager, OptionalSidechainFork}

case class Version1_5_0Fork(active: Boolean = false) extends OptionalSidechainFork

/**
* <p>This fork introduces the following major changes:</p>
* <ul>
* <li>1. It disables the backward transfers submissions as part of the strategy which stops any cross-chain transfer operation between Mainchain and Sidechain with a view to EON2 migration.</li>
* </ul>
*/
object Version1_5_0Fork {
def get(epochNumber: Int): Version1_5_0Fork = {
ForkManager.getOptionalSidechainFork[Version1_5_0Fork](epochNumber).getOrElse(DefaultFork)
}

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

private val DefaultFork: Version1_5_0Fork = Version1_5_0Fork()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package io.horizen.account.state
import com.google.common.primitives.{Bytes, Ints}
import io.horizen.account.abi.ABIUtil.{METHOD_ID_LENGTH, getABIMethodId, getArgumentsFromData, getFunctionSignature}
import io.horizen.account.abi.{ABIDecoder, ABIEncodable, ABIListEncoder, MsgProcessorInputDecoder}
import io.horizen.account.fork.Version1_5_0Fork
import io.horizen.account.state.events.AddWithdrawalRequest
import io.horizen.account.storage.MsgProcessorMetadataStorageReader
import io.horizen.account.utils.WellKnownAddresses.WITHDRAWAL_REQ_SMART_CONTRACT_ADDRESS
Expand Down Expand Up @@ -35,6 +36,10 @@ object WithdrawalMsgProcessor extends NativeSmartContractMsgProcessor with Withd
val AddNewWithdrawalReqCmdSig: String = getABIMethodId("backwardTransfer(bytes20)")
val DustThresholdInWei: BigInteger = ZenWeiConverter.convertZenniesToWei(ZenCoinsUtils.getMinDustThreshold(ZenCoinsUtils.MC_DEFAULT_FEE_RATE))

def isForkActive(consensusEpochNumber: Int): Boolean = {
Version1_5_0Fork.get(consensusEpochNumber).active
}

@throws(classOf[ExecutionFailedException])
override def process(invocation: Invocation, view: BaseAccountStateView, metadata: MsgProcessorMetadataStorageReader, context: ExecutionContext): Array[Byte] = {
val gasView = view.getGasTrackedView(invocation.gasPool)
Expand All @@ -43,6 +48,8 @@ object WithdrawalMsgProcessor extends NativeSmartContractMsgProcessor with Withd
execGetListOfWithdrawalReqRecords(invocation, gasView)

case AddNewWithdrawalReqCmdSig =>
if (isForkActive(context.blockContext.consensusEpochNumber))
throw new ExecutionRevertedException(s"fork 1.5 active, backward transfers disabled")
execAddWithdrawalRequest(invocation, gasView, context.blockContext.withdrawalEpochNumber)

case functionSig =>
Expand Down
4 changes: 2 additions & 2 deletions sdk/src/main/scala/io/horizen/fork/ForkConfigurator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ abstract class ForkConfigurator {
/**
* Mandatory for every sidechain to provide an epoch number here.
*/
val fork1activation: SidechainForkConsensusEpoch
val forkActivation: SidechainForkConsensusEpoch

/**
* Return the map of configured consensus epoch numbers to mandatory sidechain forks.
*/
final lazy val mandatorySidechainForks: Map[SidechainForkConsensusEpoch, MandatorySidechainFork] =
MandatorySidechainFork.forks(fork1activation)
MandatorySidechainFork.forks(forkActivation)

/**
* Return the map of optional sidechain forks and their consensus epoch numbers.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ abstract class ContractInteropTestBase extends MessageProcessorFixture {
@Before
def setup(): Unit = {
class TestOptionalForkConfigurator extends ForkConfigurator {
override val fork1activation: SidechainForkConsensusEpoch = SidechainForkConsensusEpoch(0, 0, 0)
override val forkActivation: SidechainForkConsensusEpoch = SidechainForkConsensusEpoch(0, 0, 0)

override def getOptionalSidechainForks: util.List[Pair[SidechainForkConsensusEpoch, OptionalSidechainFork]] =
Seq[Pair[SidechainForkConsensusEpoch, OptionalSidechainFork]](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ class ForgerStakeV2MsgProcessorTest


class TestOptionalForkConfigurator extends ForkConfigurator {
override val fork1activation: SidechainForkConsensusEpoch = SidechainForkConsensusEpoch(0, 0, 0)
override val forkActivation: SidechainForkConsensusEpoch = SidechainForkConsensusEpoch(0, 0, 0)
override def getOptionalSidechainForks: util.List[Pair[SidechainForkConsensusEpoch, OptionalSidechainFork]] =
Seq[Pair[SidechainForkConsensusEpoch, OptionalSidechainFork]](
new Pair(SidechainForkConsensusEpoch(V1_3_MOCK_FORK_POINT, V1_3_MOCK_FORK_POINT, V1_3_MOCK_FORK_POINT), Version1_3_0Fork(true)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class McAddrOwnershipMsgProcessorMultisigTest
val VER_1_2_MOCK_FORK_POINT: Int = 110

class TestOptionalForkConfigurator extends ForkConfigurator {
override val fork1activation: SidechainForkConsensusEpoch = SidechainForkConsensusEpoch(0, 0, 0)
override val forkActivation: SidechainForkConsensusEpoch = SidechainForkConsensusEpoch(0, 0, 0)

override def getOptionalSidechainForks: util.List[Pair[SidechainForkConsensusEpoch, OptionalSidechainFork]] =
Seq[Pair[SidechainForkConsensusEpoch, OptionalSidechainFork]](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class McAddrOwnershipMsgProcessorTest
val ZENDAO_MOCK_FORK_POINT: Int = 100

class TestOptionalForkConfigurator extends ForkConfigurator {
override val fork1activation: SidechainForkConsensusEpoch = SidechainForkConsensusEpoch(0, 0, 0)
override val forkActivation: SidechainForkConsensusEpoch = SidechainForkConsensusEpoch(0, 0, 0)

override def getOptionalSidechainForks: util.List[Pair[SidechainForkConsensusEpoch, OptionalSidechainFork]] =
Seq[Pair[SidechainForkConsensusEpoch, OptionalSidechainFork]](
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class ProxyMsgProcessorTest
val MOCK_FORK_POINT: Int = 100

class TestOptionalForkConfigurator extends ForkConfigurator {
override val fork1activation: SidechainForkConsensusEpoch = SidechainForkConsensusEpoch(0, 0, 0)
override val forkActivation: SidechainForkConsensusEpoch = SidechainForkConsensusEpoch(0, 0, 0)

override def getOptionalSidechainForks: util.List[Pair[SidechainForkConsensusEpoch, OptionalSidechainFork]] =
Seq[Pair[SidechainForkConsensusEpoch, OptionalSidechainFork]](
Expand Down
Loading

0 comments on commit 9fda215

Please sign in to comment.