diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index e833e5250a..1294935ef1 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -36,8 +36,6 @@ pub struct EVMServices { pub struct FinalizedBlockInfo { pub block_hash: [u8; 32], - // TODO: There's no reason for this to be hex encoded and de-coded back again - // We can just send the array of 256 directly, same as block hash. pub failed_transactions: Vec, pub total_burnt_fees: U256, pub total_priority_fees: U256, @@ -145,8 +143,8 @@ impl EVMServices { let mut executor = AinExecutor::new(&mut backend); - for (queue_tx, hash) in self.core.tx_queues.get_cloned_vec(queue_id) { - match queue_tx { + for queue_item in self.core.tx_queues.get_cloned_vec(queue_id) { + match queue_item.queue_tx { QueueTx::SignedTx(signed_tx) => { if ain_cpp_imports::past_changi_intermediate_height_4_height() { let nonce = executor.get_nonce(&signed_tx.sender); @@ -172,7 +170,7 @@ impl EVMServices { ); if !exit_reason.is_succeed() { - failed_transactions.push(hex::encode(hash)); + failed_transactions.push(hex::encode(queue_item.tx_hash)); } let gas_fee = calculate_gas_fee(&signed_tx, U256::from(used_gas), base_fee)?; @@ -190,7 +188,7 @@ impl EVMServices { ); if let Err(e) = executor.add_balance(address, amount) { debug!("[finalize_block] EvmIn failed with {e}"); - failed_transactions.push(hex::encode(hash)); + failed_transactions.push(hex::encode(queue_item.tx_hash)); } } QueueTx::BridgeTx(BridgeTx::EvmOut(BalanceUpdate { address, amount })) => { @@ -201,7 +199,7 @@ impl EVMServices { if let Err(e) = executor.sub_balance(address, amount) { debug!("[finalize_block] EvmOut failed with {e}"); - failed_transactions.push(hex::encode(hash)); + failed_transactions.push(hex::encode(queue_item.tx_hash)); } } } @@ -272,7 +270,7 @@ impl EVMServices { match self.core.tx_queues.get_total_fees(queue_id) { Some(total_fees) => { if (total_burnt_fees + total_priority_fees) != U256::from(total_fees) { - return Err(anyhow!("EVM block rejected because block total fees != (burnt fees + priority fees). Burnt fees: {}, priority fees: {}", total_burnt_fees, total_priority_fees).into()); + return Err(anyhow!("EVM block rejected because block total fees != (burnt fees + priority fees). Burnt fees: {}, priority fees: {}, total fees: {}", total_burnt_fees, total_priority_fees, total_fees).into()); } } None => { diff --git a/lib/ain-evm/src/txqueue.rs b/lib/ain-evm/src/txqueue.rs index 132538e054..805731d1da 100644 --- a/lib/ain-evm/src/txqueue.rs +++ b/lib/ain-evm/src/txqueue.rs @@ -94,13 +94,13 @@ impl TransactionQueueMap { .unwrap() .get(&queue_id) .ok_or(QueueError::NoSuchContext) - .map(|queue| queue.queue_tx((tx, hash), gas_used, base_fee))? + .map(|queue| queue.queue_tx(tx, hash, gas_used, base_fee))? } /// `drain_all` returns all transactions from the `TransactionQueue` associated with the /// provided queue ID, removing them from the queue. Transactions are returned in the /// order they were added. - pub fn drain_all(&self, queue_id: u64) -> Vec { + pub fn drain_all(&self, queue_id: u64) -> Vec { self.queues .read() .unwrap() @@ -108,7 +108,7 @@ impl TransactionQueueMap { .map_or(Vec::new(), TransactionQueue::drain_all) } - pub fn get_cloned_vec(&self, queue_id: u64) -> Vec { + pub fn get_cloned_vec(&self, queue_id: u64) -> Vec { self.queues .read() .unwrap() @@ -173,14 +173,21 @@ pub enum QueueTx { BridgeTx(BridgeTx), } -type QueueTxWithNativeHash = (QueueTx, NativeTxHash); +#[derive(Debug, Clone)] +pub struct QueueTxItem { + pub queue_tx: QueueTx, + pub tx_hash: NativeTxHash, + pub tx_fee: u64, + pub gas_used: u64, +} -/// The `TransactionQueue` holds a queue of transactions and a map of account nonces. +/// The `TransactionQueueData` holds a queue of transactions with a map of the account nonces, +/// the total gas fees and the total gas used by the transactions. /// It's used to manage and process transactions for different accounts. /// #[derive(Debug, Default)] struct TransactionQueueData { - transactions: Vec, + transactions: Vec, account_nonces: HashMap, total_fees: u64, total_gas_used: u64, @@ -216,28 +223,27 @@ impl TransactionQueue { data.transactions.clear(); } - pub fn drain_all(&self) -> Vec { + pub fn drain_all(&self) -> Vec { let mut data = self.data.lock().unwrap(); data.total_fees = 0u64; data.total_gas_used = 0u64; - - data.transactions - .drain(..) - .collect::>() + data.transactions.drain(..).collect::>() } - pub fn get_cloned_vec(&self) -> Vec { + pub fn get_cloned_vec(&self) -> Vec { self.data.lock().unwrap().transactions.clone() } pub fn queue_tx( &self, - tx: QueueTxWithNativeHash, + tx: QueueTx, + tx_hash: NativeTxHash, gas_used: u64, base_fee: U256, ) -> Result<(), QueueError> { + let mut gas_fee: u64 = 0; let mut data = self.data.lock().unwrap(); - if let QueueTx::SignedTx(signed_tx) = &tx.0 { + if let QueueTx::SignedTx(signed_tx) = &tx { if let Some(nonce) = data.account_nonces.get(&signed_tx.sender) { if signed_tx.nonce() != nonce + 1 { return Err(QueueError::InvalidNonce((signed_tx.clone(), *nonce))); @@ -246,7 +252,7 @@ impl TransactionQueue { data.account_nonces .insert(signed_tx.sender, signed_tx.nonce()); - let gas_fee = match calculate_gas_fee(signed_tx, gas_used.into(), base_fee) { + gas_fee = match calculate_gas_fee(signed_tx, gas_used.into(), base_fee) { Ok(fee) => fee.as_u64(), Err(_) => return Err(QueueError::InvalidFee), }; @@ -254,7 +260,12 @@ impl TransactionQueue { data.total_fees += gas_fee; data.total_gas_used += gas_used; } - data.transactions.push(tx); + data.transactions.push(QueueTxItem { + queue_tx: tx, + tx_hash, + tx_fee: gas_fee, + gas_used, + }); Ok(()) } @@ -264,13 +275,22 @@ impl TransactionQueue { pub fn remove_txs_by_sender(&self, sender: H160) { let mut data = self.data.lock().unwrap(); - data.transactions.retain(|(tx, _)| { - let tx_sender = match tx { + let mut fees_to_remove = 0; + let mut gas_used_to_remove = 0; + data.transactions.retain(|item| { + let tx_sender = match &item.queue_tx { QueueTx::SignedTx(tx) => tx.sender, QueueTx::BridgeTx(tx) => tx.sender(), }; - tx_sender != sender + if tx_sender == sender { + fees_to_remove += item.tx_fee; + gas_used_to_remove += item.gas_used; + return false; + } + true }); + data.total_fees -= fees_to_remove; + data.total_gas_used -= gas_used_to_remove; data.account_nonces.remove(&sender); } @@ -341,10 +361,10 @@ mod tests { // Nonce 0, sender 0xe61a3a6eb316d773c773f4ce757a542f673023c6 let tx3 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869808502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa03d28d24808c3de08c606c5544772ded91913f648ad56556f181905208e206c85a00ecd0ba938fb89fc4a17ea333ea842c7305090dee9236e2b632578f9e5045cb3").unwrap())); - queue.queue_tx((tx1, H256::from_low_u64_be(1).into()), 0u64, U256::zero())?; - queue.queue_tx((tx2, H256::from_low_u64_be(2).into()), 0u64, U256::zero())?; + queue.queue_tx(tx1, H256::from_low_u64_be(1).into(), 0u64, U256::zero())?; + queue.queue_tx(tx2, H256::from_low_u64_be(2).into(), 0u64, U256::zero())?; // Should fail as nonce 2 is already queued for this sender - let queued = queue.queue_tx((tx3, H256::from_low_u64_be(3).into()), 0u64, U256::zero()); + let queued = queue.queue_tx(tx3, H256::from_low_u64_be(3).into(), 0u64, U256::zero()); assert!(matches!(queued, Err(QueueError::InvalidNonce { .. }))); Ok(()) } @@ -368,11 +388,11 @@ mod tests { // Nonce 0, sender 0xe61a3a6eb316d773c773f4ce757a542f673023c6 let tx4 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869808502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa03d28d24808c3de08c606c5544772ded91913f648ad56556f181905208e206c85a00ecd0ba938fb89fc4a17ea333ea842c7305090dee9236e2b632578f9e5045cb3").unwrap())); - queue.queue_tx((tx1, H256::from_low_u64_be(1).into()), 0u64, U256::zero())?; - queue.queue_tx((tx2, H256::from_low_u64_be(2).into()), 0u64, U256::zero())?; - queue.queue_tx((tx3, H256::from_low_u64_be(3).into()), 0u64, U256::zero())?; + queue.queue_tx(tx1, H256::from_low_u64_be(1).into(), 0u64, U256::zero())?; + queue.queue_tx(tx2, H256::from_low_u64_be(2).into(), 0u64, U256::zero())?; + queue.queue_tx(tx3, H256::from_low_u64_be(3).into(), 0u64, U256::zero())?; // Should fail as nonce 2 is already queued for this sender - let queued = queue.queue_tx((tx4, H256::from_low_u64_be(4).into()), 0u64, U256::zero()); + let queued = queue.queue_tx(tx4, H256::from_low_u64_be(4).into(), 0u64, U256::zero()); assert!(matches!(queued, Err(QueueError::InvalidNonce { .. }))); Ok(()) } @@ -393,10 +413,10 @@ mod tests { // Nonce 2, sender 0x6bc42fd533d6cb9d973604155e1f7197a3b0e703 let tx4 = QueueTx::SignedTx(Box::new(SignedTx::try_from("f869028502540be400832dc6c0943e338e722607a8c1eab615579ace4f6dedfa19fa80840adb1a9a2aa09588b47d2cd3f474d6384309cca5cb8e360cb137679f0a1589a1c184a15cb27ca0453ddbf808b83b279cac3226b61a9d83855aba60ae0d3a8407cba0634da7459d").unwrap())); - queue.queue_tx((tx1, H256::from_low_u64_be(1).into()), 0u64, U256::zero())?; - queue.queue_tx((tx2, H256::from_low_u64_be(2).into()), 0u64, U256::zero())?; - queue.queue_tx((tx3, H256::from_low_u64_be(3).into()), 0u64, U256::zero())?; - queue.queue_tx((tx4, H256::from_low_u64_be(4).into()), 0u64, U256::zero())?; + queue.queue_tx(tx1, H256::from_low_u64_be(1).into(), 0u64, U256::zero())?; + queue.queue_tx(tx2, H256::from_low_u64_be(2).into(), 0u64, U256::zero())?; + queue.queue_tx(tx3, H256::from_low_u64_be(3).into(), 0u64, U256::zero())?; + queue.queue_tx(tx4, H256::from_low_u64_be(4).into(), 0u64, U256::zero())?; Ok(()) } } diff --git a/src/miner.cpp b/src/miner.cpp index 928bf1a2cb..efccb12cdb 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -298,29 +298,12 @@ std::unique_ptr BlockAssembler::CreateNewBlock(const CScript& sc xvm_changi = XVMChangiIntermediate{0,{0, uint256(blockHash), blockResult.total_burnt_fees}}; } - std::set failedTransactions; - for (const auto& txRustStr : blockResult.failed_transactions) { - auto txStr = std::string(txRustStr.data(), txRustStr.length()); - failedTransactions.insert(uint256S(txStr)); + std::vector failedTransactions; + for (const auto& rust_string : blockResult.failed_transactions) { + failedTransactions.emplace_back(rust_string.data(), rust_string.length()); } - std::set failedTransferDomainTxs; - - // Get All TransferDomainTxs - for (const auto &hash : failedTransactions) { - for (const auto &tx : pblock->vtx) { - if (tx && tx->GetHash() == hash) { - std::vector metadata; - const auto txType = GuessCustomTxType(*tx, metadata, false); - if (txType == CustomTxType::TransferDomain) { - failedTransferDomainTxs.insert(hash); - } - break; - } - } - } - - RemoveTxs(failedTransactions, txFees); + RemoveFailedTransactions(failedTransactions, txFees); } // TXs for the creationTx field in new tokens created via token split @@ -571,56 +554,93 @@ int BlockAssembler::UpdatePackagesForAdded(const CTxMemPool::setEntries& already return nDescendantsUpdated; } -void BlockAssembler::RemoveTxIters(const std::vector iters) { +void BlockAssembler::RemoveEVMTransactions(const std::vector iters) { + + // Make sure we only remove EVM TXs which have no descendants or TX fees. + // Removing others TXs is more complicated and should be handled by RemoveFailedTransactions. for (const auto& iter : iters) { const auto &tx = iter->GetTx(); + std::vector metadata; + const auto txType = GuessCustomTxType(tx, metadata, false); + if (txType != CustomTxType::EvmTx) { + continue; + } + for (size_t i = 0; i < pblock->vtx.size(); ++i) { - auto current = pblock->vtx[i]; - if (current && current->GetHash() == tx.GetHash()) { + if (pblock->vtx[i] && pblock->vtx[i]->GetHash() == iter->GetTx().GetHash()) { pblock->vtx.erase(pblock->vtx.begin() + i); - nBlockWeight -= iter->GetTxWeight(); - nBlockSigOpsCost -= iter->GetSigOpCost(); - nFees -= iter->GetFee(); - --nBlockTx; break; } } + + nBlockWeight -= iter->GetTxWeight(); + --nBlockTx; } } -void BlockAssembler::RemoveTxs(const std::set &txHashSet, const std::map &txFees) { - if (txHashSet.empty()) { return; } +void BlockAssembler::RemoveFailedTransactions(const std::vector &failedTransactions, const std::map &txFees) { + if (failedTransactions.empty()) { + return; + } - auto &blockFees = nFees; - auto &blockTxCount = nBlockTx; + std::vector txsToErase; + for (const auto &txStr : failedTransactions) { + const auto failedHash = uint256S(txStr); + for (const auto &tx : pblock->vtx) { + if (tx && tx->GetHash() == failedHash) { + std::vector metadata; + const auto txType = GuessCustomTxType(*tx, metadata, false); + if (txType == CustomTxType::TransferDomain) { + txsToErase.push_back(failedHash); + } + } + } + } - auto removeItem = [&txHashSet, &blockFees, &blockTxCount, &txFees](const auto &tx) { - auto hash = tx.get()->GetHash(); - if (txHashSet.count(hash) < 1) return false; - blockFees -= txFees.at(hash); - --blockTxCount; - return true; - }; + // Get a copy of the TXs to be erased for restoring to the mempool later + std::vector txsToRemove; + std::set txsToEraseSet(txsToErase.begin(), txsToErase.end()); - pblock->vtx.erase( - std::remove_if(pblock->vtx.begin(), pblock->vtx.end(), removeItem), - pblock->vtx.end()); + for (const auto &tx : pblock->vtx) { + if (tx && txsToEraseSet.count(tx->GetHash())) { + txsToRemove.push_back(tx); + } + } - // Add descendants - std::set descendantTxsToErase; - for (const auto &hash : txHashSet) { + // Add descendants and in turn add their descendants. This needs to + // be done in the order that the TXs are in the block for txsToRemove. + auto size = txsToErase.size(); + for (std::vector::size_type i{}; i < size; ++i) { for (const auto &tx : pblock->vtx) { - if (!tx) continue; - for (const auto &vin : tx->vin) { - if (vin.prevout.hash == hash) { - descendantTxsToErase.insert(hash); + if (tx) { + for (const auto &vin : tx->vin) { + if (vin.prevout.hash == txsToErase[i] && + std::find(txsToErase.begin(), txsToErase.end(), tx->GetHash()) == txsToErase.end()) + { + txsToRemove.push_back(tx); + txsToErase.push_back(tx->GetHash()); + ++size; + } } } } } - // Recursively remove descendants - RemoveTxs(descendantTxsToErase, txFees); + // Erase TXs from block + txsToEraseSet = std::set(txsToErase.begin(), txsToErase.end()); + pblock->vtx.erase( + std::remove_if(pblock->vtx.begin(), pblock->vtx.end(), + [&txsToEraseSet](const auto &tx) { + return tx && txsToEraseSet.count(tx.get()->GetHash()); + }),pblock->vtx.end()); + + for (const auto &tx : txsToRemove) { + // Remove fees. + if (txFees.count(tx->GetHash())) { + nFees -= txFees.at(tx->GetHash()); + } + --nBlockTx; + } } // Skip entries in mapTx that are already in a block or are present @@ -667,6 +687,10 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda indexed_modified_transaction_set mapModifiedTx; // Keep track of entries that failed inclusion, to avoid duplicate work CTxMemPool::setEntries failedTx; + // Keep track of EVM entries that failed nonce check + std::multimap failedNonces; + // Used for replacement Eth TXs when a TX in chain pays a higher fee + std::multimap replaceByFee; // Start by adding all descendants of previously added txs to mapModifiedTx // and modifying them for their already included ancestors @@ -687,27 +711,10 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda // Copy of the view CCoinsViewCache coinsView(&::ChainstateActive().CoinsTip()); - typedef std::array EvmAddress; - - struct EvmAddressWithNonce { - EvmAddress address; - uint64_t nonce; - - bool operator<(const EvmAddressWithNonce& item) const { - return std::tie(address, nonce) < std::tie(item.address, item.nonce); - } - }; - - struct EvmPackageContext { - // Used to track EVM TXs by sender and nonce. - std::map feeMap; - // Used to track EVM nonce and TXs by sender - std::map> addressTxsMap; - // Keep track of EVM entries that failed nonce check - std::multimap failedNonces; - // Used for replacement Eth TXs when a TX in chain pays a higher fee - std::map replaceByFee; - }; + // Used to track EVM TXs by sender and nonce. + // Key: sender address, nonce + // Value: fee + std::map, uint64_t>, uint64_t> evmTXFees; auto txIters = [](std::map &iterMap) -> std::vector { std::vector txIters; @@ -717,10 +724,10 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda return txIters; }; - EvmPackageContext evmPackageContext; - - auto& failedNonces = evmPackageContext.failedNonces; - auto& replaceByFee = evmPackageContext.replaceByFee; + // Used to track EVM TXs by sender + // Key: sender address + // Value: vector of mempool TX iterator + std::map, std::map> evmTXs; while (mi != mempool.mapTx.get().end() || !mapModifiedTx.empty() || !failedNonces.empty()) { @@ -865,44 +872,37 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda const auto obj = std::get(txMessage); CrossBoundaryResult result; - const auto txResult = evm_try_validate_raw_tx(result, HexStr(obj.evmTx), evmQueueId); + const auto txResult = evm_try_prevalidate_raw_tx(result, HexStr(obj.evmTx)); if (!result.ok) { customTxPassed = false; break; } - auto& evmFeeMap = evmPackageContext.feeMap; - auto& evmAddressTxsMap = evmPackageContext.addressTxsMap; - - const auto addrKey = EvmAddressWithNonce { txResult.sender, txResult.nonce }; - if (auto feeEntry = evmFeeMap.find(addrKey); feeEntry != evmFeeMap.end()) { - // Key already exists. We check to see if we need to prioritize higher fee tx - const auto& lastFee = feeEntry->second; - if (txResult.prepay_fee > lastFee) { + const auto evmKey = std::make_pair(txResult.sender, txResult.nonce); + if (evmTXFees.count(evmKey)) { + const auto& gasFees = evmTXFees.at(evmKey); + if (txResult.prepay_fee > gasFees) { // Higher paying fee. Remove all TXs from sender and add to collection to add them again in order. - auto addrTxs = evmAddressTxsMap[addrKey.address]; - RemoveTxIters(txIters(addrTxs)); - // Remove all fee entries relating to the address - for (auto it = evmFeeMap.begin(); it != evmFeeMap.end();) { + RemoveEVMTransactions(txIters(evmTXs[txResult.sender])); + for (auto it = evmTXFees.begin(); it != evmTXFees.end();) { const auto& [sender, nonce] = it->first; - if (sender == addrKey.address) { - it = evmFeeMap.erase(it); + if (sender == txResult.sender) { + it = evmTXFees.erase(it); } else { ++it; } } - // Buggy code to fix below: - checkedTX.erase(addrTxs[addrKey.nonce]->GetTx().GetHash()); - addrTxs[addrKey.nonce] = sortedEntries[i]; - auto count{addrKey.nonce}; - for (const auto& [nonce, entry] : addrTxs) { + checkedTX.erase(evmTXs[txResult.sender][txResult.nonce]->GetTx().GetHash()); + evmTXs[txResult.sender][txResult.nonce] = sortedEntries[i]; + auto count{txResult.nonce}; + for (const auto& [nonce, entry] : evmTXs[txResult.sender]) { inBlock.erase(entry); checkedTX.erase(entry->GetTx().GetHash()); replaceByFee.emplace(count, entry); ++count; } - evmAddressTxsMap.erase(addrKey.address); - evm_remove_txs_by_sender(evmQueueId, addrKey.address); + evmTXs.erase(txResult.sender); + evm_remove_txs_by_sender(evmQueueId, txResult.sender); customTxPassed = false; break; } @@ -918,9 +918,8 @@ void BlockAssembler::addPackageTxs(int &nPackagesSelected, int &nDescendantsUpda break; } - auto addrNonce = EvmAddressWithNonce { txResult.sender, txResult.nonce }; - evmFeeMap.insert({addrNonce, txResult.prepay_fee}); - evmAddressTxsMap[txResult.sender].emplace(txResult.nonce, sortedEntries[i]); + evmTXFees.emplace(std::make_pair(txResult.sender, txResult.nonce), txResult.prepay_fee); + evmTXs[txResult.sender].emplace(txResult.nonce, sortedEntries[i]); } const auto res = ApplyCustomTx(view, coins, tx, chainparams.GetConsensus(), nHeight, pblock->nTime, nullptr, 0, evmQueueId); diff --git a/src/miner.h b/src/miner.h index 36cd147ac6..80ae692adf 100644 --- a/src/miner.h +++ b/src/miner.h @@ -216,9 +216,9 @@ class BlockAssembler * of updated descendants. */ int UpdatePackagesForAdded(const CTxMemPool::setEntries& alreadyAdded, indexed_modified_transaction_set &mapModifiedTx) EXCLUSIVE_LOCKS_REQUIRED(mempool.cs); /** Remove failed TransferDoamin transactions from the block */ - void RemoveTxs(const std::set &txHashSet, const std::map &txFees); + void RemoveFailedTransactions(const std::vector &failedTransactions, const std::map &txFees); /** Remove specific TX from the block */ - void RemoveTxIters(const std::vector iters); + void RemoveEVMTransactions(const std::vector iters); }; /** Modify the extranonce in a block */ diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index 3e4612b96e..8aa15ec591 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -245,7 +245,8 @@ def run_test(self): 'r': '0x3a0587be1a14bd5e68bc883e627f3c0999cff9458e30ea8049f17bd7369d7d9c', 's': '0x1876f296657bc56499cc6398617f97b2327fa87189c0a49fb671b4361876142a', 'type': '0x0', - 'maxFeePerGas': '0x4e3b29200'} + 'maxFeePerGas': '0x4e3b29200', + 'chainId': '0x1'} ]) # Try and send EVM TX a second time diff --git a/test/functional/rpc_updatemasternode.py b/test/functional/rpc_updatemasternode.py index e17d5d7c21..f9bb1afe2c 100755 --- a/test/functional/rpc_updatemasternode.py +++ b/test/functional/rpc_updatemasternode.py @@ -466,7 +466,7 @@ def run_test(self): self.nodes[0].updatemasternode, mn2, {'rewardAddress': script_address}) # Move to fork height - self.nodes[0].generate(510 - self.nodes[0].getblockcount()) + self.nodes[0].generate(510) # Change reward address to script hash post-fork self.nodes[0].updatemasternode(mn2, {'rewardAddress': script_address})