From 34cfe286bca0e134681a96ce7aeeeccf702d19ad Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Tue, 17 Sep 2024 14:36:35 +0300 Subject: [PATCH] Better blocks validation --- src/main/scala/units/ConsensusClient.scala | 1 - src/main/scala/units/ELUpdater.scala | 973 +++++++++--------- src/main/scala/units/NetworkL2Block.scala | 5 +- .../client/contract/ChainContractClient.scala | 10 +- .../units/client/contract/ContractBlock.scala | 8 +- .../client/contract/ContractFunction.scala | 6 +- .../units/client/engine/EngineApiClient.scala | 24 +- .../client/engine/HttpEngineApiClient.scala | 28 +- .../client/engine/LoggedEngineApiClient.scala | 26 +- .../units/client/engine/model/EcBlock.scala | 4 +- .../scala/units/network/NetworkServer.scala | 12 +- src/main/scala/units/package.scala | 4 +- src/test/resources/main.ride | 92 +- .../units/BlockFullValidationTestSuite.scala | 18 +- .../units/BlockIssuesForgingTestSuite.scala | 78 +- .../scala/units/E2CTransfersTestSuite.scala | 56 +- src/test/scala/units/ExtensionDomain.scala | 7 +- src/test/scala/units/HasJobLogging.scala | 4 +- src/test/scala/units/TestEcBlockBuilder.scala | 1 + .../scala/units/client/TestEcClients.scala | 92 +- .../HasConsensusLayerDappTxHelpers.scala | 16 +- 21 files changed, 709 insertions(+), 756 deletions(-) diff --git a/src/main/scala/units/ConsensusClient.scala b/src/main/scala/units/ConsensusClient.scala index 55d86b46..70be3ad9 100644 --- a/src/main/scala/units/ConsensusClient.scala +++ b/src/main/scala/units/ConsensusClient.scala @@ -71,7 +71,6 @@ class ConsensusClient( def shutdown(): Future[Unit] = Future { blocksStreamCancelable.cancel() - elu.close() ownedResources.close() }(globalScheduler) diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index 48e64ad6..e6488025 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -54,8 +54,7 @@ class ELUpdater( broadcastTx: Transaction => TracedResult[ValidationError, Boolean], scheduler: Scheduler, globalScheduler: Scheduler -) extends StrictLogging - with AutoCloseable { +) extends StrictLogging { import ELUpdater.* private val handleNextUpdate = SerialCancelable() @@ -70,44 +69,48 @@ class ELUpdater( def executionBlockReceived(block: NetworkL2Block, ch: Channel): Unit = scheduler.execute { () => logger.debug(s"New block ${block.hash}->${block.parentHash} (timestamp=${block.timestamp}, height=${block.height}) appeared") - state match { - case WaitingForSyncHead(target, _) if block.hash == target.hash => - val syncStarted = for { - _ <- engineApiClient.applyNewPayload(block.payload) - fcuStatus <- confirmBlock(target, target) - } yield fcuStatus - - syncStarted match { - case Left(value) => - logger.error(s"Error starting sync: $value") - setState("1", Starting) - case Right(fcuStatus) => - setState("2", SyncingToFinalizedBlock(target.hash)) - logger.debug(s"Waiting for sync completion: $fcuStatus") - waitForSyncCompletion(target) - } - case w @ Working(_, lastEcBlock, _, _, _, FollowingChain(nodeChainInfo, _), _, returnToMainChainInfo) if block.parentHash == lastEcBlock.hash => - validateAndApply(block, ch, w, lastEcBlock, nodeChainInfo, returnToMainChainInfo, ignoreInvalid = !canSupportAnotherAltChain(nodeChainInfo)) - case w: Working[?] => - w.returnToMainChainInfo match { - case Some(rInfo) if rInfo.missedEcBlockHash == block.hash => - chainContractClient.getChainInfo(rInfo.chainId) match { - case Some(chainInfo) if chainInfo.isMain => - validateAndApply(block, ch, w, rInfo.missedBlockParent, chainInfo, None, ignoreInvalid = true) - case Some(_) => - logger.debug(s"Chain ${rInfo.chainId} is not main anymore, ignoring ${block.hash}") - case _ => - logger.error(s"Failed to get chain ${rInfo.chainId} info, ignoring ${block.hash}") - } - case _ => logger.debug(s"Expecting ${w.returnToMainChainInfo.fold("no block")(_.toString)}, ignoring unexpected ${block.hash}") - } - case other => - logger.debug(s"$other: ignoring ${block.hash}") + val now = time.correctedTime() / 1000 + if (block.timestamp - now <= MaxTimeDrift) { + state match { + case WaitingForSyncHead(target, _) if block.hash == target.hash => + val syncStarted = for { + _ <- engineApiClient.applyNewPayload(block.payload) + fcuStatus <- confirmBlock(target, target) + } yield fcuStatus + + syncStarted match { + case Left(value) => + logger.error(s"Error starting sync: $value") + setState("1", Starting) + case Right(fcuStatus) => + setState("2", SyncingToFinalizedBlock(target.hash)) + logger.debug(s"Waiting for sync completion: $fcuStatus") + waitForSyncCompletion(target) + } + case w @ Working(_, lastEcBlock, _, _, _, FollowingChain(nodeChainInfo, _), _, returnToMainChainInfo) + if block.parentHash == lastEcBlock.hash => + validateAndApply(block, ch, w, lastEcBlock, nodeChainInfo, returnToMainChainInfo) + case w: Working[ChainStatus] => + w.returnToMainChainInfo match { + case Some(rInfo) if rInfo.missedBlock.hash == block.hash => + chainContractClient.getChainInfo(rInfo.chainId) match { + case Some(chainInfo) if chainInfo.isMain => + validateAndApplyMissedBlock(block, ch, w, rInfo.missedBlock, rInfo.missedBlockParent, chainInfo) + case Some(_) => + logger.debug(s"Chain ${rInfo.chainId} is not main anymore, ignoring ${block.hash}") + case _ => + logger.error(s"Failed to get chain ${rInfo.chainId} info, ignoring ${block.hash}") + } + case _ => logger.debug(s"Expecting ${w.returnToMainChainInfo.fold("no block")(_.toString)}, ignoring unexpected ${block.hash}") + } + case other => + logger.debug(s"$other: ignoring ${block.hash}") + } + } else { + logger.debug(s"Block ${block.hash} is from future: timestamp=${block.timestamp}, now=$now, Δ${block.timestamp - now}s") } } - override def close(): Unit = {} - private def calculateEpochInfo: Either[String, EpochInfo] = { val epochNumber = blockchain.height for { @@ -161,7 +164,7 @@ class ELUpdater( } } - private def callContract(fc: FUNCTION_CALL, blockData: EcBlock, invoker: KeyPair): Job[Unit] = { + private def callContract(fc: FUNCTION_CALL, blockData: EcBlock, invoker: KeyPair): JobResult[Unit] = { val extraFee = if (blockchain.hasPaidVerifier(invoker.toAddress)) ScriptExtraFee else 0 val tx = InvokeScriptTransaction( @@ -234,8 +237,8 @@ class ELUpdater( ClientError(s"Failed to broadcast block ${newBlock.hash}: ${err.toString}") ) ecBlock = newBlock.toEcBlock - transfersRootHash <- getElToClTransfersRootHash(ecBlock.hash, chainContractOptions.elBridgeAddress) - funcCall <- contractFunction.toFunctionCall(ecBlock.hash, transfersRootHash, m.lastClToElTransferIndex) + transfersRootHash <- getE2CTransfersRootHash(ecBlock.hash, chainContractOptions.elBridgeAddress) + funcCall <- contractFunction.toFunctionCall(ecBlock.hash, transfersRootHash, m.lastC2ETransferIndex) _ <- callContract( funcCall, ecBlock, @@ -254,7 +257,7 @@ class ELUpdater( } } - private def rollbackTo[CS <: ChainStatus](prevState: Working[CS], target: L2BlockLike, finalizedBlock: ContractBlock): Job[Working[CS]] = { + private def rollbackTo(prevState: Working[ChainStatus], target: L2BlockLike, finalizedBlock: ContractBlock): JobResult[Working[ChainStatus]] = { val targetHash = target.hash for { rollbackBlock <- mkRollbackBlock(targetHash) @@ -270,37 +273,33 @@ class ELUpdater( ) } yield { logger.info(s"Rollback to $targetHash finished successfully") + val updatedLastValidatedBlock = if (lastEcBlock.height < prevState.fullValidationStatus.lastValidatedBlock.height) { + chainContractClient.getBlock(lastEcBlock.hash).getOrElse(finalizedBlock) + } else { + prevState.fullValidationStatus.lastValidatedBlock + } val newState = - prevState.copy(lastEcBlock = lastEcBlock, fullValidationStatus = prevState.fullValidationStatus.copy(lastElWithdrawalIndex = None)) + prevState.copy( + lastEcBlock = lastEcBlock, + fullValidationStatus = FullValidationStatus(updatedLastValidatedBlock, None) + ) setState("10", newState) newState } } - private def validateRandao(networkBlock: NetworkL2Block, epochNumber: Int): Either[String, Unit] = - blockchain.vrf(epochNumber) match { - case None => s"VRF of $epochNumber epoch is empty".asLeft - case Some(vrf) => - val expectedPrevRandao = calculateRandao(vrf, networkBlock.parentHash) - Either.cond( - expectedPrevRandao == networkBlock.prevRandao, - (), - s"expected prevRandao $expectedPrevRandao, got ${networkBlock.prevRandao}, VRF=$vrf of $epochNumber" - ) - } - private def startBuildingPayload( epochInfo: EpochInfo, parentBlock: EcBlock, finalizedBlock: ContractBlock, nextBlockUnixTs: Long, - lastClToElTransferIndex: Long, + lastC2ETransferIndex: Long, lastElWithdrawalIndex: WithdrawalIndex, chainContractOptions: ChainContractOptions, prevEpochMinerRewardAddress: Option[EthAddress] - ): Job[MiningData] = { - val firstElWithdrawalIndex = lastElWithdrawalIndex + 1 - val startClToElTransferIndex = lastClToElTransferIndex + 1 + ): JobResult[MiningData] = { + val firstElWithdrawalIndex = lastElWithdrawalIndex + 1 + val startC2ETransferIndex = lastC2ETransferIndex + 1 val rewardWithdrawal = prevEpochMinerRewardAddress .map(Withdrawal(firstElWithdrawalIndex, _, chainContractOptions.miningReward)) @@ -309,8 +308,8 @@ class ELUpdater( val transfers = chainContractClient .getNativeTransfers( - fromIndex = startClToElTransferIndex, - maxItems = ChainContractClient.MaxClToElTransfers - rewardWithdrawal.size + fromIndex = startC2ETransferIndex, + maxItems = ChainContractClient.MaxC2ETransfers - rewardWithdrawal.size ) val transferWithdrawals = toWithdrawals(transfers, rewardWithdrawal.lastOption.fold(firstElWithdrawalIndex)(_.index + 1)) @@ -327,14 +326,14 @@ class ELUpdater( ).map { payloadId => logger.info( s"Starting to forge payload $payloadId by miner ${epochInfo.miner} at height ${parentBlock.height + 1} " + - s"of epoch ${epochInfo.number} (ref=${parentBlock.hash}), ${withdrawals.size} withdrawals, ${transfers.size} transfers from $startClToElTransferIndex" + s"of epoch ${epochInfo.number} (ref=${parentBlock.hash}), ${withdrawals.size} withdrawals, ${transfers.size} transfers from $startC2ETransferIndex" ) - MiningData(payloadId, nextBlockUnixTs, transfers.lastOption.fold(lastClToElTransferIndex)(_.index), lastElWithdrawalIndex + withdrawals.size) + MiningData(payloadId, nextBlockUnixTs, transfers.lastOption.fold(lastC2ETransferIndex)(_.index), lastElWithdrawalIndex + withdrawals.size) } } - private def tryToStartMining(prevState: Working[?], nodeChainInfo: Either[ChainSwitchInfo, ChainInfo]): Unit = { + private def tryToStartMining(prevState: Working[ChainStatus], nodeChainInfo: Either[ChainSwitchInfo, ChainInfo]): Unit = { val parentBlock = prevState.lastEcBlock val epochInfo = prevState.epochInfo @@ -345,7 +344,7 @@ class ELUpdater( case Left(chainSwitchInfo) => chainSwitchInfo.referenceBlock case Right(chainInfo) => chainInfo.lastBlock } - val lastClToElTransferIndex = refContractBlock.lastClToElTransferIndex + val lastC2ETransferIndex = refContractBlock.lastC2ETransferIndex (for { elWithdrawalIndexBefore <- @@ -361,7 +360,7 @@ class ELUpdater( parentBlock, prevState.finalizedBlock, nextBlockUnixTs, - lastClToElTransferIndex, + lastC2ETransferIndex, elWithdrawalIndexBefore, prevState.options, Option.unless(parentBlock.height == EthereumConstants.GenesisBlockHeight)(parentBlock.minerRewardL2Address) @@ -370,7 +369,7 @@ class ELUpdater( val newState = prevState.copy( epochInfo = epochInfo, lastEcBlock = parentBlock, - chainStatus = Mining(keyPair, miningData.payloadId, nodeChainInfo, miningData.lastClToElTransferIndex, miningData.lastElWithdrawalIndex) + chainStatus = Mining(keyPair, miningData.payloadId, nodeChainInfo, miningData.lastC2ETransferIndex, miningData.lastElWithdrawalIndex) ) setState("12", newState) @@ -406,7 +405,7 @@ class ELUpdater( parentBlock, finalizedBlock, nextBlockUnixTs, - m.lastClToElTransferIndex, + m.lastC2ETransferIndex, m.lastElWithdrawalIndex, chainContractOptions, None @@ -422,7 +421,7 @@ class ELUpdater( lastEcBlock = parentBlock, chainStatus = m.copy( currentPayloadId = miningData.payloadId, - lastClToElTransferIndex = miningData.lastClToElTransferIndex, + lastC2ETransferIndex = miningData.lastC2ETransferIndex, lastElWithdrawalIndex = miningData.lastElWithdrawalIndex ) ) @@ -459,7 +458,7 @@ class ELUpdater( } yield { logger.trace(s"Following main chain ${mainChainInfo.id}") val fullValidationStatus = FullValidationStatus( - validated = Set(finalizedBlock.hash), + lastValidatedBlock = finalizedBlock, lastElWithdrawalIndex = None ) val options = chainContractClient.getOptions @@ -486,9 +485,9 @@ class ELUpdater( private def handleConsensusLayerChanged(): Unit = { state match { - case Starting => updateStartingState() - case w: Working[?] => updateWorkingState(w) - case other => logger.debug(s"Unprocessed state: $other") + case Starting => updateStartingState() + case w: Working[ChainStatus] => updateWorkingState(w) + case other => logger.debug(s"Unprocessed state: $other") } } @@ -510,7 +509,7 @@ class ELUpdater( result match { case Some(chainInfo) => - logger.debug(s"Found alternative chain #${chainInfo.id} referencing $referenceBlock") + logger.debug(s"Found alternative chain ${chainInfo.id} referencing $referenceBlock") Some(chainInfo) case _ => logger.debug(s"Not found alternative chain referencing $referenceBlock") @@ -519,26 +518,50 @@ class ELUpdater( } private def requestBlocksAndStartMining(prevState: Working[FollowingChain]): Unit = { - def check(missedBlock: BlockHash): Unit = { + def check(missedBlock: ContractBlock): Unit = { state match { case w @ Working(epochInfo, lastEcBlock, finalizedBlock, mainChainInfo, _, fc: FollowingChain, _, returnToMainChainInfo) - if fc.nextExpectedEcBlock.contains(missedBlock) && canSupportAnotherAltChain(fc.nodeChainInfo) => - logger.debug(s"Block $missedBlock wasn't received for $WaitRequestedBlockTimeout, need to switch to alternative chain") - val lastValidBlock = findAltChainReferenceHash(epochInfo, fc.nodeChainInfo, lastEcBlock, finalizedBlock, missedBlock) - rollbackTo(w, lastValidBlock, finalizedBlock).fold( - err => logger.error(err.message), - updatedState => { - val chainSwitchInfo = ChainSwitchInfo(fc.nodeChainInfo.id, lastValidBlock) - val updatedReturnToMainChainInfo = - if (fc.nodeChainInfo.isMain) { - Some(ReturnToMainChainInfo(missedBlock, lastEcBlock, mainChainInfo.id)) - } else returnToMainChainInfo - val newState = updatedState.copy(chainStatus = WaitForNewChain(chainSwitchInfo), returnToMainChainInfo = updatedReturnToMainChainInfo) - setState("9", newState) - tryToStartMining(newState, Left(chainSwitchInfo)) + if fc.nextExpectedBlock.map(_.hash).contains(missedBlock.hash) && canSupportAnotherAltChain(fc.nodeChainInfo) => + logger.debug(s"Block ${missedBlock.hash} wasn't received for $WaitRequestedBlockTimeout, need to switch to alternative chain") + (for { + lastValidBlock <- getAltChainReferenceBlock(fc.nodeChainInfo, missedBlock) + updatedState <- rollbackTo(w, lastValidBlock, finalizedBlock) + } yield { + val updatedReturnToMainChainInfo = + if (fc.nodeChainInfo.isMain) { + Some(ReturnToMainChainInfo(missedBlock, lastEcBlock, mainChainInfo.id)) + } else returnToMainChainInfo + + findAltChain(fc.nodeChainInfo.id, lastValidBlock.hash) match { + case Some(altChainInfo) => + engineApiClient.getBlockByHash(finalizedBlock.hash) match { + case Right(Some(finalizedEcBlock)) => + followChainAndStartMining( + updatedState.copy(chainStatus = FollowingChain(altChainInfo, None), returnToMainChainInfo = updatedReturnToMainChainInfo), + epochInfo, + altChainInfo.id, + finalizedEcBlock, + finalizedBlock, + chainContractClient.getOptions + ) + case Right(None) => + logger.warn(s"Finalized block ${finalizedBlock.hash} is not in EC") + case Left(err) => + logger.error(s"Could not load finalized block ${finalizedBlock.hash}", err) + } + case _ => + val chainSwitchInfo = ChainSwitchInfo(fc.nodeChainInfo.id, lastValidBlock) + + val newState = + updatedState.copy(chainStatus = WaitForNewChain(chainSwitchInfo), returnToMainChainInfo = updatedReturnToMainChainInfo) + setState("9", newState) + tryToStartMining(newState, Left(chainSwitchInfo)) } + }).fold( + err => logger.error(err.message), + _ => () ) - case w: Working[?] => + case w: Working[ChainStatus] => w.chainStatus match { case FollowingChain(_, Some(nextExpectedBlock)) => logger.debug(s"Waiting for block $nextExpectedBlock from peers") @@ -557,7 +580,7 @@ class ELUpdater( } } - prevState.chainStatus.nextExpectedEcBlock match { + prevState.chainStatus.nextExpectedBlock match { case Some(missedBlock) => scheduler.scheduleOnce(WaitRequestedBlockTimeout) { if (blockchain.height == prevState.epochInfo.number) { @@ -569,8 +592,8 @@ class ELUpdater( } } - private def followChainAndStartMining[CS <: ChainStatus]( - prevState: Working[CS], + private def followChainAndStartMining( + prevState: Working[ChainStatus], newEpochInfo: EpochInfo, prevChainId: Long, finalizedEcBlock: EcBlock, @@ -589,7 +612,7 @@ class ELUpdater( } } - private def updateMiningState(prevState: Working[Mining]): Unit = { + private def updateMiningState(prevState: Working[Mining], finalizedBlock: ContractBlock, options: ChainContractOptions): Unit = { chainContractClient.getMainChainInfo match { case Some(mainChainInfo) => val newChainInfo = prevState.chainStatus.nodeChainInfo match { @@ -601,8 +624,10 @@ class ELUpdater( setState( "13", prevState.copy( + finalizedBlock = finalizedBlock, mainChainInfo = mainChainInfo, chainStatus = prevState.chainStatus.copy(nodeChainInfo = newChainInfo.fold(prevState.chainStatus.nodeChainInfo)(Right(_))), + options = options, returnToMainChainInfo = prevState.returnToMainChainInfo.filter(rInfo => !newChainInfo.map(_.id).contains(rInfo.chainId) && rInfo.chainId == mainChainInfo.id) ) @@ -613,7 +638,7 @@ class ELUpdater( } } - private def updateWorkingState[CS <: ChainStatus](prevState: Working[CS]): Unit = { + private def updateWorkingState(prevState: Working[ChainStatus]): Unit = { val finalizedBlock = chainContractClient.getFinalizedBlock val options = chainContractClient.getOptions logger.debug(s"Finalized block is ${finalizedBlock.hash}") @@ -677,7 +702,7 @@ class ELUpdater( finalizedBlock, options ) - case m: Mining => updateMiningState(prevState.copy(chainStatus = m)) + case m: Mining => updateMiningState(prevState.copy(chainStatus = m), finalizedBlock, options) case WaitForNewChain(chainSwitchInfo) => val newChainInfo = findAltChain(chainSwitchInfo.prevChainId, chainSwitchInfo.referenceBlock.hash) newChainInfo.foreach { chainInfo => @@ -685,8 +710,8 @@ class ELUpdater( } } } - fullValidation() - requestMainChainBlock(finalizedBlock) + validateAppliedBlocks() + requestMainChainBlock() case Right(None) => logger.trace(s"Finalized block ${finalizedBlock.hash} is not in EC, requesting from peers") setState("19", WaitingForSyncHead(finalizedBlock, requestAndProcessBlock(finalizedBlock.hash))) @@ -717,48 +742,39 @@ class ELUpdater( maybeRequestNextBlock(newState, finalizedBlock) } - private def requestBlock(hash: BlockHash): BlockRequestResult = { - logger.debug(s"Requesting block $hash") - engineApiClient.getBlockByHash(hash) match { + private def requestBlock(contractBlock: ContractBlock): BlockRequestResult = { + logger.debug(s"Requesting block ${contractBlock.hash}") + engineApiClient.getBlockByHash(contractBlock.hash) match { case Right(Some(block)) => BlockRequestResult.BlockExists(block) case Right(None) => - requestAndProcessBlock(hash) - BlockRequestResult.Requested(hash) + requestAndProcessBlock(contractBlock.hash) + BlockRequestResult.Requested(contractBlock) case Left(err) => - logger.warn(s"Failed to get block $hash by hash: ${err.message}") - requestAndProcessBlock(hash) - BlockRequestResult.Requested(hash) + logger.warn(s"Failed to get block ${contractBlock.hash} by hash: ${err.message}") + requestAndProcessBlock(contractBlock.hash) + BlockRequestResult.Requested(contractBlock) } } - private def requestMainChainBlock(finalizedBlock: ContractBlock): Unit = { + private def requestMainChainBlock(): Unit = { state match { - case w: Working[?] => + case w: Working[ChainStatus] => w.returnToMainChainInfo.foreach { returnToMainChainInfo => if (w.mainChainInfo.id == returnToMainChainInfo.chainId) { - requestBlock(returnToMainChainInfo.missedEcBlockHash) match { + requestBlock(returnToMainChainInfo.missedBlock) match { case BlockRequestResult.BlockExists(block) => - (for { - _ <- confirmBlock(block, finalizedBlock) - mainChainInfo <- chainContractClient - .getChainInfo(returnToMainChainInfo.chainId) - .toRight(s"Failed to get chain ${returnToMainChainInfo.chainId} info: not found") - } yield mainChainInfo) match { - case Right(mainChainInfo) => - followChainAndRequestNextBlock( - w.epochInfo, - mainChainInfo, - block, - mainChainInfo, - finalizedBlock, - w.fullValidationStatus, - w.options, - None - ) - logger.info(s"Successfully confirmed block ${block.hash} of chain ${returnToMainChainInfo.chainId}") - fullValidation() + logger.debug(s"Block ${returnToMainChainInfo.missedBlock.hash} exists at execution chain, trying to validate") + validateAppliedBlock(returnToMainChainInfo.missedBlock, block, w) match { + case Right(updatedState) => + logger.debug(s"Missed block ${block.hash} of main chain ${returnToMainChainInfo.chainId} was successfully validated") + chainContractClient.getChainInfo(returnToMainChainInfo.chainId) match { + case Some(mainChainInfo) => + confirmBlockAndFollowChain(block, updatedState, mainChainInfo, None) + case _ => + logger.error(s"Failed to get chain ${returnToMainChainInfo.chainId} info: not found") + } case Left(err) => - logger.error(s"Failed to confirm block ${block.hash} of chain ${returnToMainChainInfo.chainId}: $err") + logger.debug(s"Missed block ${block.hash} of main chain ${returnToMainChainInfo.chainId} validation error: ${err.message}") } case BlockRequestResult.Requested(_) => } @@ -775,8 +791,8 @@ class ELUpdater( }(globalScheduler) } - private def updateToFollowChain[C <: ChainStatus]( - prevState: Working[C], + private def updateToFollowChain( + prevState: Working[ChainStatus], epochInfo: EpochInfo, prevChainId: Long, finalizedEcBlock: EcBlock, @@ -800,7 +816,7 @@ class ELUpdater( } } - def followChain[CS <: ChainStatus]( + def followChain( nodeChainInfo: ChainInfo, lastEcBlock: EcBlock, mainChainInfo: ChainInfo, @@ -888,7 +904,11 @@ class ELUpdater( chainContractClient.getMainChainInfo match { case Some(mainChainInfo) => logger.trace(s"Following main chain ${mainChainInfo.id}") - val fullValidationStatus = FullValidationStatus(validated = Set(finalizedBlockHash), lastElWithdrawalIndex = None) + val fullValidationStatus = + FullValidationStatus( + lastValidatedBlock = target, + lastElWithdrawalIndex = None + ) followChainAndRequestNextBlock( newEpochInfo, mainChainInfo, @@ -912,255 +932,191 @@ class ELUpdater( logger.debug(s"Unexpected state on sync: $other") }) - private def checkSignature(block: NetworkL2Block, isConfirmed: Boolean): Either[String, Unit] = { - if (isConfirmed) { - Right(()) - } else { - for { - signature <- Either.fromOption(block.signature, s"signature not found") - publicKey <- Either.fromOption( - chainContractClient.getMinerPublicKey(block.minerRewardL2Address), - s"public key for block miner ${block.minerRewardL2Address} not found" - ) - _ <- Either.cond( - crypto.verify(signature, Json.toBytes(block.payload), publicKey, checkWeakPk = true), + private def validateRandao(block: EcBlock, epochNumber: Int): JobResult[Unit] = + blockchain.vrf(epochNumber) match { + case None => ClientError(s"VRF of $epochNumber epoch is empty").asLeft + case Some(vrf) => + val expectedPrevRandao = calculateRandao(vrf, block.parentHash) + Either.cond( + expectedPrevRandao == block.prevRandao, (), - s"invalid signature" - ) - } yield () - } - } - - private def preValidateWithdrawals( - networkBlock: NetworkL2Block, - epochInfo: EpochInfo, - contractBlock: Option[ContractBlock] - ): Either[String, PreValidationResult] = { - - lazy val isEpochFirstOnContract = contractBlock.fold(false) { block => - val parentBlock = chainContractClient - .getBlock(networkBlock.parentHash) - .getOrElse( - throw new RuntimeException(s"Can't find parent block ${networkBlock.parentHash} of ${networkBlock.hash} on chain contract") + ClientError(s"expected prevRandao $expectedPrevRandao, got ${block.prevRandao}, VRF=$vrf of $epochNumber") ) - parentBlock.epoch < block.epoch } - lazy val isEpochFirstBlock = isEpochFirstOnContract || epochInfo.prevEpochLastBlockHash.contains(networkBlock.parentHash) - val expectReward = !networkBlock.referencesGenesis && isEpochFirstBlock - - if (expectReward) - Either.cond( - networkBlock.withdrawals.nonEmpty, - PreValidationResult(expectReward = true), - s"expected at least one withdrawal, but got none. " + - s"References genesis? ${networkBlock.referencesGenesis}. Is new epoch? $isEpochFirstBlock" - ) - else // We don't assert that there is no withdrawals, because of possible users' withdrawals - PreValidationResult(expectReward = false).asRight - } - - private def preValidateBlock( - networkBlock: NetworkL2Block, - expectedParent: EcBlock, - epochInfo: EpochInfo, - contractBlock: Option[ContractBlock] - ): Either[String, PreValidationResult] = { - val now = time.correctedTime() / 1000 - val isConfirmed = contractBlock.nonEmpty - (for { - _ <- Either.cond( - networkBlock.parentHash == expectedParent.hash, - (), - s"block doesn't reference expected execution block ${expectedParent.hash}" - ) - _ <- Either.cond( - networkBlock.timestamp - now <= MaxTimeDrift, - (), - s"block from future: new block timestamp=${networkBlock.timestamp}, now=$now, Δ${networkBlock.timestamp - now}s" - ) - _ <- Either.cond( - isConfirmed || networkBlock.minerRewardL2Address == epochInfo.rewardAddress, - (), - s"block miner ${networkBlock.minerRewardL2Address} doesn't equal to ${epochInfo.rewardAddress}" - ) - _ <- checkSignature(networkBlock, isConfirmed) - _ <- if (contractBlock.isEmpty) validateRandao(networkBlock, epochInfo.number) else Either.unit - result <- preValidateWithdrawals(networkBlock, epochInfo, contractBlock) - } yield result).leftMap { err => - s"Block ${networkBlock.hash} validation error: $err, ignoring block" + private def validateMiner(block: NetworkL2Block, epochInfo: Option[EpochInfo]): JobResult[Unit] = { + epochInfo match { + case Some(epochMeta) => + for { + _ <- Either.cond( + block.minerRewardL2Address == epochMeta.rewardAddress, + (), + ClientError(s"block miner ${block.minerRewardL2Address} doesn't equal to ${epochMeta.rewardAddress}") + ) + signature <- Either.fromOption(block.signature, ClientError(s"signature not found")) + publicKey <- Either.fromOption( + chainContractClient.getMinerPublicKey(block.minerRewardL2Address), + ClientError(s"public key for block miner ${block.minerRewardL2Address} not found") + ) + _ <- Either.cond( + crypto.verify(signature, Json.toBytes(block.payload), publicKey, checkWeakPk = true), + (), + ClientError(s"invalid signature") + ) + } yield () + case _ => Either.unit } } - private def validateBlock( - networkBlock: NetworkL2Block, - parentEcBlock: EcBlock, - contractBlock: Option[ContractBlock], - expectReward: Boolean, - chainContractOptions: ChainContractOptions - ): Job[Unit] = { - (for { - _ <- validateTimestamp(networkBlock, parentEcBlock) - _ <- validateWithdrawals(networkBlock, parentEcBlock, expectReward, chainContractOptions) - _ <- contractBlock.fold(Either.unit[String])(cb => validateRandao(networkBlock, cb.epoch)) - } yield ()).leftMap(err => ClientError(s"Network block ${networkBlock.hash} validation error: $err")) - } - - private def validateTimestamp(newNetworkBlock: NetworkL2Block, parentEcBlock: EcBlock): Either[String, Unit] = { + private def validateTimestamp(newNetworkBlock: NetworkL2Block, parentEcBlock: EcBlock): JobResult[Unit] = { val minAppendTs = parentEcBlock.timestamp + config.blockDelay.toSeconds Either.cond( newNetworkBlock.timestamp >= minAppendTs, (), - s"Timestamp (${newNetworkBlock.timestamp}) of appended block must be greater or equal $minAppendTs, " + - s"Δ${minAppendTs - newNetworkBlock.timestamp}s" + ClientError( + s"timestamp (${newNetworkBlock.timestamp}) of appended block must be greater or equal $minAppendTs, " + + s"Δ${minAppendTs - newNetworkBlock.timestamp}s" + ) ) } - private def validateWithdrawals( + private def preValidateBlock( networkBlock: NetworkL2Block, - parentEcBlock: EcBlock, - expectReward: Boolean, - chainContractOptions: ChainContractOptions - ): Either[String, Unit] = { - if (expectReward) { - for { - minerReward <- networkBlock.withdrawals.headOption.toRight(s"Expected at least one withdrawal (reward), got none") - _ <- Either.cond( - minerReward.address == parentEcBlock.minerRewardL2Address, - (), - s"Unexpected reward address: ${minerReward.address}, expected: ${parentEcBlock.minerRewardL2Address}" - ) - _ <- Either.cond( - minerReward.amount == chainContractOptions.miningReward, - (), - s"Unexpected reward amount: ${minerReward.amount}, expected: ${chainContractOptions.miningReward}" - ) - } yield () - } else Either.unit[String] + parentBlock: EcBlock, + epochInfo: Option[EpochInfo] + ): JobResult[Unit] = { + for { + _ <- validateTimestamp(networkBlock, parentBlock) + _ <- validateMiner(networkBlock, epochInfo) + _ <- engineApiClient.applyNewPayload(networkBlock.payload) + } yield () } - private def findAltChainReferenceHash( - epochInfo: EpochInfo, - nodeChainInfo: ChainInfo, - lastEcBlock: EcBlock, - finalizedBlock: ContractBlock, - invalidBlockHash: BlockHash - ): ContractBlock = { - @tailrec - def loop(curBlock: ContractBlock, invalidBlockEpoch: Int): ContractBlock = { - if (curBlock.height == finalizedBlock.height) { - curBlock - } else { - chainContractClient.getBlock(curBlock.hash) match { - case Some(block) if block.epoch < invalidBlockEpoch => curBlock - case Some(_) => - loop(chainContractClient.getBlock(curBlock.parentHash).getOrElse(finalizedBlock), invalidBlockEpoch) - case _ => finalizedBlock - } - } - } - + private def getAltChainReferenceBlock(nodeChainInfo: ChainInfo, lastContractBlock: ContractBlock): JobResult[ContractBlock] = { if (nodeChainInfo.isMain) { - val startBlock = chainContractClient.getBlock(lastEcBlock.hash).getOrElse(nodeChainInfo.lastBlock) - val invalidBlockEpoch = chainContractClient.getBlock(invalidBlockHash).map(_.epoch).getOrElse(epochInfo.number) - - loop(startBlock, invalidBlockEpoch) + for { + lastEpoch <- chainContractClient + .getEpochMeta(lastContractBlock.epoch) + .toRight( + ClientError( + s"Can't find the epoch #${lastContractBlock.epoch} metadata of invalid block ${lastContractBlock.hash} on contract" + ) + ) + prevEpoch <- chainContractClient + .getEpochMeta(lastEpoch.prevEpoch) + .toRight(ClientError(s"Can't find a previous epoch #${lastEpoch.prevEpoch} metadata on contract")) + referenceBlockHash = prevEpoch.lastBlockHash + referenceBlock <- chainContractClient + .getBlock(referenceBlockHash) + .toRight(ClientError(s"Can't find a last block $referenceBlockHash of epoch #${lastEpoch.prevEpoch} on contract")) + } yield referenceBlock } else { val blockId = nodeChainInfo.firstBlock.parentHash - chainContractClient.getBlock(blockId) match { - case Some(block) => block - case None => - logger.warn(s"Parent block $blockId for first block ${nodeChainInfo.firstBlock.hash} of chain ${nodeChainInfo.id} not found at contract") - finalizedBlock - } + chainContractClient + .getBlock(blockId) + .toRight( + ClientError(s"Parent block $blockId for first block ${nodeChainInfo.firstBlock.hash} of chain ${nodeChainInfo.id} not found at contract") + ) } } - private def validateAndApply[CS <: ChainStatus]( + private def validateAndApplyMissedBlock( networkBlock: NetworkL2Block, ch: Channel, - prevState: Working[CS], - expectedParent: EcBlock, - nodeChainInfo: ChainInfo, - returnToMainChainInfo: Option[ReturnToMainChainInfo], - ignoreInvalid: Boolean + prevState: Working[ChainStatus], + contractBlock: ContractBlock, + parentBlock: EcBlock, + nodeChainInfo: ChainInfo ): Unit = { - val contractBlock = chainContractClient.getBlock(networkBlock.hash) - preValidateBlock(networkBlock, expectedParent, prevState.epochInfo, contractBlock) match { + validateBlockFull(networkBlock, contractBlock, parentBlock, prevState) match { + case Right(updatedState) => + logger.debug(s"Missed block ${networkBlock.hash} of main chain ${nodeChainInfo.id} was successfully validated") + broadcastAndConfirmBlock(networkBlock, ch, updatedState, nodeChainInfo, None) case Left(err) => - logger.debug(err) - case Right(preValidationResult) => - val applyResult = for { - _ <- validateBlock(networkBlock, expectedParent, contractBlock, preValidationResult.expectReward, prevState.options) - _ = logger.debug(s"Block ${networkBlock.hash} was partially validated, trying to apply and broadcast") - _ <- engineApiClient.applyNewPayload(networkBlock.payload) - } yield () - applyResult match { + logger.debug(s"Missed block ${networkBlock.hash} of main chain ${nodeChainInfo.id} validation error: ${err.message}, ignoring block") + } + } + + private def validateAndApply( + networkBlock: NetworkL2Block, + ch: Channel, + prevState: Working[ChainStatus], + parentBlock: EcBlock, + nodeChainInfo: ChainInfo, + returnToMainChainInfo: Option[ReturnToMainChainInfo] + ): Unit = { + chainContractClient.getBlock(networkBlock.hash) match { + case Some(contractBlock) if prevState.fullValidationStatus.lastValidatedBlock.hash == parentBlock.hash => + // all blocks before current was fully validated, so we can perform full validation of this block + validateBlockFull(networkBlock, contractBlock, parentBlock, prevState) match { + case Right(updatedState) => + logger.debug(s"Block ${networkBlock.hash} was successfully validated") + broadcastAndConfirmBlock(networkBlock, ch, updatedState, nodeChainInfo, returnToMainChainInfo) case Left(err) => - if (ignoreInvalid) { - logger.debug(s"Ignoring invalid block ${networkBlock.hash}: ${err.message}") - } else { - logger.error(err.message) - - val lastValidBlock = - findAltChainReferenceHash(prevState.epochInfo, nodeChainInfo, expectedParent, prevState.finalizedBlock, networkBlock.hash) - rollbackTo(prevState, lastValidBlock, prevState.finalizedBlock).fold( - err => logger.error(err.message), - updatedState => - findAltChain(nodeChainInfo.id, lastValidBlock.hash) match { - case Some(altChainInfo) => - setState( - "20", - updatedState.copy( - chainStatus = FollowingChain(altChainInfo, None), - returnToMainChainInfo = if (nodeChainInfo.isMain) None else updatedState.returnToMainChainInfo - ) - ) - case _ => - setState( - "21", - updatedState.copy( - chainStatus = WaitForNewChain(ChainSwitchInfo(nodeChainInfo.id, lastValidBlock)), - returnToMainChainInfo = if (nodeChainInfo.isMain) None else updatedState.returnToMainChainInfo - ) - ) - } - ) - } - case _ => - Try(allChannels.broadcast(networkBlock, Some(ch))).recover { err => - logger.error(s"Failed to broadcast block ${networkBlock.hash}: ${err.getMessage}") - } + logger.debug(s"Block ${networkBlock.hash} validation error: ${err.message}") + processInvalidBlock(contractBlock, prevState, Some(nodeChainInfo)) + } + case contractBlock => + // we should check block miner based on epochInfo if block is not at contract yet + val epochInfo = if (contractBlock.isEmpty) Some(prevState.epochInfo) else None + + preValidateBlock(networkBlock, parentBlock, epochInfo) match { + case Right(_) => + logger.debug(s"Block ${networkBlock.hash} was successfully partially validated") + broadcastAndConfirmBlock(networkBlock, ch, prevState, nodeChainInfo, returnToMainChainInfo) + case Left(err) => + logger.error(s"Block ${networkBlock.hash} prevalidation error: ${err.message}, ignoring block") + } + } + } - val finalizedBlock = prevState.finalizedBlock - confirmBlock(networkBlock, finalizedBlock) - .fold[Unit]( - err => logger.error(s"Can't confirm block ${networkBlock.hash} of chain ${nodeChainInfo.id}: ${err.message}"), - _ => { - logger.info(s"Successfully applied block ${networkBlock.hash} to chain ${nodeChainInfo.id}") - followChainAndRequestNextBlock( - prevState.epochInfo, - nodeChainInfo, - networkBlock.toEcBlock, - prevState.mainChainInfo, - finalizedBlock, - prevState.fullValidationStatus, - prevState.options, - returnToMainChainInfo - ) - fullValidation() - } - ) + private def confirmBlockAndFollowChain( + block: EcBlock, + prevState: Working[ChainStatus], + nodeChainInfo: ChainInfo, + returnToMainChainInfo: Option[ReturnToMainChainInfo] + ): Unit = { + val finalizedBlock = prevState.finalizedBlock + confirmBlock(block, finalizedBlock) + .fold[Unit]( + err => logger.error(s"Can't confirm block ${block.hash} of chain ${nodeChainInfo.id}: ${err.message}"), + _ => { + logger.info(s"Successfully confirmed block ${block.hash} of chain ${nodeChainInfo.id}") + followChainAndRequestNextBlock( + prevState.epochInfo, + nodeChainInfo, + block, + prevState.mainChainInfo, + finalizedBlock, + prevState.fullValidationStatus, + prevState.options, + returnToMainChainInfo + ) + validateAppliedBlocks() } + ) + } + + private def broadcastAndConfirmBlock( + networkBlock: NetworkL2Block, + ch: Channel, + prevState: Working[ChainStatus], + nodeChainInfo: ChainInfo, + returnToMainChainInfo: Option[ReturnToMainChainInfo] + ): Unit = { + Try(allChannels.broadcast(networkBlock, Some(ch))).recover { err => + logger.error(s"Failed to broadcast block ${networkBlock.hash}: ${err.getMessage}") } + + confirmBlockAndFollowChain(networkBlock.toEcBlock, prevState, nodeChainInfo, returnToMainChainInfo) } - private def findBlockChild(parent: BlockHash, lastBlockHash: BlockHash): Either[String, BlockHash] = { + private def findBlockChild(parent: BlockHash, lastBlockHash: BlockHash): Either[String, ContractBlock] = { @tailrec - def loop(b: BlockHash): Option[BlockHash] = chainContractClient.getBlock(b) match { + def loop(b: BlockHash): Option[ContractBlock] = chainContractClient.getBlock(b) match { case None => None case Some(cb) => - if (cb.parentHash == parent) Some(cb.hash) + if (cb.parentHash == parent) Some(cb) else loop(cb.parentHash) } @@ -1175,24 +1131,24 @@ class ELUpdater( case Left(error) => logger.error(s"Could not find child of ${prevState.lastEcBlock.hash} on contract: $error") prevState - case Right(hash) => - requestBlock(hash) match { - case BlockRequestResult.BlockExists(block) => - logger.debug(s"Block $hash exists at EC chain, trying to confirm") - confirmBlock(block, finalizedBlock) match { + case Right(contractBlock) => + requestBlock(contractBlock) match { + case BlockRequestResult.BlockExists(ecBlock) => + logger.debug(s"Block ${contractBlock.hash} exists at EC chain, trying to confirm") + confirmBlock(ecBlock, finalizedBlock) match { case Right(_) => val newState = prevState.copy( - lastEcBlock = block, + lastEcBlock = ecBlock, chainStatus = FollowingChain(prevState.chainStatus.nodeChainInfo, None) ) setState("7", newState) maybeRequestNextBlock(newState, finalizedBlock) case Left(err) => - logger.error(s"Failed to confirm next block ${block.hash}: ${err.message}") + logger.error(s"Failed to confirm next block ${ecBlock.hash}: ${err.message}") prevState } - case BlockRequestResult.Requested(hash) => - val newState = prevState.copy(chainStatus = prevState.chainStatus.copy(nextExpectedEcBlock = Some(hash))) + case BlockRequestResult.Requested(contractBlock) => + val newState = prevState.copy(chainStatus = prevState.chainStatus.copy(nextExpectedBlock = Some(contractBlock))) setState("8", newState) newState } @@ -1203,7 +1159,7 @@ class ELUpdater( } } - private def mkRollbackBlock(rollbackTargetBlockId: BlockHash): Job[RollbackBlock] = for { + private def mkRollbackBlock(rollbackTargetBlockId: BlockHash): JobResult[RollbackBlock] = for { targetBlockFromContract <- Right(chainContractClient.getBlock(rollbackTargetBlockId)) targetBlockOpt <- targetBlockFromContract match { case None => engineApiClient.getBlockByHash(rollbackTargetBlockId) @@ -1222,7 +1178,7 @@ class ELUpdater( Withdrawal(index, x.destElAddress, Bridge.clToGweiNativeTokenAmount(x.amount)) } - private def getLastWithdrawalIndex(hash: BlockHash): Job[WithdrawalIndex] = + private def getLastWithdrawalIndex(hash: BlockHash): JobResult[WithdrawalIndex] = engineApiClient.getBlockByHash(hash).flatMap { case None => Left(ClientError(s"Can't find $hash block on EC during withdrawal search")) case Some(ecBlock) => @@ -1234,7 +1190,7 @@ class ELUpdater( } } - private def getElToClTransfersRootHash(hash: BlockHash, elBridgeAddress: EthAddress): Job[Digest] = + private def getE2CTransfersRootHash(hash: BlockHash, elBridgeAddress: EthAddress): JobResult[Digest] = for { elRawLogs <- engineApiClient.getLogs(hash, elBridgeAddress, Bridge.ElSentNativeEventTopic) rootHash <- { @@ -1252,46 +1208,62 @@ class ELUpdater( } } yield rootHash - private def fullValidation(): Unit = { - (state match { - case w: Working[?] => - getBlockForValidation(w.lastContractBlock, w.lastEcBlock, w.finalizedBlock, w.fullValidationStatus).flatMap { - case BlockForValidation.Found(contractBlock, ecBlock) => fullBlockValidation(contractBlock, ecBlock, w) - case BlockForValidation.SkippedFinalized(block) => - logger.debug(s"Full validation of ${block.hash} skipped, because of finalization") - setState("4", w.copy(fullValidationStatus = w.fullValidationStatus.copy(validated = Set(block.hash), lastElWithdrawalIndex = None))) - Either.unit - case BlockForValidation.NotFound => Either.unit - } + private def skipFinalizedBlocksValidation(curState: Working[ChainStatus]) = { + if (curState.finalizedBlock.height > curState.fullValidationStatus.lastValidatedBlock.height) { + val newState = curState.copy(fullValidationStatus = FullValidationStatus(curState.finalizedBlock, None)) + setState("4", newState) + newState + } else curState + } + + private def validateAppliedBlocks(): Unit = { + state match { + case w: Working[ChainStatus] => + val startState = skipFinalizedBlocksValidation(w) + getContractBlocksForValidation(startState).fold[Unit]( + err => logger.error(s"Validation of applied blocks error: ${err.message}"), + blocksToValidate => + blocksToValidate.foldLeft[JobResult[Working[ChainStatus]]](Right(startState)) { + case (Right(curState), block) => + logger.debug(s"Trying to validate applied block ${block.hash}") + validateAppliedBlock(block.contractBlock, block.ecBlock, curState) match { + case Right(updatedState) => + logger.debug(s"Block ${block.hash} was successfully validated") + Right(updatedState) + case Left(err) => + logger.debug(s"Validation of applied block ${block.hash} failed: ${err.message}") + processInvalidBlock(block.contractBlock, curState, None) + Left(err) + } + case (err, _) => err + } + ) case other => - logger.debug(s"Skipping full validation: $other") + logger.debug(s"Skipping validation of applied blocks: $other") Either.unit - }).fold( - err => logger.warn(s"Full validation error: ${err.message}"), - identity - ) + } } - private def validateElToClTransfers(contractBlock: ContractBlock, elBridgeAddress: EthAddress): Job[Unit] = - getElToClTransfersRootHash(contractBlock.hash, elBridgeAddress).flatMap { elRootHash => + private def validateE2CTransfers(contractBlock: ContractBlock, elBridgeAddress: EthAddress): JobResult[Unit] = + getE2CTransfersRootHash(contractBlock.hash, elBridgeAddress).flatMap { elRootHash => // elRootHash is the source of true - if (java.util.Arrays.equals(contractBlock.elToClTransfersRootHash, elRootHash)) Either.unit + if (java.util.Arrays.equals(contractBlock.e2cTransfersRootHash, elRootHash)) Either.unit else Left( ClientError( s"EL to CL transfers hash of ${contractBlock.hash} are different: " + s"EL=${toHexNoPrefix(elRootHash)}, " + - s"CL=${toHexNoPrefix(contractBlock.elToClTransfersRootHash)}" + s"CL=${toHexNoPrefix(contractBlock.e2cTransfersRootHash)}" ) ) } - private def fullWithdrawalsValidation( + private def validateWithdrawals( contractBlock: ContractBlock, ecBlock: EcBlock, fullValidationStatus: FullValidationStatus, chainContractOptions: ChainContractOptions - ): Job[Option[WithdrawalIndex]] = { + ): JobResult[Option[WithdrawalIndex]] = { val blockEpoch = chainContractClient .getEpochMeta(contractBlock.epoch) .getOrElse(throw new RuntimeException(s"Can't find an epoch ${contractBlock.epoch} data of block ${contractBlock.hash} on chain contract")) @@ -1313,7 +1285,7 @@ class ELUpdater( if (ecBlock.height - 1 <= EthereumConstants.GenesisBlockHeight) Right(-1L) else getLastWithdrawalIndex(ecBlock.parentHash) } - lastElWithdrawalIndex <- validateClToElTransfers( + lastElWithdrawalIndex <- validateC2ETransfers( ecBlock, contractBlock, prevMinerElRewardAddress, @@ -1324,113 +1296,121 @@ class ELUpdater( } yield Some(lastElWithdrawalIndex) } + private def validateBlockFull( + networkBlock: NetworkL2Block, + contractBlock: ContractBlock, + parentBlock: EcBlock, + prevState: Working[ChainStatus] + ): JobResult[Working[ChainStatus]] = { + logger.debug(s"Trying to do full validation of block ${networkBlock.hash}") + for { + _ <- preValidateBlock(networkBlock, parentBlock, None) + ecBlock = networkBlock.toEcBlock + updatedState <- validateAppliedBlock(contractBlock, ecBlock, prevState) + } yield updatedState + } + // Note: we can not do this validation before block application, because we need block logs - private def fullBlockValidation[CS <: ChainStatus]( + private def validateAppliedBlock( contractBlock: ContractBlock, ecBlock: EcBlock, - prevState: Working[CS] - ): Job[Unit] = { - logger.debug(s"Full validation of ${contractBlock.hash}") - val validation = for { - _ <- Either.cond( - contractBlock.minerRewardL2Address == ecBlock.minerRewardL2Address, - (), - ClientError(s"Miner in EC block ${ecBlock.minerRewardL2Address} should be equal to miner on contract ${contractBlock.minerRewardL2Address}") + prevState: Working[ChainStatus] + ): JobResult[Working[ChainStatus]] = { + val validationResult = + for { + _ <- Either.cond( + contractBlock.minerRewardL2Address == ecBlock.minerRewardL2Address, + (), + ClientError(s"Miner in EC block ${ecBlock.minerRewardL2Address} should be equal to miner on contract ${contractBlock.minerRewardL2Address}") + ) + _ <- validateE2CTransfers(contractBlock, prevState.options.elBridgeAddress) + updatedLastElWithdrawalIndex <- validateWithdrawals(contractBlock, ecBlock, prevState.fullValidationStatus, prevState.options) + _ <- validateRandao(ecBlock, contractBlock.epoch) + } yield updatedLastElWithdrawalIndex + + validationResult.map { lastElWithdrawalIndex => + val newState = prevState.copy(fullValidationStatus = + FullValidationStatus( + lastValidatedBlock = contractBlock, + lastElWithdrawalIndex = lastElWithdrawalIndex + ) ) - _ <- validateElToClTransfers(contractBlock, prevState.options.elBridgeAddress) - updatedLastElWithdrawalIndex <- fullWithdrawalsValidation(contractBlock, ecBlock, prevState.fullValidationStatus, prevState.options) - } yield updatedLastElWithdrawalIndex + setState("5", newState) + newState + } + } - validation - .map { lastElWithdrawalIndex => - setState( - "5", - prevState.copy(fullValidationStatus = - FullValidationStatus( - validated = Set(contractBlock.hash), - lastElWithdrawalIndex = lastElWithdrawalIndex - ) - ) - ) - } - .recoverWith { e => - logger.debug(s"Full validation of ${contractBlock.hash} failed: ${e.message}") - chainContractClient.getChainInfo(contractBlock.chainId) match { - case Some(nodeChainInfo) if canSupportAnotherAltChain(nodeChainInfo) => - for { - lastEpoch <- chainContractClient - .getEpochMeta(contractBlock.epoch) - .toRight( - ClientError( - s"Impossible case: can't find the epoch #${contractBlock.epoch} metadata of invalid block ${contractBlock.hash} on contract" - ) - ) - prevEpoch <- chainContractClient - .getEpochMeta(lastEpoch.prevEpoch) - .toRight(ClientError(s"Impossible case: can't find a previous epoch #${lastEpoch.prevEpoch} metadata on contract")) - toBlockHash = prevEpoch.lastBlockHash - toBlock <- chainContractClient - .getBlock(toBlockHash) - .toRight(ClientError(s"Impossible case: can't find a last block $toBlockHash of epoch #${lastEpoch.prevEpoch} on contract")) - updatedState <- rollbackTo(prevState, toBlock, prevState.finalizedBlock) - lastValidBlock <- chainContractClient - .getBlock(updatedState.lastEcBlock.hash) - .toRight(ClientError(s"Block ${updatedState.lastEcBlock.hash} not found at contract")) - } yield { - setState( - "6", - updatedState.copy( - fullValidationStatus = FullValidationStatus( - validated = Set(toBlockHash, contractBlock.hash), - lastElWithdrawalIndex = None - ), - chainStatus = WaitForNewChain(ChainSwitchInfo(contractBlock.chainId, lastValidBlock)), - returnToMainChainInfo = None - ) + private def processInvalidBlock( + contractBlock: ContractBlock, + prevState: Working[ChainStatus], + nodeChainInfo: Option[ChainInfo] + ): Unit = { + nodeChainInfo.orElse(chainContractClient.getChainInfo(contractBlock.chainId)) match { + case Some(chainInfo) if canSupportAnotherAltChain(chainInfo) => + (for { + referenceBlock <- getAltChainReferenceBlock(chainInfo, contractBlock) + updatedState <- rollbackTo(prevState, referenceBlock, prevState.finalizedBlock) + lastValidBlock <- chainContractClient + .getBlock(updatedState.lastEcBlock.hash) + .toRight(ClientError(s"Block ${updatedState.lastEcBlock.hash} not found at contract")) + } yield { + findAltChain(chainInfo.id, lastValidBlock.hash) match { + case Some(altChainInfo) => + val newState = updatedState.copy( + chainStatus = FollowingChain(altChainInfo, None), + returnToMainChainInfo = if (chainInfo.isMain) None else updatedState.returnToMainChainInfo + ) + setState("20", newState) + newState + case _ => + val newState = updatedState.copy( + chainStatus = WaitForNewChain(ChainSwitchInfo(chainInfo.id, lastValidBlock)), + returnToMainChainInfo = if (chainInfo.isMain) None else updatedState.returnToMainChainInfo ) + setState("21", newState) + newState + } + }).fold( + err => logger.error(err.message), + _ => () + ) + case Some(_) => + logger.debug(s"Can't support another alt chain: ignoring invalid block ${contractBlock.hash}") + case _ => + logger.error(s"Chain ${contractBlock.chainId} meta not found at contract") + } + } + + private def getContractBlocksForValidation(curState: Working[ChainStatus]): JobResult[List[BlockForValidation]] = { + @tailrec + def loop(curBlock: ContractBlock, acc: List[BlockForValidation]): JobResult[List[BlockForValidation]] = { + if (curBlock.height <= curState.fullValidationStatus.lastValidatedBlock.height || curBlock.height <= curState.finalizedBlock.height) { + Right(acc) + } else { + chainContractClient.getBlock(curBlock.parentHash) match { + case Some(parentBlock) => + if (curBlock.height > curState.lastEcBlock.height) { + loop(parentBlock, acc) + } else { + engineApiClient.getBlockByHash(curBlock.hash) match { + case Right(Some(ecBlock)) => + loop(parentBlock, BlockForValidation(curBlock, ecBlock) :: acc) + case Right(None) => + Left(ClientError(s"Block ${curBlock.hash} not found on EC client for full validation")) + case Left(err) => + Left(ClientError(s"Can't get EC block ${curBlock.hash} for full validation: ${err.message}")) + } } - case Some(_) => - logger.debug(s"Ignoring invalid block ${contractBlock.hash}") - Either.unit case _ => - logger.warn(s"Chain ${contractBlock.chainId} meta not found at contract") - Either.unit + Left(ClientError(s"Block ${curBlock.parentHash} not found at contract during full validation")) } } - } + } - private def getBlockForValidation( - lastContractBlock: ContractBlock, - lastEcBlock: EcBlock, - finalizedBlock: ContractBlock, - fullValidationStatus: FullValidationStatus - ): Job[BlockForValidation] = { - if (lastContractBlock.height <= lastEcBlock.height) { - // Checking height on else to avoid unnecessary logs - if (fullValidationStatus.validated.contains(lastContractBlock.hash)) Right(BlockForValidation.NotFound) - else if (lastContractBlock.height <= finalizedBlock.height) Right(BlockForValidation.SkippedFinalized(lastContractBlock)) - else - engineApiClient - .getBlockByHash(lastContractBlock.hash) - .map { - case Some(ecBlock) => BlockForValidation.Found(lastContractBlock, ecBlock) - case None => - logger.debug(s"Can't find a block ${lastContractBlock.hash} on EC client for a full validation") - BlockForValidation.NotFound - } - } else - Right { - if (fullValidationStatus.validated.contains(lastEcBlock.hash)) BlockForValidation.NotFound - else if (lastEcBlock.height <= finalizedBlock.height) BlockForValidation.SkippedFinalized(lastEcBlock) - else - chainContractClient.getBlock(lastEcBlock.hash) match { - case None => BlockForValidation.NotFound - case Some(contractBlock) => BlockForValidation.Found(contractBlock, lastEcBlock) - } - } + loop(curState.lastContractBlock, List.empty) } - private def validateClToElTransfers( + private def validateC2ETransfers( ecBlock: EcBlock, contractBlock: ContractBlock, prevMinerElRewardAddress: Option[EthAddress], @@ -1442,8 +1422,8 @@ class ELUpdater( .getOrElse(throw new RuntimeException(s"Can't find a parent block ${contractBlock.parentHash} of block ${contractBlock.hash}")) val expectedTransfers = chainContractClient.getNativeTransfers( - parentContractBlock.lastClToElTransferIndex + 1, - contractBlock.lastClToElTransferIndex - parentContractBlock.lastClToElTransferIndex + parentContractBlock.lastC2ETransferIndex + 1, + contractBlock.lastC2ETransferIndex - parentContractBlock.lastC2ETransferIndex ) val firstWithdrawalIndex = elWithdrawalIndexBefore + 1 @@ -1461,11 +1441,11 @@ class ELUpdater( (rewardWithdrawal +: userWithdrawals).asRight } else s"Expected ${expectedTransfers.size + 1} (at least reward) withdrawals, got ${ecBlock.withdrawals.size}".asLeft } - _ <- validateClToElTransfers(ecBlock, expectedWithdrawals) + _ <- validateC2ETransfers(ecBlock, expectedWithdrawals) } yield expectedWithdrawals.lastOption.fold(elWithdrawalIndexBefore)(_.index) } - private def validateClToElTransfers(ecBlock: EcBlock, expectedWithdrawals: Seq[Withdrawal]): Either[String, Unit] = + private def validateC2ETransfers(ecBlock: EcBlock, expectedWithdrawals: Seq[Withdrawal]): Either[String, Unit] = ecBlock.withdrawals .zip(expectedWithdrawals) .zipWithIndex @@ -1491,12 +1471,12 @@ class ELUpdater( } .map(_ => ()) - private def confirmBlock(block: L2BlockLike, finalizedBlock: L2BlockLike): Job[String] = { + private def confirmBlock(block: L2BlockLike, finalizedBlock: L2BlockLike): JobResult[String] = { val finalizedBlockHash = if (finalizedBlock.height > block.height) block.hash else finalizedBlock.hash engineApiClient.forkChoiceUpdate(block.hash, finalizedBlockHash) } - private def confirmBlock(hash: BlockHash, finalizedBlockHash: BlockHash): Job[String] = + private def confirmBlock(hash: BlockHash, finalizedBlockHash: BlockHash): JobResult[String] = engineApiClient.forkChoiceUpdate(hash, finalizedBlockHash) private def confirmBlockAndStartMining( @@ -1506,7 +1486,7 @@ class ELUpdater( suggestedFeeRecipient: EthAddress, prevRandao: String, withdrawals: Vector[Withdrawal] - ): Job[String] = { + ): JobResult[String] = { val finalizedBlockHash = if (finalizedBlock.height > lastBlock.height) lastBlock.hash else finalizedBlock.hash engineApiClient .forkChoiceUpdateWithPayloadId( @@ -1545,7 +1525,7 @@ object ELUpdater { object State { case object Starting extends State - case class Working[CS <: ChainStatus]( + case class Working[+CS <: ChainStatus]( epochInfo: EpochInfo, lastEcBlock: EcBlock, finalizedBlock: ContractBlock, @@ -1562,14 +1542,14 @@ object ELUpdater { def lastContractBlock: ContractBlock } object ChainStatus { - case class FollowingChain(nodeChainInfo: ChainInfo, nextExpectedEcBlock: Option[BlockHash]) extends ChainStatus { + case class FollowingChain(nodeChainInfo: ChainInfo, nextExpectedBlock: Option[ContractBlock]) extends ChainStatus { override def lastContractBlock: ContractBlock = nodeChainInfo.lastBlock } case class Mining( keyPair: KeyPair, currentPayloadId: String, nodeChainInfo: Either[ChainSwitchInfo, ChainInfo], - lastClToElTransferIndex: Long, + lastC2ETransferIndex: Long, lastElWithdrawalIndex: WithdrawalIndex ) extends ChainStatus { override def lastContractBlock: ContractBlock = nodeChainInfo match { @@ -1591,32 +1571,27 @@ object ELUpdater { case class ChainSwitchInfo(prevChainId: Long, referenceBlock: ContractBlock) - /** We haven't received a EC-block {@link missedEcBlockHash} of a previous epoch when started a mining on a new epoch. We can return to the main - * chain, if get a missed EC-block. + /** 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 + * get a missed EC-block. */ - case class ReturnToMainChainInfo(missedEcBlockHash: BlockHash, missedBlockParent: EcBlock, chainId: Long) + case class ReturnToMainChainInfo(missedBlock: ContractBlock, missedBlockParent: EcBlock, chainId: Long) sealed trait BlockRequestResult private object BlockRequestResult { - case class BlockExists(block: EcBlock) extends BlockRequestResult - case class Requested(hash: BlockHash) extends BlockRequestResult + case class BlockExists(block: EcBlock) extends BlockRequestResult + case class Requested(contractBlock: ContractBlock) extends BlockRequestResult } - private case class MiningData(payloadId: PayloadId, nextBlockUnixTs: Long, lastClToElTransferIndex: Long, lastElWithdrawalIndex: WithdrawalIndex) - - private case class PreValidationResult(expectReward: Boolean) + private case class MiningData(payloadId: PayloadId, nextBlockUnixTs: Long, lastC2ETransferIndex: Long, lastElWithdrawalIndex: WithdrawalIndex) - private sealed trait BlockForValidation - private object BlockForValidation { - case class Found(contractBlock: ContractBlock, ecBlock: EcBlock) extends BlockForValidation - case class SkippedFinalized(block: L2BlockLike) extends BlockForValidation - case object NotFound extends BlockForValidation + private case class BlockForValidation(contractBlock: ContractBlock, ecBlock: EcBlock) { + val hash: BlockHash = contractBlock.hash } - case class FullValidationStatus(validated: Set[BlockHash], lastElWithdrawalIndex: Option[WithdrawalIndex]) { + case class FullValidationStatus(lastValidatedBlock: ContractBlock, lastElWithdrawalIndex: Option[WithdrawalIndex]) { // If we didn't validate the parent block last time, then the index is outdated def checkedLastElWithdrawalIndex(parentBlockHash: BlockHash): Option[WithdrawalIndex] = - lastElWithdrawalIndex.filter(_ => validated.contains(parentBlockHash)) + lastElWithdrawalIndex.filter(_ => parentBlockHash == lastValidatedBlock.hash) } def calculateRandao(hitSource: ByteStr, parentHash: BlockHash): String = { diff --git a/src/main/scala/units/NetworkL2Block.scala b/src/main/scala/units/NetworkL2Block.scala index 3e6cdf45..21b6d5eb 100644 --- a/src/main/scala/units/NetworkL2Block.scala +++ b/src/main/scala/units/NetworkL2Block.scala @@ -38,10 +38,11 @@ class NetworkL2Block private ( height = height, timestamp = timestamp, minerRewardL2Address = minerRewardL2Address, + baseFeePerGas = baseFeePerGas, gasLimit = gasLimit, gasUsed = gasUsed, - withdrawals = withdrawals, - baseFeePerGas = baseFeePerGas + prevRandao = prevRandao, + withdrawals = withdrawals ) override def toString: String = s"NetworkL2Block($hash)" diff --git a/src/main/scala/units/client/contract/ChainContractClient.scala b/src/main/scala/units/client/contract/ChainContractClient.scala index 4c2006dd..3deab4cc 100644 --- a/src/main/scala/units/client/contract/ChainContractClient.scala +++ b/src/main/scala/units/client/contract/ChainContractClient.scala @@ -66,8 +66,8 @@ trait ChainContractClient { val clGenerator = ByteStr(bb.getByteArray(Address.AddressLength)) val chainId = if (bb.remaining() >= 8) bb.getLong() else 0L - val e2CTransfersRootHash = - if (bb.remaining() >= ContractBlock.ElToClTransfersRootHashLength) bb.getByteArray(ContractBlock.ElToClTransfersRootHashLength) + val e2cTransfersRootHash = + if (bb.remaining() >= ContractBlock.E2CTransfersRootHashLength) bb.getByteArray(ContractBlock.E2CTransfersRootHashLength) else Array.emptyByteArray val lastC2ETransferIndex = if (bb.remaining() >= 8) bb.getLong() else -1L @@ -76,7 +76,7 @@ trait ChainContractClient { !bb.hasRemaining, s"Not parsed ${bb.remaining()} bytes from ${blockMeta.base64}, read data: " + s"chainHeight=$chainHeight, epoch=$epoch, parentHash=$parentHash, clGenerator=$clGenerator, chainId=$chainId, " + - s"e2CTransfersRootHash=${HexBytesConverter.toHex(e2CTransfersRootHash)}, lastC2ETransferIndex=$lastC2ETransferIndex" + s"e2cTransfersRootHash=${HexBytesConverter.toHex(e2cTransfersRootHash)}, lastC2ETransferIndex=$lastC2ETransferIndex" ) val minerRewardElAddress = @@ -91,7 +91,7 @@ trait ChainContractClient { clGenerator, minerRewardElAddress, chainId, - e2CTransfersRootHash, + e2cTransfersRootHash, lastC2ETransferIndex ) } catch { @@ -290,7 +290,7 @@ object ChainContractClient { private val BlockHashBytesSize = 32 private val Sep = "," - val MaxClToElTransfers = 16 + val MaxC2ETransfers = 16 private class InconsistentContractData(message: String, cause: Throwable = null) extends IllegalStateException(s"Probably, your have to upgrade your client. $message", cause) diff --git a/src/main/scala/units/client/contract/ContractBlock.scala b/src/main/scala/units/client/contract/ContractBlock.scala index a1ea7858..3471bdb6 100644 --- a/src/main/scala/units/client/contract/ContractBlock.scala +++ b/src/main/scala/units/client/contract/ContractBlock.scala @@ -15,14 +15,14 @@ case class ContractBlock( generator: ByteStr, minerRewardL2Address: EthAddress, chainId: Long, - elToClTransfersRootHash: Digest, - lastClToElTransferIndex: Long + e2cTransfersRootHash: Digest, + lastC2ETransferIndex: Long ) extends L2BlockLike { override def toString: String = s"ContractBlock($hash, p=$parentHash, e=$epoch, h=$height, m=$minerRewardL2Address ($generator), c=$chainId, " + - s"e2c=${if (elToClTransfersRootHash.isEmpty) "" else toHex(elToClTransfersRootHash)}, c2e=$lastClToElTransferIndex)" + s"e2c=${if (e2cTransfersRootHash.isEmpty) "" else toHex(e2cTransfersRootHash)}, c2e=$lastC2ETransferIndex)" } object ContractBlock { - val ElToClTransfersRootHashLength = 32 // bytes + val E2CTransfersRootHashLength = 32 // bytes } diff --git a/src/main/scala/units/client/contract/ContractFunction.scala b/src/main/scala/units/client/contract/ContractFunction.scala index b469a4ed..6375fe35 100644 --- a/src/main/scala/units/client/contract/ContractFunction.scala +++ b/src/main/scala/units/client/contract/ContractFunction.scala @@ -8,17 +8,17 @@ import com.wavesplatform.lang.v1.FunctionHeader import com.wavesplatform.lang.v1.compiler.Terms.{CONST_BYTESTR, CONST_LONG, CONST_STRING, EVALUATED, FUNCTION_CALL} import org.web3j.utils.Numeric.cleanHexPrefix import units.util.HexBytesConverter.toHexNoPrefix -import units.{BlockHash, ClientError, Job} +import units.{BlockHash, ClientError, JobResult} abstract class ContractFunction(name: String, reference: BlockHash, extraArgs: Either[CommonError, List[EVALUATED]]) { - def toFunctionCall(blockHash: BlockHash, transfersRootHash: Digest, lastClToElTransferIndex: Long): Job[FUNCTION_CALL] = (for { + def toFunctionCall(blockHash: BlockHash, transfersRootHash: Digest, lastC2ETransferIndex: Long): JobResult[FUNCTION_CALL] = (for { hash <- CONST_STRING(cleanHexPrefix(blockHash)) ref <- CONST_STRING(cleanHexPrefix(reference)) trh <- CONST_STRING(toHexNoPrefix(transfersRootHash)) xtra <- extraArgs } yield FUNCTION_CALL( FunctionHeader.User(name), - List(hash, ref) ++ xtra ++ List(trh, CONST_LONG(lastClToElTransferIndex)) + List(hash, ref) ++ xtra ++ List(trh, CONST_LONG(lastC2ETransferIndex)) )).leftMap(e => ClientError(s"Error building function call for $name: $e")) } diff --git a/src/main/scala/units/client/engine/EngineApiClient.scala b/src/main/scala/units/client/engine/EngineApiClient.scala index 200b7e4e..9731a530 100644 --- a/src/main/scala/units/client/engine/EngineApiClient.scala +++ b/src/main/scala/units/client/engine/EngineApiClient.scala @@ -4,10 +4,10 @@ import play.api.libs.json.* import units.client.engine.EngineApiClient.PayloadId import units.client.engine.model.* import units.eth.EthAddress -import units.{BlockHash, Job} +import units.{BlockHash, JobResult} trait EngineApiClient { - def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): Job[String] // TODO Replace String with an appropriate type + def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[String] // TODO Replace String with an appropriate type def forkChoiceUpdateWithPayloadId( lastBlockHash: BlockHash, @@ -16,25 +16,25 @@ trait EngineApiClient { suggestedFeeRecipient: EthAddress, prevRandao: String, withdrawals: Vector[Withdrawal] = Vector.empty - ): Job[PayloadId] + ): JobResult[PayloadId] - def getPayload(payloadId: PayloadId): Job[JsObject] + def getPayload(payloadId: PayloadId): JobResult[JsObject] - def applyNewPayload(payload: JsObject): Job[Option[BlockHash]] + def applyNewPayload(payload: JsObject): JobResult[Option[BlockHash]] - def getPayloadBodyByHash(hash: BlockHash): Job[Option[JsObject]] + def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] - def getBlockByNumber(number: BlockNumber): Job[Option[EcBlock]] + def getBlockByNumber(number: BlockNumber): JobResult[Option[EcBlock]] - def getBlockByHash(hash: BlockHash): Job[Option[EcBlock]] + def getBlockByHash(hash: BlockHash): JobResult[Option[EcBlock]] - def getBlockByHashJson(hash: BlockHash): Job[Option[JsObject]] + def getBlockByHashJson(hash: BlockHash): JobResult[Option[JsObject]] - def getLastExecutionBlock: Job[EcBlock] + def getLastExecutionBlock: JobResult[EcBlock] - def blockExists(hash: BlockHash): Job[Boolean] + def blockExists(hash: BlockHash): JobResult[Boolean] - def getLogs(hash: BlockHash, address: EthAddress, topic: String): Job[List[GetLogsResponseEntry]] + def getLogs(hash: BlockHash, address: EthAddress, topic: String): JobResult[List[GetLogsResponseEntry]] } object EngineApiClient { diff --git a/src/main/scala/units/client/engine/HttpEngineApiClient.scala b/src/main/scala/units/client/engine/HttpEngineApiClient.scala index 810af2a7..e5bd48e0 100644 --- a/src/main/scala/units/client/engine/HttpEngineApiClient.scala +++ b/src/main/scala/units/client/engine/HttpEngineApiClient.scala @@ -11,7 +11,7 @@ import units.client.engine.HttpEngineApiClient.* import units.client.engine.model.* import units.client.engine.model.ForkChoiceUpdatedRequest.ForkChoiceAttributes import units.eth.EthAddress -import units.{BlockHash, ClientConfig, ClientError, Job} +import units.{BlockHash, ClientConfig, ClientError, JobResult} import scala.concurrent.duration.{DurationInt, FiniteDuration} @@ -19,7 +19,7 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide val apiUrl: Uri = uri"${config.executionClientAddress}" - def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): Job[String] = { + def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[String] = { sendEngineRequest[ForkChoiceUpdatedRequest, ForkChoiceUpdatedResponse]( ForkChoiceUpdatedRequest(blockHash, finalizedBlockHash, None), BlockExecutionTimeout @@ -39,7 +39,7 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide suggestedFeeRecipient: EthAddress, prevRandao: String, withdrawals: Vector[Withdrawal] - ): Job[PayloadId] = { + ): JobResult[PayloadId] = { sendEngineRequest[ForkChoiceUpdatedRequest, ForkChoiceUpdatedResponse]( ForkChoiceUpdatedRequest( lastBlockHash, @@ -59,11 +59,11 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide } } - def getPayload(payloadId: PayloadId): Job[JsObject] = { + def getPayload(payloadId: PayloadId): JobResult[JsObject] = { sendEngineRequest[GetPayloadRequest, GetPayloadResponse](GetPayloadRequest(payloadId), NonBlockExecutionTimeout).map(_.executionPayload) } - def applyNewPayload(payload: JsObject): Job[Option[BlockHash]] = { + def applyNewPayload(payload: JsObject): JobResult[Option[BlockHash]] = { sendEngineRequest[NewPayloadRequest, PayloadStatus](NewPayloadRequest(payload), BlockExecutionTimeout).flatMap { case PayloadStatus(_, _, Some(validationError)) => Left(ClientError(s"Payload validation error: $validationError")) case PayloadStatus(status, Some(latestValidHash), _) if status == "VALID" => Right(Some(latestValidHash)) @@ -73,47 +73,47 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide } } - def getPayloadBodyByHash(hash: BlockHash): Job[Option[JsObject]] = { + def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] = { sendEngineRequest[GetPayloadBodyByHash, JsArray](GetPayloadBodyByHash(hash), NonBlockExecutionTimeout) .map(_.value.headOption.flatMap(_.asOpt[JsObject])) } - def getBlockByNumber(number: BlockNumber): Job[Option[EcBlock]] = { + def getBlockByNumber(number: BlockNumber): JobResult[Option[EcBlock]] = { for { json <- getBlockByNumberJson(number.str) blockMeta <- json.traverse(parseJson[EcBlock](_)) } yield blockMeta } - def getBlockByHash(hash: BlockHash): Job[Option[EcBlock]] = { + def getBlockByHash(hash: BlockHash): JobResult[Option[EcBlock]] = { sendRequest[GetBlockByHashRequest, EcBlock](GetBlockByHashRequest(hash)) .leftMap(err => ClientError(s"Error getting block by hash $hash: $err")) } - def getBlockByHashJson(hash: BlockHash): Job[Option[JsObject]] = { + def getBlockByHashJson(hash: BlockHash): JobResult[Option[JsObject]] = { sendRequest[GetBlockByHashRequest, JsObject](GetBlockByHashRequest(hash)) .leftMap(err => ClientError(s"Error getting block json by hash $hash: $err")) } - def getLastExecutionBlock: Job[EcBlock] = for { + def getLastExecutionBlock: JobResult[EcBlock] = for { lastEcBlockOpt <- getBlockByNumber(BlockNumber.Latest) lastEcBlock <- Either.fromOption(lastEcBlockOpt, ClientError("Impossible: EC doesn't have blocks")) } yield lastEcBlock - def blockExists(hash: BlockHash): Job[Boolean] = + def blockExists(hash: BlockHash): JobResult[Boolean] = getBlockByHash(hash).map(_.isDefined) - private def getBlockByNumberJson(number: String): Job[Option[JsObject]] = { + private def getBlockByNumberJson(number: String): JobResult[Option[JsObject]] = { sendRequest[GetBlockByNumberRequest, JsObject](GetBlockByNumberRequest(number)) .leftMap(err => ClientError(s"Error getting block by number $number: $err")) } - override def getLogs(hash: BlockHash, address: EthAddress, topic: String): Job[List[GetLogsResponseEntry]] = + override def getLogs(hash: BlockHash, address: EthAddress, topic: String): JobResult[List[GetLogsResponseEntry]] = sendRequest[GetLogsRequest, List[GetLogsResponseEntry]](GetLogsRequest(hash, address, List(topic))) .leftMap(err => ClientError(s"Error getting block logs by hash $hash: $err")) .map(_.getOrElse(List.empty)) - private def sendEngineRequest[A: Writes, B: Reads](request: A, timeout: FiniteDuration): Job[B] = { + private def sendEngineRequest[A: Writes, B: Reads](request: A, timeout: FiniteDuration): JobResult[B] = { sendRequest(request, timeout) match { case Right(response) => response.toRight(ClientError(s"Unexpected engine API empty response")) case Left(err) => Left(ClientError(s"Engine API request error: $err")) diff --git a/src/main/scala/units/client/engine/LoggedEngineApiClient.scala b/src/main/scala/units/client/engine/LoggedEngineApiClient.scala index 0a183309..8e297439 100644 --- a/src/main/scala/units/client/engine/LoggedEngineApiClient.scala +++ b/src/main/scala/units/client/engine/LoggedEngineApiClient.scala @@ -7,7 +7,7 @@ import units.client.engine.EngineApiClient.PayloadId import units.client.engine.LoggedEngineApiClient.excludedJsonFields import units.client.engine.model.* import units.eth.EthAddress -import units.{BlockHash, Job} +import units.{BlockHash, JobResult} import java.util.concurrent.ThreadLocalRandom import scala.util.chaining.scalaUtilChainingOps @@ -15,7 +15,7 @@ import scala.util.chaining.scalaUtilChainingOps class LoggedEngineApiClient(underlying: EngineApiClient) extends EngineApiClient { protected val log = LoggerFacade(LoggerFactory.getLogger(underlying.getClass)) - override def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): Job[String] = + override def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[String] = wrap(s"forkChoiceUpdate($blockHash, f=$finalizedBlockHash)", underlying.forkChoiceUpdate(blockHash, finalizedBlockHash)) override def forkChoiceUpdateWithPayloadId( @@ -25,40 +25,40 @@ class LoggedEngineApiClient(underlying: EngineApiClient) extends EngineApiClient suggestedFeeRecipient: EthAddress, prevRandao: String, withdrawals: Vector[Withdrawal] - ): Job[PayloadId] = wrap( + ): JobResult[PayloadId] = wrap( s"forkChoiceUpdateWithPayloadId(l=$lastBlockHash, f=$finalizedBlockHash, ts=$unixEpochSeconds, m=$suggestedFeeRecipient, " + s"r=$prevRandao, w={${withdrawals.mkString(", ")}}", underlying.forkChoiceUpdateWithPayloadId(lastBlockHash, finalizedBlockHash, unixEpochSeconds, suggestedFeeRecipient, prevRandao, withdrawals) ) - override def getPayload(payloadId: PayloadId): Job[JsObject] = + override def getPayload(payloadId: PayloadId): JobResult[JsObject] = wrap(s"getPayload($payloadId)", underlying.getPayload(payloadId), filteredJson) - override def applyNewPayload(payload: JsObject): Job[Option[BlockHash]] = + override def applyNewPayload(payload: JsObject): JobResult[Option[BlockHash]] = wrap(s"applyNewPayload(${filteredJson(payload)})", underlying.applyNewPayload(payload), _.fold("None")(_.toString)) - override def getPayloadBodyByHash(hash: BlockHash): Job[Option[JsObject]] = + override def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] = wrap(s"getPayloadBodyByHash($hash)", underlying.getPayloadBodyByHash(hash), _.fold("None")(filteredJson)) - override def getBlockByNumber(number: BlockNumber): Job[Option[EcBlock]] = + override def getBlockByNumber(number: BlockNumber): JobResult[Option[EcBlock]] = wrap(s"getBlockByNumber($number)", underlying.getBlockByNumber(number), _.fold("None")(_.toString)) - override def getBlockByHash(hash: BlockHash): Job[Option[EcBlock]] = + override def getBlockByHash(hash: BlockHash): JobResult[Option[EcBlock]] = wrap(s"getBlockByHash($hash)", underlying.getBlockByHash(hash), _.fold("None")(_.toString)) - override def getBlockByHashJson(hash: BlockHash): Job[Option[JsObject]] = + override def getBlockByHashJson(hash: BlockHash): JobResult[Option[JsObject]] = wrap(s"getBlockByHashJson($hash)", underlying.getBlockByHashJson(hash), _.fold("None")(filteredJson)) - override def getLastExecutionBlock: Job[EcBlock] = + override def getLastExecutionBlock: JobResult[EcBlock] = wrap("getLastExecutionBlock", underlying.getLastExecutionBlock) - override def blockExists(hash: BlockHash): Job[Boolean] = + override def blockExists(hash: BlockHash): JobResult[Boolean] = wrap(s"blockExists($hash)", underlying.blockExists(hash)) - override def getLogs(hash: BlockHash, address: EthAddress, topic: String): Job[List[GetLogsResponseEntry]] = + override def getLogs(hash: BlockHash, address: EthAddress, topic: String): JobResult[List[GetLogsResponseEntry]] = wrap(s"getLogs($hash, a=$address, t=$topic)", underlying.getLogs(hash, address, topic), _.view.map(_.data).mkString("{", ", ", "}")) - protected def wrap[R](method: String, f: => Job[R], toMsg: R => String = (_: R).toString): Job[R] = { + protected def wrap[R](method: String, f: => JobResult[R], toMsg: R => String = (_: R).toString): JobResult[R] = { val currRequestId = ThreadLocalRandom.current().nextInt(10000, 100000).toString log.debug(s"[$currRequestId] $method") diff --git a/src/main/scala/units/client/engine/model/EcBlock.scala b/src/main/scala/units/client/engine/model/EcBlock.scala index 587e87b3..2d8cbb54 100644 --- a/src/main/scala/units/client/engine/model/EcBlock.scala +++ b/src/main/scala/units/client/engine/model/EcBlock.scala @@ -24,6 +24,7 @@ case class EcBlock( baseFeePerGas: Uint256, gasLimit: Long, gasUsed: Long, + prevRandao: String, withdrawals: Vector[Withdrawal] ) extends L2BlockLike { override def toString: String = @@ -34,13 +35,14 @@ object EcBlock { implicit val reads: Reads[EcBlock] = ( (JsPath \ "hash").read[BlockHash] and (JsPath \ "parentHash").read[BlockHash] and - (JsPath \ "stateRoot").read[BlockHash] and + (JsPath \ "stateRoot").read[String] and (JsPath \ "number").read[String].map(toLong) and (JsPath \ "timestamp").read[String].map(toLong) and (JsPath \ "miner").read[EthAddress] and (JsPath \ "baseFeePerGas").read[String].map(toUint256) and (JsPath \ "gasLimit").read[String].map(toLong) and (JsPath \ "gasUsed").read[String].map(toLong) and + (JsPath \ "mixHash").read[String] and (JsPath \ "withdrawals").readWithDefault(Vector.empty[Withdrawal]) )(EcBlock.apply _) } diff --git a/src/main/scala/units/network/NetworkServer.scala b/src/main/scala/units/network/NetworkServer.scala index dd0424dd..49b1ebcf 100644 --- a/src/main/scala/units/network/NetworkServer.scala +++ b/src/main/scala/units/network/NetworkServer.scala @@ -11,12 +11,12 @@ import java.util.concurrent.ConcurrentHashMap object NetworkServer { def apply( - settings: ClientConfig, - historyReplier: HistoryReplier, - peerDatabase: PeerDatabase, - messageObserver: MessageObserver, - allChannels: ChannelGroup, - peerInfo: ConcurrentHashMap[Channel, PeerInfo] + settings: ClientConfig, + historyReplier: HistoryReplier, + peerDatabase: PeerDatabase, + messageObserver: MessageObserver, + allChannels: ChannelGroup, + peerInfo: ConcurrentHashMap[Channel, PeerInfo] ): NS = { val applicationName = s"${Constants.ApplicationName}l2-${settings.chainContract.take(8)}" diff --git a/src/main/scala/units/package.scala b/src/main/scala/units/package.scala index a437e4fb..b8b13ff3 100644 --- a/src/main/scala/units/package.scala +++ b/src/main/scala/units/package.scala @@ -1,4 +1,4 @@ package object units { - type BlockHash = BlockHash.Type - type Job[A] = Either[ClientError, A] + type BlockHash = BlockHash.Type + type JobResult[A] = Either[ClientError, A] } diff --git a/src/test/resources/main.ride b/src/test/resources/main.ride index 6602980e..9dfb916d 100644 --- a/src/test/resources/main.ride +++ b/src/test/resources/main.ride @@ -44,7 +44,7 @@ func pad(i: Int) = { } } -func blockElToClTransfersKey(blockHashHex: String) = "elToClTransfers_0x" + blockHashHex +func blockE2CTransfersKey(blockHashHex: String) = "elToClTransfers_0x" + blockHashHex func epochMetaKey(epoch: Int) = "epoch_" + pad(epoch) # {minerAddress},{prevEpoch},{lastBlockHash} func chainFirstBlockIdKey(chainId: Int) = "chain" + chainId.toString() + "FirstBlock" func chainMetaKey(chainId: Int) = "chain_" + pad(chainId) # {height},{lastBlockHash} @@ -154,28 +154,28 @@ func blockMeta(blockId: String) = { let baseOffset = 24 + BLOCK_HASH_SIZE + ADDRESS_SIZE let remainingBytes = metaSize - baseOffset - let elToClTransfersRootHash = if (remainingBytes >= ROOT_HASH_SIZE) # _6 + let e2cTransfersRootHash = if (remainingBytes >= ROOT_HASH_SIZE) # _6 then meta.drop(baseOffset).take(ROOT_HASH_SIZE) else base16'' - let lastClToElTransferIndex = if (remainingBytes == 8 || remainingBytes > ROOT_HASH_SIZE) # _7 - then meta.toInt(baseOffset + elToClTransfersRootHash.size()) + let lastC2ETransferIndex = if (remainingBytes == 8 || remainingBytes > ROOT_HASH_SIZE) # _7 + then meta.toInt(baseOffset + e2cTransfersRootHash.size()) else -1 - (blockHeight, blockEpoch, blockParent, blockGenerator, chainId, elToClTransfersRootHash, lastClToElTransferIndex) + (blockHeight, blockEpoch, blockParent, blockGenerator, chainId, e2cTransfersRootHash, lastC2ETransferIndex) } func mkBlockMetaEntry( blockHashHex: String, blockHeight: Int, blockParentHex: String, blockGenerator: Address, chainId: Int, - elToClTransfersRootHashHex: String, lastClToElTransferIndex: Int + e2cTransfersRootHashHex: String, lastC2ETransferIndex: Int ) = { - let elToClTransfersRootHashBytes = elToClTransfersRootHashHex.fromBase16String() - let rootHashBytesSize = elToClTransfersRootHashBytes.size() + let e2cTransfersRootHashBytes = e2cTransfersRootHashHex.fromBase16String() + let rootHashBytesSize = e2cTransfersRootHashBytes.size() strict checkRootHash = if (rootHashBytesSize == 0 || rootHashBytesSize == ROOT_HASH_SIZE) then true else throw("Transfers root hash should have 0 or " + ROOT_HASH_SIZE.toString() + " bytes, got " + rootHashBytesSize.toString()) let blockMetaBytes = blockHeight.toBytes() + height.toBytes() + blockParentHex.fromBase16String() + blockGenerator.bytes + - chainId.toBytes() + elToClTransfersRootHashBytes + lastClToElTransferIndex.toBytes() + chainId.toBytes() + e2cTransfersRootHashBytes + lastC2ETransferIndex.toBytes() BinaryEntry(blockMetaK + blockHashHex, blockMetaBytes) } @@ -359,14 +359,14 @@ func extendMainChainImpl( blockHashHex: String, referenceHex: String, vrf: ByteVector, - elToClTransfersRootHashHex: String, - lastClToElTransferIndex: Int + e2cTransfersRootHashHex: String, + lastC2ETransferIndex: Int ) = { strict checkBlockHash = validateBlockHash(blockHashHex) strict checkEpoch = ensureCorrectEpoch(vrf) strict checkChain = ensureExpectedOrInactiveChain(i.originCaller, mainChainId, unit) strict checkReference = isReferenceCorrect(referenceHex, mainChainLastBlock) - strict checkTransfers = ensureCorrectTransfers(blockMeta(referenceHex)._7, lastClToElTransferIndex, true) + strict checkTransfers = ensureCorrectTransfers(blockMeta(referenceHex)._7, lastC2ETransferIndex, true) strict thisEpochMeta = match epochMeta(height) { case _: Unit => StringEntry(epochMetaKey(height), i.originCaller.toString() + SEP + mainChainEpoch.toString() + SEP + blockHashHex) @@ -379,7 +379,7 @@ func extendMainChainImpl( let newChainHeight = mainChainHeight + 1 [ - mkBlockMetaEntry(blockHashHex, newChainHeight, mainChainLastBlock, i.originCaller, mainChainId, elToClTransfersRootHashHex, lastClToElTransferIndex), + mkBlockMetaEntry(blockHashHex, newChainHeight, mainChainLastBlock, i.originCaller, mainChainId, e2cTransfersRootHashHex, lastC2ETransferIndex), mkChainMetaEntry(mainChainId, newChainHeight, blockHashHex), IntegerEntry(minerChainIdKey(i.originCaller), mainChainId), IntegerEntry(chainLastHeightKey(mainChainId, i.originCaller), newChainHeight), @@ -392,8 +392,8 @@ func startAltChainImpl( blockHashHex: String, referenceHex: String, vrf: ByteVector, - elToClTransfersRootHashHex: String, - lastClToElTransferIndex: Int + e2cTransfersRootHashHex: String, + lastC2ETransferIndex: Int ) = { strict checkBlockHash = validateBlockHash(blockHashHex) strict checkEpoch = ensureCorrectEpoch(vrf) @@ -404,7 +404,7 @@ func startAltChainImpl( else throw("Can not start alt chain from epoch " + refEpoch.toString() + ", epoch " + finalizedEpoch.toString() + " is finalized") strict checkChain = ensureExpectedOrInactiveChain(i.originCaller, mainChainId, referenceHex) - strict checkTransfers = ensureCorrectTransfers(refTransferIndex, lastClToElTransferIndex, true) + strict checkTransfers = ensureCorrectTransfers(refTransferIndex, lastC2ETransferIndex, true) let newChainId = getInteger(lastChainIdKey).valueOrElse(0) + 1 let newChainHeight = refChainHeight + 1 @@ -417,7 +417,7 @@ func startAltChainImpl( [ thisEpochMeta, - mkBlockMetaEntry(blockHashHex, newChainHeight, referenceHex, i.originCaller, newChainId, elToClTransfersRootHashHex, lastClToElTransferIndex), + mkBlockMetaEntry(blockHashHex, newChainHeight, referenceHex, i.originCaller, newChainId, e2cTransfersRootHashHex, lastC2ETransferIndex), StringEntry(chainFirstBlockIdKey(newChainId), blockHashHex), mkChainMetaEntry(newChainId, newChainHeight, blockHashHex), IntegerEntry(minerChainIdKey(i.originCaller), newChainId), @@ -434,8 +434,8 @@ func extendAltChainImpl( blockHashHex: String, referenceHex: String, vrf: ByteVector, - elToClTransfersRootHashHex: String, - lastClToElTransferIndex: Int + e2cTransfersRootHashHex: String, + lastC2ETransferIndex: Int ) = { # 1. if this is the first block of a new epoch in a fork, store total supporting balance of a fork. # 2. if this fork's total supporting balance is >= 1/2 of the total balance, make this the new main chain. @@ -448,7 +448,7 @@ func extendAltChainImpl( strict checkChain = ensureExpectedOrInactiveChain(i.originCaller, chainId, chainFirstBlockMeta._3.toBase16String()) let (chainHeight, chainLastBlock) = chainMeta(chainId) strict checkReference = isReferenceCorrect(referenceHex, chainLastBlock) - strict checkTransfers = ensureCorrectTransfers(blockMeta(referenceHex)._7, lastClToElTransferIndex, true) + strict checkTransfers = ensureCorrectTransfers(blockMeta(referenceHex)._7, lastC2ETransferIndex, true) let newChainHeight = chainHeight + 1 @@ -477,7 +477,7 @@ func extendAltChainImpl( [IntegerEntry(chainLastHeightKey(mainChainId, i.originCaller), chainFirstBlockMeta._1)] } else [] [ - mkBlockMetaEntry(blockHashHex, newChainHeight, referenceHex, i.originCaller, chainId, elToClTransfersRootHashHex, lastClToElTransferIndex), + mkBlockMetaEntry(blockHashHex, newChainHeight, referenceHex, i.originCaller, chainId, e2cTransfersRootHashHex, lastC2ETransferIndex), mkChainMetaEntry(chainId, newChainHeight, blockHashHex), thisEpochMeta, IntegerEntry(minerChainIdKey(i.originCaller), chainId), @@ -497,9 +497,9 @@ func extendMainChain_v3( blockHashHex: String, referenceHex: String, epoch: Int, - elToClTransfersRootHashHex: String, - lastClToElTransferIndex: Int -) = extendMainChainImpl(i, blockHashHex, referenceHex, allowOmittingVrf(epoch), elToClTransfersRootHashHex, lastClToElTransferIndex) + e2cTransfersRootHashHex: String, + lastC2ETransferIndex: Int +) = extendMainChainImpl(i, blockHashHex, referenceHex, allowOmittingVrf(epoch), e2cTransfersRootHashHex, lastC2ETransferIndex) @Callable(i) @@ -507,27 +507,27 @@ func extendMainChain_v4( blockHashHex: String, referenceHex: String, vrf: ByteVector, - elToClTransfersRootHashHex: String, - lastClToElTransferIndex: Int -) = extendMainChainImpl(i, blockHashHex, referenceHex, vrf, elToClTransfersRootHashHex, lastClToElTransferIndex) + e2cTransfersRootHashHex: String, + lastC2ETransferIndex: Int +) = extendMainChainImpl(i, blockHashHex, referenceHex, vrf, e2cTransfersRootHashHex, lastC2ETransferIndex) @Callable(i) func startAltChain_v3( blockHashHex: String, referenceHex: String, epoch: Int, - elToClTransfersRootHashHex: String, - lastClToElTransferIndex: Int -) = startAltChainImpl(i, blockHashHex, referenceHex, allowOmittingVrf(epoch), elToClTransfersRootHashHex, lastClToElTransferIndex) + e2cTransfersRootHashHex: String, + lastC2ETransferIndex: Int +) = startAltChainImpl(i, blockHashHex, referenceHex, allowOmittingVrf(epoch), e2cTransfersRootHashHex, lastC2ETransferIndex) @Callable(i) func startAltChain_v4( blockHashHex: String, referenceHex: String, vrf: ByteVector, - elToClTransfersRootHashHex: String, - lastClToElTransferIndex: Int -) = startAltChainImpl(i, blockHashHex, referenceHex, vrf, elToClTransfersRootHashHex, lastClToElTransferIndex) + e2cTransfersRootHashHex: String, + lastC2ETransferIndex: Int +) = startAltChainImpl(i, blockHashHex, referenceHex, vrf, e2cTransfersRootHashHex, lastC2ETransferIndex) @Callable(i) func extendAltChain_v3( @@ -535,9 +535,9 @@ func extendAltChain_v3( blockHashHex: String, referenceHex: String, epoch: Int, - elToClTransfersRootHashHex: String, - lastClToElTransferIndex: Int -) = extendAltChainImpl(i, chainId, blockHashHex, referenceHex, allowOmittingVrf(epoch), elToClTransfersRootHashHex, lastClToElTransferIndex) + e2cTransfersRootHashHex: String, + lastC2ETransferIndex: Int +) = extendAltChainImpl(i, chainId, blockHashHex, referenceHex, allowOmittingVrf(epoch), e2cTransfersRootHashHex, lastC2ETransferIndex) @Callable(i) func extendAltChain_v4( @@ -545,16 +545,16 @@ func extendAltChain_v4( referenceHex: String, vrf: ByteVector, chainId: Int, - elToClTransfersRootHashHex: String, - lastClToElTransferIndex: Int -) = extendAltChainImpl(i, chainId, blockHashHex, referenceHex, vrf, elToClTransfersRootHashHex, lastClToElTransferIndex) + e2cTransfersRootHashHex: String, + lastC2ETransferIndex: Int +) = extendAltChainImpl(i, chainId, blockHashHex, referenceHex, vrf, e2cTransfersRootHashHex, lastC2ETransferIndex) @Callable(i) func appendBlock_v3( blockHashHex: String, referenceHex: String, - elToClTransfersRootHashHex: String, - lastClToElTransferIndex: Int + e2cTransfersRootHashHex: String, + lastC2ETransferIndex: Int ) = { strict checkCaller = if (thisEpochMiner == i.originCaller) then true @@ -567,12 +567,12 @@ func appendBlock_v3( let (chainHeight, lastBlockId) = chainMeta(chainId) strict checkReference = isReferenceCorrect(referenceHex, lastBlockId) - strict checkTransfers = ensureCorrectTransfers(blockMeta(referenceHex)._7, lastClToElTransferIndex, false) + strict checkTransfers = ensureCorrectTransfers(blockMeta(referenceHex)._7, lastC2ETransferIndex, false) let newChainHeight = chainHeight + 1 strict checkBlockHash = validateBlockHash(blockHashHex) [ - mkBlockMetaEntry(blockHashHex, newChainHeight, lastBlockId, i.originCaller, chainId, elToClTransfersRootHashHex, lastClToElTransferIndex), + mkBlockMetaEntry(blockHashHex, newChainHeight, lastBlockId, i.originCaller, chainId, e2cTransfersRootHashHex, lastC2ETransferIndex), IntegerEntry(chainLastHeightKey(chainId, i.originCaller), newChainHeight), mkChainMetaEntry(chainId, newChainHeight, blockHashHex), StringEntry(epochMetaKey(height), thisEpochMiner.value().toString() + SEP + thisEpochRef.toString() + SEP + blockHashHex) @@ -720,7 +720,7 @@ func withdraw(blockHashHex: String, merkleProof: List[ByteVector], transferIndex let expectedRootHash = withdrawBlockMeta._6 if (calculatedRootHash == expectedRootHash) then { let tokenId = getStringValue(tokenIdKey).fromBase58String() - let transfersKey = blockElToClTransfersKey(blockHashHex) + let transfersKey = blockE2CTransfersKey(blockHashHex) [ Reissue(tokenId, amount, true), ScriptTransfer(recipient, amount, tokenId), @@ -767,8 +767,8 @@ func setup(genesisBlockHashHex: String, minerRewardInGwei: Int, stakingContractA genesisBlockReferenceHash, genesisMinerAddress, 0, # chainId - base16''.toBase16String(), # elToClTransfersRootHashHex - -1 # lastClToElTransferIndex + base16''.toBase16String(), # e2cTransfersRootHashHex + -1 # lastC2ETransferIndex ) [ diff --git a/src/test/scala/units/BlockFullValidationTestSuite.scala b/src/test/scala/units/BlockFullValidationTestSuite.scala index 9aff63b0..643f029c 100644 --- a/src/test/scala/units/BlockFullValidationTestSuite.scala +++ b/src/test/scala/units/BlockFullValidationTestSuite.scala @@ -22,10 +22,9 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { "Full validation when the block is available on EL and CL" - { "doesn't happen for finalized blocks" in withExtensionDomain(defaultSettings.copy(initialMiners = List(reliable))) { d => - val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) - step("Start new epoch for ecBlock") d.advanceNewBlocks(reliable.address) + val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) d.advanceConsensusLayerChanged() step(s"Receive ecBlock ${ecBlock.hash} from a peer") @@ -42,7 +41,7 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { d.waitForWorking("Block considered validated and following") { s => val vs = s.fullValidationStatus - vs.validated should contain(ecBlock.hash) + vs.lastValidatedBlock.hash shouldBe ecBlock.hash vs.lastElWithdrawalIndex shouldBe empty is[FollowingChain](s.chainStatus) @@ -51,10 +50,9 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { "happens for not finalized blocks" - { "successful validation updates the chain information" in withExtensionDomain() { d => - val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) - step("Start new epoch for ecBlock") d.advanceNewBlocks(reliable.address) + val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) d.advanceConsensusLayerChanged() step(s"Receive ecBlock ${ecBlock.hash} from a peer") @@ -73,7 +71,7 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { d.waitForWorking("Block considered validated") { s => val vs = s.fullValidationStatus - vs.validated should contain(ecBlock.hash) + vs.lastValidatedBlock.hash shouldBe ecBlock.hash vs.lastElWithdrawalIndex.value shouldBe -1L } } @@ -107,12 +105,8 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { d.receiveNetworkBlock(ecBlock2, malfunction.account) d.triggerScheduledTasks() - d.waitForWorking("Block considered validated and forking") { s => - val vs = s.fullValidationStatus - vs.validated should contain(ecBlock2.hash) - vs.lastElWithdrawalIndex shouldBe empty - - is[WaitForNewChain](s.chainStatus) + d.waitForCS[WaitForNewChain]("Forking") { cs => + cs.chainSwitchInfo.referenceBlock.hash shouldBe ecBlock1.hash } } diff --git a/src/test/scala/units/BlockIssuesForgingTestSuite.scala b/src/test/scala/units/BlockIssuesForgingTestSuite.scala index d17cfb10..f581ac79 100644 --- a/src/test/scala/units/BlockIssuesForgingTestSuite.scala +++ b/src/test/scala/units/BlockIssuesForgingTestSuite.scala @@ -26,24 +26,24 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { "We're on the main chain and" - { def test(f: (ExtensionDomain, EcBlock, Int) => Unit): Unit = withExtensionDomain() { d => - val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1 ${ecBlock1.hash}") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1") d.advanceNewBlocks(otherMiner1.address) + val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() d.ecClients.addKnown(ecBlock1) d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBlock1)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.lastBlock.hash shouldBe ecBlock1.hash } - val ecBlock2 = d.createEcBlockBuilder("0-0", otherMiner1, ecBlock1).rewardPrevMiner().buildAndSetLogs() - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock2 ${ecBlock2.hash}") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock2") d.advanceNewBlocks(otherMiner1.address) + val ecBlock2 = d.createEcBlockBuilder("0-0", otherMiner1, ecBlock1).rewardPrevMiner().buildAndSetLogs() val ecBlock2Epoch = d.blockchain.height d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBlock2)) d.waitForCS[FollowingChain](s"Waiting ecBlock2 ${ecBlock2.hash}") { s => s.nodeChainInfo.lastBlock.hash shouldBe ecBlock2.hash - s.nextExpectedEcBlock.value shouldBe ecBlock2.hash + s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock2.hash } step(s"Start a new epoch of thisMiner ${thisMiner.address}") @@ -55,7 +55,7 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { "EC-block comes within timeout - then we continue forging" in test { (d, ecBlock2, ecBlock2Epoch) => d.advanceElu(WaitRequestedBlockTimeout - 1.millis) d.waitForCS[FollowingChain](s"Still waiting ecBlock2 ${ecBlock2.hash}") { s => - s.nextExpectedEcBlock.value shouldBe ecBlock2.hash + s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock2.hash } step(s"Receive EC-block ${ecBlock2.hash} from network") @@ -76,9 +76,9 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { "We're on the alternative chain and" - { "EC-block comes within timeout - then we continue forging" in withExtensionDomain() { d => - val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1 ${ecBlock1.hash}") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1") d.advanceNewBlocks(otherMiner1.address) + val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() d.ecClients.addKnown(ecBlock1) d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBlock1)) @@ -87,9 +87,9 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { s.nodeChainInfo.lastBlock.hash shouldBe ecBlock1.hash } - val ecBadBlock2 = d.createEcBlockBuilder("0-0", otherMiner1, ecBlock1).rewardPrevMiner().buildAndSetLogs() - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBadBlock2 ${ecBadBlock2.hash}") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBadBlock2") d.advanceNewBlocks(otherMiner1.address) + val ecBadBlock2 = d.createEcBlockBuilder("0-0", otherMiner1, ecBlock1).rewardPrevMiner().buildAndSetLogs() d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBadBlock2)) d.waitForCS[FollowingChain]() { s => @@ -97,9 +97,9 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { s.nodeChainInfo.lastBlock.hash shouldBe ecBadBlock2.hash } - val ecBlock2 = d.createEcBlockBuilder("0-1", otherMiner2, ecBlock1).rewardPrevMiner().buildAndSetLogs() - step(s"Start a new epoch of otherMiner2 ${otherMiner2.address} with alternative chain ecBlock2 ${ecBlock2.hash}") + step(s"Start a new epoch of otherMiner2 ${otherMiner2.address} with alternative chain ecBlock2") d.advanceNewBlocks(otherMiner2.address) + val ecBlock2 = d.createEcBlockBuilder("0-1", otherMiner2, ecBlock1).rewardPrevMiner().buildAndSetLogs() d.waitForCS[WaitForNewChain]() { s => s.chainSwitchInfo.referenceBlock.hash shouldBe ecBlock1.hash @@ -110,26 +110,26 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe ecBlock2.hash - s.nextExpectedEcBlock.value shouldBe ecBlock2.hash + s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock2.hash } d.receiveNetworkBlock(ecBlock2, otherMiner2.account, d.blockchain.height) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe ecBlock2.hash - s.nextExpectedEcBlock shouldBe empty + s.nextExpectedBlock shouldBe empty } - val ecBlock3 = d.createEcBlockBuilder("0-1-1", otherMiner2, parent = ecBlock2).rewardPrevMiner(1).buildAndSetLogs() - step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with ecBlock3 ${ecBlock3.hash}") + step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with ecBlock3") d.advanceNewBlocks(otherMiner2.address) + val ecBlock3 = d.createEcBlockBuilder("0-1-1", otherMiner2, parent = ecBlock2).rewardPrevMiner(1).buildAndSetLogs() val ecBlock3Epoch = d.blockchain.height d.appendMicroBlockAndVerify(d.chainContract.extendAltChain(otherMiner2.account, ecBlock3, chainId = 1)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe ecBlock3.hash - s.nextExpectedEcBlock.value shouldBe ecBlock3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock3.hash } step(s"Start a new epoch of thisMiner ${thisMiner.address}") @@ -140,7 +140,7 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { d.waitForCS[FollowingChain](s"Waiting ecBlock3 ${ecBlock3.hash}") { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe ecBlock3.hash - s.nextExpectedEcBlock.value shouldBe ecBlock3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock3.hash } step(s"Receive ecBlock3 ${ecBlock3.hash}") @@ -153,9 +153,9 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { } "We mined before the alternative chain before and EC-block doesn't come - then we still wait for it" in withExtensionDomain() { d => - val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1 ${ecBlock1.hash}") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1") d.advanceNewBlocks(otherMiner1.address) + val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() d.ecClients.addKnown(ecBlock1) d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBlock1)) @@ -164,9 +164,9 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { s.nodeChainInfo.lastBlock.hash shouldBe ecBlock1.hash } - val ecBadBlock2 = d.createEcBlockBuilder("0-0", otherMiner1, ecBlock1).rewardPrevMiner().buildAndSetLogs() - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBadBlock2 ${ecBadBlock2.hash}") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBadBlock2") d.advanceNewBlocks(otherMiner1.address) + val ecBadBlock2 = d.createEcBlockBuilder("0-0", otherMiner1, ecBlock1).rewardPrevMiner().buildAndSetLogs() d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBadBlock2)) d.waitForCS[FollowingChain]() { s => @@ -174,9 +174,9 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { s.nodeChainInfo.lastBlock.hash shouldBe ecBadBlock2.hash } - val ecBlock2 = d.createEcBlockBuilder("0-1", thisMiner, ecBlock1).rewardPrevMiner().buildAndSetLogs() - step(s"Start a new epoch of thisMiner ${thisMiner.address} with alternative chain ecBlock2 ${ecBlock2.hash}") + step(s"Start a new epoch of thisMiner ${thisMiner.address} with alternative chain ecBlock2") d.advanceNewBlocks(thisMiner.address) + val ecBlock2 = d.createEcBlockBuilder("0-1", thisMiner, ecBlock1).rewardPrevMiner().buildAndSetLogs() d.ecClients.willForge(ecBlock2) d.ecClients.willForge(d.createEcBlockBuilder("0-1-i", thisMiner, ecBlock2).buildAndSetLogs()) @@ -193,15 +193,15 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { ci.lastBlock.hash shouldBe ecBlock2.hash } - val ecBadBlock3 = d.createEcBlockBuilder("0-1-1", otherMiner2, ecBlock2).rewardMiner(otherMiner2.elRewardAddress, 1).buildAndSetLogs() - step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with ecBadBlock3 ${ecBadBlock3.hash}") + step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with ecBadBlock3") d.advanceNewBlocks(otherMiner2.address) + val ecBadBlock3 = d.createEcBlockBuilder("0-1-1", otherMiner2, ecBlock2).rewardMiner(otherMiner2.elRewardAddress, 1).buildAndSetLogs() d.appendMicroBlockAndVerify(d.chainContract.extendAltChain(otherMiner2.account, ecBadBlock3, chainId = 1)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe ecBadBlock3.hash - s.nextExpectedEcBlock.value shouldBe ecBadBlock3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe ecBadBlock3.hash } step(s"Continue an alternative chain by thisMiner ${thisMiner.address}") @@ -213,15 +213,15 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { d.waitForCS[FollowingChain](s"Still wait for ecBadBlock3 ${ecBadBlock3.hash}") { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe ecBadBlock3.hash - s.nextExpectedEcBlock.value shouldBe ecBadBlock3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe ecBadBlock3.hash } } "We haven't mined the alternative chain before and EC-block doesn't come - then we wait for a new alternative chain" in withExtensionDomain() { d => - val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1 ${ecBlock1.hash}") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1") d.advanceNewBlocks(otherMiner1.address) + val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() d.ecClients.addKnown(ecBlock1) d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBlock1)) @@ -230,9 +230,9 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { s.nodeChainInfo.lastBlock.hash shouldBe ecBlock1.hash } - val ecBadBlock2 = d.createEcBlockBuilder("0-0", otherMiner1, ecBlock1).rewardPrevMiner().buildAndSetLogs() - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBadBlock2 ${ecBadBlock2.hash}") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBadBlock2") d.advanceNewBlocks(otherMiner1.address) + val ecBadBlock2 = d.createEcBlockBuilder("0-0", otherMiner1, ecBlock1).rewardPrevMiner().buildAndSetLogs() d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBadBlock2)) d.waitForCS[FollowingChain]() { s => @@ -240,9 +240,9 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { s.nodeChainInfo.lastBlock.hash shouldBe ecBadBlock2.hash } - val ecBlock2 = d.createEcBlockBuilder("0-1", otherMiner2, ecBlock1).rewardPrevMiner().buildAndSetLogs() - step(s"Start a new epoch of otherMiner2 ${otherMiner2.address} with alternative chain ecBlock2 ${ecBlock2.hash}") + step(s"Start a new epoch of otherMiner2 ${otherMiner2.address} with alternative chain ecBlock2") d.advanceNewBlocks(otherMiner2.address) + val ecBlock2 = d.createEcBlockBuilder("0-1", otherMiner2, ecBlock1).rewardPrevMiner().buildAndSetLogs() d.waitForCS[WaitForNewChain]() { s => s.chainSwitchInfo.referenceBlock.hash shouldBe ecBlock1.hash @@ -253,25 +253,25 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe ecBlock2.hash - s.nextExpectedEcBlock.value shouldBe ecBlock2.hash + s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock2.hash } d.receiveNetworkBlock(ecBlock2, otherMiner2.account, d.blockchain.height) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe ecBlock2.hash - s.nextExpectedEcBlock shouldBe empty + s.nextExpectedBlock shouldBe empty } - val ecBlock3 = d.createEcBlockBuilder("0-1-1", otherMiner2, ecBlock2).rewardPrevMiner(1).buildAndSetLogs() - step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with ecBlock3 ${ecBlock3.hash}") + step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with ecBlock3") d.advanceNewBlocks(otherMiner2.address) + val ecBlock3 = d.createEcBlockBuilder("0-1-1", otherMiner2, ecBlock2).rewardPrevMiner(1).buildAndSetLogs() d.appendMicroBlockAndVerify(d.chainContract.extendAltChain(otherMiner2.account, ecBlock3, chainId = 1)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe ecBlock3.hash - s.nextExpectedEcBlock.value shouldBe ecBlock3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock3.hash } step(s"Start a new epoch of thisMiner ${thisMiner.address}") diff --git a/src/test/scala/units/E2CTransfersTestSuite.scala b/src/test/scala/units/E2CTransfersTestSuite.scala index 1c2b8382..6881ccf5 100644 --- a/src/test/scala/units/E2CTransfersTestSuite.scala +++ b/src/test/scala/units/E2CTransfersTestSuite.scala @@ -52,15 +52,15 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { ) withExtensionDomain(settings) { d => - val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) + step(s"Start new epoch for ecBlock") + d.advanceNewBlocks(reliable.address) + val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) def tryWithdraw(): Either[Throwable, BlockId] = d.appendMicroBlockE( d.chainContract.withdraw(transferReceiver1, ecBlock, transfer1Proofs, 0, transfer1.amount), d.chainContract.withdraw(transferReceiver2, ecBlock, transfer2Proofs, 1, transfer2.amount) ) - step(s"Start new epoch for ecBlock ${ecBlock.hash}") - d.advanceNewBlocks(reliable.address) tryWithdraw() should produce("not found for the contract address") step("Append a CL micro block with ecBlock confirmation") @@ -88,10 +88,9 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { ) ) { case (index, errorMessage) => withExtensionDomain() { d => - val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) - - step(s"Start new epoch with ecBlock ${ecBlock.hash}") + step(s"Start new epoch with ecBlock") d.advanceNewBlocks(reliable.address) + val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) d.ecClients.addKnown(ecBlock) d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, ecBlock, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() @@ -111,10 +110,9 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { ) ) { amount => withExtensionDomain() { d => - val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) - - step(s"Start new epoch with ecBlock ${ecBlock.hash}") + step(s"Start new epoch with ecBlock") d.advanceNewBlocks(reliable.address) + val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) d.ecClients.addKnown(ecBlock) d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, ecBlock, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() @@ -127,13 +125,12 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { } "Can't get transferred tokens if the data is incorrect and able if it is correct" in withExtensionDomain() { d => + step(s"Start new epoch with ecBlock") + d.advanceNewBlocks(reliable.address) val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) - def tryWithdraw(): Either[Throwable, BlockId] = d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, ecBlock, transferProofs, 0, transfer.amount)) - step(s"Start new epoch with ecBlock ${ecBlock.hash}") - d.advanceNewBlocks(reliable.address) tryWithdraw() should produce("not found for the contract address") d.ecClients.addKnown(ecBlock) d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, ecBlock, e2CTransfersRootHashHex)) @@ -152,13 +149,12 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { ) withExtensionDomain(settings) { d => + step(s"Start new epoch with ecBlock") + d.advanceNewBlocks(reliable.address) val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) - def tryWithdraw(): Either[Throwable, BlockId] = d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, ecBlock, transferProofs, 0, transfer.amount)) - step(s"Start new epoch with ecBlock ${ecBlock.hash}") - d.advanceNewBlocks(reliable.address) tryWithdraw() should produce("not found for the contract address") d.ecClients.addKnown(ecBlock) d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, ecBlock, e2CTransfersRootHashHex)) @@ -186,14 +182,12 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { ) ) { transferEvent => withExtensionDomain(settings) { d => - val transferEvents = List(transferEvent) - val ecBlock1 = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(transferEvents) - + step(s"Start new epoch with ecBlock1") + d.advanceNewBlocks(reliable.address) + val ecBlock1 = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(List(transferEvent)) def tryWithdraw(): Either[Throwable, BlockId] = d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, ecBlock1, transferProofs, 0, transfer.amount)) - step(s"Start new epoch with ecBlock1 ${ecBlock1.hash}") - d.advanceNewBlocks(reliable.address) d.ecClients.willForge(ecBlock1) d.advanceConsensusLayerChanged() @@ -213,11 +207,9 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { "Fails on wrong data" in { val settings = defaultSettings.withEnabledElMining withExtensionDomain(settings) { d => - val transferEvents = List(transferEvent.copy(data = "d3ad884fa04292")) - val ecBlock1 = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(transferEvents) - - step(s"Start new epoch with ecBlock1 ${ecBlock1.hash}") + step(s"Start new epoch with ecBlock1") d.advanceNewBlocks(reliable.address) + val ecBlock1 = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(List(transferEvent.copy(data = "d3ad884fa04292"))) d.ecClients.willForge(ecBlock1) d.ecClients.willForge(d.createEcBlockBuilder("0-0", reliable).build()) @@ -231,19 +223,19 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { "Can't get transferred tokens from a fork and can after the fork becomes a main chain" in { val settings = defaultSettings.copy(initialMiners = List(reliable, malfunction)).withEnabledElMining withExtensionDomain(settings) { d => + step(s"Start a new epoch of malfunction miner ${malfunction.address} with ecBlock1") + d.advanceNewBlocks(malfunction.address) // Need this block, because we can not rollback to the genesis block val ecBlock1 = d.createEcBlockBuilder("0", malfunction).buildAndSetLogs() - step(s"Start a new epoch of malfunction miner ${malfunction.address} with ecBlock1 ${ecBlock1.hash}") - d.advanceNewBlocks(malfunction.address) d.advanceConsensusLayerChanged() d.ecClients.addKnown(ecBlock1) d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, ecBlock1)) d.advanceConsensusLayerChanged() - val ecBadBlock2 = d.createEcBlockBuilder("0-0", malfunction, ecBlock1).rewardPrevMiner().buildAndSetLogs(ecBlockLogs) - step(s"Try to append a block with a wrong transfers root hash ${ecBadBlock2.hash}") + step(s"Try to append a block with a wrong transfers root hash") d.advanceNewBlocks(malfunction.address) + val ecBadBlock2 = d.createEcBlockBuilder("0-0", malfunction, ecBlock1).rewardPrevMiner().buildAndSetLogs(ecBlockLogs) d.advanceConsensusLayerChanged() // No root hash in extendMainChain tx @@ -255,9 +247,9 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { s.chainSwitchInfo.referenceBlock.hash shouldBe ecBlock1.hash } - val ecBlock2 = d.createEcBlockBuilder("0-1", reliable, ecBlock1).rewardPrevMiner().buildAndSetLogs(ecBlockLogs) - step(s"Start an alternative chain by a reliable miner ${reliable.address} with ecBlock2 ${ecBlock2.hash}") + step(s"Start an alternative chain by a reliable miner ${reliable.address} with ecBlock2") d.advanceNewBlocks(reliable.address) + val ecBlock2 = d.createEcBlockBuilder("0-1", reliable, ecBlock1).rewardPrevMiner().buildAndSetLogs(ecBlockLogs) d.ecClients.willForge(ecBlock2) // Prepare a following block, because we start mining it immediately d.ecClients.willForge(d.createEcBlockBuilder("0-1-1", reliable, ecBlock2).build()) @@ -287,9 +279,9 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { tryWithdraw() should produce("is not finalized") } - val ecBlock3 = d.createEcBlockBuilder("0-1-1-1", reliable, ecBlock2).rewardPrevMiner(1).buildAndSetLogs() - step(s"Moving whole network to the alternative chain with ecBlock3 ${ecBlock3.hash}") + step(s"Moving whole network to the alternative chain with ecBlock3") d.advanceNewBlocks(reliable.address) + val ecBlock3 = d.createEcBlockBuilder("0-1-1-1", reliable, ecBlock2).rewardPrevMiner(1).buildAndSetLogs() d.ecClients.willForge(ecBlock3) d.advanceConsensusLayerChanged() diff --git a/src/test/scala/units/ExtensionDomain.scala b/src/test/scala/units/ExtensionDomain.scala index 5c264fb4..91dd9a2f 100644 --- a/src/test/scala/units/ExtensionDomain.scala +++ b/src/test/scala/units/ExtensionDomain.scala @@ -81,6 +81,7 @@ class ExtensionDomain( baseFeePerGas = Uint256.DEFAULT, gasLimit = 0, gasUsed = 0, + prevRandao = EthereumConstants.EmptyPrevRandaoHex, withdrawals = Vector.empty ) @@ -345,13 +346,15 @@ class ExtensionDomain( def createEcBlockBuilder(hashPath: String, miner: ElMinerSettings, parent: EcBlock = ecGenesisBlock): TestEcBlockBuilder = createEcBlockBuilder(hashPath, miner.elRewardAddress, parent) - def createEcBlockBuilder(hashPath: String, minerRewardL2Address: EthAddress, parent: EcBlock): TestEcBlockBuilder = + def createEcBlockBuilder(hashPath: String, minerRewardL2Address: EthAddress, parent: EcBlock): TestEcBlockBuilder = { TestEcBlockBuilder(ecClients, elBridgeAddress, elMinerDefaultReward, l2Config.blockDelay, parent = parent).updateBlock( _.copy( hash = TestEcBlockBuilder.createBlockHash(hashPath), - minerRewardL2Address = minerRewardL2Address + minerRewardL2Address = minerRewardL2Address, + prevRandao = ELUpdater.calculateRandao(blockchain.vrf(blockchain.height).get, parent.hash) ) ) + } override def currentHitSource: BlockId = blockchain.hitSource(blockchain.height).getOrElse(throw new RuntimeException(s"Can't get hit source for ${blockchain.height}")) diff --git a/src/test/scala/units/HasJobLogging.scala b/src/test/scala/units/HasJobLogging.scala index f562860b..c3fcc229 100644 --- a/src/test/scala/units/HasJobLogging.scala +++ b/src/test/scala/units/HasJobLogging.scala @@ -7,7 +7,7 @@ import java.util.concurrent.ThreadLocalRandom import scala.util.chaining.scalaUtilChainingOps trait HasJobLogging extends ScorexLogging { - protected def wrap[A](method: String, f: => Job[A], toMsg: A => String = (_: A).toString): Job[A] = { + protected def wrap[A](method: String, f: => JobResult[A], toMsg: A => String = (_: A).toString): JobResult[A] = { val currRequestId = ThreadLocalRandom.current().nextInt(10000, 100000).toString log.debug(s"[$currRequestId] $method") @@ -17,5 +17,5 @@ trait HasJobLogging extends ScorexLogging { } } - protected def l(text: String): Job[Unit] = log.debug(text).asRight + protected def l(text: String): JobResult[Unit] = log.debug(text).asRight } diff --git a/src/test/scala/units/TestEcBlockBuilder.scala b/src/test/scala/units/TestEcBlockBuilder.scala index ca451a3d..52c195a7 100644 --- a/src/test/scala/units/TestEcBlockBuilder.scala +++ b/src/test/scala/units/TestEcBlockBuilder.scala @@ -56,6 +56,7 @@ object TestEcBlockBuilder { baseFeePerGas = Uint256.DEFAULT, gasLimit = 0, gasUsed = 0, + prevRandao = EthereumConstants.EmptyPrevRandaoHex, withdrawals = Vector.empty ), parent diff --git a/src/test/scala/units/client/TestEcClients.scala b/src/test/scala/units/client/TestEcClients.scala index ed33382d..c402a3f6 100644 --- a/src/test/scala/units/client/TestEcClients.scala +++ b/src/test/scala/units/client/TestEcClients.scala @@ -6,60 +6,49 @@ import com.wavesplatform.state.Blockchain import com.wavesplatform.utils.ScorexLogging import monix.execution.atomic.{Atomic, AtomicInt} import play.api.libs.json.JsObject -import units.ELUpdater.calculateRandao import units.client.TestEcClients.* import units.client.engine.EngineApiClient.PayloadId import units.client.engine.model.* import units.client.engine.{EngineApiClient, LoggedEngineApiClient} import units.collections.ListOps.* -import units.eth.{EthAddress, EthereumConstants} -import units.{BlockHash, Job, NetworkL2Block} - -import scala.collection.View -import scala.util.chaining.scalaUtilChainingOps +import units.eth.EthAddress +import units.{BlockHash, JobResult, NetworkL2Block} class TestEcClients private ( knownBlocks: Atomic[Map[BlockHash, ChainId]], - chains: Atomic[Map[ChainId, List[TestEcBlock]]], + chains: Atomic[Map[ChainId, List[EcBlock]]], currChainIdValue: AtomicInt, blockchain: Blockchain ) extends ScorexLogging { def this(genesis: EcBlock, blockchain: Blockchain) = this( knownBlocks = Atomic(Map(genesis.hash -> 0)), - chains = Atomic(Map(0 -> List(TestEcBlock(genesis, EthereumConstants.EmptyPrevRandaoHex)))), + chains = Atomic(Map(0 -> List(genesis))), currChainIdValue = AtomicInt(0), blockchain = blockchain ) private def currChainId: ChainId = currChainIdValue.get() - def addKnown(ecBlock: EcBlock, epochNumber: Int = blockchain.height): TestEcBlock = { + def addKnown(ecBlock: EcBlock): EcBlock = { knownBlocks.transform(_.updated(ecBlock.hash, currChainId)) - mkTestEcBlock(ecBlock, epochNumber).tap(prependToCurrentChain) + prependToCurrentChain(ecBlock) + ecBlock } - private def mkTestEcBlock(ecBlock: EcBlock, epochNumber: Int): TestEcBlock = TestEcBlock( - ecBlock, - calculateRandao( - blockchain.vrf(epochNumber).getOrElse(throw new RuntimeException(s"VRF is empty for epoch $epochNumber")), - ecBlock.parentHash - ) - ) - - private def prependToCurrentChain(b: TestEcBlock): Unit = + private def prependToCurrentChain(b: EcBlock): Unit = prependToChain(currChainId, b) - private def prependToChain(chainId: ChainId, b: TestEcBlock): Unit = + private def prependToChain(chainId: ChainId, b: EcBlock): Unit = chains.transform { chains => chains.updated(chainId, b :: chains(chainId)) } - private def currChain: View[EcBlock] = - chains.get().getOrElse(currChainId, throw new RuntimeException(s"Unknown chain $currChainId")).view.map(_.ecBlock) + private def currChain: List[EcBlock] = + chains.get().getOrElse(currChainId, throw new RuntimeException(s"Unknown chain $currChainId")) private val forgingBlocks = Atomic(List.empty[ForgingBlock]) - def willForge(ecBlock: EcBlock, epochNumber: Int = blockchain.height): Unit = - forgingBlocks.transform(ForgingBlock(mkTestEcBlock(ecBlock, epochNumber)) :: _) + def willForge(ecBlock: EcBlock): Unit = + forgingBlocks.transform(ForgingBlock(ecBlock) :: _) private val logs = Atomic(Map.empty[GetLogsRequest, List[GetLogsResponseEntry]]) def setBlockLogs(hash: BlockHash, address: EthAddress, topic: String, blockLogs: List[GetLogsResponseEntry]): Unit = { @@ -75,12 +64,12 @@ class TestEcClients private ( val engineApi = new LoggedEngineApiClient( new EngineApiClient { - override def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): Job[String] = { + override def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[String] = { knownBlocks.get().get(blockHash) match { case Some(cid) => currChainIdValue.set(cid) chains.transform { chains => - chains.updated(cid, chains(cid).dropWhile(_.ecBlock.hash != blockHash)) + chains.updated(cid, chains(cid).dropWhile(_.hash != blockHash)) } log.debug(s"Curr chain: ${currChain.map(_.hash).mkString(" <- ")}") "VALID" @@ -97,40 +86,39 @@ class TestEcClients private ( suggestedFeeRecipient: EthAddress, prevRandao: String, withdrawals: Vector[Withdrawal] - ): Job[PayloadId] = + ): JobResult[PayloadId] = forgingBlocks .get() - .collectFirst { case fb if fb.testBlock.ecBlock.parentHash == lastBlockHash => fb } match { + .collectFirst { case fb if fb.testBlock.parentHash == lastBlockHash => fb } match { case None => throw new RuntimeException( - s"Can't find a suitable block among: ${forgingBlocks.get().map(_.testBlock.ecBlock.hash).mkString(", ")}. Call willForge" + s"Can't find a suitable block among: ${forgingBlocks.get().map(_.testBlock.hash).mkString(", ")}. Call willForge" ) case Some(fb) => fb.payloadId.asRight } - override def getPayload(payloadId: PayloadId): Job[JsObject] = + override def getPayload(payloadId: PayloadId): JobResult[JsObject] = forgingBlocks.transformAndExtract(_.withoutFirst { fb => fb.payloadId == payloadId }) match { - case Some(fb) => TestEcBlocks.toPayload(fb.testBlock.ecBlock, fb.testBlock.prevRandao).asRight + case Some(fb) => TestEcBlocks.toPayload(fb.testBlock, fb.testBlock.prevRandao).asRight case None => throw new RuntimeException( - s"Can't find payload $payloadId among: ${forgingBlocks.get().map(_.testBlock.ecBlock.hash).mkString(", ")}. Call willForge" + s"Can't find payload $payloadId among: ${forgingBlocks.get().map(_.testBlock.hash).mkString(", ")}. Call willForge" ) } - override def applyNewPayload(payload: JsObject): Job[Option[BlockHash]] = { - val newBlock = NetworkL2Block(payload).explicitGet().toEcBlock - val newTestBlock = TestEcBlock(newBlock, (payload \ "prevRandao").as[String]) + override def applyNewPayload(payload: JsObject): JobResult[Option[BlockHash]] = { + val newBlock = NetworkL2Block(payload).explicitGet().toEcBlock knownBlocks.get().get(newBlock.parentHash) match { case Some(cid) => val chain = chains.get()(cid) - if (newBlock.parentHash == chain.head.ecBlock.hash) { - prependToChain(cid, newTestBlock) + if (newBlock.parentHash == chain.head.hash) { + prependToChain(cid, newBlock) knownBlocks.transform(_.updated(newBlock.hash, cid)) } else { // Rollback log.debug(s"A rollback using ${newBlock.hash} detected") val newCid = currChainIdValue.incrementAndGet() - val newChain = newTestBlock :: chain.dropWhile(_.ecBlock.hash != newBlock.parentHash) + val newChain = newBlock :: chain.dropWhile(_.hash != newBlock.parentHash) chains.transform(_.updated(newCid, newChain)) knownBlocks.transform(_.updated(newBlock.hash, newCid)) } @@ -140,31 +128,31 @@ class TestEcClients private ( Some(newBlock.hash) }.asRight - override def getPayloadBodyByHash(hash: BlockHash): Job[Option[JsObject]] = + override def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] = getBlockByHashJson(hash) - override def getBlockByNumber(number: BlockNumber): Job[Option[EcBlock]] = + override def getBlockByNumber(number: BlockNumber): JobResult[Option[EcBlock]] = number match { case BlockNumber.Latest => currChain.headOption.asRight case BlockNumber.Number(n) => currChain.find(_.height == n).asRight } - override def getBlockByHash(hash: BlockHash): Job[Option[EcBlock]] = { + override def getBlockByHash(hash: BlockHash): JobResult[Option[EcBlock]] = { for { cid <- knownBlocks.get().get(hash) c <- chains.get().get(cid) - b <- c.find(_.ecBlock.hash == hash) - } yield b.ecBlock + b <- c.find(_.hash == hash) + } yield b }.asRight - override def getBlockByHashJson(hash: BlockHash): Job[Option[JsObject]] = + override def getBlockByHashJson(hash: BlockHash): JobResult[Option[JsObject]] = notImplementedMethodJob("getBlockByHashJson") - override def getLastExecutionBlock: Job[EcBlock] = currChain.head.asRight + override def getLastExecutionBlock: JobResult[EcBlock] = currChain.head.asRight - override def blockExists(hash: BlockHash): Job[Boolean] = notImplementedMethodJob("blockExists") + override def blockExists(hash: BlockHash): JobResult[Boolean] = notImplementedMethodJob("blockExists") - override def getLogs(hash: BlockHash, address: EthAddress, topic: String): Job[List[GetLogsResponseEntry]] = { + override def getLogs(hash: BlockHash, address: EthAddress, topic: String): JobResult[List[GetLogsResponseEntry]] = { val request = GetLogsRequest(hash, address, List(topic)) getLogsCalls.transform(_ + hash) logs.get().getOrElse(request, throw notImplementedCase("call setBlockLogs")) @@ -172,7 +160,7 @@ class TestEcClients private ( } ) - protected def notImplementedMethodJob[A](text: String): Job[A] = throw new NotImplementedMethod(text) + protected def notImplementedMethodJob[A](text: String): JobResult[A] = throw new NotImplementedMethod(text) protected def notImplementedCase(text: String): Throwable = new NotImplementedCase(text) } @@ -182,11 +170,9 @@ object TestEcClients { private type ChainId = Int - private case class ForgingBlock(payloadId: String, testBlock: TestEcBlock) + private case class ForgingBlock(payloadId: String, testBlock: EcBlock) private object ForgingBlock { - def apply(testBlock: TestEcBlock): ForgingBlock = - new ForgingBlock(testBlock.ecBlock.hash.take(16), testBlock) + def apply(testBlock: EcBlock): ForgingBlock = + new ForgingBlock(testBlock.hash.take(16), testBlock) } - - case class TestEcBlock(ecBlock: EcBlock, prevRandao: String) } diff --git a/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala b/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala index 8e7a8061..e108a626 100644 --- a/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala +++ b/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala @@ -76,7 +76,7 @@ trait HasConsensusLayerDappTxHelpers { def extendMainChain( minerAccount: KeyPair, block: L2BlockLike, - e2CTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, lastC2ETransferIndex: Long = -1, vrf: ByteStr = currentHitSource ): InvokeScriptTransaction = @@ -88,7 +88,7 @@ trait HasConsensusLayerDappTxHelpers { Terms.CONST_STRING(block.hash.drop(2)).explicitGet(), Terms.CONST_STRING(block.parentHash.drop(2)).explicitGet(), Terms.CONST_BYTESTR(vrf).explicitGet(), - Terms.CONST_STRING(e2CTransfersRootHashHex.drop(2)).explicitGet(), + Terms.CONST_STRING(e2cTransfersRootHashHex.drop(2)).explicitGet(), Terms.CONST_LONG(lastC2ETransferIndex) ), fee = extendMainChainFee @@ -97,7 +97,7 @@ trait HasConsensusLayerDappTxHelpers { def appendBlock( minerAccount: KeyPair, block: L2BlockLike, - e2CTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, lastC2ETransferIndex: Long = -1 ): InvokeScriptTransaction = TxHelpers.invoke( @@ -107,7 +107,7 @@ trait HasConsensusLayerDappTxHelpers { args = List( Terms.CONST_STRING(block.hash.drop(2)).explicitGet(), Terms.CONST_STRING(block.parentHash.drop(2)).explicitGet(), - Terms.CONST_STRING(e2CTransfersRootHashHex.drop(2)).explicitGet(), + Terms.CONST_STRING(e2cTransfersRootHashHex.drop(2)).explicitGet(), Terms.CONST_LONG(lastC2ETransferIndex) ), fee = appendBlockFee @@ -116,7 +116,7 @@ trait HasConsensusLayerDappTxHelpers { def startAltChain( minerAccount: KeyPair, block: L2BlockLike, - e2CTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, lastC2ETransferIndex: Long = -1, vrf: ByteStr = currentHitSource ): InvokeScriptTransaction = @@ -128,7 +128,7 @@ trait HasConsensusLayerDappTxHelpers { Terms.CONST_STRING(block.hash.drop(2)).explicitGet(), Terms.CONST_STRING(block.parentHash.drop(2)).explicitGet(), Terms.CONST_BYTESTR(vrf).explicitGet(), - Terms.CONST_STRING(e2CTransfersRootHashHex.drop(2)).explicitGet(), + Terms.CONST_STRING(e2cTransfersRootHashHex.drop(2)).explicitGet(), Terms.CONST_LONG(lastC2ETransferIndex) ), fee = startAltChainFee @@ -138,7 +138,7 @@ trait HasConsensusLayerDappTxHelpers { minerAccount: KeyPair, block: L2BlockLike, chainId: Long, - e2CTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, lastC2ETransferIndex: Long = -1, vrf: ByteStr = currentHitSource ): InvokeScriptTransaction = @@ -151,7 +151,7 @@ trait HasConsensusLayerDappTxHelpers { Terms.CONST_STRING(block.parentHash.drop(2)).explicitGet(), Terms.CONST_BYTESTR(vrf).explicitGet(), Terms.CONST_LONG(chainId), - Terms.CONST_STRING(e2CTransfersRootHashHex.drop(2)).explicitGet(), + Terms.CONST_STRING(e2cTransfersRootHashHex.drop(2)).explicitGet(), Terms.CONST_LONG(lastC2ETransferIndex) ), fee = extendAltChainFee