From 105563c0d7d981be8ac02eb72a8b1dfaddf95766 Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Thu, 19 Sep 2024 18:24:21 +0300 Subject: [PATCH 01/14] Rename block classes --- local-network/deploy/src/waves-utils.ts | 8 +- local-network/deploy/test/miner_big-join.ts | 23 - local-network/deploy/test/transfer-e2c.ts | 2 +- src/main/scala/units/ELUpdater.scala | 504 +++++++++--------- src/main/scala/units/NetworkBlock.scala | 99 ++++ src/main/scala/units/NetworkL2Block.scala | 99 ---- .../scala/units/client/CommonBlockData.scala | 13 + src/main/scala/units/client/L2BlockLike.scala | 18 - .../units/client/contract/ContractBlock.scala | 22 +- .../units/client/engine/EngineApiClient.scala | 16 +- .../client/engine/HttpEngineApiClient.scala | 48 +- .../client/engine/LoggedEngineApiClient.scala | 44 +- .../{EcBlock.scala => ExecutionPayload.scala} | 39 +- .../engine/model/PayloadJsonData.scala} | 38 +- ...{EmptyL2Block.scala => EmptyPayload.scala} | 26 +- .../units/network/BasicMessagesRepo.scala | 12 +- .../scala/units/network/BlocksObserver.scala | 4 +- .../units/network/BlocksObserverImpl.scala | 10 +- .../scala/units/network/HistoryReplier.scala | 23 +- .../units/network/LegacyFrameCodec.scala | 10 +- .../scala/units/network/MessageObserver.scala | 6 +- .../scala/units/network/TrafficLogger.scala | 6 +- .../units/BaseIntegrationTestSuite.scala | 2 +- .../units/BlockBriefValidationTestSuite.scala | 24 +- .../units/BlockFullValidationTestSuite.scala | 71 +-- .../units/BlockIssuesForgingTestSuite.scala | 255 ++++----- .../scala/units/E2CTransfersTestSuite.scala | 118 ++-- src/test/scala/units/ExtensionDomain.scala | 74 +-- src/test/scala/units/TestEcBlockBuilder.scala | 66 --- src/test/scala/units/TestPayloadBuilder.scala | 66 +++ src/test/scala/units/TestSettings.scala | 12 +- .../scala/units/client/TestEcClients.scala | 89 ++-- .../HasConsensusLayerDappTxHelpers.scala | 74 +-- .../client/engine/model/TestEcBlocks.scala | 26 - .../client/engine/model/TestPayloads.scala | 26 + ...uite.scala => EmptyPayloadTestSuite.scala} | 22 +- .../units/network/TestBlocksObserver.scala | 6 +- 37 files changed, 989 insertions(+), 1012 deletions(-) delete mode 100644 local-network/deploy/test/miner_big-join.ts create mode 100644 src/main/scala/units/NetworkBlock.scala delete mode 100644 src/main/scala/units/NetworkL2Block.scala create mode 100644 src/main/scala/units/client/CommonBlockData.scala delete mode 100644 src/main/scala/units/client/L2BlockLike.scala rename src/main/scala/units/client/engine/model/{EcBlock.scala => ExecutionPayload.scala} (56%) rename src/main/scala/units/{util/BlockToPayloadMapper.scala => client/engine/model/PayloadJsonData.scala} (77%) rename src/main/scala/units/eth/{EmptyL2Block.scala => EmptyPayload.scala} (85%) delete mode 100644 src/test/scala/units/TestEcBlockBuilder.scala create mode 100644 src/test/scala/units/TestPayloadBuilder.scala delete mode 100644 src/test/scala/units/client/engine/model/TestEcBlocks.scala create mode 100644 src/test/scala/units/client/engine/model/TestPayloads.scala rename src/test/scala/units/eth/{EmptyL2BlockTestSuite.scala => EmptyPayloadTestSuite.scala} (91%) diff --git a/local-network/deploy/src/waves-utils.ts b/local-network/deploy/src/waves-utils.ts index a95c9478..d31074f4 100644 --- a/local-network/deploy/src/waves-utils.ts +++ b/local-network/deploy/src/waves-utils.ts @@ -8,7 +8,7 @@ export type LibraryWavesApi = ReturnType; export type ExtendedWavesApi = LibraryWavesApi & { base: string }; export type WavesSignedTransaction = SignedTransaction & { id: string }; -export interface EcBlockContractInfo { +export interface BlockContractInfo { chainHeight: number, epochNumber: number } @@ -126,7 +126,7 @@ export async function signAndBroadcast(wavesApi: ExtendedWavesApi, name: string, if (options.wait) await waitForTxn(wavesApi, id).then(x => logger.debug(`Sent %O result: %O`, unsignedTxJson, x)); } -function parseBlockMeta(response: object): EcBlockContractInfo { +function parseBlockMeta(response: object): BlockContractInfo { // @ts-ignore: Property 'value' does not exist on type 'object'. const rawMeta = response.result.value; return { @@ -135,7 +135,7 @@ function parseBlockMeta(response: object): EcBlockContractInfo { }; } -export async function waitForEcBlock(wavesApi: LibraryWavesApi, chainContractAddress: string, blockHash: string): Promise { +export async function waitForBlock(wavesApi: LibraryWavesApi, chainContractAddress: string, blockHash: string): Promise { const getBlockData = async () => { try { return parseBlockMeta(await wavesApi.utils.fetchEvaluate(chainContractAddress, `blockMeta("${blockHash.slice(2)}")`)); @@ -168,7 +168,7 @@ export function prepareE2CWithdrawTxnJson( }; } -export async function chainContractCurrFinalizedBlock(wavesApi: LibraryWavesApi, chainContractAddress: string): Promise { +export async function chainContractCurrFinalizedBlock(wavesApi: LibraryWavesApi, chainContractAddress: string): Promise { // @ts-ignore: Property 'value' does not exist on type 'object'. return parseBlockMeta(await wavesApi.utils.fetchEvaluate(chainContractAddress, `blockMeta(getStringValue("finalizedBlock"))`)); } diff --git a/local-network/deploy/test/miner_big-join.ts b/local-network/deploy/test/miner_big-join.ts deleted file mode 100644 index 6e1841c8..00000000 --- a/local-network/deploy/test/miner_big-join.ts +++ /dev/null @@ -1,23 +0,0 @@ -import * as logger from '../src/logger'; -import { setup } from '../setup'; - -const { waves } = await setup(false); - -logger.info('Join a miner with the balance of 50% + 1'); - -const unsignedTxnJson = { - sender: "3FSrRN8X7cDsLyYTScS8Yf8KSwZgJBwf1jU", - feeAssetId: null, - fee: 2400000, - version: 2, - type: 12, - data: [ - { - key: "%s__3FSsLw2bximwiBGP1KNTjbTHJVgYiNBojrS", - type: "string", - value: "%d%d%d%d__9__5500001__40__5500001" - } - ] -}; - -await waves.utils.signAndBroadcast(waves.wavesApi2, 'join big', unsignedTxnJson, { wait: true }); diff --git a/local-network/deploy/test/transfer-e2c.ts b/local-network/deploy/test/transfer-e2c.ts index 11edb11e..a6b498a7 100644 --- a/local-network/deploy/test/transfer-e2c.ts +++ b/local-network/deploy/test/transfer-e2c.ts @@ -57,7 +57,7 @@ if (rawLogsInBlock.length == 0) throw new Error(`Can't find logs in ${blockHash} const logsInBlock = rawLogsInBlock as Exclude[]; logger.info(`Waiting EL block ${blockHash} confirmation on CL`); -const withdrawBlockMeta = await waves.utils.waitForEcBlock(waves.wavesApi1, waves.chainContractAddress, blockHash); +const withdrawBlockMeta = await waves.utils.waitForBlock(waves.wavesApi1, waves.chainContractAddress, blockHash); logger.info(`Withdraw block meta: %O`, withdrawBlockMeta); let rawData: string[] = []; diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index 714adf57..547dc6db 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -27,13 +27,13 @@ import monix.execution.{CancelableFuture, Scheduler} import play.api.libs.json.* import units.ELUpdater.State.* import units.ELUpdater.State.ChainStatus.{FollowingChain, Mining, WaitForNewChain} -import units.client.L2BlockLike +import units.client.CommonBlockData import units.client.contract.* import units.client.engine.EngineApiClient import units.client.engine.EngineApiClient.PayloadId import units.client.engine.model.* import units.client.engine.model.Withdrawal.WithdrawalIndex -import units.eth.{EmptyL2Block, EthAddress, EthereumConstants} +import units.eth.{EmptyPayload, EthAddress, EthereumConstants} import units.network.BlocksObserverImpl.BlockWithChannel import units.util.HexBytesConverter import units.util.HexBytesConverter.toHexNoPrefix @@ -66,7 +66,7 @@ class ELUpdater( def consensusLayerChanged(): Unit = handleNextUpdate := scheduler.scheduleOnce(ClChangedProcessingDelay)(handleConsensusLayerChanged()) - def executionBlockReceived(block: NetworkL2Block, ch: Channel): Unit = scheduler.execute { () => + def executionBlockReceived(block: NetworkBlock, ch: Channel): Unit = scheduler.execute { () => logger.debug(s"New block ${block.hash}->${block.parentHash} (timestamp=${block.timestamp}, height=${block.height}) appeared") val now = time.correctedTime() / 1000 @@ -74,7 +74,7 @@ class ELUpdater( state match { case WaitingForSyncHead(target, _) if block.hash == target.hash => val syncStarted = for { - _ <- engineApiClient.applyNewPayload(block.payload) + _ <- engineApiClient.applyNewPayload(block.payloadJson) fcuStatus <- confirmBlock(target, target) } yield fcuStatus @@ -87,15 +87,15 @@ class ELUpdater( 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(_, lastPayload, _, _, _, FollowingChain(nodeChainInfo, _), _, returnToMainChainInfo) + if block.parentHash == lastPayload.hash => + validateAndApply(block, ch, w, lastPayload, 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) + validateAndApplyMissedBlock(block, ch, w, rInfo.missedBlock, rInfo.missedBlockParentPayload, chainInfo) case Some(_) => logger.debug(s"Chain ${rInfo.chainId} is not main anymore, ignoring ${block.hash}") case _ => @@ -164,7 +164,7 @@ class ELUpdater( } } - private def callContract(fc: FUNCTION_CALL, blockData: EcBlock, invoker: KeyPair): JobResult[Unit] = { + private def callContract(fc: FUNCTION_CALL, payload: ExecutionPayload, invoker: KeyPair): JobResult[Unit] = { val extraFee = if (blockchain.hasPaidVerifier(invoker.toAddress)) ScriptExtraFee else 0 val tx = InvokeScriptTransaction( @@ -179,7 +179,7 @@ class ELUpdater( Proofs.empty, blockchain.settings.addressSchemeCharacter.toByte ).signWith(invoker.privateKey) - logger.info(s"Invoking $contractAddress '${fc.function.funcName}' for block ${blockData.hash}->${blockData.parentHash}, txId=${tx.id()}") + logger.info(s"Invoking $contractAddress '${fc.function.funcName}' for block ${payload.hash}->${payload.parentHash}, txId=${tx.id()}") cleanPriorityPool() broadcastTx(tx).resultE match { @@ -226,27 +226,23 @@ class ELUpdater( ) case _ => (for { - payload <- engineApiClient.getPayload(payloadId) + payloadJson <- engineApiClient.getPayloadJson(payloadId) _ = logger.info(s"Forged payload $payloadId") - latestValidHashOpt <- engineApiClient.applyNewPayload(payload) + latestValidHashOpt <- engineApiClient.applyNewPayload(payloadJson) latestValidHash <- Either.fromOption(latestValidHashOpt, ClientError("Latest valid hash not defined")) _ = logger.info(s"Applied payload $payloadId, block hash is $latestValidHash, timestamp = $timestamp") - newBlock <- NetworkL2Block.signed(payload, m.keyPair.privateKey) + newBlock <- NetworkBlock.signed(payloadJson, m.keyPair.privateKey) _ = logger.debug(s"Broadcasting block ${newBlock.hash}") _ <- Try(allChannels.broadcast(newBlock)).toEither.leftMap(err => ClientError(s"Failed to broadcast block ${newBlock.hash}: ${err.toString}") ) - ecBlock = newBlock.toEcBlock - transfersRootHash <- getE2CTransfersRootHash(ecBlock.hash, chainContractOptions.elBridgeAddress) - funcCall <- contractFunction.toFunctionCall(ecBlock.hash, transfersRootHash, m.lastC2ETransferIndex) - _ <- callContract( - funcCall, - ecBlock, - m.keyPair - ) - } yield ecBlock).fold( + executionPayload = newBlock.toPayload + transfersRootHash <- getE2CTransfersRootHash(executionPayload.hash, chainContractOptions.elBridgeAddress) + funcCall <- contractFunction.toFunctionCall(executionPayload.hash, transfersRootHash, m.lastC2ETransferIndex) + _ <- callContract(funcCall, executionPayload, m.keyPair) + } yield executionPayload).fold( err => logger.error(s"Failed to forge block for payloadId $payloadId at epoch ${epochInfo.number}: ${err.message}"), - newEcBlock => scheduler.execute { () => tryToForgeNextBlock(epochInfo.number, newEcBlock, chainContractOptions) } + newPayload => scheduler.execute { () => tryToForgeNextBlock(epochInfo.number, newPayload, chainContractOptions) } ) } case Working(_, _, _, _, _, _: Mining | _: FollowingChain, _, _) => @@ -257,30 +253,30 @@ class ELUpdater( } } - private def rollbackTo(prevState: Working[ChainStatus], target: L2BlockLike, finalizedBlock: ContractBlock): JobResult[Working[ChainStatus]] = { + private def rollbackTo(prevState: Working[ChainStatus], target: CommonBlockData, finalizedBlock: ContractBlock): JobResult[Working[ChainStatus]] = { val targetHash = target.hash for { rollbackBlock <- mkRollbackBlock(targetHash) _ = logger.info(s"Starting rollback to $targetHash using rollback block ${rollbackBlock.hash}") - fixedFinalizedBlock = if (finalizedBlock.height > rollbackBlock.parentBlock.height) rollbackBlock.parentBlock else finalizedBlock + fixedFinalizedBlock = if (finalizedBlock.height > rollbackBlock.parentPayload.height) rollbackBlock.parentPayload else finalizedBlock _ <- confirmBlock(rollbackBlock.hash, fixedFinalizedBlock.hash) _ <- confirmBlock(target, fixedFinalizedBlock) - lastEcBlock <- engineApiClient.getLastExecutionBlock + lastPayload <- engineApiClient.getLastPayload _ <- Either.cond( - targetHash == lastEcBlock.hash, + targetHash == lastPayload.hash, (), - ClientError(s"Rollback to $targetHash error: last execution block ${lastEcBlock.hash} is not equal to target block hash") + ClientError(s"Rollback to $targetHash error: last block hash ${lastPayload.hash} is not equal to target block hash") ) } yield { logger.info(s"Rollback to $targetHash finished successfully") - val updatedLastValidatedBlock = if (lastEcBlock.height < prevState.fullValidationStatus.lastValidatedBlock.height) { - chainContractClient.getBlock(lastEcBlock.hash).getOrElse(finalizedBlock) + val updatedLastValidatedBlock = if (lastPayload.height < prevState.fullValidationStatus.lastValidatedBlock.height) { + chainContractClient.getBlock(lastPayload.hash).getOrElse(finalizedBlock) } else { prevState.fullValidationStatus.lastValidatedBlock } val newState = prevState.copy( - lastEcBlock = lastEcBlock, + lastPayload = lastPayload, fullValidationStatus = FullValidationStatus(updatedLastValidatedBlock, None) ) setState("10", newState) @@ -290,7 +286,7 @@ class ELUpdater( private def startBuildingPayload( epochInfo: EpochInfo, - parentBlock: EcBlock, + parentPayload: ExecutionPayload, finalizedBlock: ContractBlock, nextBlockUnixTs: Long, lastC2ETransferIndex: Long, @@ -317,16 +313,16 @@ class ELUpdater( val withdrawals = rewardWithdrawal ++ transferWithdrawals confirmBlockAndStartMining( - parentBlock, + parentPayload, finalizedBlock, nextBlockUnixTs, epochInfo.rewardAddress, - calculateRandao(epochInfo.hitSource, parentBlock.hash), + calculateRandao(epochInfo.hitSource, parentPayload.hash), withdrawals ).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 $startC2ETransferIndex" + s"Starting to forge payload $payloadId by miner ${epochInfo.miner} at height ${parentPayload.height + 1} " + + s"of epoch ${epochInfo.number} (ref=${parentPayload.hash}), ${withdrawals.size} withdrawals, ${transfers.size} transfers from $startC2ETransferIndex" ) MiningData(payloadId, nextBlockUnixTs, transfers.lastOption.fold(lastC2ETransferIndex)(_.index), lastElWithdrawalIndex + withdrawals.size) @@ -334,8 +330,8 @@ class ELUpdater( } private def tryToStartMining(prevState: Working[ChainStatus], nodeChainInfo: Either[ChainSwitchInfo, ChainInfo]): Unit = { - val parentBlock = prevState.lastEcBlock - val epochInfo = prevState.epochInfo + val parentPayload = prevState.lastPayload + val epochInfo = prevState.epochInfo wallet.privateKeyAccount(epochInfo.miner) match { case Right(keyPair) if config.miningEnable => @@ -348,27 +344,27 @@ class ELUpdater( (for { elWithdrawalIndexBefore <- - parentBlock.withdrawals.lastOption.map(_.index) match { + parentPayload.withdrawals.lastOption.map(_.index) match { case Some(r) => Right(r) case None => - if (parentBlock.height - 1 <= EthereumConstants.GenesisBlockHeight) Right(-1L) - else getLastWithdrawalIndex(parentBlock.parentHash) + if (parentPayload.height - 1 <= EthereumConstants.GenesisBlockHeight) Right(-1L) + else getLastWithdrawalIndex(parentPayload.parentHash) } - nextBlockUnixTs = (parentBlock.timestamp + config.blockDelay.toSeconds).max(time.correctedTime() / 1000 + config.blockDelay.toSeconds) + nextBlockUnixTs = (parentPayload.timestamp + config.blockDelay.toSeconds).max(time.correctedTime() / 1000 + config.blockDelay.toSeconds) miningData <- startBuildingPayload( epochInfo, - parentBlock, + parentPayload, prevState.finalizedBlock, nextBlockUnixTs, lastC2ETransferIndex, elWithdrawalIndexBefore, prevState.options, - Option.unless(parentBlock.height == EthereumConstants.GenesisBlockHeight)(parentBlock.minerRewardL2Address) + Option.unless(parentPayload.height == EthereumConstants.GenesisBlockHeight)(parentPayload.minerRewardAddress) ) } yield { val newState = prevState.copy( epochInfo = epochInfo, - lastEcBlock = parentBlock, + lastPayload = parentPayload, chainStatus = Mining(keyPair, miningData.payloadId, nodeChainInfo, miningData.lastC2ETransferIndex, miningData.lastElWithdrawalIndex) ) @@ -376,9 +372,9 @@ class ELUpdater( scheduler.scheduleOnce((miningData.nextBlockUnixTs - time.correctedTime() / 1000).min(1).seconds)( prepareAndApplyPayload( miningData.payloadId, - parentBlock.hash, + parentPayload.hash, miningData.nextBlockUnixTs, - newState.options.startEpochChainFunction(parentBlock.hash, epochInfo.hitSource, nodeChainInfo.toOption), + newState.options.startEpochChainFunction(parentPayload.hash, epochInfo.hitSource, nodeChainInfo.toOption), newState.options ) ) @@ -393,16 +389,16 @@ class ELUpdater( private def tryToForgeNextBlock( epochNumber: Int, - parentBlock: EcBlock, + parentPayload: ExecutionPayload, chainContractOptions: ChainContractOptions ): Unit = { state match { case w @ Working(epochInfo, _, finalizedBlock, _, _, m: Mining, _, _) if epochInfo.number == epochNumber && blockchain.height == epochNumber => - val nextBlockUnixTs = (parentBlock.timestamp + config.blockDelay.toSeconds).max(time.correctedTime() / 1000) + val nextBlockUnixTs = (parentPayload.timestamp + config.blockDelay.toSeconds).max(time.correctedTime() / 1000) startBuildingPayload( epochInfo, - parentBlock, + parentPayload, finalizedBlock, nextBlockUnixTs, m.lastC2ETransferIndex, @@ -413,12 +409,12 @@ class ELUpdater( err => { logger.error(s"Error starting payload build process: ${err.message}") scheduler.scheduleOnce(MiningRetryInterval) { - tryToForgeNextBlock(epochNumber, parentBlock, chainContractOptions) + tryToForgeNextBlock(epochNumber, parentPayload, chainContractOptions) } }, miningData => { val newState = w.copy( - lastEcBlock = parentBlock, + lastPayload = parentPayload, chainStatus = m.copy( currentPayloadId = miningData.payloadId, lastC2ETransferIndex = miningData.lastC2ETransferIndex, @@ -429,15 +425,16 @@ class ELUpdater( scheduler.scheduleOnce((miningData.nextBlockUnixTs - time.correctedTime() / 1000).min(1).seconds)( prepareAndApplyPayload( miningData.payloadId, - parentBlock.hash, + parentPayload.hash, miningData.nextBlockUnixTs, - chainContractOptions.appendFunction(parentBlock.hash), + chainContractOptions.appendFunction(parentPayload.hash), chainContractOptions ) ) } ) - case other => logger.debug(s"Unexpected state $other attempting to start building block referencing ${parentBlock.hash} at epoch $epochNumber") + case other => + logger.debug(s"Unexpected state $other attempting to start building block referencing ${parentPayload.hash} at epoch $epochNumber") } } @@ -447,14 +444,14 @@ class ELUpdater( else { val finalizedBlock = chainContractClient.getFinalizedBlock logger.debug(s"Finalized block is ${finalizedBlock.hash}") - engineApiClient.getBlockByHash(finalizedBlock.hash) match { - case Left(error) => logger.error(s"Could not load finalized block", error) - case Right(Some(finalizedEcBlock)) => - logger.trace(s"Finalized block ${finalizedBlock.hash} is at height ${finalizedEcBlock.height}") + engineApiClient.getPayloadByHash(finalizedBlock.hash) match { + case Left(error) => logger.error(s"Could not load finalized block payload", error) + case Right(Some(finalizedBlockPayload)) => + logger.trace(s"Finalized block ${finalizedBlock.hash} is at height ${finalizedBlockPayload.height}") (for { newEpochInfo <- calculateEpochInfo mainChainInfo <- chainContractClient.getMainChainInfo.toRight("Can't get main chain info") - lastEcBlock <- engineApiClient.getLastExecutionBlock.leftMap(_.message) + lastPayload <- engineApiClient.getLastPayload.leftMap(_.message) } yield { logger.trace(s"Following main chain ${mainChainInfo.id}") val fullValidationStatus = FullValidationStatus( @@ -465,7 +462,7 @@ class ELUpdater( followChainAndRequestNextBlock( newEpochInfo, mainChainInfo, - lastEcBlock, + lastPayload, mainChainInfo, finalizedBlock, fullValidationStatus, @@ -477,7 +474,7 @@ class ELUpdater( _ => () ) case Right(None) => - logger.trace(s"Finalized block ${finalizedBlock.hash} is not in EC, requesting from peers") + logger.trace(s"Finalized block ${finalizedBlock.hash} payload is not in EC, requesting block from peers") setState("15", WaitingForSyncHead(finalizedBlock, requestAndProcessBlock(finalizedBlock.hash))) } } @@ -520,7 +517,7 @@ class ELUpdater( private def requestBlocksAndStartMining(prevState: Working[FollowingChain]): Unit = { def check(missedBlock: ContractBlock): Unit = { state match { - case w @ Working(epochInfo, lastEcBlock, finalizedBlock, mainChainInfo, _, fc: FollowingChain, _, returnToMainChainInfo) + case w @ Working(epochInfo, lastPayload, finalizedBlock, mainChainInfo, _, fc: FollowingChain, _, returnToMainChainInfo) 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 { @@ -529,25 +526,25 @@ class ELUpdater( } yield { val updatedReturnToMainChainInfo = if (fc.nodeChainInfo.isMain) { - Some(ReturnToMainChainInfo(missedBlock, lastEcBlock, mainChainInfo.id)) + Some(ReturnToMainChainInfo(missedBlock, lastPayload, mainChainInfo.id)) } else returnToMainChainInfo findAltChain(fc.nodeChainInfo.id, lastValidBlock.hash) match { case Some(altChainInfo) => - engineApiClient.getBlockByHash(finalizedBlock.hash) match { - case Right(Some(finalizedEcBlock)) => + engineApiClient.getPayloadByHash(finalizedBlock.hash) match { + case Right(Some(finalizedBlockPayload)) => followChainAndStartMining( updatedState.copy(chainStatus = FollowingChain(altChainInfo, None), returnToMainChainInfo = updatedReturnToMainChainInfo), epochInfo, altChainInfo.id, - finalizedEcBlock, + finalizedBlockPayload, finalizedBlock, chainContractClient.getOptions ) case Right(None) => - logger.warn(s"Finalized block ${finalizedBlock.hash} is not in EC") + logger.warn(s"Finalized block ${finalizedBlock.hash} payload is not in EC") case Left(err) => - logger.error(s"Could not load finalized block ${finalizedBlock.hash}", err) + logger.error(s"Could not load finalized block ${finalizedBlock.hash} payload", err) } case _ => val chainSwitchInfo = ChainSwitchInfo(fc.nodeChainInfo.id, lastValidBlock) @@ -596,7 +593,7 @@ class ELUpdater( prevState: Working[ChainStatus], newEpochInfo: EpochInfo, prevChainId: Long, - finalizedEcBlock: EcBlock, + finalizedBlockPayload: ExecutionPayload, finalizedBlock: ContractBlock, options: ChainContractOptions ): Unit = { @@ -604,7 +601,7 @@ class ELUpdater( prevState, newEpochInfo, prevChainId, - finalizedEcBlock, + finalizedBlockPayload, finalizedBlock, options ).foreach { newState => @@ -642,10 +639,10 @@ class ELUpdater( val finalizedBlock = chainContractClient.getFinalizedBlock val options = chainContractClient.getOptions logger.debug(s"Finalized block is ${finalizedBlock.hash}") - engineApiClient.getBlockByHash(finalizedBlock.hash) match { - case Left(error) => logger.error(s"Could not load finalized block", error) - case Right(Some(finalizedEcBlock)) => - logger.trace(s"Finalized block ${finalizedBlock.hash} is at height ${finalizedEcBlock.height}") + engineApiClient.getPayloadByHash(finalizedBlock.hash) match { + case Left(error) => logger.error(s"Could not load finalized block payload", error) + case Right(Some(finalizedBlockPayload)) => + logger.trace(s"Finalized block ${finalizedBlock.hash} is at height ${finalizedBlockPayload.height}") if (blockchain.height != prevState.epochInfo.number || !blockchain.vrf(blockchain.height).contains(prevState.epochInfo.hitSource)) { calculateEpochInfo match { case Left(error) => @@ -658,7 +655,7 @@ class ELUpdater( prevState, newEpochInfo, nodeChainInfo.id, - finalizedEcBlock, + finalizedBlockPayload, finalizedBlock, options ) @@ -669,7 +666,7 @@ class ELUpdater( prevState, newEpochInfo, chainId, - finalizedEcBlock, + finalizedBlockPayload, finalizedBlock, options ) @@ -680,7 +677,7 @@ class ELUpdater( prevState, newEpochInfo, chainInfo.id, - finalizedEcBlock, + finalizedBlockPayload, finalizedBlock, options ) @@ -698,7 +695,7 @@ class ELUpdater( prevState, prevState.epochInfo, nodeChainInfo.id, - finalizedEcBlock, + finalizedBlockPayload, finalizedBlock, options ) @@ -706,14 +703,14 @@ class ELUpdater( case WaitForNewChain(chainSwitchInfo) => val newChainInfo = findAltChain(chainSwitchInfo.prevChainId, chainSwitchInfo.referenceBlock.hash) newChainInfo.foreach { chainInfo => - updateToFollowChain(prevState, prevState.epochInfo, chainInfo.id, finalizedEcBlock, finalizedBlock, options) + updateToFollowChain(prevState, prevState.epochInfo, chainInfo.id, finalizedBlockPayload, finalizedBlock, options) } } } validateAppliedBlocks() requestMainChainBlock() case Right(None) => - logger.trace(s"Finalized block ${finalizedBlock.hash} is not in EC, requesting from peers") + logger.trace(s"Finalized block ${finalizedBlock.hash} payload is not in EC, requesting block from peers") setState("19", WaitingForSyncHead(finalizedBlock, requestAndProcessBlock(finalizedBlock.hash))) } } @@ -721,7 +718,7 @@ class ELUpdater( private def followChainAndRequestNextBlock( epochInfo: EpochInfo, nodeChainInfo: ChainInfo, - lastEcBlock: EcBlock, + lastPayload: ExecutionPayload, mainChainInfo: ChainInfo, finalizedBlock: ContractBlock, fullValidationStatus: FullValidationStatus, @@ -730,7 +727,7 @@ class ELUpdater( ): Working[FollowingChain] = { val newState = Working( epochInfo, - lastEcBlock, + lastPayload, finalizedBlock, mainChainInfo, fullValidationStatus, @@ -744,8 +741,8 @@ class ELUpdater( 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) + engineApiClient.getPayloadByHash(contractBlock.hash) match { + case Right(Some(payload)) => BlockRequestResult.PayloadExists(payload) case Right(None) => requestAndProcessBlock(contractBlock.hash) BlockRequestResult.Requested(contractBlock) @@ -762,19 +759,19 @@ class ELUpdater( w.returnToMainChainInfo.foreach { returnToMainChainInfo => if (w.mainChainInfo.id == returnToMainChainInfo.chainId) { requestBlock(returnToMainChainInfo.missedBlock) match { - case BlockRequestResult.BlockExists(block) => - logger.debug(s"Block ${returnToMainChainInfo.missedBlock.hash} exists at execution chain, trying to validate") - validateAppliedBlock(returnToMainChainInfo.missedBlock, block, w) match { + case BlockRequestResult.PayloadExists(payload) => + logger.debug(s"Block ${returnToMainChainInfo.missedBlock.hash} payload exists at execution chain, trying to validate") + validateAppliedBlock(returnToMainChainInfo.missedBlock, payload, w) match { case Right(updatedState) => - logger.debug(s"Missed block ${block.hash} of main chain ${returnToMainChainInfo.chainId} was successfully validated") + logger.debug(s"Missed block ${payload.hash} of main chain ${returnToMainChainInfo.chainId} was successfully validated") chainContractClient.getChainInfo(returnToMainChainInfo.chainId) match { case Some(mainChainInfo) => - confirmBlockAndFollowChain(block, updatedState, mainChainInfo, None) + confirmBlockAndFollowChain(payload, updatedState, mainChainInfo, None) case _ => logger.error(s"Failed to get chain ${returnToMainChainInfo.chainId} info: not found") } case Left(err) => - logger.debug(s"Missed block ${block.hash} of main chain ${returnToMainChainInfo.chainId} validation error: ${err.message}") + logger.debug(s"Missed block ${payload.hash} of main chain ${returnToMainChainInfo.chainId} validation error: ${err.message}") } case BlockRequestResult.Requested(_) => } @@ -784,7 +781,7 @@ class ELUpdater( } } - private def requestAndProcessBlock(hash: BlockHash): CancelableFuture[(Channel, NetworkL2Block)] = { + private def requestAndProcessBlock(hash: BlockHash): CancelableFuture[(Channel, NetworkBlock)] = { requestBlockFromPeers(hash).andThen { case Success((ch, block)) => executionBlockReceived(block, ch) case Failure(exception) => logger.error(s"Error loading block $hash", exception) @@ -795,37 +792,37 @@ class ELUpdater( prevState: Working[ChainStatus], epochInfo: EpochInfo, prevChainId: Long, - finalizedEcBlock: EcBlock, + finalizedBlockPayload: ExecutionPayload, finalizedContractBlock: ContractBlock, options: ChainContractOptions ): Option[Working[FollowingChain]] = { @tailrec - def findLastEcBlock(curBlock: ContractBlock): EcBlock = { - engineApiClient.getBlockByHash(curBlock.hash) match { - case Right(Some(block)) => block + def findLastPayload(curBlock: ContractBlock): ExecutionPayload = { + engineApiClient.getPayloadByHash(curBlock.hash) match { + case Right(Some(payload)) => payload case Right(_) => chainContractClient.getBlock(curBlock.parentHash) match { - case Some(parent) => findLastEcBlock(parent) + case Some(parent) => findLastPayload(parent) case _ => logger.warn(s"Block ${curBlock.parentHash} not found at contract") - finalizedEcBlock + finalizedBlockPayload } case Left(err) => - logger.warn(s"Failed to get block ${curBlock.hash} by hash: ${err.message}") - finalizedEcBlock + logger.warn(s"Failed to get block ${curBlock.hash} payload by hash: ${err.message}") + finalizedBlockPayload } } def followChain( nodeChainInfo: ChainInfo, - lastEcBlock: EcBlock, + lastPayload: ExecutionPayload, mainChainInfo: ChainInfo, fullValidationStatus: FullValidationStatus, returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Working[FollowingChain] = { val newState = Working( epochInfo, - lastEcBlock, + lastPayload, finalizedContractBlock, mainChainInfo, fullValidationStatus, @@ -838,28 +835,28 @@ class ELUpdater( } def rollbackAndFollowChain( - target: L2BlockLike, - nodeChainInfo: ChainInfo, - mainChainInfo: ChainInfo, - returnToMainChainInfo: Option[ReturnToMainChainInfo] + target: CommonBlockData, + nodeChainInfo: ChainInfo, + mainChainInfo: ChainInfo, + returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Option[Working[FollowingChain]] = { rollbackTo(prevState, target, finalizedContractBlock) match { case Right(updatedState) => - Some(followChain(nodeChainInfo, updatedState.lastEcBlock, mainChainInfo, updatedState.fullValidationStatus, returnToMainChainInfo)) + Some(followChain(nodeChainInfo, updatedState.lastPayload, mainChainInfo, updatedState.fullValidationStatus, returnToMainChainInfo)) case Left(err) => logger.error(s"Failed to rollback to ${target.hash}: ${err.message}") None } } - def rollbackAndFollowMainChain(target: L2BlockLike, mainChainInfo: ChainInfo): Option[Working[FollowingChain]] = + def rollbackAndFollowMainChain(target: CommonBlockData, mainChainInfo: ChainInfo): Option[Working[FollowingChain]] = rollbackAndFollowChain(target, mainChainInfo, mainChainInfo, None) (chainContractClient.getMainChainInfo, chainContractClient.getChainInfo(prevChainId)) match { case (Some(mainChainInfo), Some(prevChainInfo)) => if (mainChainInfo.id != prevState.mainChainInfo.id) { - val updatedLastEcBlock = findLastEcBlock(mainChainInfo.lastBlock) - rollbackAndFollowMainChain(updatedLastEcBlock, mainChainInfo) + val updatedLastPayload = findLastPayload(mainChainInfo.lastBlock) + rollbackAndFollowMainChain(updatedLastPayload, mainChainInfo) } else if (prevChainInfo.firstBlock.height < finalizedContractBlock.height && !prevChainInfo.isMain) { val targetBlockHash = prevChainInfo.firstBlock.parentHash chainContractClient.getBlock(targetBlockHash) match { @@ -868,33 +865,33 @@ class ELUpdater( logger.error(s"Failed to get block $targetBlockHash meta at contract") None } - } else if (isLastEcBlockOnFork(prevChainInfo, prevState.lastEcBlock)) { - val updatedLastEcBlock = findLastEcBlock(prevChainInfo.lastBlock) - rollbackAndFollowChain(updatedLastEcBlock, prevChainInfo, mainChainInfo, prevState.returnToMainChainInfo) + } else if (isLastPayloadOnFork(prevChainInfo, prevState.lastPayload)) { + val updatedLastPayload = findLastPayload(prevChainInfo.lastBlock) + rollbackAndFollowChain(updatedLastPayload, prevChainInfo, mainChainInfo, prevState.returnToMainChainInfo) } else { - Some(followChain(prevChainInfo, prevState.lastEcBlock, mainChainInfo, prevState.fullValidationStatus, prevState.returnToMainChainInfo)) + Some(followChain(prevChainInfo, prevState.lastPayload, mainChainInfo, prevState.fullValidationStatus, prevState.returnToMainChainInfo)) } case (Some(mainChainInfo), None) => - rollbackAndFollowMainChain(finalizedEcBlock, mainChainInfo) + rollbackAndFollowMainChain(finalizedBlockPayload, mainChainInfo) case (None, _) => logger.error("Failed to get main chain info") None } } - private def isLastEcBlockOnFork(chainInfo: ChainInfo, lastEcBlock: EcBlock) = - chainInfo.lastBlock.height == lastEcBlock.height && chainInfo.lastBlock.hash != lastEcBlock.hash || - chainInfo.lastBlock.height > lastEcBlock.height && !chainContractClient.blockExists(lastEcBlock.hash) || - chainInfo.lastBlock.height < lastEcBlock.height + private def isLastPayloadOnFork(chainInfo: ChainInfo, lastPayload: ExecutionPayload) = + chainInfo.lastBlock.height == lastPayload.height && chainInfo.lastBlock.hash != lastPayload.hash || + chainInfo.lastBlock.height > lastPayload.height && !chainContractClient.blockExists(lastPayload.hash) || + chainInfo.lastBlock.height < lastPayload.height private def waitForSyncCompletion(target: ContractBlock): Unit = scheduler.scheduleOnce(5.seconds)(state match { case SyncingToFinalizedBlock(finalizedBlockHash) if finalizedBlockHash == target.hash => logger.debug(s"Checking if EL has synced to ${target.hash} on height ${target.height}") - engineApiClient.getLastExecutionBlock match { + engineApiClient.getLastPayload match { case Left(error) => logger.error(s"Sync to ${target.hash} was not completed, error=${error.message}") setState("23", Starting) - case Right(lastBlock) if lastBlock.hash == target.hash => + case Right(lastPayload) if lastPayload.hash == target.hash => logger.debug(s"Finished synchronization to ${target.hash} successfully") calculateEpochInfo match { case Left(err) => @@ -912,7 +909,7 @@ class ELUpdater( followChainAndRequestNextBlock( newEpochInfo, mainChainInfo, - lastBlock, + lastPayload, mainChainInfo, target, fullValidationStatus, @@ -924,42 +921,42 @@ class ELUpdater( setState("25", Starting) } } - case Right(lastBlock) => - logger.debug(s"Sync to ${target.hash} is in progress: current last block is ${lastBlock.hash} at height ${lastBlock.height}") + case Right(lastPayload) => + logger.debug(s"Sync to ${target.hash} is in progress: current last block is ${lastPayload.hash} at height ${lastPayload.height}") waitForSyncCompletion(target) } case other => logger.debug(s"Unexpected state on sync: $other") }) - private def validateRandao(block: EcBlock, epochNumber: Int): JobResult[Unit] = + private def validateRandao(payload: ExecutionPayload, 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) + val expectedPrevRandao = calculateRandao(vrf, payload.parentHash) Either.cond( - expectedPrevRandao == block.prevRandao, + expectedPrevRandao == payload.prevRandao, (), - ClientError(s"expected prevRandao $expectedPrevRandao, got ${block.prevRandao}, VRF=$vrf of $epochNumber") + ClientError(s"expected prevRandao $expectedPrevRandao, got ${payload.prevRandao}, VRF=$vrf of $epochNumber") ) } - private def validateMiner(block: NetworkL2Block, epochInfo: Option[EpochInfo]): JobResult[Unit] = { + private def validateMiner(block: NetworkBlock, epochInfo: Option[EpochInfo]): JobResult[Unit] = { epochInfo match { case Some(epochMeta) => for { _ <- Either.cond( - block.minerRewardL2Address == epochMeta.rewardAddress, + block.minerRewardAddress == epochMeta.rewardAddress, (), - ClientError(s"block miner ${block.minerRewardL2Address} doesn't equal to ${epochMeta.rewardAddress}") + ClientError(s"block miner ${block.minerRewardAddress} 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") + chainContractClient.getMinerPublicKey(block.minerRewardAddress), + ClientError(s"public key for block miner ${block.minerRewardAddress} not found") ) _ <- Either.cond( - crypto.verify(signature, Json.toBytes(block.payload), publicKey, checkWeakPk = true), + crypto.verify(signature, Json.toBytes(block.payloadJson), publicKey, checkWeakPk = true), (), ClientError(s"invalid signature") ) @@ -968,27 +965,27 @@ class ELUpdater( } } - private def validateTimestamp(newNetworkBlock: NetworkL2Block, parentEcBlock: EcBlock): JobResult[Unit] = { - val minAppendTs = parentEcBlock.timestamp + config.blockDelay.toSeconds + private def validateTimestamp(block: NetworkBlock, parentPayload: ExecutionPayload): JobResult[Unit] = { + val minAppendTs = parentPayload.timestamp + config.blockDelay.toSeconds Either.cond( - newNetworkBlock.timestamp >= minAppendTs, + block.timestamp >= minAppendTs, (), ClientError( - s"timestamp (${newNetworkBlock.timestamp}) of appended block must be greater or equal $minAppendTs, " + - s"Δ${minAppendTs - newNetworkBlock.timestamp}s" + s"timestamp (${block.timestamp}) of appended block must be greater or equal $minAppendTs, " + + s"Δ${minAppendTs - block.timestamp}s" ) ) } private def preValidateBlock( - networkBlock: NetworkL2Block, - parentBlock: EcBlock, - epochInfo: Option[EpochInfo] + block: NetworkBlock, + parentPayload: ExecutionPayload, + epochInfo: Option[EpochInfo] ): JobResult[Unit] = { for { - _ <- validateTimestamp(networkBlock, parentBlock) - _ <- validateMiner(networkBlock, epochInfo) - _ <- engineApiClient.applyNewPayload(networkBlock.payload) + _ <- validateTimestamp(block, parentPayload) + _ <- validateMiner(block, epochInfo) + _ <- engineApiClient.applyNewPayload(block.payloadJson) } yield () } @@ -1021,71 +1018,71 @@ class ELUpdater( } private def validateAndApplyMissedBlock( - networkBlock: NetworkL2Block, - ch: Channel, - prevState: Working[ChainStatus], - contractBlock: ContractBlock, - parentBlock: EcBlock, - nodeChainInfo: ChainInfo + block: NetworkBlock, + ch: Channel, + prevState: Working[ChainStatus], + contractBlock: ContractBlock, + parentPayload: ExecutionPayload, + nodeChainInfo: ChainInfo ): Unit = { - validateBlockFull(networkBlock, contractBlock, parentBlock, prevState) match { + validateBlockFull(block, contractBlock, parentPayload, 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) + logger.debug(s"Missed block ${block.hash} of main chain ${nodeChainInfo.id} was successfully validated") + broadcastAndConfirmBlock(block, ch, updatedState, nodeChainInfo, None) case Left(err) => - logger.debug(s"Missed block ${networkBlock.hash} of main chain ${nodeChainInfo.id} validation error: ${err.message}, ignoring block") + logger.debug(s"Missed block ${block.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] + block: NetworkBlock, + ch: Channel, + prevState: Working[ChainStatus], + parentPayload: ExecutionPayload, + nodeChainInfo: ChainInfo, + returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Unit = { - chainContractClient.getBlock(networkBlock.hash) match { - case Some(contractBlock) if prevState.fullValidationStatus.lastValidatedBlock.hash == parentBlock.hash => + chainContractClient.getBlock(block.hash) match { + case Some(contractBlock) if prevState.fullValidationStatus.lastValidatedBlock.hash == parentPayload.hash => // all blocks before current was fully validated, so we can perform full validation of this block - validateBlockFull(networkBlock, contractBlock, parentBlock, prevState) match { + validateBlockFull(block, contractBlock, parentPayload, prevState) match { case Right(updatedState) => - logger.debug(s"Block ${networkBlock.hash} was successfully validated") - broadcastAndConfirmBlock(networkBlock, ch, updatedState, nodeChainInfo, returnToMainChainInfo) + logger.debug(s"Block ${block.hash} was successfully validated") + broadcastAndConfirmBlock(block, ch, updatedState, nodeChainInfo, returnToMainChainInfo) case Left(err) => - logger.debug(s"Block ${networkBlock.hash} validation error: ${err.message}") + logger.debug(s"Block ${block.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 { + preValidateBlock(block, parentPayload, epochInfo) match { case Right(_) => - logger.debug(s"Block ${networkBlock.hash} was successfully partially validated") - broadcastAndConfirmBlock(networkBlock, ch, prevState, nodeChainInfo, returnToMainChainInfo) + logger.debug(s"Block ${block.hash} was successfully partially validated") + broadcastAndConfirmBlock(block, ch, prevState, nodeChainInfo, returnToMainChainInfo) case Left(err) => - logger.error(s"Block ${networkBlock.hash} prevalidation error: ${err.message}, ignoring block") + logger.error(s"Block ${block.hash} prevalidation error: ${err.message}, ignoring block") } } } private def confirmBlockAndFollowChain( - block: EcBlock, + payload: ExecutionPayload, prevState: Working[ChainStatus], nodeChainInfo: ChainInfo, returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Unit = { val finalizedBlock = prevState.finalizedBlock - confirmBlock(block, finalizedBlock) + confirmBlock(payload, finalizedBlock) .fold[Unit]( - err => logger.error(s"Can't confirm block ${block.hash} of chain ${nodeChainInfo.id}: ${err.message}"), + err => logger.error(s"Can't confirm block ${payload.hash} of chain ${nodeChainInfo.id}: ${err.message}"), _ => { - logger.info(s"Successfully confirmed block ${block.hash} of chain ${nodeChainInfo.id}") + logger.info(s"Successfully confirmed block ${payload.hash} of chain ${nodeChainInfo.id}") followChainAndRequestNextBlock( prevState.epochInfo, nodeChainInfo, - block, + payload, prevState.mainChainInfo, finalizedBlock, prevState.fullValidationStatus, @@ -1098,17 +1095,17 @@ class ELUpdater( } private def broadcastAndConfirmBlock( - networkBlock: NetworkL2Block, - ch: Channel, - prevState: Working[ChainStatus], - nodeChainInfo: ChainInfo, - returnToMainChainInfo: Option[ReturnToMainChainInfo] + block: NetworkBlock, + 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}") + Try(allChannels.broadcast(block, Some(ch))).recover { err => + logger.error(s"Failed to broadcast block ${block.hash}: ${err.getMessage}") } - confirmBlockAndFollowChain(networkBlock.toEcBlock, prevState, nodeChainInfo, returnToMainChainInfo) + confirmBlockAndFollowChain(block.toPayload, prevState, nodeChainInfo, returnToMainChainInfo) } private def findBlockChild(parent: BlockHash, lastBlockHash: BlockHash): Either[String, ContractBlock] = { @@ -1125,26 +1122,26 @@ class ELUpdater( @tailrec private def maybeRequestNextBlock(prevState: Working[FollowingChain], finalizedBlock: ContractBlock): Working[FollowingChain] = { - if (prevState.lastEcBlock.height < prevState.chainStatus.nodeChainInfo.lastBlock.height) { + if (prevState.lastPayload.height < prevState.chainStatus.nodeChainInfo.lastBlock.height) { logger.debug(s"EC chain is not synced, trying to find next block to request") - findBlockChild(prevState.lastEcBlock.hash, prevState.chainStatus.nodeChainInfo.lastBlock.hash) match { + findBlockChild(prevState.lastPayload.hash, prevState.chainStatus.nodeChainInfo.lastBlock.hash) match { case Left(error) => - logger.error(s"Could not find child of ${prevState.lastEcBlock.hash} on contract: $error") + logger.error(s"Could not find child of ${prevState.lastPayload.hash} on contract: $error") prevState 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 BlockRequestResult.PayloadExists(payload) => + logger.debug(s"Block ${contractBlock.hash} payload exists at EC chain, trying to confirm") + confirmBlock(payload, finalizedBlock) match { case Right(_) => val newState = prevState.copy( - lastEcBlock = ecBlock, + lastPayload = payload, chainStatus = FollowingChain(prevState.chainStatus.nodeChainInfo, None) ) setState("7", newState) maybeRequestNextBlock(newState, finalizedBlock) case Left(err) => - logger.error(s"Failed to confirm next block ${ecBlock.hash}: ${err.message}") + logger.error(s"Failed to confirm next block ${payload.hash}: ${err.message}") prevState } case BlockRequestResult.Requested(contractBlock) => @@ -1160,17 +1157,16 @@ class ELUpdater( } private def mkRollbackBlock(rollbackTargetBlockId: BlockHash): JobResult[RollbackBlock] = for { - targetBlockFromContract <- Right(chainContractClient.getBlock(rollbackTargetBlockId)) - targetBlockOpt <- targetBlockFromContract match { - case None => engineApiClient.getBlockByHash(rollbackTargetBlockId) + targetBlockDataOpt <- chainContractClient.getBlock(rollbackTargetBlockId) match { + case None => engineApiClient.getPayloadByHash(rollbackTargetBlockId) case x => Right(x) } - targetBlock <- Either.fromOption(targetBlockOpt, ClientError(s"Can't find block $rollbackTargetBlockId neither on a contract, nor in EC")) - parentBlock <- engineApiClient.getBlockByHash(targetBlock.parentHash) - parentBlock <- Either.fromOption(parentBlock, ClientError(s"Can't find parent block $rollbackTargetBlockId in execution client")) - rollbackBlockOpt <- engineApiClient.applyNewPayload(EmptyL2Block.mkExecutionPayload(parentBlock)) + targetBlockData <- Either.fromOption(targetBlockDataOpt, ClientError(s"Can't find block $rollbackTargetBlockId neither on a contract, nor in EC")) + parentPayloadOpt <- engineApiClient.getPayloadByHash(targetBlockData.parentHash) + parentPayload <- Either.fromOption(parentPayloadOpt, ClientError(s"Can't find block $rollbackTargetBlockId parent payload in execution client")) + rollbackBlockOpt <- engineApiClient.applyNewPayload(EmptyPayload.mkExecutionPayloadJson(parentPayload)) rollbackBlock <- Either.fromOption(rollbackBlockOpt, ClientError("Rollback block hash is not defined as latest valid hash")) - } yield RollbackBlock(rollbackBlock, parentBlock) + } yield RollbackBlock(rollbackBlock, parentPayload) private def toWithdrawals(transfers: Vector[ChainContractClient.ContractTransfer], firstWithdrawalIndex: Long): Vector[Withdrawal] = transfers.zipWithIndex.map { case (x, i) => @@ -1179,14 +1175,14 @@ class ELUpdater( } 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) => - ecBlock.withdrawals.lastOption match { + engineApiClient.getPayloadByHash(hash).flatMap { + case None => Left(ClientError(s"Can't find block $hash payload on EC during withdrawal search")) + case Some(payload) => + payload.withdrawals.lastOption match { case Some(lastWithdrawal) => Right(lastWithdrawal.index) case None => - if (ecBlock.height == 0) Right(-1L) - else getLastWithdrawalIndex(ecBlock.parentHash) + if (payload.height == 0) Right(-1L) + else getLastWithdrawalIndex(payload.parentHash) } } @@ -1226,7 +1222,7 @@ class ELUpdater( 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 { + validateAppliedBlock(block.contractBlock, block.payload, curState) match { case Right(updatedState) => logger.debug(s"Block ${block.hash} was successfully validated") Right(updatedState) @@ -1260,7 +1256,7 @@ class ELUpdater( private def validateWithdrawals( contractBlock: ContractBlock, - ecBlock: EcBlock, + payload: ExecutionPayload, fullValidationStatus: FullValidationStatus, chainContractOptions: ChainContractOptions ): JobResult[Option[WithdrawalIndex]] = { @@ -1279,14 +1275,14 @@ class ELUpdater( val prevMinerElRewardAddress = if (expectMiningReward) chainContractClient.getElRewardAddress(blockPrevEpoch.miner) else None for { - elWithdrawalIndexBefore <- fullValidationStatus.checkedLastElWithdrawalIndex(ecBlock.parentHash) match { + elWithdrawalIndexBefore <- fullValidationStatus.checkedLastElWithdrawalIndex(payload.parentHash) match { case Some(r) => Right(r) case None => - if (ecBlock.height - 1 <= EthereumConstants.GenesisBlockHeight) Right(-1L) - else getLastWithdrawalIndex(ecBlock.parentHash) + if (payload.height - 1 <= EthereumConstants.GenesisBlockHeight) Right(-1L) + else getLastWithdrawalIndex(payload.parentHash) } lastElWithdrawalIndex <- validateC2ETransfers( - ecBlock, + payload, contractBlock, prevMinerElRewardAddress, chainContractOptions, @@ -1297,35 +1293,37 @@ class ELUpdater( } private def validateBlockFull( - networkBlock: NetworkL2Block, - contractBlock: ContractBlock, - parentBlock: EcBlock, - prevState: Working[ChainStatus] + block: NetworkBlock, + contractBlock: ContractBlock, + parentPayload: ExecutionPayload, + prevState: Working[ChainStatus] ): JobResult[Working[ChainStatus]] = { - logger.debug(s"Trying to do full validation of block ${networkBlock.hash}") + logger.debug(s"Trying to do full validation of block ${block.hash}") for { - _ <- preValidateBlock(networkBlock, parentBlock, None) - ecBlock = networkBlock.toEcBlock - updatedState <- validateAppliedBlock(contractBlock, ecBlock, prevState) + _ <- preValidateBlock(block, parentPayload, None) + payload = block.toPayload + updatedState <- validateAppliedBlock(contractBlock, payload, prevState) } yield updatedState } // Note: we can not do this validation before block application, because we need block logs private def validateAppliedBlock( contractBlock: ContractBlock, - ecBlock: EcBlock, + payload: ExecutionPayload, prevState: Working[ChainStatus] ): JobResult[Working[ChainStatus]] = { val validationResult = for { _ <- Either.cond( - contractBlock.minerRewardL2Address == ecBlock.minerRewardL2Address, + contractBlock.minerRewardAddress == payload.minerRewardAddress, (), - ClientError(s"Miner in EC block ${ecBlock.minerRewardL2Address} should be equal to miner on contract ${contractBlock.minerRewardL2Address}") + ClientError( + s"Miner in block payload (${payload.minerRewardAddress}) should be equal to miner on contract (${contractBlock.minerRewardAddress})" + ) ) _ <- validateE2CTransfers(contractBlock, prevState.options.elBridgeAddress) - updatedLastElWithdrawalIndex <- validateWithdrawals(contractBlock, ecBlock, prevState.fullValidationStatus, prevState.options) - _ <- validateRandao(ecBlock, contractBlock.epoch) + updatedLastElWithdrawalIndex <- validateWithdrawals(contractBlock, payload, prevState.fullValidationStatus, prevState.options) + _ <- validateRandao(payload, contractBlock.epoch) } yield updatedLastElWithdrawalIndex validationResult.map { lastElWithdrawalIndex => @@ -1351,8 +1349,8 @@ class ELUpdater( 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")) + .getBlock(updatedState.lastPayload.hash) + .toRight(ClientError(s"Block ${updatedState.lastPayload.hash} not found at contract")) } yield { findAltChain(chainInfo.id, lastValidBlock.hash) match { case Some(altChainInfo) => @@ -1389,16 +1387,16 @@ class ELUpdater( } else { chainContractClient.getBlock(curBlock.parentHash) match { case Some(parentBlock) => - if (curBlock.height > curState.lastEcBlock.height) { + if (curBlock.height > curState.lastPayload.height) { loop(parentBlock, acc) } else { - engineApiClient.getBlockByHash(curBlock.hash) match { - case Right(Some(ecBlock)) => - loop(parentBlock, BlockForValidation(curBlock, ecBlock) :: acc) + engineApiClient.getPayloadByHash(curBlock.hash) match { + case Right(Some(payload)) => + loop(parentBlock, BlockForValidation(curBlock, payload) :: acc) case Right(None) => - Left(ClientError(s"Block ${curBlock.hash} not found on EC client for full validation")) + Left(ClientError(s"Block ${curBlock.hash} payload 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}")) + Left(ClientError(s"Can't get block ${curBlock.hash} payload for full validation: ${err.message}")) } } case _ => @@ -1411,7 +1409,7 @@ class ELUpdater( } private def validateC2ETransfers( - ecBlock: EcBlock, + payload: ExecutionPayload, contractBlock: ContractBlock, prevMinerElRewardAddress: Option[EthAddress], options: ChainContractOptions, @@ -1430,23 +1428,23 @@ class ELUpdater( for { expectedWithdrawals <- prevMinerElRewardAddress match { case None => - if (ecBlock.withdrawals.size == expectedTransfers.size) toWithdrawals(expectedTransfers, firstWithdrawalIndex).asRight - else s"Expected ${expectedTransfers.size} withdrawals, got ${ecBlock.withdrawals.size}".asLeft + if (payload.withdrawals.size == expectedTransfers.size) toWithdrawals(expectedTransfers, firstWithdrawalIndex).asRight + else s"Expected ${expectedTransfers.size} withdrawals, got ${payload.withdrawals.size}".asLeft case Some(prevMinerElRewardAddress) => - if (ecBlock.withdrawals.size == expectedTransfers.size + 1) { // +1 for reward + if (payload.withdrawals.size == expectedTransfers.size + 1) { // +1 for reward val rewardWithdrawal = Withdrawal(firstWithdrawalIndex, prevMinerElRewardAddress, options.miningReward) val userWithdrawals = toWithdrawals(expectedTransfers, rewardWithdrawal.index + 1) (rewardWithdrawal +: userWithdrawals).asRight - } else s"Expected ${expectedTransfers.size + 1} (at least reward) withdrawals, got ${ecBlock.withdrawals.size}".asLeft + } else s"Expected ${expectedTransfers.size + 1} (at least reward) withdrawals, got ${payload.withdrawals.size}".asLeft } - _ <- validateC2ETransfers(ecBlock, expectedWithdrawals) + _ <- validateC2ETransfers(payload, expectedWithdrawals) } yield expectedWithdrawals.lastOption.fold(elWithdrawalIndexBefore)(_.index) } - private def validateC2ETransfers(ecBlock: EcBlock, expectedWithdrawals: Seq[Withdrawal]): Either[String, Unit] = - ecBlock.withdrawals + private def validateC2ETransfers(payload: ExecutionPayload, expectedWithdrawals: Seq[Withdrawal]): Either[String, Unit] = + payload.withdrawals .zip(expectedWithdrawals) .zipWithIndex .toList @@ -1471,26 +1469,26 @@ class ELUpdater( } .map(_ => ()) - private def confirmBlock(block: L2BlockLike, finalizedBlock: L2BlockLike): JobResult[PayloadStatus] = { - val finalizedBlockHash = if (finalizedBlock.height > block.height) block.hash else finalizedBlock.hash - engineApiClient.forkChoiceUpdate(block.hash, finalizedBlockHash) + private def confirmBlock(blockData: CommonBlockData, finalizedBlockData: CommonBlockData): JobResult[PayloadStatus] = { + val finalizedBlockHash = if (finalizedBlockData.height > blockData.height) blockData.hash else finalizedBlockData.hash + engineApiClient.forkChoiceUpdate(blockData.hash, finalizedBlockHash) } private def confirmBlock(hash: BlockHash, finalizedBlockHash: BlockHash): JobResult[PayloadStatus] = engineApiClient.forkChoiceUpdate(hash, finalizedBlockHash) private def confirmBlockAndStartMining( - lastBlock: EcBlock, + lastPayload: ExecutionPayload, finalizedBlock: ContractBlock, unixEpochSeconds: Long, suggestedFeeRecipient: EthAddress, prevRandao: String, withdrawals: Vector[Withdrawal] ): JobResult[PayloadId] = { - val finalizedBlockHash = if (finalizedBlock.height > lastBlock.height) lastBlock.hash else finalizedBlock.hash + val finalizedBlockHash = if (finalizedBlock.height > lastPayload.height) lastPayload.hash else finalizedBlock.hash engineApiClient .forkChoiceUpdateWithPayloadId( - lastBlock.hash, + lastPayload.hash, finalizedBlockHash, unixEpochSeconds, suggestedFeeRecipient, @@ -1527,7 +1525,7 @@ object ELUpdater { case class Working[+CS <: ChainStatus]( epochInfo: EpochInfo, - lastEcBlock: EcBlock, + lastPayload: ExecutionPayload, finalizedBlock: ContractBlock, mainChainInfo: ChainInfo, fullValidationStatus: FullValidationStatus, @@ -1567,24 +1565,24 @@ object ELUpdater { case class SyncingToFinalizedBlock(target: BlockHash) extends State } - private case class RollbackBlock(hash: BlockHash, parentBlock: EcBlock) + private case class RollbackBlock(hash: BlockHash, parentPayload: ExecutionPayload) case class ChainSwitchInfo(prevChainId: Long, referenceBlock: ContractBlock) /** 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(missedBlock: ContractBlock, missedBlockParent: EcBlock, chainId: Long) + case class ReturnToMainChainInfo(missedBlock: ContractBlock, missedBlockParentPayload: ExecutionPayload, chainId: Long) sealed trait BlockRequestResult private object BlockRequestResult { - case class BlockExists(block: EcBlock) extends BlockRequestResult - case class Requested(contractBlock: ContractBlock) extends BlockRequestResult + case class PayloadExists(payload: ExecutionPayload) extends BlockRequestResult + case class Requested(contractBlock: ContractBlock) extends BlockRequestResult } private case class MiningData(payloadId: PayloadId, nextBlockUnixTs: Long, lastC2ETransferIndex: Long, lastElWithdrawalIndex: WithdrawalIndex) - private case class BlockForValidation(contractBlock: ContractBlock, ecBlock: EcBlock) { + private case class BlockForValidation(contractBlock: ContractBlock, payload: ExecutionPayload) { val hash: BlockHash = contractBlock.hash } diff --git a/src/main/scala/units/NetworkBlock.scala b/src/main/scala/units/NetworkBlock.scala new file mode 100644 index 00000000..76b47fc5 --- /dev/null +++ b/src/main/scala/units/NetworkBlock.scala @@ -0,0 +1,99 @@ +package units + +import cats.syntax.either.* +import com.wavesplatform.account.PrivateKey +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.crypto +import com.wavesplatform.crypto.{DigestLength, SignatureLength} +import org.web3j.abi.datatypes.generated.Uint256 +import play.api.libs.json.{JsObject, Json} +import units.client.CommonBlockData +import units.client.engine.model.{ExecutionPayload, Withdrawal} +import units.eth.EthAddress +import units.util.HexBytesConverter.* + +// TODO Refactor to eliminate a manual deserialization, e.g. (raw: JsonObject, parsed: ParsedBlockL2) +class NetworkBlock private ( + val hash: BlockHash, + val timestamp: Long, // UNIX epoch seconds + val height: Long, + val parentHash: BlockHash, + val stateRoot: String, + val minerRewardAddress: EthAddress, + val baseFeePerGas: Uint256, + val gasLimit: Long, + val gasUsed: Long, + val prevRandao: String, + val withdrawals: Vector[Withdrawal], + val payloadBytes: Array[Byte], + val payloadJson: JsObject, + val signature: Option[ByteStr] +) extends CommonBlockData { + def isEpochFirstBlock: Boolean = withdrawals.nonEmpty + + def toPayload: ExecutionPayload = ExecutionPayload( + hash = hash, + parentHash = parentHash, + stateRoot = stateRoot, + height = height, + timestamp = timestamp, + minerRewardAddress = minerRewardAddress, + baseFeePerGas = baseFeePerGas, + gasLimit = gasLimit, + gasUsed = gasUsed, + prevRandao = prevRandao, + withdrawals = withdrawals + ) + + override def toString: String = s"NetworkBlock($hash)" +} + +object NetworkBlock { + private def apply(payload: JsObject, payloadBytes: Array[Byte], signature: Option[ByteStr]): Either[ClientError, NetworkBlock] = { + // See BlockToPayloadMapper for all available fields + (for { + hash <- (payload \ "blockHash").asOpt[BlockHash].toRight("hash not defined") + timestamp <- (payload \ "timestamp").asOpt[String].map(toLong).toRight("timestamp not defined") + height <- (payload \ "blockNumber").asOpt[String].map(toLong).toRight("height not defined") + parentHash <- (payload \ "parentHash").asOpt[BlockHash].toRight("parent hash not defined") + stateRoot <- (payload \ "stateRoot").asOpt[String].toRight("state root not defined") + minerRewardAddress <- (payload \ "feeRecipient").asOpt[EthAddress].toRight("fee recipient not defined") + baseFeePerGas <- (payload \ "baseFeePerGas").asOpt[String].map(toUint256).toRight("baseFeePerGas not defined") + gasLimit <- (payload \ "gasLimit").asOpt[String].map(toLong).toRight("gasLimit not defined") + gasUsed <- (payload \ "gasUsed").asOpt[String].map(toLong).toRight("gasUsed not defined") + prevRandao <- (payload \ "prevRandao").asOpt[String].toRight("prevRandao not defined") + withdrawals <- (payload \ "withdrawals").asOpt[Vector[Withdrawal]].toRight("withdrawals are not defined") + _ <- Either.cond(signature.forall(_.size == SignatureLength), (), "invalid signature size") + } yield new NetworkBlock( + hash, + timestamp, + height, + parentHash, + stateRoot, + minerRewardAddress, + baseFeePerGas, + gasLimit, + gasUsed, + prevRandao, + withdrawals, + payloadBytes, + payload, + signature + )).leftMap(err => ClientError(s"Error creating NetworkBlock from payload ${new String(payloadBytes)}: $err at payload")) + } + + def apply(payloadBytes: Array[Byte], signature: Option[ByteStr]): Either[ClientError, NetworkBlock] = for { + payload <- Json.parse(payloadBytes).asOpt[JsObject].toRight(ClientError("Payload is not a valid JSON object")) + block <- apply(payload, payloadBytes, signature) + } yield block + + def signed(payload: JsObject, signer: PrivateKey): Either[ClientError, NetworkBlock] = { + val payloadBytes = Json.toBytes(payload) + NetworkBlock(payload, payloadBytes, Some(crypto.sign(signer, payloadBytes))) + } + + def apply(payload: JsObject): Either[ClientError, NetworkBlock] = apply(payload, Json.toBytes(payload), None) + + def validateReferenceLength(length: Int): Boolean = + length == DigestLength +} diff --git a/src/main/scala/units/NetworkL2Block.scala b/src/main/scala/units/NetworkL2Block.scala deleted file mode 100644 index 21b6d5eb..00000000 --- a/src/main/scala/units/NetworkL2Block.scala +++ /dev/null @@ -1,99 +0,0 @@ -package units - -import cats.syntax.either.* -import com.wavesplatform.account.PrivateKey -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.crypto -import com.wavesplatform.crypto.{DigestLength, SignatureLength} -import org.web3j.abi.datatypes.generated.Uint256 -import play.api.libs.json.{JsObject, Json} -import units.client.L2BlockLike -import units.client.engine.model.{EcBlock, Withdrawal} -import units.eth.EthAddress -import units.util.HexBytesConverter.* - -// TODO Refactor to eliminate a manual deserialization, e.g. (raw: JsonObject, parsed: ParsedBlockL2) -class NetworkL2Block private ( - val hash: BlockHash, - val timestamp: Long, // UNIX epoch seconds - val height: Long, - val parentHash: BlockHash, - val stateRoot: String, - val minerRewardL2Address: EthAddress, - val baseFeePerGas: Uint256, - val gasLimit: Long, - val gasUsed: Long, - val prevRandao: String, - val withdrawals: Vector[Withdrawal], - val payloadBytes: Array[Byte], - val payload: JsObject, - val signature: Option[ByteStr] -) extends L2BlockLike { - def isEpochFirstBlock: Boolean = withdrawals.nonEmpty - - def toEcBlock: EcBlock = EcBlock( - hash = hash, - parentHash = parentHash, - stateRoot = stateRoot, - height = height, - timestamp = timestamp, - minerRewardL2Address = minerRewardL2Address, - baseFeePerGas = baseFeePerGas, - gasLimit = gasLimit, - gasUsed = gasUsed, - prevRandao = prevRandao, - withdrawals = withdrawals - ) - - override def toString: String = s"NetworkL2Block($hash)" -} - -object NetworkL2Block { - private def apply(payload: JsObject, payloadBytes: Array[Byte], signature: Option[ByteStr]): Either[ClientError, NetworkL2Block] = { - // See BlockToPayloadMapper for all available fields - (for { - hash <- (payload \ "blockHash").asOpt[BlockHash].toRight("hash not defined") - timestamp <- (payload \ "timestamp").asOpt[String].map(toLong).toRight("timestamp not defined") - height <- (payload \ "blockNumber").asOpt[String].map(toLong).toRight("height not defined") - parentHash <- (payload \ "parentHash").asOpt[BlockHash].toRight("parent hash not defined") - stateRoot <- (payload \ "stateRoot").asOpt[String].toRight("state root not defined") - minerRewardL2Address <- (payload \ "feeRecipient").asOpt[EthAddress].toRight("fee recipient not defined") - baseFeePerGas <- (payload \ "baseFeePerGas").asOpt[String].map(toUint256).toRight("baseFeePerGas not defined") - gasLimit <- (payload \ "gasLimit").asOpt[String].map(toLong).toRight("gasLimit not defined") - gasUsed <- (payload \ "gasUsed").asOpt[String].map(toLong).toRight("gasUsed not defined") - prevRandao <- (payload \ "prevRandao").asOpt[String].toRight("prevRandao not defined") - withdrawals <- (payload \ "withdrawals").asOpt[Vector[Withdrawal]].toRight("withdrawals are not defined") - _ <- Either.cond(signature.forall(_.size == SignatureLength), (), "invalid signature size") - } yield new NetworkL2Block( - hash, - timestamp, - height, - parentHash, - stateRoot, - minerRewardL2Address, - baseFeePerGas, - gasLimit, - gasUsed, - prevRandao, - withdrawals, - payloadBytes, - payload, - signature - )).leftMap(err => ClientError(s"Error creating BlockL2 from payload ${new String(payloadBytes)}: $err at payload")) - } - - def apply(payloadBytes: Array[Byte], signature: Option[ByteStr]): Either[ClientError, NetworkL2Block] = for { - payload <- Json.parse(payloadBytes).asOpt[JsObject].toRight(ClientError("Payload is not a valid JSON object")) - block <- apply(payload, payloadBytes, signature) - } yield block - - def signed(payload: JsObject, signer: PrivateKey): Either[ClientError, NetworkL2Block] = { - val payloadBytes = Json.toBytes(payload) - NetworkL2Block(payload, payloadBytes, Some(crypto.sign(signer, payloadBytes))) - } - - def apply(payload: JsObject): Either[ClientError, NetworkL2Block] = apply(payload, Json.toBytes(payload), None) - - def validateReferenceLength(length: Int): Boolean = - length == DigestLength -} diff --git a/src/main/scala/units/client/CommonBlockData.scala b/src/main/scala/units/client/CommonBlockData.scala new file mode 100644 index 00000000..bff4a673 --- /dev/null +++ b/src/main/scala/units/client/CommonBlockData.scala @@ -0,0 +1,13 @@ +package units.client + +import units.BlockHash +import units.eth.{EthAddress, EthereumConstants} + +trait CommonBlockData { + def hash: BlockHash + def parentHash: BlockHash + def height: Long + def minerRewardAddress: EthAddress + + def referencesGenesis: Boolean = height == EthereumConstants.GenesisBlockHeight + 1 +} diff --git a/src/main/scala/units/client/L2BlockLike.scala b/src/main/scala/units/client/L2BlockLike.scala deleted file mode 100644 index 6b30a0a4..00000000 --- a/src/main/scala/units/client/L2BlockLike.scala +++ /dev/null @@ -1,18 +0,0 @@ -package units.client - -import com.wavesplatform.common.state.ByteStr -import units.BlockHash -import units.eth.{EthAddress, EthereumConstants} -import units.util.HexBytesConverter.toByteStr - -trait L2BlockLike { - def hash: BlockHash - def parentHash: BlockHash - def height: Long - def minerRewardL2Address: EthAddress - - lazy val hashByteStr: ByteStr = toByteStr(hash) - lazy val parentHashByteStr: ByteStr = toByteStr(parentHash) - - def referencesGenesis: Boolean = height == EthereumConstants.GenesisBlockHeight + 1 -} diff --git a/src/main/scala/units/client/contract/ContractBlock.scala b/src/main/scala/units/client/contract/ContractBlock.scala index 2beb8088..a49111fe 100644 --- a/src/main/scala/units/client/contract/ContractBlock.scala +++ b/src/main/scala/units/client/contract/ContractBlock.scala @@ -2,22 +2,22 @@ package units.client.contract import com.wavesplatform.common.merkle.Digest import units.BlockHash -import units.client.L2BlockLike +import units.client.CommonBlockData import units.eth.EthAddress import units.util.HexBytesConverter.toHex case class ContractBlock( - hash: BlockHash, - parentHash: BlockHash, - epoch: Int, - height: Long, - minerRewardL2Address: EthAddress, - chainId: Long, - e2cTransfersRootHash: Digest, - lastC2ETransferIndex: Long -) extends L2BlockLike { + hash: BlockHash, + parentHash: BlockHash, + epoch: Int, + height: Long, + minerRewardAddress: EthAddress, + chainId: Long, + e2cTransfersRootHash: Digest, + lastC2ETransferIndex: Long +) extends CommonBlockData { override def toString: String = - s"ContractBlock($hash, p=$parentHash, e=$epoch, h=$height, m=$minerRewardL2Address, c=$chainId, " + + s"ContractBlock($hash, p=$parentHash, e=$epoch, h=$height, m=$minerRewardAddress, c=$chainId, " + s"e2c=${if (e2cTransfersRootHash.isEmpty) "" else toHex(e2cTransfersRootHash)}, c2e=$lastC2ETransferIndex)" } diff --git a/src/main/scala/units/client/engine/EngineApiClient.scala b/src/main/scala/units/client/engine/EngineApiClient.scala index a8c0a675..3ad5b759 100644 --- a/src/main/scala/units/client/engine/EngineApiClient.scala +++ b/src/main/scala/units/client/engine/EngineApiClient.scala @@ -18,21 +18,21 @@ trait EngineApiClient { withdrawals: Vector[Withdrawal] = Vector.empty ): JobResult[PayloadId] - def getPayload(payloadId: PayloadId): JobResult[JsObject] + def getPayloadJson(payloadId: PayloadId): JobResult[JsObject] - def applyNewPayload(payload: JsObject): JobResult[Option[BlockHash]] + def applyNewPayload(payloadJson: JsObject): JobResult[Option[BlockHash]] - def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] + def getPayloadBodyJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] - def getBlockByNumber(number: BlockNumber): JobResult[Option[EcBlock]] + def getPayloadByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] - def getBlockByHash(hash: BlockHash): JobResult[Option[EcBlock]] + def getPayloadByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] - def getBlockByHashJson(hash: BlockHash): JobResult[Option[JsObject]] + def getLastPayload: JobResult[ExecutionPayload] - def getLastExecutionBlock: JobResult[EcBlock] + def getBlockJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] - def blockExists(hash: BlockHash): JobResult[Boolean] + def getPayloadJsonDataByHash(hash: BlockHash): JobResult[PayloadJsonData] def getLogs(hash: BlockHash, address: EthAddress, topic: String): JobResult[List[GetLogsResponseEntry]] } diff --git a/src/main/scala/units/client/engine/HttpEngineApiClient.scala b/src/main/scala/units/client/engine/HttpEngineApiClient.scala index 41deab4e..aeab4cb8 100644 --- a/src/main/scala/units/client/engine/HttpEngineApiClient.scala +++ b/src/main/scala/units/client/engine/HttpEngineApiClient.scala @@ -60,12 +60,12 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide } } - def getPayload(payloadId: PayloadId): JobResult[JsObject] = { + def getPayloadJson(payloadId: PayloadId): JobResult[JsObject] = { sendEngineRequest[GetPayloadRequest, GetPayloadResponse](GetPayloadRequest(payloadId), NonBlockExecutionTimeout).map(_.executionPayload) } - def applyNewPayload(payload: JsObject): JobResult[Option[BlockHash]] = { - sendEngineRequest[NewPayloadRequest, PayloadState](NewPayloadRequest(payload), BlockExecutionTimeout).flatMap { + def applyNewPayload(payloadJson: JsObject): JobResult[Option[BlockHash]] = { + sendEngineRequest[NewPayloadRequest, PayloadState](NewPayloadRequest(payloadJson), BlockExecutionTimeout).flatMap { case PayloadState(_, _, Some(validationError)) => Left(ClientError(s"Payload validation error: $validationError")) case PayloadState(Valid, Some(latestValidHash), _) => Right(Some(latestValidHash)) case PayloadState(Syncing, latestValidHash, _) => Right(latestValidHash) @@ -74,42 +74,44 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide } } - def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] = { + def getPayloadBodyJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = { sendEngineRequest[GetPayloadBodyByHash, JsArray](GetPayloadBodyByHash(hash), NonBlockExecutionTimeout) .map(_.value.headOption.flatMap(_.asOpt[JsObject])) } - def getBlockByNumber(number: BlockNumber): JobResult[Option[EcBlock]] = { + def getPayloadByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] = { for { - json <- getBlockByNumberJson(number.str) - blockMeta <- json.traverse(parseJson[EcBlock](_)) + json <- sendRequest[GetBlockByNumberRequest, JsObject](GetBlockByNumberRequest(number.str)) + .leftMap(err => ClientError(s"Error getting payload by number $number: $err")) + blockMeta <- json.traverse(parseJson[ExecutionPayload](_)) } yield blockMeta } - def getBlockByHash(hash: BlockHash): JobResult[Option[EcBlock]] = { - sendRequest[GetBlockByHashRequest, EcBlock](GetBlockByHashRequest(hash)) - .leftMap(err => ClientError(s"Error getting block by hash $hash: $err")) + def getPayloadByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] = { + sendRequest[GetBlockByHashRequest, ExecutionPayload](GetBlockByHashRequest(hash)) + .leftMap(err => ClientError(s"Error getting payload by hash $hash: $err")) } - def getBlockByHashJson(hash: BlockHash): JobResult[Option[JsObject]] = { + def getLastPayload: JobResult[ExecutionPayload] = for { + lastPayloadOpt <- getPayloadByNumber(BlockNumber.Latest) + lastPayload <- Either.fromOption(lastPayloadOpt, ClientError("Impossible: EC doesn't have payloads")) + } yield lastPayload + + def getBlockJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = { sendRequest[GetBlockByHashRequest, JsObject](GetBlockByHashRequest(hash)) .leftMap(err => ClientError(s"Error getting block json by hash $hash: $err")) } - 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): JobResult[Boolean] = - getBlockByHash(hash).map(_.isDefined) - - private def getBlockByNumberJson(number: String): JobResult[Option[JsObject]] = { - sendRequest[GetBlockByNumberRequest, JsObject](GetBlockByNumberRequest(number)) - .leftMap(err => ClientError(s"Error getting block by number $number: $err")) + def getPayloadJsonDataByHash(hash: BlockHash): JobResult[PayloadJsonData] = { + for { + blockJsonOpt <- getBlockJsonByHash(hash) + blockJson <- Either.fromOption(blockJsonOpt, ClientError("block not found")) + payloadBodyJsonOpt <- getPayloadBodyJsonByHash(hash) + payloadBodyJson <- Either.fromOption(payloadBodyJsonOpt, ClientError("payload body not found")) + } yield PayloadJsonData(blockJson, payloadBodyJson) } - override def getLogs(hash: BlockHash, address: EthAddress, topic: String): JobResult[List[GetLogsResponseEntry]] = + 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)) diff --git a/src/main/scala/units/client/engine/LoggedEngineApiClient.scala b/src/main/scala/units/client/engine/LoggedEngineApiClient.scala index 54a10f64..c01ee0fb 100644 --- a/src/main/scala/units/client/engine/LoggedEngineApiClient.scala +++ b/src/main/scala/units/client/engine/LoggedEngineApiClient.scala @@ -31,29 +31,34 @@ class LoggedEngineApiClient(underlying: EngineApiClient) extends EngineApiClient underlying.forkChoiceUpdateWithPayloadId(lastBlockHash, finalizedBlockHash, unixEpochSeconds, suggestedFeeRecipient, prevRandao, withdrawals) ) - override def getPayload(payloadId: PayloadId): JobResult[JsObject] = - wrap(s"getPayload($payloadId)", underlying.getPayload(payloadId), filteredJson) + override def getPayloadJson(payloadId: PayloadId): JobResult[JsObject] = + wrap(s"getPayloadJson($payloadId)", underlying.getPayloadJson(payloadId), filteredJsonStr) - override def applyNewPayload(payload: JsObject): JobResult[Option[BlockHash]] = - wrap(s"applyNewPayload(${filteredJson(payload)})", underlying.applyNewPayload(payload), _.fold("None")(_.toString)) + override def applyNewPayload(payloadJson: JsObject): JobResult[Option[BlockHash]] = + wrap(s"applyNewPayload(${filteredJsonStr(payloadJson)})", underlying.applyNewPayload(payloadJson), _.fold("None")(identity)) - override def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] = - wrap(s"getPayloadBodyByHash($hash)", underlying.getPayloadBodyByHash(hash), _.fold("None")(filteredJson)) + override def getPayloadBodyJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = + wrap(s"getPayloadBodyJsonByHash($hash)", underlying.getPayloadBodyJsonByHash(hash), _.fold("None")(filteredJsonStr)) - override def getBlockByNumber(number: BlockNumber): JobResult[Option[EcBlock]] = - wrap(s"getBlockByNumber($number)", underlying.getBlockByNumber(number), _.fold("None")(_.toString)) + override def getPayloadByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] = + wrap(s"getPayloadByNumber($number)", underlying.getPayloadByNumber(number), _.fold("None")(_.toString)) - override def getBlockByHash(hash: BlockHash): JobResult[Option[EcBlock]] = - wrap(s"getBlockByHash($hash)", underlying.getBlockByHash(hash), _.fold("None")(_.toString)) + override def getPayloadByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] = + wrap(s"getPayloadByHash($hash)", underlying.getPayloadByHash(hash), _.fold("None")(_.toString)) - override def getBlockByHashJson(hash: BlockHash): JobResult[Option[JsObject]] = - wrap(s"getBlockByHashJson($hash)", underlying.getBlockByHashJson(hash), _.fold("None")(filteredJson)) + override def getLastPayload: JobResult[ExecutionPayload] = + wrap("getLastPayload", underlying.getLastPayload) - override def getLastExecutionBlock: JobResult[EcBlock] = - wrap("getLastExecutionBlock", underlying.getLastExecutionBlock) + override def getBlockJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = + wrap(s"getBlockByHashJson($hash)", underlying.getBlockJsonByHash(hash), _.fold("None")(filteredJsonStr)) - override def blockExists(hash: BlockHash): JobResult[Boolean] = - wrap(s"blockExists($hash)", underlying.blockExists(hash)) + override def getPayloadJsonDataByHash(hash: BlockHash): JobResult[PayloadJsonData] = { + wrap( + s"getPayloadJsonDataByHash($hash)", + underlying.getPayloadJsonDataByHash(hash), + pjd => PayloadJsonData(filteredJson(pjd.blockJson), filteredJson(pjd.bodyJson)).toString + ) + } 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("{", ", ", "}")) @@ -68,9 +73,12 @@ class LoggedEngineApiClient(underlying: EngineApiClient) extends EngineApiClient } } - private def filteredJson(jsObject: JsObject): String = JsObject( + private def filteredJson(jsObject: JsObject): JsObject = JsObject( jsObject.fields.filterNot { case (k, _) => excludedJsonFields.contains(k) } - ).toString() + ) + + private def filteredJsonStr(jsObject: JsObject): String = + filteredJson(jsObject).toString } object LoggedEngineApiClient { diff --git a/src/main/scala/units/client/engine/model/EcBlock.scala b/src/main/scala/units/client/engine/model/ExecutionPayload.scala similarity index 56% rename from src/main/scala/units/client/engine/model/EcBlock.scala rename to src/main/scala/units/client/engine/model/ExecutionPayload.scala index 2d8cbb54..61a46d0a 100644 --- a/src/main/scala/units/client/engine/model/EcBlock.scala +++ b/src/main/scala/units/client/engine/model/ExecutionPayload.scala @@ -5,34 +5,33 @@ import play.api.libs.functional.syntax.* import play.api.libs.json.* import play.api.libs.json.Format.GenericFormat import units.BlockHash -import units.client.L2BlockLike +import units.client.CommonBlockData import units.eth.EthAddress import units.util.HexBytesConverter.* -/** Block in EC API, not a payload of Engine API! See BlockHeader in besu. - * @param timestamp +/** @param timestamp * In seconds, see ProcessableBlockHeader.timestamp comment <- SealableBlockHeader <- BlockHeader * https://besu.hyperledger.org/stable/public-networks/reference/engine-api/objects#execution-payload-object tells about milliseconds */ -case class EcBlock( - hash: BlockHash, - parentHash: BlockHash, - stateRoot: String, - height: Long, - timestamp: Long, - minerRewardL2Address: EthAddress, - baseFeePerGas: Uint256, - gasLimit: Long, - gasUsed: Long, - prevRandao: String, - withdrawals: Vector[Withdrawal] -) extends L2BlockLike { +case class ExecutionPayload( + hash: BlockHash, + parentHash: BlockHash, + stateRoot: String, + height: Long, + timestamp: Long, + minerRewardAddress: EthAddress, + baseFeePerGas: Uint256, + gasLimit: Long, + gasUsed: Long, + prevRandao: String, + withdrawals: Vector[Withdrawal] +) extends CommonBlockData { override def toString: String = - s"EcBlock($hash, p=$parentHash, h=$height, t=$timestamp, m=$minerRewardL2Address, w={${withdrawals.mkString(", ")}})" + s"ExecutionPayload($hash, p=$parentHash, h=$height, t=$timestamp, m=$minerRewardAddress, w={${withdrawals.mkString(", ")}})" } -object EcBlock { - implicit val reads: Reads[EcBlock] = ( +object ExecutionPayload { + implicit val reads: Reads[ExecutionPayload] = ( (JsPath \ "hash").read[BlockHash] and (JsPath \ "parentHash").read[BlockHash] and (JsPath \ "stateRoot").read[String] and @@ -44,5 +43,5 @@ object EcBlock { (JsPath \ "gasUsed").read[String].map(toLong) and (JsPath \ "mixHash").read[String] and (JsPath \ "withdrawals").readWithDefault(Vector.empty[Withdrawal]) - )(EcBlock.apply _) + )(ExecutionPayload.apply _) } diff --git a/src/main/scala/units/util/BlockToPayloadMapper.scala b/src/main/scala/units/client/engine/model/PayloadJsonData.scala similarity index 77% rename from src/main/scala/units/util/BlockToPayloadMapper.scala rename to src/main/scala/units/client/engine/model/PayloadJsonData.scala index fbbc1f20..bd8b0be6 100644 --- a/src/main/scala/units/util/BlockToPayloadMapper.scala +++ b/src/main/scala/units/client/engine/model/PayloadJsonData.scala @@ -1,9 +1,25 @@ -package units.util +package units.client.engine.model -import units.eth.EthereumConstants import play.api.libs.json.{JsObject, JsString} +import units.client.engine.model.PayloadJsonData.fieldsMapping +import units.eth.EthereumConstants + +case class PayloadJsonData(blockJson: JsObject, bodyJson: JsObject) { + def toPayloadJson: JsObject = { + val blockJsonData = blockJson.value + + JsObject( + fieldsMapping.flatMap { case (blockField, payloadField) => + blockJsonData.get(blockField).map(payloadField -> _) + } ++ List( + "blobGasUsed" -> JsString(EthereumConstants.ZeroHex), + "excessBlobGas" -> JsString(EthereumConstants.ZeroHex) + ) + ) ++ bodyJson + } +} -object BlockToPayloadMapper { +object PayloadJsonData { private val commonFields = Seq( "parentHash", @@ -21,18 +37,4 @@ object BlockToPayloadMapper { Seq("miner" -> "feeRecipient", "number" -> "blockNumber", "hash" -> "blockHash", "mixHash" -> "prevRandao") ++ commonFields.map(field => field -> field ) - - def toPayloadJson(blockJson: JsObject, payloadBodyJson: JsObject): JsObject = { - val blockJsonData = blockJson.value - - JsObject( - fieldsMapping.flatMap { case (blockField, payloadField) => - blockJsonData.get(blockField).map(payloadField -> _) - } ++ List( - "blobGasUsed" -> JsString(EthereumConstants.ZeroHex), - "excessBlobGas" -> JsString(EthereumConstants.ZeroHex) - ) - ) ++ payloadBodyJson - } - -} +} \ No newline at end of file diff --git a/src/main/scala/units/eth/EmptyL2Block.scala b/src/main/scala/units/eth/EmptyPayload.scala similarity index 85% rename from src/main/scala/units/eth/EmptyL2Block.scala rename to src/main/scala/units/eth/EmptyPayload.scala index f0165c66..f6909dc7 100644 --- a/src/main/scala/units/eth/EmptyL2Block.scala +++ b/src/main/scala/units/eth/EmptyPayload.scala @@ -4,10 +4,10 @@ import org.web3j.abi.datatypes.generated.Uint256 import org.web3j.rlp.{RlpEncoder, RlpList, RlpString} import play.api.libs.json.{JsObject, Json} import units.BlockHash -import units.client.engine.model.EcBlock +import units.client.engine.model.ExecutionPayload import units.util.HexBytesConverter -object EmptyL2Block { +object EmptyPayload { case class Params( parentHash: BlockHash, parentStateRoot: String, @@ -20,24 +20,24 @@ object EmptyL2Block { private val InternalBlockTimestampDiff = 1 // seconds - def mkExecutionPayload(parent: EcBlock, feeRecipient: EthAddress = EthAddress.empty): JsObject = - mkExecutionPayload( + def mkExecutionPayloadJson(parentPayload: ExecutionPayload, feeRecipient: EthAddress = EthAddress.empty): JsObject = + mkExecutionPayloadJson( Params( - parentHash = parent.hash, - parentStateRoot = parent.stateRoot, - parentGasLimit = parent.gasLimit, - newBlockTimestamp = parent.timestamp + InternalBlockTimestampDiff, - newBlockNumber = parent.height + 1, + parentHash = parentPayload.hash, + parentStateRoot = parentPayload.stateRoot, + parentGasLimit = parentPayload.gasLimit, + newBlockTimestamp = parentPayload.timestamp + InternalBlockTimestampDiff, + newBlockNumber = parentPayload.height + 1, baseFee = calculateGasFee( - parentGasLimit = parent.gasLimit, - parentBaseFeePerGas = parent.baseFeePerGas, - parentGasUsed = parent.gasUsed + parentGasLimit = parentPayload.gasLimit, + parentBaseFeePerGas = parentPayload.baseFeePerGas, + parentGasUsed = parentPayload.gasUsed ), feeRecipient = feeRecipient ) ) - def mkExecutionPayload(params: Params): JsObject = Json.obj( + def mkExecutionPayloadJson(params: Params): JsObject = Json.obj( "parentHash" -> params.parentHash, "feeRecipient" -> params.feeRecipient, "stateRoot" -> params.parentStateRoot, diff --git a/src/main/scala/units/network/BasicMessagesRepo.scala b/src/main/scala/units/network/BasicMessagesRepo.scala index 39d2dc8a..51febc1b 100644 --- a/src/main/scala/units/network/BasicMessagesRepo.scala +++ b/src/main/scala/units/network/BasicMessagesRepo.scala @@ -6,7 +6,7 @@ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto import com.wavesplatform.crypto.SignatureLength import units.util.HexBytesConverter.* -import units.{BlockHash, NetworkL2Block} +import units.{BlockHash, NetworkBlock} import com.wavesplatform.network.message.Message.MessageCode import com.wavesplatform.network.message.{Message, MessageSpec} import com.wavesplatform.network.{InetSocketAddressSeqSpec, NetworkServer} @@ -45,31 +45,31 @@ object GetBlockL2Spec extends MessageSpec[GetBlock] { override def deserializeData(bytes: Array[Byte]): Try[GetBlock] = Try { require( - NetworkL2Block.validateReferenceLength(bytes.length), + NetworkBlock.validateReferenceLength(bytes.length), s"Invalid hash length ${bytes.length} in GetBlock message, expecting ${crypto.DigestLength}" ) GetBlock(BlockHash(bytes)) } } -object BlockSpec extends MessageSpec[NetworkL2Block] { +object BlockSpec extends MessageSpec[NetworkBlock] { override val messageCode: MessageCode = 4: Byte override val maxLength: Int = NetworkServer.MaxFrameLength - override def serializeData(block: NetworkL2Block): Array[Byte] = { + override def serializeData(block: NetworkBlock): Array[Byte] = { val signatureBytes = block.signature.map(sig => Bytes.concat(Array(1.toByte), sig.arr)).getOrElse(Array(0.toByte)) Bytes.concat(signatureBytes, block.payloadBytes) } - override def deserializeData(bytes: Array[Byte]): Try[NetworkL2Block] = { + override def deserializeData(bytes: Array[Byte]): Try[NetworkBlock] = { // We need a signature only for blocks those are not confirmed on the chain contract val isWithSignature = bytes.headOption.contains(1.toByte) val signature = if (isWithSignature) Some(ByteStr(bytes.slice(1, SignatureLength + 1))) else None val payloadOffset = if (isWithSignature) SignatureLength + 1 else 1 for { _ <- Either.cond(signature.forall(_.size == SignatureLength), (), new RuntimeException("Invalid block signature size")).toTry - block <- NetworkL2Block(bytes.drop(payloadOffset), signature).leftMap(err => new RuntimeException(err.message)).toTry + block <- NetworkBlock(bytes.drop(payloadOffset), signature).leftMap(err => new RuntimeException(err.message)).toTry } yield block } } diff --git a/src/main/scala/units/network/BlocksObserver.scala b/src/main/scala/units/network/BlocksObserver.scala index 33d8844a..84b1c599 100644 --- a/src/main/scala/units/network/BlocksObserver.scala +++ b/src/main/scala/units/network/BlocksObserver.scala @@ -4,10 +4,10 @@ import units.network.BlocksObserverImpl.BlockWithChannel import com.wavesplatform.network.ChannelObservable import monix.eval.Task import monix.execution.CancelableFuture -import units.{BlockHash, NetworkL2Block} +import units.{BlockHash, NetworkBlock} trait BlocksObserver { - def getBlockStream: ChannelObservable[NetworkL2Block] + def getBlockStream: ChannelObservable[NetworkBlock] def requestBlock(req: BlockHash): Task[BlockWithChannel] diff --git a/src/main/scala/units/network/BlocksObserverImpl.scala b/src/main/scala/units/network/BlocksObserverImpl.scala index 2fb53f52..0c702341 100644 --- a/src/main/scala/units/network/BlocksObserverImpl.scala +++ b/src/main/scala/units/network/BlocksObserverImpl.scala @@ -9,15 +9,15 @@ import monix.eval.Task import monix.execution.{Cancelable, CancelableFuture, CancelablePromise, Scheduler} import monix.reactive.subjects.ConcurrentSubject import units.network.BlocksObserverImpl.{BlockWithChannel, State} -import units.{BlockHash, NetworkL2Block} +import units.{BlockHash, NetworkBlock} import java.time.Duration import scala.concurrent.duration.FiniteDuration import scala.jdk.CollectionConverters.* import scala.util.{Failure, Success} -class BlocksObserverImpl(allChannels: DefaultChannelGroup, blocks: ChannelObservable[NetworkL2Block], syncTimeout: FiniteDuration)(implicit - sc: Scheduler +class BlocksObserverImpl(allChannels: DefaultChannelGroup, blocks: ChannelObservable[NetworkBlock], syncTimeout: FiniteDuration)(implicit + sc: Scheduler ) extends BlocksObserver with ScorexLogging { @@ -63,7 +63,7 @@ class BlocksObserverImpl(allChannels: DefaultChannelGroup, blocks: ChannelObserv blocksResult.onNext(v) } - def getBlockStream: ChannelObservable[NetworkL2Block] = blocksResult + def getBlockStream: ChannelObservable[NetworkBlock] = blocksResult def requestBlock(req: BlockHash): Task[BlockWithChannel] = Task .defer { @@ -113,7 +113,7 @@ class BlocksObserverImpl(allChannels: DefaultChannelGroup, blocks: ChannelObserv object BlocksObserverImpl { - type BlockWithChannel = (Channel, NetworkL2Block) + type BlockWithChannel = (Channel, NetworkBlock) sealed trait State diff --git a/src/main/scala/units/network/HistoryReplier.scala b/src/main/scala/units/network/HistoryReplier.scala index 04db98ba..b3cfcde1 100644 --- a/src/main/scala/units/network/HistoryReplier.scala +++ b/src/main/scala/units/network/HistoryReplier.scala @@ -1,14 +1,12 @@ package units.network -import cats.syntax.either.* import com.wavesplatform.network.id import com.wavesplatform.utils.ScorexLogging import io.netty.channel.ChannelHandler.Sharable import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter} import monix.execution.Scheduler import units.client.engine.EngineApiClient -import units.util.BlockToPayloadMapper -import units.{BlockHash, ClientError, NetworkL2Block} +import units.{BlockHash, ClientError, NetworkBlock} import scala.concurrent.Future import scala.util.{Failure, Success} @@ -30,24 +28,19 @@ class HistoryReplier(engineApiClient: EngineApiClient)(implicit sc: Scheduler) e case GetBlock(hash) => respondWith( ctx, - loadBlockL2(hash) + loadBlock(hash) .map { - case Right(blockL2) => - RawBytes(BlockSpec.messageCode, BlockSpec.serializeData(blockL2)) + case Right(block) => + RawBytes(BlockSpec.messageCode, BlockSpec.serializeData(block)) case Left(err) => throw new NoSuchElementException(s"Error loading block $hash: $err") } ) case _ => super.channelRead(ctx, msg) } - private def loadBlockL2(hash: BlockHash): Future[Either[ClientError, NetworkL2Block]] = Future { - for { - blockJsonOpt <- engineApiClient.getBlockByHashJson(hash) - blockJson <- Either.fromOption(blockJsonOpt, ClientError("block not found")) - payloadBodyJsonOpt <- engineApiClient.getPayloadBodyByHash(hash) - payloadBodyJson <- Either.fromOption(payloadBodyJsonOpt, ClientError("payload body not found")) - payload = BlockToPayloadMapper.toPayloadJson(blockJson, payloadBodyJson) - blockL2 <- NetworkL2Block(payload) - } yield blockL2 + private def loadBlock(hash: BlockHash): Future[Either[ClientError, NetworkBlock]] = Future { + engineApiClient.getPayloadJsonDataByHash(hash).flatMap { payloadJsonData => + NetworkBlock(payloadJsonData.toPayloadJson) + } } } diff --git a/src/main/scala/units/network/LegacyFrameCodec.scala b/src/main/scala/units/network/LegacyFrameCodec.scala index e8c34893..1079ffc8 100644 --- a/src/main/scala/units/network/LegacyFrameCodec.scala +++ b/src/main/scala/units/network/LegacyFrameCodec.scala @@ -1,6 +1,6 @@ package units.network -import units.NetworkL2Block +import units.NetworkBlock import com.wavesplatform.network.BasicMessagesRepo.Spec import com.wavesplatform.network.LegacyFrameCodec.MessageRawData import com.wavesplatform.network.message.Message.MessageCode @@ -12,12 +12,12 @@ class LegacyFrameCodec(peerDatabase: PeerDatabase) extends LFC(peerDatabase) { override protected def specsByCodes: Map[MessageCode, Spec] = BasicMessagesRepo.specsByCodes override protected def messageToRawData(msg: Any): MessageRawData = { - val rawBytesL2 = (msg: @unchecked) match { - case rb: RawBytes => rb - case block: NetworkL2Block => RawBytes.from(BlockSpec, block) + val rawBytes = (msg: @unchecked) match { + case rb: RawBytes => rb + case block: NetworkBlock => RawBytes.from(BlockSpec, block) } - MessageRawData(rawBytesL2.code, rawBytesL2.data) + MessageRawData(rawBytes.code, rawBytes.data) } protected def rawDataToMessage(rawData: MessageRawData): AnyRef = diff --git a/src/main/scala/units/network/MessageObserver.scala b/src/main/scala/units/network/MessageObserver.scala index 484b1396..f3de7fdb 100644 --- a/src/main/scala/units/network/MessageObserver.scala +++ b/src/main/scala/units/network/MessageObserver.scala @@ -5,17 +5,17 @@ import io.netty.channel.ChannelHandler.Sharable import io.netty.channel.{Channel, ChannelHandlerContext, ChannelInboundHandlerAdapter} import monix.execution.schedulers.SchedulerService import monix.reactive.subjects.{ConcurrentSubject, Subject} -import units.NetworkL2Block +import units.NetworkBlock @Sharable class MessageObserver extends ChannelInboundHandlerAdapter { private implicit val scheduler: SchedulerService = Schedulers.fixedPool(2, "message-observer-l2") - val blocks: Subject[(Channel, NetworkL2Block), (Channel, NetworkL2Block)] = ConcurrentSubject.publish[(Channel, NetworkL2Block)] + val blocks: Subject[(Channel, NetworkBlock), (Channel, NetworkBlock)] = ConcurrentSubject.publish[(Channel, NetworkBlock)] override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = msg match { - case b: NetworkL2Block => blocks.onNext((ctx.channel(), b)) + case b: NetworkBlock => blocks.onNext((ctx.channel(), b)) case _ => super.channelRead(ctx, msg) } diff --git a/src/main/scala/units/network/TrafficLogger.scala b/src/main/scala/units/network/TrafficLogger.scala index c26a62c9..c22a836d 100644 --- a/src/main/scala/units/network/TrafficLogger.scala +++ b/src/main/scala/units/network/TrafficLogger.scala @@ -2,7 +2,7 @@ package units.network import com.wavesplatform.network.{Handshake, HandshakeSpec, TrafficLogger as TL} import io.netty.channel.ChannelHandler.Sharable -import units.NetworkL2Block +import units.NetworkBlock import units.network.BasicMessagesRepo.specsByCodes @Sharable @@ -13,7 +13,7 @@ class TrafficLogger(settings: TL.Settings) extends TL(settings) { protected def codeOf(msg: AnyRef): Option[Byte] = { val aux: PartialFunction[AnyRef, Byte] = { case x: RawBytes => x.code - case _: NetworkL2Block => BlockSpec.messageCode + case _: NetworkBlock => BlockSpec.messageCode case x: Message => specsByClasses(x.getClass).messageCode case _: Handshake => HandshakeSpec.messageCode } @@ -22,7 +22,7 @@ class TrafficLogger(settings: TL.Settings) extends TL(settings) { } protected def stringify(msg: Any): String = msg match { - case b: NetworkL2Block => s"${b.hash}" + case b: NetworkBlock => s"${b.hash}" case RawBytes(code, data) => s"RawBytes(${specsByCodes(code).messageName}, ${data.length} bytes)" case other => other.toString } diff --git a/src/test/scala/units/BaseIntegrationTestSuite.scala b/src/test/scala/units/BaseIntegrationTestSuite.scala index c94b4e42..a208ec10 100644 --- a/src/test/scala/units/BaseIntegrationTestSuite.scala +++ b/src/test/scala/units/BaseIntegrationTestSuite.scala @@ -37,7 +37,7 @@ trait BaseIntegrationTestSuite val txs = List( d.chainContract.setScript(), - d.chainContract.setup(d.ecGenesisBlock, elMinerDefaultReward.amount.longValue()) + d.chainContract.setup(d.genesisBlockPayload, elMinerDefaultReward.amount.longValue()) ) ++ settings.initialMiners.map { x => d.chainContract.join(x.account, x.elRewardAddress) } diff --git a/src/test/scala/units/BlockBriefValidationTestSuite.scala b/src/test/scala/units/BlockBriefValidationTestSuite.scala index ada4ebf5..19f576c8 100644 --- a/src/test/scala/units/BlockBriefValidationTestSuite.scala +++ b/src/test/scala/units/BlockBriefValidationTestSuite.scala @@ -11,29 +11,29 @@ class BlockBriefValidationTestSuite extends BaseIntegrationTestSuite { initialMiners = List(miner) ) - "Brief validation of EC Block incoming from network" - { + "Brief validation of network block" - { "accepts if it is valid" in test { d => - val ecBlock = d.createEcBlockBuilder("0", miner).build() + val payload = d.createPayloadBuilder("0", miner).build() - step(s"Receive ecBlock ${ecBlock.hash} from a peer") - d.receiveNetworkBlock(ecBlock, miner.account) - withClue("Brief EL block validation:") { + step(s"Receive network block ${payload.hash} with payload from a peer") + d.receiveNetworkBlock(payload, miner.account) + withClue("Brief block validation:") { d.triggerScheduledTasks() d.pollSentNetworkBlock() match { - case Some(sent) => sent.hash shouldBe ecBlock.hash - case None => fail(s"${ecBlock.hash} should not be ignored") + case Some(sent) => sent.hash shouldBe payload.hash + case None => fail(s"${payload.hash} should not be ignored") } } } "otherwise ignoring" in test { d => - val ecBlock = d.createEcBlockBuilder("0", minerRewardL2Address = EthAddress.empty, parent = d.ecGenesisBlock).build() + val payload = d.createPayloadBuilder("0", minerRewardAddress = EthAddress.empty, parentPayload = d.genesisBlockPayload).build() - step(s"Receive ecBlock ${ecBlock.hash} from a peer") - d.receiveNetworkBlock(ecBlock, miner.account) - withClue("Brief EL block validation:") { + step(s"Receive network block ${payload.hash} with payload from a peer") + d.receiveNetworkBlock(payload, miner.account) + withClue("Brief block validation:") { d.triggerScheduledTasks() - if (d.pollSentNetworkBlock().nonEmpty) fail(s"${ecBlock.hash} should be ignored, because it is invalid by brief validation rules") + if (d.pollSentNetworkBlock().nonEmpty) fail(s"${payload.hash} should be ignored, because it is invalid by brief validation rules") } } } diff --git a/src/test/scala/units/BlockFullValidationTestSuite.scala b/src/test/scala/units/BlockFullValidationTestSuite.scala index 643f029c..bde3ceb6 100644 --- a/src/test/scala/units/BlockFullValidationTestSuite.scala +++ b/src/test/scala/units/BlockFullValidationTestSuite.scala @@ -4,14 +4,14 @@ import com.wavesplatform.common.utils.EitherExt2 import com.wavesplatform.transaction.TxHelpers import units.ELUpdater.State.ChainStatus.{FollowingChain, WaitForNewChain} import units.client.contract.HasConsensusLayerDappTxHelpers.EmptyE2CTransfersRootHashHex -import units.client.engine.model.{EcBlock, GetLogsResponseEntry} +import units.client.engine.model.{ExecutionPayload, GetLogsResponseEntry} import units.eth.EthAddress import units.util.HexBytesConverter class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { private val transferEvents = List(Bridge.ElSentNativeEvent(TxHelpers.defaultAddress, 1)) - private val ecBlockLogs = transferEvents.map(getLogsResponseEntry) - private val e2CTransfersRootHashHex = HexBytesConverter.toHex(Bridge.mkTransfersHash(ecBlockLogs).explicitGet()) + private val blockLogs = transferEvents.map(getLogsResponseEntry) + private val e2CTransfersRootHashHex = HexBytesConverter.toHex(Bridge.mkTransfersHash(blockLogs).explicitGet()) private val reliable = ElMinerSettings(TxHelpers.signer(1)) private val malfunction = ElMinerSettings(TxHelpers.signer(2)) // Prevents a block finalization @@ -22,17 +22,17 @@ 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 => - step("Start new epoch for ecBlock") + step("Start new epoch for payload") d.advanceNewBlocks(reliable.address) - val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) + val payload = d.createPayloadBuilder("0", reliable).buildAndSetLogs(blockLogs) d.advanceConsensusLayerChanged() - step(s"Receive ecBlock ${ecBlock.hash} from a peer") - d.receiveNetworkBlock(ecBlock, reliable.account) + step(s"Receive network block ${payload.hash} with payload from a peer") + d.receiveNetworkBlock(payload, reliable.account) d.triggerScheduledTasks() - step(s"Append a CL micro block with ecBlock ${ecBlock.hash} confirmation") - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, ecBlock)) + step(s"Append a CL micro block with payload ${payload.hash} confirmation") + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, payload)) d.advanceConsensusLayerChanged() withClue("Validation doesn't happen:") { @@ -41,7 +41,7 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { d.waitForWorking("Block considered validated and following") { s => val vs = s.fullValidationStatus - vs.lastValidatedBlock.hash shouldBe ecBlock.hash + vs.lastValidatedBlock.hash shouldBe payload.hash vs.lastElWithdrawalIndex shouldBe empty is[FollowingChain](s.chainStatus) @@ -50,28 +50,28 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { "happens for not finalized blocks" - { "successful validation updates the chain information" in withExtensionDomain() { d => - step("Start new epoch for ecBlock") + step("Start new epoch for payload") d.advanceNewBlocks(reliable.address) - val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) + val payload = d.createPayloadBuilder("0", reliable).buildAndSetLogs(blockLogs) d.advanceConsensusLayerChanged() - step(s"Receive ecBlock ${ecBlock.hash} from a peer") - d.receiveNetworkBlock(ecBlock, reliable.account) + step(s"Receive network block ${payload.hash} with payload from a peer") + d.receiveNetworkBlock(payload, reliable.account) d.triggerScheduledTasks() - step(s"Append a CL micro block with ecBlock ${ecBlock.hash} confirmation") - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, ecBlock, e2CTransfersRootHashHex)) + step(s"Append a CL micro block with payload ${payload.hash} confirmation") + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, payload, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() d.waitForCS[FollowingChain]("Following chain") { _ => } withClue("Validation happened:") { - d.ecClients.fullValidatedBlocks shouldBe Set(ecBlock.hash) + d.ecClients.fullValidatedBlocks shouldBe Set(payload.hash) } d.waitForWorking("Block considered validated") { s => val vs = s.fullValidationStatus - vs.lastValidatedBlock.hash shouldBe ecBlock.hash + vs.lastValidatedBlock.hash shouldBe payload.hash vs.lastElWithdrawalIndex.value shouldBe -1L } } @@ -80,54 +80,55 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { def e2CTest( blockLogs: List[GetLogsResponseEntry], e2CTransfersRootHashHex: String, - badBlockPostProcessing: EcBlock => EcBlock = identity + badBlockPayloadPostProcessing: ExecutionPayload => ExecutionPayload = identity ): Unit = withExtensionDomain() { d => - step("Start new epoch for ecBlock1") + step("Start new epoch for payload1") d.advanceNewBlocks(malfunction.address) d.advanceConsensusLayerChanged() - val ecBlock1 = d.createEcBlockBuilder("0", malfunction).buildAndSetLogs() - d.ecClients.addKnown(ecBlock1) - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, ecBlock1)) + val payload1 = d.createPayloadBuilder("0", malfunction).buildAndSetLogs() + d.ecClients.addKnown(payload1) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, payload1)) d.advanceConsensusLayerChanged() - step("Start new epoch for ecBlock2") + step("Start new epoch for payload2") d.advanceNewBlocks(malfunction.address) d.advanceConsensusLayerChanged() - val ecBlock2 = badBlockPostProcessing(d.createEcBlockBuilder("0-0", malfunction, ecBlock1).rewardPrevMiner().buildAndSetLogs(blockLogs)) + val payload2 = + badBlockPayloadPostProcessing(d.createPayloadBuilder("0-0", malfunction, payload1).rewardPrevMiner().buildAndSetLogs(blockLogs)) - step(s"Append a CL micro block with ecBlock2 ${ecBlock2.hash} confirmation") - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, ecBlock2, e2CTransfersRootHashHex)) + step(s"Append a CL micro block with payload2 ${payload2.hash} confirmation") + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, payload2, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() - step(s"Receive ecBlock2 ${ecBlock2.hash} from a peer") - d.receiveNetworkBlock(ecBlock2, malfunction.account) + step(s"Receive network block ${payload2.hash} with payload2 from a peer") + d.receiveNetworkBlock(payload2, malfunction.account) d.triggerScheduledTasks() d.waitForCS[WaitForNewChain]("Forking") { cs => - cs.chainSwitchInfo.referenceBlock.hash shouldBe ecBlock1.hash + cs.chainSwitchInfo.referenceBlock.hash shouldBe payload1.hash } } "CL confirmation without a transfers root hash" in e2CTest( - blockLogs = ecBlockLogs, + blockLogs = blockLogs, e2CTransfersRootHashHex = EmptyE2CTransfersRootHashHex ) "Events from an unexpected EL bridge address" in { val fakeBridgeAddress = EthAddress.unsafeFrom("0x53481054Ad294207F6ed4B6C2E6EaE34E1Bb8704") - val ecBlock2Logs = transferEvents.map(x => getLogsResponseEntry(x).copy(address = fakeBridgeAddress)) + val block2Logs = transferEvents.map(x => getLogsResponseEntry(x).copy(address = fakeBridgeAddress)) e2CTest( - blockLogs = ecBlock2Logs, + blockLogs = block2Logs, e2CTransfersRootHashHex = e2CTransfersRootHashHex ) } "Different miners in CL and EL" in e2CTest( - blockLogs = ecBlockLogs, + blockLogs = blockLogs, e2CTransfersRootHashHex = e2CTransfersRootHashHex, - badBlockPostProcessing = _.copy(minerRewardL2Address = reliable.elRewardAddress) + badBlockPayloadPostProcessing = _.copy(minerRewardAddress = reliable.elRewardAddress) ) } } diff --git a/src/test/scala/units/BlockIssuesForgingTestSuite.scala b/src/test/scala/units/BlockIssuesForgingTestSuite.scala index f581ac79..008dc9cc 100644 --- a/src/test/scala/units/BlockIssuesForgingTestSuite.scala +++ b/src/test/scala/units/BlockIssuesForgingTestSuite.scala @@ -6,7 +6,7 @@ import com.wavesplatform.wallet.Wallet import units.ELUpdater.State.ChainStatus.{FollowingChain, Mining, WaitForNewChain} import units.ELUpdater.WaitRequestedBlockTimeout import units.client.contract.HasConsensusLayerDappTxHelpers.defaultFees -import units.client.engine.model.EcBlock +import units.client.engine.model.ExecutionPayload import scala.concurrent.duration.DurationInt @@ -25,111 +25,111 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { .withEnabledElMining "We're on the main chain and" - { - def test(f: (ExtensionDomain, EcBlock, Int) => Unit): Unit = withExtensionDomain() { d => - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1") + def test(f: (ExtensionDomain, ExecutionPayload, Int) => Unit): Unit = withExtensionDomain() { d => + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with payload1") d.advanceNewBlocks(otherMiner1.address) - val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() - d.ecClients.addKnown(ecBlock1) - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBlock1)) + val payload1 = d.createPayloadBuilder("0", otherMiner1).buildAndSetLogs() + d.ecClients.addKnown(payload1) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, payload1)) d.waitForCS[FollowingChain]() { s => - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock1.hash + s.nodeChainInfo.lastBlock.hash shouldBe payload1.hash } - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock2") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with payload2") 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)) + val payload2 = d.createPayloadBuilder("0-0", otherMiner1, payload1).rewardPrevMiner().buildAndSetLogs() + val block2Epoch = d.blockchain.height + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, payload2)) - d.waitForCS[FollowingChain](s"Waiting ecBlock2 ${ecBlock2.hash}") { s => - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock2.hash - s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock2.hash + d.waitForCS[FollowingChain](s"Waiting payload2 ${payload2.hash}") { s => + s.nodeChainInfo.lastBlock.hash shouldBe payload2.hash + s.nextExpectedBlock.map(_.hash).value shouldBe payload2.hash } step(s"Start a new epoch of thisMiner ${thisMiner.address}") d.advanceNewBlocks(thisMiner.address) - f(d, ecBlock2, ecBlock2Epoch) + f(d, payload2, block2Epoch) } - "EC-block comes within timeout - then we continue forging" in test { (d, ecBlock2, ecBlock2Epoch) => + "EC-block comes within timeout - then we continue forging" in test { (d, payload2, block2Epoch) => d.advanceElu(WaitRequestedBlockTimeout - 1.millis) - d.waitForCS[FollowingChain](s"Still waiting ecBlock2 ${ecBlock2.hash}") { s => - s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock2.hash + d.waitForCS[FollowingChain](s"Still waiting payload2 ${payload2.hash}") { s => + s.nextExpectedBlock.map(_.hash).value shouldBe payload2.hash } - step(s"Receive EC-block ${ecBlock2.hash} from network") - d.receiveNetworkBlock(ecBlock2, otherMiner1.account, ecBlock2Epoch) + step(s"Receive network block ${payload2.hash} with payload2") + d.receiveNetworkBlock(payload2, otherMiner1.account, block2Epoch) d.triggerScheduledTasks() - d.ecClients.willForge(d.createEcBlockBuilder("0-0-0", otherMiner1, ecBlock2).rewardPrevMiner().build()) + d.ecClients.willForge(d.createPayloadBuilder("0-0-0", otherMiner1, payload2).rewardPrevMiner().build()) d.waitForCS[Mining]("Continue") { s => s.nodeChainInfo.isRight shouldBe true } } - "EC-block doesn't come - then we start an alternative chain" in test { (d, _, _) => + "Network block with payload doesn't come - then we start an alternative chain" in test { (d, _, _) => d.waitForCS[WaitForNewChain](s"Switched to alternative chain") { _ => } } } "We're on the alternative chain and" - { - "EC-block comes within timeout - then we continue forging" in withExtensionDomain() { d => - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1") + "Network block with payload comes within timeout - then we continue forging" in withExtensionDomain() { d => + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with payload1") d.advanceNewBlocks(otherMiner1.address) - val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() - d.ecClients.addKnown(ecBlock1) - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBlock1)) + val payload1 = d.createPayloadBuilder("0", otherMiner1).buildAndSetLogs() + d.ecClients.addKnown(payload1) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, payload1)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe true - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock1.hash + s.nodeChainInfo.lastBlock.hash shouldBe payload1.hash } - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBadBlock2") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with badPayload2") d.advanceNewBlocks(otherMiner1.address) - val ecBadBlock2 = d.createEcBlockBuilder("0-0", otherMiner1, ecBlock1).rewardPrevMiner().buildAndSetLogs() - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBadBlock2)) + val badPayload2 = d.createPayloadBuilder("0-0", otherMiner1, payload1).rewardPrevMiner().buildAndSetLogs() + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, badPayload2)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe true - s.nodeChainInfo.lastBlock.hash shouldBe ecBadBlock2.hash + s.nodeChainInfo.lastBlock.hash shouldBe badPayload2.hash } - step(s"Start a new epoch of otherMiner2 ${otherMiner2.address} with alternative chain ecBlock2") + step(s"Start a new epoch of otherMiner2 ${otherMiner2.address} with alternative chain payload2") d.advanceNewBlocks(otherMiner2.address) - val ecBlock2 = d.createEcBlockBuilder("0-1", otherMiner2, ecBlock1).rewardPrevMiner().buildAndSetLogs() + val payload2 = d.createPayloadBuilder("0-1", otherMiner2, payload1).rewardPrevMiner().buildAndSetLogs() d.waitForCS[WaitForNewChain]() { s => - s.chainSwitchInfo.referenceBlock.hash shouldBe ecBlock1.hash + s.chainSwitchInfo.referenceBlock.hash shouldBe payload1.hash } - d.appendMicroBlockAndVerify(d.chainContract.startAltChain(otherMiner2.account, ecBlock2)) + d.appendMicroBlockAndVerify(d.chainContract.startAltChain(otherMiner2.account, payload2)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock2.hash - s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock2.hash + s.nodeChainInfo.lastBlock.hash shouldBe payload2.hash + s.nextExpectedBlock.map(_.hash).value shouldBe payload2.hash } - d.receiveNetworkBlock(ecBlock2, otherMiner2.account, d.blockchain.height) + d.receiveNetworkBlock(payload2, otherMiner2.account, d.blockchain.height) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock2.hash + s.nodeChainInfo.lastBlock.hash shouldBe payload2.hash s.nextExpectedBlock shouldBe empty } - step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with ecBlock3") + step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with payload3") 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)) + val payload3 = d.createPayloadBuilder("0-1-1", otherMiner2, parentPayload = payload2).rewardPrevMiner(1).buildAndSetLogs() + val block3Epoch = d.blockchain.height + d.appendMicroBlockAndVerify(d.chainContract.extendAltChain(otherMiner2.account, payload3, chainId = 1)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock3.hash - s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock3.hash + s.nodeChainInfo.lastBlock.hash shouldBe payload3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe payload3.hash } step(s"Start a new epoch of thisMiner ${thisMiner.address}") @@ -137,147 +137,148 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { d.advanceConsensusLayerChanged() d.advanceElu(WaitRequestedBlockTimeout - 1.millis) - d.waitForCS[FollowingChain](s"Waiting ecBlock3 ${ecBlock3.hash}") { s => + d.waitForCS[FollowingChain](s"Waiting payload3 ${payload3.hash}") { s => s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock3.hash - s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock3.hash + s.nodeChainInfo.lastBlock.hash shouldBe payload3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe payload3.hash } - step(s"Receive ecBlock3 ${ecBlock3.hash}") - d.receiveNetworkBlock(ecBlock3, thisMiner.account, ecBlock3Epoch) + step(s"Receive network block ${payload3.hash} with payload3") + d.receiveNetworkBlock(payload3, thisMiner.account, block3Epoch) - d.ecClients.willForge(d.createEcBlockBuilder("0-1-1-1", thisMiner, ecBlock3).rewardPrevMiner(2).build()) + d.ecClients.willForge(d.createPayloadBuilder("0-1-1-1", thisMiner, payload3).rewardPrevMiner(2).build()) d.waitForCS[Mining]() { s => s.nodeChainInfo.value.isMain shouldBe false } } - "We mined before the alternative chain before and EC-block doesn't come - then we still wait for it" in withExtensionDomain() { d => - 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)) + "We mined before the alternative chain before and network block with payload doesn't come - then we still wait for it" in withExtensionDomain() { + d => + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with payload1") + d.advanceNewBlocks(otherMiner1.address) + val payload1 = d.createPayloadBuilder("0", otherMiner1).buildAndSetLogs() + d.ecClients.addKnown(payload1) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, payload1)) - d.waitForCS[FollowingChain]() { s => - s.nodeChainInfo.isMain shouldBe true - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock1.hash - } + d.waitForCS[FollowingChain]() { s => + s.nodeChainInfo.isMain shouldBe true + s.nodeChainInfo.lastBlock.hash shouldBe payload1.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)) + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with badPayload2") + d.advanceNewBlocks(otherMiner1.address) + val badPayload2 = d.createPayloadBuilder("0-0", otherMiner1, payload1).rewardPrevMiner().buildAndSetLogs() + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, badPayload2)) - d.waitForCS[FollowingChain]() { s => - s.nodeChainInfo.isMain shouldBe true - s.nodeChainInfo.lastBlock.hash shouldBe ecBadBlock2.hash - } + d.waitForCS[FollowingChain]() { s => + s.nodeChainInfo.isMain shouldBe true + s.nodeChainInfo.lastBlock.hash shouldBe badPayload2.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()) + step(s"Start a new epoch of thisMiner ${thisMiner.address} with alternative chain payload2") + d.advanceNewBlocks(thisMiner.address) + val payload2 = d.createPayloadBuilder("0-1", thisMiner, payload1).rewardPrevMiner().buildAndSetLogs() + d.ecClients.willForge(payload2) + d.ecClients.willForge(d.createPayloadBuilder("0-1-i", thisMiner, payload2).buildAndSetLogs()) - d.waitForCS[Mining]() { s => - val ci = s.nodeChainInfo.left.value - ci.referenceBlock.hash shouldBe ecBlock1.hash - } + d.waitForCS[Mining]() { s => + val ci = s.nodeChainInfo.left.value + ci.referenceBlock.hash shouldBe payload1.hash + } - d.advanceMining() - d.forgeFromUtxPool() + d.advanceMining() + d.forgeFromUtxPool() - d.waitForCS[Mining]() { s => - val ci = s.nodeChainInfo.value - ci.lastBlock.hash shouldBe ecBlock2.hash - } + d.waitForCS[Mining]() { s => + val ci = s.nodeChainInfo.value + ci.lastBlock.hash shouldBe payload2.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)) + step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with badPayload3") + d.advanceNewBlocks(otherMiner2.address) + val badPayload3 = d.createPayloadBuilder("0-1-1", otherMiner2, payload2).rewardMiner(otherMiner2.elRewardAddress, 1).buildAndSetLogs() + d.appendMicroBlockAndVerify(d.chainContract.extendAltChain(otherMiner2.account, badPayload3, chainId = 1)) - d.waitForCS[FollowingChain]() { s => - s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe ecBadBlock3.hash - s.nextExpectedBlock.map(_.hash).value shouldBe ecBadBlock3.hash - } + d.waitForCS[FollowingChain]() { s => + s.nodeChainInfo.isMain shouldBe false + s.nodeChainInfo.lastBlock.hash shouldBe badPayload3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe badPayload3.hash + } - step(s"Continue an alternative chain by thisMiner ${thisMiner.address}") - d.advanceNewBlocks(thisMiner.address) + step(s"Continue an alternative chain by thisMiner ${thisMiner.address}") + d.advanceNewBlocks(thisMiner.address) - d.advanceWaitRequestedBlock() - d.advanceWaitRequestedBlock() + d.advanceWaitRequestedBlock() + d.advanceWaitRequestedBlock() - d.waitForCS[FollowingChain](s"Still wait for ecBadBlock3 ${ecBadBlock3.hash}") { s => - s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe ecBadBlock3.hash - s.nextExpectedBlock.map(_.hash).value shouldBe ecBadBlock3.hash - } + d.waitForCS[FollowingChain](s"Still wait for badPayload3 ${badPayload3.hash}") { s => + s.nodeChainInfo.isMain shouldBe false + s.nodeChainInfo.lastBlock.hash shouldBe badPayload3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe badPayload3.hash + } } - "We haven't mined the alternative chain before and EC-block doesn't come - then we wait for a new alternative chain" in + "We haven't mined the alternative chain before and network block with payload doesn't come - then we wait for a new alternative chain" in withExtensionDomain() { d => - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBlock1") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with payload1") d.advanceNewBlocks(otherMiner1.address) - val ecBlock1 = d.createEcBlockBuilder("0", otherMiner1).buildAndSetLogs() - d.ecClients.addKnown(ecBlock1) - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBlock1)) + val payload1 = d.createPayloadBuilder("0", otherMiner1).buildAndSetLogs() + d.ecClients.addKnown(payload1) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, payload1)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe true - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock1.hash + s.nodeChainInfo.lastBlock.hash shouldBe payload1.hash } - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with ecBadBlock2") + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with badPayload2") d.advanceNewBlocks(otherMiner1.address) - val ecBadBlock2 = d.createEcBlockBuilder("0-0", otherMiner1, ecBlock1).rewardPrevMiner().buildAndSetLogs() - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, ecBadBlock2)) + val badPayload2 = d.createPayloadBuilder("0-0", otherMiner1, payload1).rewardPrevMiner().buildAndSetLogs() + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, badPayload2)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe true - s.nodeChainInfo.lastBlock.hash shouldBe ecBadBlock2.hash + s.nodeChainInfo.lastBlock.hash shouldBe badPayload2.hash } - step(s"Start a new epoch of otherMiner2 ${otherMiner2.address} with alternative chain ecBlock2") + step(s"Start a new epoch of otherMiner2 ${otherMiner2.address} with alternative chain payload2") d.advanceNewBlocks(otherMiner2.address) - val ecBlock2 = d.createEcBlockBuilder("0-1", otherMiner2, ecBlock1).rewardPrevMiner().buildAndSetLogs() + val payload2 = d.createPayloadBuilder("0-1", otherMiner2, payload1).rewardPrevMiner().buildAndSetLogs() d.waitForCS[WaitForNewChain]() { s => - s.chainSwitchInfo.referenceBlock.hash shouldBe ecBlock1.hash + s.chainSwitchInfo.referenceBlock.hash shouldBe payload1.hash } - d.appendMicroBlockAndVerify(d.chainContract.startAltChain(otherMiner2.account, ecBlock2)) + d.appendMicroBlockAndVerify(d.chainContract.startAltChain(otherMiner2.account, payload2)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock2.hash - s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock2.hash + s.nodeChainInfo.lastBlock.hash shouldBe payload2.hash + s.nextExpectedBlock.map(_.hash).value shouldBe payload2.hash } - d.receiveNetworkBlock(ecBlock2, otherMiner2.account, d.blockchain.height) + d.receiveNetworkBlock(payload2, otherMiner2.account, d.blockchain.height) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock2.hash + s.nodeChainInfo.lastBlock.hash shouldBe payload2.hash s.nextExpectedBlock shouldBe empty } - step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with ecBlock3") + step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with payload3") 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)) + val payload3 = d.createPayloadBuilder("0-1-1", otherMiner2, payload2).rewardPrevMiner(1).buildAndSetLogs() + d.appendMicroBlockAndVerify(d.chainContract.extendAltChain(otherMiner2.account, payload3, chainId = 1)) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe ecBlock3.hash - s.nextExpectedBlock.map(_.hash).value shouldBe ecBlock3.hash + s.nodeChainInfo.lastBlock.hash shouldBe payload3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe payload3.hash } step(s"Start a new epoch of thisMiner ${thisMiner.address}") d.advanceNewBlocks(thisMiner.address) d.waitForCS[WaitForNewChain]() { s => - s.chainSwitchInfo.referenceBlock.hash shouldBe ecBlock1.hash + s.chainSwitchInfo.referenceBlock.hash shouldBe payload1.hash } } } diff --git a/src/test/scala/units/E2CTransfersTestSuite.scala b/src/test/scala/units/E2CTransfersTestSuite.scala index 6881ccf5..9b0a869f 100644 --- a/src/test/scala/units/E2CTransfersTestSuite.scala +++ b/src/test/scala/units/E2CTransfersTestSuite.scala @@ -21,8 +21,8 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { private val transferReceiver = TxHelpers.secondSigner private val transfer = Bridge.ElSentNativeEvent(transferReceiver.toAddress, 1) private val transferEvent = getLogsResponseEntry(transfer) - private val ecBlockLogs = List(transferEvent) - private val e2CTransfersRootHashHex = HexBytesConverter.toHex(Bridge.mkTransfersHash(ecBlockLogs).explicitGet()) + private val blockLogs = List(transferEvent) + private val e2CTransfersRootHashHex = HexBytesConverter.toHex(Bridge.mkTransfersHash(blockLogs).explicitGet()) private val transferProofs = Bridge.mkTransferProofs(List(transfer), 0).reverse // Contract requires from bottom to top private val reliable = ElMinerSettings(Wallet.generateNewAccount(TestSettings.Default.walletSeed, 0)) @@ -39,8 +39,8 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { val transfer1 = Bridge.ElSentNativeEvent(transferReceiver1.toAddress, 1) val transfer2 = Bridge.ElSentNativeEvent(transferReceiver2.toAddress, 1) val transferEvents = List(transfer1, transfer2) - val ecBlockLogs = transferEvents.map(getLogsResponseEntry) - val e2CTransfersRootHashHex = HexBytesConverter.toHex(Bridge.mkTransfersHash(ecBlockLogs).explicitGet()) + val blockLogs = transferEvents.map(getLogsResponseEntry) + val e2CTransfersRootHashHex = HexBytesConverter.toHex(Bridge.mkTransfersHash(blockLogs).explicitGet()) val transfer1Proofs = Bridge.mkTransferProofs(transferEvents, 0).reverse val transfer2Proofs = Bridge.mkTransferProofs(transferEvents, 1).reverse @@ -52,20 +52,20 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { ) withExtensionDomain(settings) { d => - step(s"Start new epoch for ecBlock") + step(s"Start new epoch for payload") d.advanceNewBlocks(reliable.address) - val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) + val payload = d.createPayloadBuilder("0", reliable).buildAndSetLogs(blockLogs) 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) + d.chainContract.withdraw(transferReceiver1, payload, transfer1Proofs, 0, transfer1.amount), + d.chainContract.withdraw(transferReceiver2, payload, transfer2Proofs, 1, transfer2.amount) ) tryWithdraw() should produce("not found for the contract address") - step("Append a CL micro block with ecBlock confirmation") - d.ecClients.addKnown(ecBlock) - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, ecBlock, e2CTransfersRootHashHex)) + step("Append a CL micro block with payload confirmation") + d.ecClients.addKnown(payload) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, payload, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() tryWithdraw() should beRight @@ -88,15 +88,15 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { ) ) { case (index, errorMessage) => withExtensionDomain() { d => - step(s"Start new epoch with ecBlock") + step(s"Start new epoch with payload") 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)) + val payload = d.createPayloadBuilder("0", reliable).buildAndSetLogs(blockLogs) + d.ecClients.addKnown(payload) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, payload, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() def tryWithdraw(): Either[Throwable, BlockId] = - d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, ecBlock, transferProofs, index, transfer.amount)) + d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, payload, transferProofs, index, transfer.amount)) tryWithdraw() should produce(errorMessage) } @@ -110,30 +110,30 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { ) ) { amount => withExtensionDomain() { d => - step(s"Start new epoch with ecBlock") + step(s"Start new epoch with payload") 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)) + val payload = d.createPayloadBuilder("0", reliable).buildAndSetLogs(blockLogs) + d.ecClients.addKnown(payload) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, payload, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() def tryWithdraw(): Either[Throwable, BlockId] = - d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, ecBlock, transferProofs, 0, amount)) + d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, payload, transferProofs, 0, amount)) tryWithdraw() should produce("Amount should be positive") } } "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") + step(s"Start new epoch with payload") d.advanceNewBlocks(reliable.address) - val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) + val payload = d.createPayloadBuilder("0", reliable).buildAndSetLogs(blockLogs) def tryWithdraw(): Either[Throwable, BlockId] = - d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, ecBlock, transferProofs, 0, transfer.amount)) + d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, payload, transferProofs, 0, transfer.amount)) tryWithdraw() should produce("not found for the contract address") - d.ecClients.addKnown(ecBlock) - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, ecBlock, e2CTransfersRootHashHex)) + d.ecClients.addKnown(payload) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, payload, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() tryWithdraw() should beRight @@ -149,15 +149,15 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { ) withExtensionDomain(settings) { d => - step(s"Start new epoch with ecBlock") + step(s"Start new epoch with payload") d.advanceNewBlocks(reliable.address) - val ecBlock = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(ecBlockLogs) + val payload = d.createPayloadBuilder("0", reliable).buildAndSetLogs(blockLogs) def tryWithdraw(): Either[Throwable, BlockId] = - d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, ecBlock, transferProofs, 0, transfer.amount)) + d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, payload, transferProofs, 0, transfer.amount)) tryWithdraw() should produce("not found for the contract address") - d.ecClients.addKnown(ecBlock) - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, ecBlock, e2CTransfersRootHashHex)) + d.ecClients.addKnown(payload) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, payload, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() tryWithdraw() should beRight @@ -182,13 +182,13 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { ) ) { transferEvent => withExtensionDomain(settings) { d => - step(s"Start new epoch with ecBlock1") + step(s"Start new epoch with payload1") d.advanceNewBlocks(reliable.address) - val ecBlock1 = d.createEcBlockBuilder("0", reliable).buildAndSetLogs(List(transferEvent)) + val payload1 = d.createPayloadBuilder("0", reliable).buildAndSetLogs(List(transferEvent)) def tryWithdraw(): Either[Throwable, BlockId] = - d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, ecBlock1, transferProofs, 0, transfer.amount)) + d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, payload1, transferProofs, 0, transfer.amount)) - d.ecClients.willForge(ecBlock1) + d.ecClients.willForge(payload1) d.advanceConsensusLayerChanged() d.advanceMining() @@ -207,11 +207,11 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { "Fails on wrong data" in { val settings = defaultSettings.withEnabledElMining withExtensionDomain(settings) { d => - step(s"Start new epoch with ecBlock1") + step(s"Start new epoch with payload1") 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()) + val payload1 = d.createPayloadBuilder("0", reliable).buildAndSetLogs(List(transferEvent.copy(data = "d3ad884fa04292"))) + d.ecClients.willForge(payload1) + d.ecClients.willForge(d.createPayloadBuilder("0-0", reliable).build()) d.advanceConsensusLayerChanged() @@ -223,50 +223,50 @@ 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") + step(s"Start a new epoch of malfunction miner ${malfunction.address} with payload1") d.advanceNewBlocks(malfunction.address) // Need this block, because we can not rollback to the genesis block - val ecBlock1 = d.createEcBlockBuilder("0", malfunction).buildAndSetLogs() + val payload1 = d.createPayloadBuilder("0", malfunction).buildAndSetLogs() d.advanceConsensusLayerChanged() - d.ecClients.addKnown(ecBlock1) - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, ecBlock1)) + d.ecClients.addKnown(payload1) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, payload1)) d.advanceConsensusLayerChanged() 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) + val badPayload2 = d.createPayloadBuilder("0-0", malfunction, payload1).rewardPrevMiner().buildAndSetLogs(blockLogs) d.advanceConsensusLayerChanged() // No root hash in extendMainChain tx - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, ecBadBlock2)) // No root hash - d.receiveNetworkBlock(ecBadBlock2, malfunction.account) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, badPayload2)) // No root hash + d.receiveNetworkBlock(badPayload2, malfunction.account) d.advanceConsensusLayerChanged() d.waitForCS[WaitForNewChain]("State is expected") { s => - s.chainSwitchInfo.referenceBlock.hash shouldBe ecBlock1.hash + s.chainSwitchInfo.referenceBlock.hash shouldBe payload1.hash } - step(s"Start an alternative chain by a reliable miner ${reliable.address} with ecBlock2") + step(s"Start an alternative chain by a reliable miner ${reliable.address} with payload2") d.advanceNewBlocks(reliable.address) - val ecBlock2 = d.createEcBlockBuilder("0-1", reliable, ecBlock1).rewardPrevMiner().buildAndSetLogs(ecBlockLogs) - d.ecClients.willForge(ecBlock2) + val payload2 = d.createPayloadBuilder("0-1", reliable, payload1).rewardPrevMiner().buildAndSetLogs(blockLogs) + d.ecClients.willForge(payload2) // Prepare a following block, because we start mining it immediately - d.ecClients.willForge(d.createEcBlockBuilder("0-1-1", reliable, ecBlock2).build()) + d.ecClients.willForge(d.createPayloadBuilder("0-1-1", reliable, payload2).build()) d.advanceConsensusLayerChanged() d.waitForCS[Mining]("State is expected") { s => - s.nodeChainInfo.left.value.referenceBlock.hash shouldBe ecBlock1.hash + s.nodeChainInfo.left.value.referenceBlock.hash shouldBe payload1.hash } d.advanceMining() d.waitForCS[Mining]("State is expected") { s => - s.nodeChainInfo.left.value.referenceBlock.hash shouldBe ecBlock1.hash + s.nodeChainInfo.left.value.referenceBlock.hash shouldBe payload1.hash } step(s"Confirm startAltChain and append with new blocks and remove a malfunction miner") d.appendMicroBlockAndVerify( - d.chainContract.startAltChain(reliable.account, ecBlock2, e2CTransfersRootHashHex), + d.chainContract.startAltChain(reliable.account, payload2, e2CTransfersRootHashHex), d.chainContract.leave(malfunction.account) ) d.advanceConsensusLayerChanged() @@ -274,21 +274,21 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { d.waitForCS[Mining]("State is expected") { _ => } def tryWithdraw(): Either[Throwable, BlockId] = - d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, ecBlock2, transferProofs, 0, transfer.amount)) + d.appendMicroBlockE(d.chainContract.withdraw(transferReceiver, payload2, transferProofs, 0, transfer.amount)) withClue("Can't withdraw from a fork:") { tryWithdraw() should produce("is not finalized") } - step(s"Moving whole network to the alternative chain with ecBlock3") + step(s"Moving whole network to the alternative chain with payload3") d.advanceNewBlocks(reliable.address) - val ecBlock3 = d.createEcBlockBuilder("0-1-1-1", reliable, ecBlock2).rewardPrevMiner(1).buildAndSetLogs() - d.ecClients.willForge(ecBlock3) + val payload3 = d.createPayloadBuilder("0-1-1-1", reliable, payload2).rewardPrevMiner(1).buildAndSetLogs() + d.ecClients.willForge(payload3) d.advanceConsensusLayerChanged() step("Confirm extendAltChain to make this chain main") d.advanceMining() - d.appendMicroBlockAndVerify(d.chainContract.extendAltChain(reliable.account, ecBlock3, chainId = 1)) + d.appendMicroBlockAndVerify(d.chainContract.extendAltChain(reliable.account, payload3, chainId = 1)) d.advanceConsensusLayerChanged() d.waitForCS[Mining]("State is expected") { _ => } diff --git a/src/test/scala/units/ExtensionDomain.scala b/src/test/scala/units/ExtensionDomain.scala index 7c7fa740..2321fba7 100644 --- a/src/test/scala/units/ExtensionDomain.scala +++ b/src/test/scala/units/ExtensionDomain.scala @@ -41,8 +41,8 @@ import units.ELUpdater.State.{ChainStatus, Working} import units.ExtensionDomain.* import units.client.contract.HasConsensusLayerDappTxHelpers import units.client.contract.HasConsensusLayerDappTxHelpers.EmptyE2CTransfersRootHashHex -import units.client.engine.model.{EcBlock, TestEcBlocks} -import units.client.{L2BlockLike, TestEcClients} +import units.client.engine.model.{ExecutionPayload, TestPayloads} +import units.client.{CommonBlockData, TestEcClients} import units.eth.{EthAddress, EthereumConstants, Gwei} import units.network.TestBlocksObserver import units.test.CustomMatchers @@ -67,16 +67,16 @@ class ExtensionDomain( with ScorexLogging { self => override val chainContractAccount: KeyPair = KeyPair("chain-contract".getBytes(StandardCharsets.UTF_8)) - val l2Config = settings.config.as[ClientConfig]("waves.l2") + val l2Config: ClientConfig = settings.config.as[ClientConfig]("waves.l2") require(l2Config.chainContractAddress == chainContractAddress, "Check settings") - val ecGenesisBlock = EcBlock( - hash = TestEcBlockBuilder.createBlockHash(""), + val genesisBlockPayload: ExecutionPayload = ExecutionPayload( + hash = TestPayloadBuilder.createBlockHash(""), parentHash = BlockHash(EthereumConstants.EmptyBlockHashHex), // see main.ride stateRoot = EthereumConstants.EmptyRootHashHex, height = 0, timestamp = testTime.getTimestamp() / 1000 - l2Config.blockDelay.toSeconds, - minerRewardL2Address = EthAddress.empty, + minerRewardAddress = EthAddress.empty, baseFeePerGas = Uint256.DEFAULT, gasLimit = 0, gasUsed = 0, @@ -84,23 +84,23 @@ class ExtensionDomain( withdrawals = Vector.empty ) - val ecClients = new TestEcClients(ecGenesisBlock, blockchain) + val ecClients = new TestEcClients(genesisBlockPayload, blockchain) - val globalScheduler = TestScheduler(ExecutionModel.AlwaysAsyncExecution) - val eluScheduler = TestScheduler(ExecutionModel.AlwaysAsyncExecution) + val globalScheduler: TestScheduler = TestScheduler(ExecutionModel.AlwaysAsyncExecution) + val eluScheduler: TestScheduler = TestScheduler(ExecutionModel.AlwaysAsyncExecution) - val elBlockStream = PublishSubject[(Channel, NetworkL2Block)]() - val blockObserver = new TestBlocksObserver(elBlockStream) + val elBlockStream: PublishSubject[(Channel, NetworkBlock)] = PublishSubject[(Channel, NetworkBlock)]() + val blockObserver: TestBlocksObserver = new TestBlocksObserver(elBlockStream) val neighbourChannel = new EmbeddedChannel() val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) allChannels.add(neighbourChannel) - def pollSentNetworkBlock(): Option[NetworkL2Block] = Option(neighbourChannel.readOutbound[NetworkL2Block]) - def receiveNetworkBlock(ecBlock: EcBlock, miner: SeedKeyPair, epochNumber: Int = blockchain.height): Unit = - receiveNetworkBlock(toNetworkBlock(ecBlock, miner, epochNumber)) - def receiveNetworkBlock(incomingNetworkBlock: NetworkL2Block): Unit = elBlockStream.onNext((new EmbeddedChannel(), incomingNetworkBlock)) + def pollSentNetworkBlock(): Option[NetworkBlock] = Option(neighbourChannel.readOutbound[NetworkBlock]) + def receiveNetworkBlock(payload: ExecutionPayload, miner: SeedKeyPair, epochNumber: Int = blockchain.height): Unit = + receiveNetworkBlock(toNetworkBlock(payload, miner, epochNumber)) + def receiveNetworkBlock(incomingNetworkBlock: NetworkBlock): Unit = elBlockStream.onNext((new EmbeddedChannel(), incomingNetworkBlock)) - val extensionContext = new Context { + val extensionContext: Context = new Context { override def settings: WavesSettings = self.settings override def blockchain: Blockchain = self.blockchain @@ -131,9 +131,9 @@ class ExtensionDomain( ) triggers = triggers.appended(consensusClient) - val defaultMaxTimeout = + val defaultMaxTimeout: FiniteDuration = List(WaitForReferenceConfirmInterval, ClChangedProcessingDelay, MiningRetryInterval, WaitRequestedBlockTimeout).max + 1.millis - val defaultInterval = ClChangedProcessingDelay + val defaultInterval: FiniteDuration = ClChangedProcessingDelay def waitForWorking( title: String = "", @@ -169,14 +169,14 @@ class ExtensionDomain( f(is[CS](s.chainStatus)) } - def toNetworkBlock(ecBlock: EcBlock, miner: SeedKeyPair, epochNumber: Int): NetworkL2Block = - NetworkL2Block + def toNetworkBlock(payload: ExecutionPayload, miner: SeedKeyPair, epochNumber: Int): NetworkBlock = + NetworkBlock .signed( - TestEcBlocks.toPayload( - ecBlock, + TestPayloads.toPayloadJson( + payload, calculateRandao( blockchain.vrf(epochNumber).getOrElse(throw new RuntimeException(s"VRF is empty for epoch $epochNumber")), - ecBlock.parentHash + payload.parentHash ) ), miner.privateKey @@ -232,11 +232,11 @@ class ExtensionDomain( // Useful for debugging purposes def evaluateExtendAltChain( - minerAccount: KeyPair, - chainId: Long, - block: L2BlockLike, - epoch: Long, - e2CTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex + minerAccount: KeyPair, + chainId: Long, + blockData: CommonBlockData, + epoch: Long, + e2CTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex ): Either[String, JsObject] = { val r = evaluate( chainContractAddress, @@ -244,8 +244,8 @@ class ExtensionDomain( "extendAltChain", List[Terms.EVALUATED]( Terms.CONST_LONG(chainId), - Terms.CONST_STRING(block.hash.drop(2)).explicitGet(), - Terms.CONST_STRING(block.parentHash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.hash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.parentHash.drop(2)).explicitGet(), Terms.CONST_LONG(epoch), Terms.CONST_STRING(e2CTransfersRootHashHex.drop(2)).explicitGet() ) @@ -342,15 +342,15 @@ class ExtensionDomain( utxPool.close() } - def createEcBlockBuilder(hashPath: String, miner: ElMinerSettings, parent: EcBlock = ecGenesisBlock): TestEcBlockBuilder = - createEcBlockBuilder(hashPath, miner.elRewardAddress, parent) + def createPayloadBuilder(hashPath: String, miner: ElMinerSettings, parentPayload: ExecutionPayload = genesisBlockPayload): TestPayloadBuilder = + createPayloadBuilder(hashPath, miner.elRewardAddress, parentPayload) - def createEcBlockBuilder(hashPath: String, minerRewardL2Address: EthAddress, parent: EcBlock): TestEcBlockBuilder = { - TestEcBlockBuilder(ecClients, elBridgeAddress, elMinerDefaultReward, l2Config.blockDelay, parent = parent).updateBlock( + def createPayloadBuilder(hashPath: String, minerRewardAddress: EthAddress, parentPayload: ExecutionPayload): TestPayloadBuilder = { + TestPayloadBuilder(ecClients, elBridgeAddress, elMinerDefaultReward, l2Config.blockDelay, parentPayload = parentPayload).updatePayload( _.copy( - hash = TestEcBlockBuilder.createBlockHash(hashPath), - minerRewardL2Address = minerRewardL2Address, - prevRandao = ELUpdater.calculateRandao(blockchain.vrf(blockchain.height).get, parent.hash) + hash = TestPayloadBuilder.createBlockHash(hashPath), + minerRewardAddress = minerRewardAddress, + prevRandao = ELUpdater.calculateRandao(blockchain.vrf(blockchain.height).get, parentPayload.hash) ) ) } diff --git a/src/test/scala/units/TestEcBlockBuilder.scala b/src/test/scala/units/TestEcBlockBuilder.scala deleted file mode 100644 index 52c195a7..00000000 --- a/src/test/scala/units/TestEcBlockBuilder.scala +++ /dev/null @@ -1,66 +0,0 @@ -package units - -import org.web3j.abi.datatypes.generated.Uint256 -import units.client.TestEcClients -import units.client.engine.model.{EcBlock, GetLogsResponseEntry, Withdrawal} -import units.eth.{EthAddress, EthereumConstants, Gwei} - -import java.nio.charset.StandardCharsets -import scala.concurrent.duration.FiniteDuration - -class TestEcBlockBuilder private ( - testEcClients: TestEcClients, - elBridgeAddress: EthAddress, - elMinerDefaultReward: Gwei, - private var block: EcBlock, - parentBlock: EcBlock -) { - def updateBlock(f: EcBlock => EcBlock): TestEcBlockBuilder = { - block = f(block) - this - } - - def rewardPrevMiner(elWithdrawalIndex: Int = 0): TestEcBlockBuilder = rewardMiner(parentBlock.minerRewardL2Address, elWithdrawalIndex) - - def rewardMiner(minerRewardL2Address: EthAddress, elWithdrawalIndex: Int = 0): TestEcBlockBuilder = { - block = block.copy(withdrawals = Vector(Withdrawal(elWithdrawalIndex, minerRewardL2Address, elMinerDefaultReward))) - this - } - - def build(): EcBlock = block - def buildAndSetLogs(logs: List[GetLogsResponseEntry] = Nil): EcBlock = { - testEcClients.setBlockLogs(block.hash, elBridgeAddress, Bridge.ElSentNativeEventTopic, logs) - block - } -} - -object TestEcBlockBuilder { - def apply( - testEcClients: TestEcClients, - elBridgeAddress: EthAddress, - elMinerDefaultReward: Gwei, - blockDelay: FiniteDuration, - parent: EcBlock - ): TestEcBlockBuilder = - new TestEcBlockBuilder( - testEcClients, - elBridgeAddress, - elMinerDefaultReward, - EcBlock( - hash = createBlockHash("???"), - parentHash = parent.hash, - stateRoot = EthereumConstants.EmptyRootHashHex, - height = parent.height + 1, - timestamp = parent.timestamp + blockDelay.toSeconds, - minerRewardL2Address = EthAddress.empty, - baseFeePerGas = Uint256.DEFAULT, - gasLimit = 0, - gasUsed = 0, - prevRandao = EthereumConstants.EmptyPrevRandaoHex, - withdrawals = Vector.empty - ), - parent - ) - - def createBlockHash(path: String): BlockHash = BlockHash(eth.hash(path.getBytes(StandardCharsets.UTF_8))) -} diff --git a/src/test/scala/units/TestPayloadBuilder.scala b/src/test/scala/units/TestPayloadBuilder.scala new file mode 100644 index 00000000..96460072 --- /dev/null +++ b/src/test/scala/units/TestPayloadBuilder.scala @@ -0,0 +1,66 @@ +package units + +import org.web3j.abi.datatypes.generated.Uint256 +import units.client.TestEcClients +import units.client.engine.model.{ExecutionPayload, GetLogsResponseEntry, Withdrawal} +import units.eth.{EthAddress, EthereumConstants, Gwei} + +import java.nio.charset.StandardCharsets +import scala.concurrent.duration.FiniteDuration + +class TestPayloadBuilder private ( + testEcClients: TestEcClients, + elBridgeAddress: EthAddress, + elMinerDefaultReward: Gwei, + private var payload: ExecutionPayload, + parentPayload: ExecutionPayload +) { + def updatePayload(f: ExecutionPayload => ExecutionPayload): TestPayloadBuilder = { + payload = f(payload) + this + } + + def rewardPrevMiner(elWithdrawalIndex: Int = 0): TestPayloadBuilder = rewardMiner(parentPayload.minerRewardAddress, elWithdrawalIndex) + + def rewardMiner(minerRewardAddress: EthAddress, elWithdrawalIndex: Int = 0): TestPayloadBuilder = { + payload = payload.copy(withdrawals = Vector(Withdrawal(elWithdrawalIndex, minerRewardAddress, elMinerDefaultReward))) + this + } + + def build(): ExecutionPayload = payload + def buildAndSetLogs(logs: List[GetLogsResponseEntry] = Nil): ExecutionPayload = { + testEcClients.setBlockLogs(payload.hash, elBridgeAddress, Bridge.ElSentNativeEventTopic, logs) + payload + } +} + +object TestPayloadBuilder { + def apply( + testEcClients: TestEcClients, + elBridgeAddress: EthAddress, + elMinerDefaultReward: Gwei, + blockDelay: FiniteDuration, + parentPayload: ExecutionPayload + ): TestPayloadBuilder = + new TestPayloadBuilder( + testEcClients, + elBridgeAddress, + elMinerDefaultReward, + ExecutionPayload( + hash = createBlockHash("???"), + parentHash = parentPayload.hash, + stateRoot = EthereumConstants.EmptyRootHashHex, + height = parentPayload.height + 1, + timestamp = parentPayload.timestamp + blockDelay.toSeconds, + minerRewardAddress = EthAddress.empty, + baseFeePerGas = Uint256.DEFAULT, + gasLimit = 0, + gasUsed = 0, + prevRandao = EthereumConstants.EmptyPrevRandaoHex, + withdrawals = Vector.empty + ), + parentPayload + ) + + def createBlockHash(path: String): BlockHash = BlockHash(eth.hash(path.getBytes(StandardCharsets.UTF_8))) +} diff --git a/src/test/scala/units/TestSettings.scala b/src/test/scala/units/TestSettings.scala index 9a9e7116..ec0aeccb 100644 --- a/src/test/scala/units/TestSettings.scala +++ b/src/test/scala/units/TestSettings.scala @@ -1,7 +1,7 @@ package units import com.typesafe.config.ConfigFactory -import com.wavesplatform.account.SeedKeyPair +import com.wavesplatform.account.{Address, SeedKeyPair} import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.settings.WavesSettings import com.wavesplatform.test.{DomainPresets, NumericExt} @@ -23,11 +23,11 @@ case class TestSettings( } object TestSettings { - val Default = TestSettings() + val Default: TestSettings = TestSettings() private object Waves { - val Default = DomainPresets.TransactionStateSnapshot - val WithMining = Default.copy(config = ConfigFactory.parseString("waves.l2.mining-enable = true").withFallback(Default.config)) + val Default: WavesSettings = DomainPresets.TransactionStateSnapshot + val WithMining: WavesSettings = Default.copy(config = ConfigFactory.parseString("waves.l2.mining-enable = true").withFallback(Default.config)) } } @@ -36,6 +36,6 @@ case class ElMinerSettings( wavesBalance: Long = 20_100.waves, stakingBalance: Long = 50_000_000L ) { - val address = account.toAddress - val elRewardAddress = EthAddress.unsafeFrom(address.toEthAddress) + val address: Address = account.toAddress + val elRewardAddress: EthAddress = EthAddress.unsafeFrom(address.toEthAddress) } diff --git a/src/test/scala/units/client/TestEcClients.scala b/src/test/scala/units/client/TestEcClients.scala index 53ad627b..ba7e7fdb 100644 --- a/src/test/scala/units/client/TestEcClients.scala +++ b/src/test/scala/units/client/TestEcClients.scala @@ -12,43 +12,43 @@ import units.client.engine.model.* import units.client.engine.{EngineApiClient, LoggedEngineApiClient} import units.collections.ListOps.* import units.eth.EthAddress -import units.{BlockHash, JobResult, NetworkL2Block} +import units.{BlockHash, JobResult, NetworkBlock} class TestEcClients private ( knownBlocks: Atomic[Map[BlockHash, ChainId]], - chains: Atomic[Map[ChainId, List[EcBlock]]], + chains: Atomic[Map[ChainId, List[ExecutionPayload]]], currChainIdValue: AtomicInt, blockchain: Blockchain ) extends ScorexLogging { - def this(genesis: EcBlock, blockchain: Blockchain) = this( - knownBlocks = Atomic(Map(genesis.hash -> 0)), - chains = Atomic(Map(0 -> List(genesis))), + def this(genesisPayload: ExecutionPayload, blockchain: Blockchain) = this( + knownBlocks = Atomic(Map(genesisPayload.hash -> 0)), + chains = Atomic(Map(0 -> List(genesisPayload))), currChainIdValue = AtomicInt(0), blockchain = blockchain ) private def currChainId: ChainId = currChainIdValue.get() - def addKnown(ecBlock: EcBlock): EcBlock = { - knownBlocks.transform(_.updated(ecBlock.hash, currChainId)) - prependToCurrentChain(ecBlock) - ecBlock + def addKnown(payload: ExecutionPayload): ExecutionPayload = { + knownBlocks.transform(_.updated(payload.hash, currChainId)) + prependToCurrentChain(payload) + payload } - private def prependToCurrentChain(b: EcBlock): Unit = - prependToChain(currChainId, b) + private def prependToCurrentChain(payload: ExecutionPayload): Unit = + prependToChain(currChainId, payload) - private def prependToChain(chainId: ChainId, b: EcBlock): Unit = + private def prependToChain(chainId: ChainId, payload: ExecutionPayload): Unit = chains.transform { chains => - chains.updated(chainId, b :: chains(chainId)) + chains.updated(chainId, payload :: chains(chainId)) } - private def currChain: List[EcBlock] = + private def currChain: List[ExecutionPayload] = chains.get().getOrElse(currChainId, throw new RuntimeException(s"Unknown chain $currChainId")) private val forgingBlocks = Atomic(List.empty[ForgingBlock]) - def willForge(ecBlock: EcBlock): Unit = - forgingBlocks.transform(ForgingBlock(ecBlock) :: _) + def willForge(payload: ExecutionPayload): Unit = + forgingBlocks.transform(ForgingBlock(payload) :: _) private val logs = Atomic(Map.empty[GetLogsRequest, List[GetLogsResponseEntry]]) def setBlockLogs(hash: BlockHash, address: EthAddress, topic: String, blockLogs: List[GetLogsResponseEntry]): Unit = { @@ -89,55 +89,55 @@ class TestEcClients private ( ): JobResult[PayloadId] = forgingBlocks .get() - .collectFirst { case fb if fb.testBlock.parentHash == lastBlockHash => fb } match { + .collectFirst { case fb if fb.testPayload.parentHash == lastBlockHash => fb } match { case None => throw new RuntimeException( - s"Can't find a suitable block among: ${forgingBlocks.get().map(_.testBlock.hash).mkString(", ")}. Call willForge" + s"Can't find a suitable block among: ${forgingBlocks.get().map(_.testPayload.hash).mkString(", ")}. Call willForge" ) case Some(fb) => fb.payloadId.asRight } - override def getPayload(payloadId: PayloadId): JobResult[JsObject] = + override def getPayloadJson(payloadId: PayloadId): JobResult[JsObject] = forgingBlocks.transformAndExtract(_.withoutFirst { fb => fb.payloadId == payloadId }) match { - case Some(fb) => TestEcBlocks.toPayload(fb.testBlock, fb.testBlock.prevRandao).asRight + case Some(fb) => TestPayloads.toPayloadJson(fb.testPayload, fb.testPayload.prevRandao).asRight case None => throw new RuntimeException( - s"Can't find payload $payloadId among: ${forgingBlocks.get().map(_.testBlock.hash).mkString(", ")}. Call willForge" + s"Can't find payload $payloadId among: ${forgingBlocks.get().map(_.testPayload.hash).mkString(", ")}. Call willForge" ) } - override def applyNewPayload(payload: JsObject): JobResult[Option[BlockHash]] = { - val newBlock = NetworkL2Block(payload).explicitGet().toEcBlock - knownBlocks.get().get(newBlock.parentHash) match { + override def applyNewPayload(payloadJson: JsObject): JobResult[Option[BlockHash]] = { + val newPayload = NetworkBlock(payloadJson).explicitGet().toPayload + knownBlocks.get().get(newPayload.parentHash) match { case Some(cid) => val chain = chains.get()(cid) - if (newBlock.parentHash == chain.head.hash) { - prependToChain(cid, newBlock) - knownBlocks.transform(_.updated(newBlock.hash, cid)) + if (newPayload.parentHash == chain.head.hash) { + prependToChain(cid, newPayload) + knownBlocks.transform(_.updated(newPayload.hash, cid)) } else { // Rollback - log.debug(s"A rollback using ${newBlock.hash} detected") + log.debug(s"A rollback using ${newPayload.hash} detected") val newCid = currChainIdValue.incrementAndGet() - val newChain = newBlock :: chain.dropWhile(_.hash != newBlock.parentHash) + val newChain = newPayload :: chain.dropWhile(_.hash != newPayload.parentHash) chains.transform(_.updated(newCid, newChain)) - knownBlocks.transform(_.updated(newBlock.hash, newCid)) + knownBlocks.transform(_.updated(newPayload.hash, newCid)) } - case None => throw notImplementedCase(s"Can't find a parent block ${newBlock.parentHash} for ${newBlock.hash}") + case None => throw notImplementedCase(s"Can't find a parent block ${newPayload.parentHash} for ${newPayload.hash}") } - Some(newBlock.hash) + Some(newPayload.hash) }.asRight - override def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] = - getBlockByHashJson(hash) + override def getPayloadBodyJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = + notImplementedMethodJob("getPayloadBodyJsonByHash") - override def getBlockByNumber(number: BlockNumber): JobResult[Option[EcBlock]] = + override def getPayloadByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] = number match { case BlockNumber.Latest => currChain.headOption.asRight case BlockNumber.Number(n) => currChain.find(_.height == n).asRight } - override def getBlockByHash(hash: BlockHash): JobResult[Option[EcBlock]] = { + override def getPayloadByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] = { for { cid <- knownBlocks.get().get(hash) c <- chains.get().get(cid) @@ -145,18 +145,19 @@ class TestEcClients private ( } yield b }.asRight - override def getBlockByHashJson(hash: BlockHash): JobResult[Option[JsObject]] = - notImplementedMethodJob("getBlockByHashJson") + override def getBlockJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = + notImplementedMethodJob("getBlockJsonByHash") - override def getLastExecutionBlock: JobResult[EcBlock] = currChain.head.asRight - - override def blockExists(hash: BlockHash): JobResult[Boolean] = notImplementedMethodJob("blockExists") + override def getLastPayload: JobResult[ExecutionPayload] = currChain.head.asRight 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")) }.asRight + + override def getPayloadJsonDataByHash(hash: BlockHash): JobResult[PayloadJsonData] = + notImplementedMethodJob("getPayloadJsonDataByHash") } ) @@ -170,9 +171,9 @@ object TestEcClients { private type ChainId = Int - private case class ForgingBlock(payloadId: String, testBlock: EcBlock) + private case class ForgingBlock(payloadId: String, testPayload: ExecutionPayload) private object ForgingBlock { - def apply(testBlock: EcBlock): ForgingBlock = - new ForgingBlock(testBlock.hash.take(16), testBlock) + def apply(testPayload: ExecutionPayload): ForgingBlock = + new ForgingBlock(testPayload.hash.take(16), testPayload) } } diff --git a/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala b/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala index 597ef44e..7cb92d2b 100644 --- a/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala +++ b/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala @@ -9,7 +9,7 @@ import com.wavesplatform.lang.v1.compiler.Terms import com.wavesplatform.test.NumericExt import com.wavesplatform.transaction.smart.{InvokeScriptTransaction, SetScriptTransaction} import com.wavesplatform.transaction.{Asset, TxHelpers} -import units.client.L2BlockLike +import units.client.CommonBlockData import units.client.contract.HasConsensusLayerDappTxHelpers.* import units.client.contract.HasConsensusLayerDappTxHelpers.defaultFees.chainContract.* import units.eth.{EthAddress, EthereumConstants} @@ -24,11 +24,11 @@ trait HasConsensusLayerDappTxHelpers { object chainContract { def setScript(): SetScriptTransaction = TxHelpers.setScript(chainContractAccount, CompiledChainContract.script, fee = setScriptFee) - def setup(genesisBlock: L2BlockLike, elMinerReward: Long): InvokeScriptTransaction = TxHelpers.invoke( + def setup(genesisBlockData: CommonBlockData, elMinerReward: Long): InvokeScriptTransaction = TxHelpers.invoke( dApp = chainContractAddress, func = "setup".some, args = List( - Terms.CONST_STRING(genesisBlock.hash.drop(2)).explicitGet(), + Terms.CONST_STRING(genesisBlockData.hash.drop(2)).explicitGet(), Terms.CONST_LONG(elMinerReward) ), fee = setupFee @@ -50,19 +50,19 @@ trait HasConsensusLayerDappTxHelpers { ) def extendMainChain( - minerAccount: KeyPair, - block: L2BlockLike, - e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, - lastC2ETransferIndex: Long = -1, - vrf: ByteStr = currentHitSource + minerAccount: KeyPair, + blockData: CommonBlockData, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + lastC2ETransferIndex: Long = -1, + vrf: ByteStr = currentHitSource ): InvokeScriptTransaction = TxHelpers.invoke( invoker = minerAccount, dApp = chainContractAddress, func = "extendMainChain".some, args = List( - Terms.CONST_STRING(block.hash.drop(2)).explicitGet(), - Terms.CONST_STRING(block.parentHash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.hash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.parentHash.drop(2)).explicitGet(), Terms.CONST_BYTESTR(vrf).explicitGet(), Terms.CONST_STRING(e2cTransfersRootHashHex.drop(2)).explicitGet(), Terms.CONST_LONG(lastC2ETransferIndex) @@ -71,18 +71,18 @@ trait HasConsensusLayerDappTxHelpers { ) def appendBlock( - minerAccount: KeyPair, - block: L2BlockLike, - e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, - lastC2ETransferIndex: Long = -1 + minerAccount: KeyPair, + blockData: CommonBlockData, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + lastC2ETransferIndex: Long = -1 ): InvokeScriptTransaction = TxHelpers.invoke( invoker = minerAccount, dApp = chainContractAddress, func = "appendBlock".some, args = List( - Terms.CONST_STRING(block.hash.drop(2)).explicitGet(), - Terms.CONST_STRING(block.parentHash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.hash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.parentHash.drop(2)).explicitGet(), Terms.CONST_STRING(e2cTransfersRootHashHex.drop(2)).explicitGet(), Terms.CONST_LONG(lastC2ETransferIndex) ), @@ -90,19 +90,19 @@ trait HasConsensusLayerDappTxHelpers { ) def startAltChain( - minerAccount: KeyPair, - block: L2BlockLike, - e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, - lastC2ETransferIndex: Long = -1, - vrf: ByteStr = currentHitSource + minerAccount: KeyPair, + blockData: CommonBlockData, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + lastC2ETransferIndex: Long = -1, + vrf: ByteStr = currentHitSource ): InvokeScriptTransaction = TxHelpers.invoke( invoker = minerAccount, dApp = chainContractAddress, func = "startAltChain".some, args = List( - Terms.CONST_STRING(block.hash.drop(2)).explicitGet(), - Terms.CONST_STRING(block.parentHash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.hash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.parentHash.drop(2)).explicitGet(), Terms.CONST_BYTESTR(vrf).explicitGet(), Terms.CONST_STRING(e2cTransfersRootHashHex.drop(2)).explicitGet(), Terms.CONST_LONG(lastC2ETransferIndex) @@ -111,20 +111,20 @@ trait HasConsensusLayerDappTxHelpers { ) def extendAltChain( - minerAccount: KeyPair, - block: L2BlockLike, - chainId: Long, - e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, - lastC2ETransferIndex: Long = -1, - vrf: ByteStr = currentHitSource + minerAccount: KeyPair, + blockData: CommonBlockData, + chainId: Long, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + lastC2ETransferIndex: Long = -1, + vrf: ByteStr = currentHitSource ): InvokeScriptTransaction = TxHelpers.invoke( invoker = minerAccount, dApp = chainContractAddress, func = "extendAltChain".some, args = List( - Terms.CONST_STRING(block.hash.drop(2)).explicitGet(), - Terms.CONST_STRING(block.parentHash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.hash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.parentHash.drop(2)).explicitGet(), Terms.CONST_BYTESTR(vrf).explicitGet(), Terms.CONST_LONG(chainId), Terms.CONST_STRING(e2cTransfersRootHashHex.drop(2)).explicitGet(), @@ -165,18 +165,18 @@ trait HasConsensusLayerDappTxHelpers { ) def withdraw( - sender: KeyPair, - block: L2BlockLike, - merkleProof: Seq[Digest], - transferIndexInBlock: Int, - amount: Long + sender: KeyPair, + blockData: CommonBlockData, + merkleProof: Seq[Digest], + transferIndexInBlock: Int, + amount: Long ): InvokeScriptTransaction = TxHelpers.invoke( invoker = sender, dApp = chainContractAddress, func = "withdraw".some, args = List( - Terms.CONST_STRING(block.hash.drop(2)).explicitGet(), + Terms.CONST_STRING(blockData.hash.drop(2)).explicitGet(), Terms.ARR(merkleProof.map[Terms.EVALUATED](x => Terms.CONST_BYTESTR(ByteStr(x)).explicitGet()).toVector, limited = false).explicitGet(), Terms.CONST_LONG(transferIndexInBlock), Terms.CONST_LONG(amount) diff --git a/src/test/scala/units/client/engine/model/TestEcBlocks.scala b/src/test/scala/units/client/engine/model/TestEcBlocks.scala deleted file mode 100644 index 389f9d91..00000000 --- a/src/test/scala/units/client/engine/model/TestEcBlocks.scala +++ /dev/null @@ -1,26 +0,0 @@ -package units.client.engine.model - -import com.wavesplatform.account.SeedKeyPair -import com.wavesplatform.common.utils.EitherExt2 -import play.api.libs.json.{JsObject, Json} -import units.NetworkL2Block -import units.util.HexBytesConverter.toHex - -object TestEcBlocks { - def toNetworkBlock(ecBlock: EcBlock, miner: SeedKeyPair, prevRandao: String): NetworkL2Block = - NetworkL2Block.signed(TestEcBlocks.toPayload(ecBlock, prevRandao), miner.privateKey).explicitGet() - - def toPayload(ecBlock: EcBlock, prevRandao: String): JsObject = Json.obj( - "blockHash" -> ecBlock.hash, - "timestamp" -> toHex(ecBlock.timestamp), - "blockNumber" -> toHex(ecBlock.height), - "parentHash" -> ecBlock.parentHash, - "stateRoot" -> ecBlock.stateRoot, - "feeRecipient" -> ecBlock.minerRewardL2Address, - "prevRandao" -> prevRandao, - "baseFeePerGas" -> toHex(ecBlock.baseFeePerGas), - "gasLimit" -> toHex(ecBlock.gasLimit), - "gasUsed" -> toHex(ecBlock.gasUsed), - "withdrawals" -> ecBlock.withdrawals - ) -} diff --git a/src/test/scala/units/client/engine/model/TestPayloads.scala b/src/test/scala/units/client/engine/model/TestPayloads.scala new file mode 100644 index 00000000..b7718d5e --- /dev/null +++ b/src/test/scala/units/client/engine/model/TestPayloads.scala @@ -0,0 +1,26 @@ +package units.client.engine.model + +import com.wavesplatform.account.SeedKeyPair +import com.wavesplatform.common.utils.EitherExt2 +import play.api.libs.json.{JsObject, Json} +import units.NetworkBlock +import units.util.HexBytesConverter.toHex + +object TestPayloads { + def toNetworkBlock(payload: ExecutionPayload, miner: SeedKeyPair, prevRandao: String): NetworkBlock = + NetworkBlock.signed(TestPayloads.toPayloadJson(payload, prevRandao), miner.privateKey).explicitGet() + + def toPayloadJson(payload: ExecutionPayload, prevRandao: String): JsObject = Json.obj( + "blockHash" -> payload.hash, + "timestamp" -> toHex(payload.timestamp), + "blockNumber" -> toHex(payload.height), + "parentHash" -> payload.parentHash, + "stateRoot" -> payload.stateRoot, + "feeRecipient" -> payload.minerRewardAddress, + "prevRandao" -> prevRandao, + "baseFeePerGas" -> toHex(payload.baseFeePerGas), + "gasLimit" -> toHex(payload.gasLimit), + "gasUsed" -> toHex(payload.gasUsed), + "withdrawals" -> payload.withdrawals + ) +} diff --git a/src/test/scala/units/eth/EmptyL2BlockTestSuite.scala b/src/test/scala/units/eth/EmptyPayloadTestSuite.scala similarity index 91% rename from src/test/scala/units/eth/EmptyL2BlockTestSuite.scala rename to src/test/scala/units/eth/EmptyPayloadTestSuite.scala index c965af99..1dc34562 100644 --- a/src/test/scala/units/eth/EmptyL2BlockTestSuite.scala +++ b/src/test/scala/units/eth/EmptyPayloadTestSuite.scala @@ -5,15 +5,15 @@ import org.scalatest.freespec.AnyFreeSpec import org.web3j.abi.datatypes.generated.Uint256 import play.api.libs.json.Json import units.BlockHash -import units.client.engine.model.EcBlock -import units.eth.EmptyL2Block.Params +import units.client.engine.model.ExecutionPayload +import units.eth.EmptyPayload.Params -class EmptyL2BlockTestSuite extends AnyFreeSpec with BaseSuite { +class EmptyPayloadTestSuite extends AnyFreeSpec with BaseSuite { private val DefaultFeeRecipient = EthAddress.unsafeFrom("0x283c3c6ad2043af4d4e7d261809260fdab4a62d2") "mkExecutionPayload" in { // Got from eth_getBlockByHash - val parentEcBlockJson = + val parentBlockJson = """{ | "number": "0xf", | "hash": "0x296e3d6de01dbe3c109e13799d6efd2b68469075149ee4e1d8d644765e2cd620", @@ -50,8 +50,8 @@ class EmptyL2BlockTestSuite extends AnyFreeSpec with BaseSuite { | "parentBeaconBlockRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" |}""".stripMargin - val parentEcBlock = Json.parse(parentEcBlockJson).as[EcBlock] - EmptyL2Block.mkExecutionPayload(parentEcBlock) shouldBe Json.parse( + val parentPayload = Json.parse(parentBlockJson).as[ExecutionPayload] + EmptyPayload.mkExecutionPayloadJson(parentPayload) shouldBe Json.parse( """{ | "parentHash": "0x296e3d6de01dbe3c109e13799d6efd2b68469075149ee4e1d8d644765e2cd620", | "feeRecipient": "0x0000000000000000000000000000000000000000", @@ -77,7 +77,7 @@ class EmptyL2BlockTestSuite extends AnyFreeSpec with BaseSuite { "calculateHash" - { "hash 1" in { val expected = "0xfb0c23d4b394710700e8b4588905cc0a123c088044a6326266280798cb6a5a92" - val actual = EmptyL2Block.calculateHash( + val actual = EmptyPayload.calculateHash( Params( parentHash = BlockHash("0x49a8da0a609fbf1d68535a0766ecb0fe35d9cdac6ba459281daa944fa4c273b9"), parentStateRoot = "0xc0687a72deb7cec7caa72e99a985f3475cb780321d0572a9975fa7638c29c4e1", @@ -93,7 +93,7 @@ class EmptyL2BlockTestSuite extends AnyFreeSpec with BaseSuite { "hash 2" in { val expected = "0x86ecb0dc1b4f2a2f90f9cac69831ad9c3fc029e59d900b84b37fbee5e9963275" - val actual = EmptyL2Block.calculateHash( + val actual = EmptyPayload.calculateHash( Params( parentHash = BlockHash("0xfd07fb55cefbec6c0a74aa37058dd0462b71c6ef385ec5388cd2b2092618e895"), parentStateRoot = "0xd254bdd872c29ea362569bc5427843b416efca19a2c8e60af941b694cf48e40d", @@ -110,7 +110,7 @@ class EmptyL2BlockTestSuite extends AnyFreeSpec with BaseSuite { "calculateGasFee" - { "0x11ebac8b" in { - EmptyL2Block.calculateGasFee( + EmptyPayload.calculateGasFee( parentGasLimit = 0x1002000, parentBaseFeePerGas = new Uint256(0x147b0e55), 0 @@ -118,7 +118,7 @@ class EmptyL2BlockTestSuite extends AnyFreeSpec with BaseSuite { } "0xfae36fa" in { - EmptyL2Block.calculateGasFee( + EmptyPayload.calculateGasFee( parentGasLimit = 0x1002800, parentBaseFeePerGas = new Uint256(0x11ebac8b), 0 @@ -127,7 +127,7 @@ class EmptyL2BlockTestSuite extends AnyFreeSpec with BaseSuite { // TODO test with parentGasUsed != 0 "0x1ac012b7" in { - EmptyL2Block.calculateGasFee( + EmptyPayload.calculateGasFee( parentGasLimit = 0x1001400, parentBaseFeePerGas = new Uint256(0x1e925e88), 0 diff --git a/src/test/scala/units/network/TestBlocksObserver.scala b/src/test/scala/units/network/TestBlocksObserver.scala index fd60f705..499884a3 100644 --- a/src/test/scala/units/network/TestBlocksObserver.scala +++ b/src/test/scala/units/network/TestBlocksObserver.scala @@ -6,10 +6,10 @@ import io.netty.channel.Channel import monix.eval.Task import monix.execution.CancelableFuture import units.network.BlocksObserverImpl.BlockWithChannel -import units.{BlockHash, NetworkL2Block} +import units.{BlockHash, NetworkBlock} -class TestBlocksObserver(override val getBlockStream: ChannelObservable[NetworkL2Block]) extends BlocksObserver with ScorexLogging { - override def loadBlock(req: BlockHash): CancelableFuture[(Channel, NetworkL2Block)] = { +class TestBlocksObserver(override val getBlockStream: ChannelObservable[NetworkBlock]) extends BlocksObserver with ScorexLogging { + override def loadBlock(req: BlockHash): CancelableFuture[(Channel, NetworkBlock)] = { log.debug(s"loadBlock($req)") CancelableFuture.never } From 68829538e52c41a1504a22562fde0ed24c50e4ce Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Thu, 19 Sep 2024 18:32:12 +0300 Subject: [PATCH 02/14] Fix formatting --- src/main/scala/units/ELUpdater.scala | 56 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index 547dc6db..99f0f135 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -835,10 +835,10 @@ class ELUpdater( } def rollbackAndFollowChain( - target: CommonBlockData, - nodeChainInfo: ChainInfo, - mainChainInfo: ChainInfo, - returnToMainChainInfo: Option[ReturnToMainChainInfo] + target: CommonBlockData, + nodeChainInfo: ChainInfo, + mainChainInfo: ChainInfo, + returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Option[Working[FollowingChain]] = { rollbackTo(prevState, target, finalizedContractBlock) match { case Right(updatedState) => @@ -978,9 +978,9 @@ class ELUpdater( } private def preValidateBlock( - block: NetworkBlock, - parentPayload: ExecutionPayload, - epochInfo: Option[EpochInfo] + block: NetworkBlock, + parentPayload: ExecutionPayload, + epochInfo: Option[EpochInfo] ): JobResult[Unit] = { for { _ <- validateTimestamp(block, parentPayload) @@ -1018,12 +1018,12 @@ class ELUpdater( } private def validateAndApplyMissedBlock( - block: NetworkBlock, - ch: Channel, - prevState: Working[ChainStatus], - contractBlock: ContractBlock, - parentPayload: ExecutionPayload, - nodeChainInfo: ChainInfo + block: NetworkBlock, + ch: Channel, + prevState: Working[ChainStatus], + contractBlock: ContractBlock, + parentPayload: ExecutionPayload, + nodeChainInfo: ChainInfo ): Unit = { validateBlockFull(block, contractBlock, parentPayload, prevState) match { case Right(updatedState) => @@ -1035,12 +1035,12 @@ class ELUpdater( } private def validateAndApply( - block: NetworkBlock, - ch: Channel, - prevState: Working[ChainStatus], - parentPayload: ExecutionPayload, - nodeChainInfo: ChainInfo, - returnToMainChainInfo: Option[ReturnToMainChainInfo] + block: NetworkBlock, + ch: Channel, + prevState: Working[ChainStatus], + parentPayload: ExecutionPayload, + nodeChainInfo: ChainInfo, + returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Unit = { chainContractClient.getBlock(block.hash) match { case Some(contractBlock) if prevState.fullValidationStatus.lastValidatedBlock.hash == parentPayload.hash => @@ -1095,11 +1095,11 @@ class ELUpdater( } private def broadcastAndConfirmBlock( - block: NetworkBlock, - ch: Channel, - prevState: Working[ChainStatus], - nodeChainInfo: ChainInfo, - returnToMainChainInfo: Option[ReturnToMainChainInfo] + block: NetworkBlock, + ch: Channel, + prevState: Working[ChainStatus], + nodeChainInfo: ChainInfo, + returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Unit = { Try(allChannels.broadcast(block, Some(ch))).recover { err => logger.error(s"Failed to broadcast block ${block.hash}: ${err.getMessage}") @@ -1293,10 +1293,10 @@ class ELUpdater( } private def validateBlockFull( - block: NetworkBlock, - contractBlock: ContractBlock, - parentPayload: ExecutionPayload, - prevState: Working[ChainStatus] + block: NetworkBlock, + contractBlock: ContractBlock, + parentPayload: ExecutionPayload, + prevState: Working[ChainStatus] ): JobResult[Working[ChainStatus]] = { logger.debug(s"Trying to do full validation of block ${block.hash}") for { From ad4c7967c66bfa606111dc85570b1363fc8713d4 Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Fri, 20 Sep 2024 10:50:46 +0300 Subject: [PATCH 03/14] Fix formatting --- .../units/client/contract/ContractBlock.scala | 16 +++--- .../engine/model/ExecutionPayload.scala | 22 ++++---- src/test/scala/units/ExtensionDomain.scala | 12 ++--- .../HasConsensusLayerDappTxHelpers.scala | 50 +++++++++---------- 4 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/main/scala/units/client/contract/ContractBlock.scala b/src/main/scala/units/client/contract/ContractBlock.scala index a49111fe..8d1b5140 100644 --- a/src/main/scala/units/client/contract/ContractBlock.scala +++ b/src/main/scala/units/client/contract/ContractBlock.scala @@ -7,14 +7,14 @@ import units.eth.EthAddress import units.util.HexBytesConverter.toHex case class ContractBlock( - hash: BlockHash, - parentHash: BlockHash, - epoch: Int, - height: Long, - minerRewardAddress: EthAddress, - chainId: Long, - e2cTransfersRootHash: Digest, - lastC2ETransferIndex: Long + hash: BlockHash, + parentHash: BlockHash, + epoch: Int, + height: Long, + minerRewardAddress: EthAddress, + chainId: Long, + e2cTransfersRootHash: Digest, + lastC2ETransferIndex: Long ) extends CommonBlockData { override def toString: String = s"ContractBlock($hash, p=$parentHash, e=$epoch, h=$height, m=$minerRewardAddress, c=$chainId, " + diff --git a/src/main/scala/units/client/engine/model/ExecutionPayload.scala b/src/main/scala/units/client/engine/model/ExecutionPayload.scala index 61a46d0a..27e517fe 100644 --- a/src/main/scala/units/client/engine/model/ExecutionPayload.scala +++ b/src/main/scala/units/client/engine/model/ExecutionPayload.scala @@ -14,17 +14,17 @@ import units.util.HexBytesConverter.* * https://besu.hyperledger.org/stable/public-networks/reference/engine-api/objects#execution-payload-object tells about milliseconds */ case class ExecutionPayload( - hash: BlockHash, - parentHash: BlockHash, - stateRoot: String, - height: Long, - timestamp: Long, - minerRewardAddress: EthAddress, - baseFeePerGas: Uint256, - gasLimit: Long, - gasUsed: Long, - prevRandao: String, - withdrawals: Vector[Withdrawal] + hash: BlockHash, + parentHash: BlockHash, + stateRoot: String, + height: Long, + timestamp: Long, + minerRewardAddress: EthAddress, + baseFeePerGas: Uint256, + gasLimit: Long, + gasUsed: Long, + prevRandao: String, + withdrawals: Vector[Withdrawal] ) extends CommonBlockData { override def toString: String = s"ExecutionPayload($hash, p=$parentHash, h=$height, t=$timestamp, m=$minerRewardAddress, w={${withdrawals.mkString(", ")}})" diff --git a/src/test/scala/units/ExtensionDomain.scala b/src/test/scala/units/ExtensionDomain.scala index 2321fba7..8729227d 100644 --- a/src/test/scala/units/ExtensionDomain.scala +++ b/src/test/scala/units/ExtensionDomain.scala @@ -90,7 +90,7 @@ class ExtensionDomain( val eluScheduler: TestScheduler = TestScheduler(ExecutionModel.AlwaysAsyncExecution) val elBlockStream: PublishSubject[(Channel, NetworkBlock)] = PublishSubject[(Channel, NetworkBlock)]() - val blockObserver: TestBlocksObserver = new TestBlocksObserver(elBlockStream) + val blockObserver: TestBlocksObserver = new TestBlocksObserver(elBlockStream) val neighbourChannel = new EmbeddedChannel() val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) @@ -232,11 +232,11 @@ class ExtensionDomain( // Useful for debugging purposes def evaluateExtendAltChain( - minerAccount: KeyPair, - chainId: Long, - blockData: CommonBlockData, - epoch: Long, - e2CTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex + minerAccount: KeyPair, + chainId: Long, + blockData: CommonBlockData, + epoch: Long, + e2CTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex ): Either[String, JsObject] = { val r = evaluate( chainContractAddress, diff --git a/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala b/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala index 7cb92d2b..5a225953 100644 --- a/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala +++ b/src/test/scala/units/client/contract/HasConsensusLayerDappTxHelpers.scala @@ -50,11 +50,11 @@ trait HasConsensusLayerDappTxHelpers { ) def extendMainChain( - minerAccount: KeyPair, - blockData: CommonBlockData, - e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, - lastC2ETransferIndex: Long = -1, - vrf: ByteStr = currentHitSource + minerAccount: KeyPair, + blockData: CommonBlockData, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + lastC2ETransferIndex: Long = -1, + vrf: ByteStr = currentHitSource ): InvokeScriptTransaction = TxHelpers.invoke( invoker = minerAccount, @@ -71,10 +71,10 @@ trait HasConsensusLayerDappTxHelpers { ) def appendBlock( - minerAccount: KeyPair, - blockData: CommonBlockData, - e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, - lastC2ETransferIndex: Long = -1 + minerAccount: KeyPair, + blockData: CommonBlockData, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + lastC2ETransferIndex: Long = -1 ): InvokeScriptTransaction = TxHelpers.invoke( invoker = minerAccount, @@ -90,11 +90,11 @@ trait HasConsensusLayerDappTxHelpers { ) def startAltChain( - minerAccount: KeyPair, - blockData: CommonBlockData, - e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, - lastC2ETransferIndex: Long = -1, - vrf: ByteStr = currentHitSource + minerAccount: KeyPair, + blockData: CommonBlockData, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + lastC2ETransferIndex: Long = -1, + vrf: ByteStr = currentHitSource ): InvokeScriptTransaction = TxHelpers.invoke( invoker = minerAccount, @@ -111,12 +111,12 @@ trait HasConsensusLayerDappTxHelpers { ) def extendAltChain( - minerAccount: KeyPair, - blockData: CommonBlockData, - chainId: Long, - e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, - lastC2ETransferIndex: Long = -1, - vrf: ByteStr = currentHitSource + minerAccount: KeyPair, + blockData: CommonBlockData, + chainId: Long, + e2cTransfersRootHashHex: String = EmptyE2CTransfersRootHashHex, + lastC2ETransferIndex: Long = -1, + vrf: ByteStr = currentHitSource ): InvokeScriptTransaction = TxHelpers.invoke( invoker = minerAccount, @@ -165,11 +165,11 @@ trait HasConsensusLayerDappTxHelpers { ) def withdraw( - sender: KeyPair, - blockData: CommonBlockData, - merkleProof: Seq[Digest], - transferIndexInBlock: Int, - amount: Long + sender: KeyPair, + blockData: CommonBlockData, + merkleProof: Seq[Digest], + transferIndexInBlock: Int, + amount: Long ): InvokeScriptTransaction = TxHelpers.invoke( invoker = sender, From 216e1dac01a43007406295a639028741de170bfb Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Mon, 23 Sep 2024 15:45:41 +0300 Subject: [PATCH 04/14] Rename block classes (WIP) --- src/main/scala/units/BlockHash.scala | 8 +- src/main/scala/units/ClientError.scala | 1 + src/main/scala/units/ConsensusClient.scala | 22 ++--- src/main/scala/units/ELUpdater.scala | 90 ++++++++--------- .../scala/units/ExecutionPayloadInfo.scala | 6 ++ src/main/scala/units/NetworkBlock.scala | 5 +- .../units/client/engine/EngineApiClient.scala | 14 +-- .../client/engine/HttpEngineApiClient.scala | 18 ++-- .../client/engine/LoggedEngineApiClient.scala | 32 +++---- .../units/network/BasicMessagesRepo.scala | 45 +++------ .../scala/units/network/BlocksObserver.scala | 15 --- .../scala/units/network/HistoryReplier.scala | 13 +-- .../units/network/LegacyFrameCodec.scala | 5 +- src/main/scala/units/network/Message.scala | 2 +- .../scala/units/network/MessageCodec.scala | 2 +- .../scala/units/network/MessageObserver.scala | 9 +- .../scala/units/network/PayloadMessage.scala | 89 +++++++++++++++++ .../scala/units/network/PayloadObserver.scala | 15 +++ ...erImpl.scala => PayloadObserverImpl.scala} | 96 +++++++++---------- .../scala/units/network/TrafficLogger.scala | 13 ++- src/test/scala/units/ExtensionDomain.scala | 4 +- .../scala/units/client/TestEcClients.scala | 14 +-- ...server.scala => TestPayloadObserver.scala} | 8 +- 23 files changed, 302 insertions(+), 224 deletions(-) create mode 100644 src/main/scala/units/ExecutionPayloadInfo.scala delete mode 100644 src/main/scala/units/network/BlocksObserver.scala create mode 100644 src/main/scala/units/network/PayloadMessage.scala create mode 100644 src/main/scala/units/network/PayloadObserver.scala rename src/main/scala/units/network/{BlocksObserverImpl.scala => PayloadObserverImpl.scala} (52%) rename src/test/scala/units/network/{TestBlocksObserver.scala => TestPayloadObserver.scala} (52%) diff --git a/src/main/scala/units/BlockHash.scala b/src/main/scala/units/BlockHash.scala index 2a8c92fe..73210821 100644 --- a/src/main/scala/units/BlockHash.scala +++ b/src/main/scala/units/BlockHash.scala @@ -6,16 +6,20 @@ import play.api.libs.json.{Format, Reads, Writes} import supertagged.TaggedType object BlockHash extends TaggedType[String] { + + val BytesSize: Int = 32 + val HexSize: Int = 66 + def apply(hex: String): BlockHash = { require(hex.startsWith("0x"), "Expected hash to start with 0x") - require(hex.length == 66, s"Expected hash size of 66, got: ${hex.length}. Hex: $hex") // "0x" + 32 bytes + require(hex.length == HexSize, s"Expected hash size of $HexSize, got: ${hex.length}. Hex: $hex") // "0x" + 32 bytes BlockHash @@ hex } def apply(xs: ByteStr): BlockHash = BlockHash @@ HexBytesConverter.toHex(xs) def apply(xs: Array[Byte]): BlockHash = { - require(xs.length == 32, "Block hash size must be 32 bytes") + require(xs.length == BytesSize, s"Block hash size must be $BytesSize bytes") BlockHash @@ HexBytesConverter.toHex(xs) } diff --git a/src/main/scala/units/ClientError.scala b/src/main/scala/units/ClientError.scala index 313c0477..706db9f6 100644 --- a/src/main/scala/units/ClientError.scala +++ b/src/main/scala/units/ClientError.scala @@ -1,3 +1,4 @@ package units +// TODO: maybe remove? case class ClientError(message: String) diff --git a/src/main/scala/units/ConsensusClient.scala b/src/main/scala/units/ConsensusClient.scala index 70be3ad9..b210efce 100644 --- a/src/main/scala/units/ConsensusClient.scala +++ b/src/main/scala/units/ConsensusClient.scala @@ -27,7 +27,7 @@ class ConsensusClient( config: ClientConfig, context: ExtensionContext, engineApiClient: EngineApiClient, - blockObserver: BlocksObserver, + payloadObserver: PayloadObserver, allChannels: DefaultChannelGroup, globalScheduler: Scheduler, eluScheduler: Scheduler, @@ -40,7 +40,7 @@ class ConsensusClient( deps.config, context, deps.engineApiClient, - deps.blockObserver, + deps.payloadObserver, deps.allChannels, deps.globalScheduler, deps.eluScheduler, @@ -58,19 +58,19 @@ class ConsensusClient( config, context.time, context.wallet, - blockObserver.loadBlock, + payloadObserver.loadPayload, context.broadcastTransaction, eluScheduler, globalScheduler ) - private val blocksStreamCancelable: CancelableFuture[Unit] = - blockObserver.getBlockStream.foreach { case (ch, block) => elu.executionBlockReceived(block, ch) }(globalScheduler) + private val payloadsStreamCancelable: CancelableFuture[Unit] = + payloadObserver.getPayloadStream.foreach { case (ch, ep) => elu.executionPayloadReceived(ep, ch) }(globalScheduler) override def start(): Unit = {} def shutdown(): Future[Unit] = Future { - blocksStreamCancelable.cancel() + payloadsStreamCancelable.cancel() ownedResources.close() }(globalScheduler) @@ -101,9 +101,9 @@ class ConsensusClientDependencies(context: ExtensionContext) extends AutoCloseab val config: ClientConfig = context.settings.config.as[ClientConfig]("waves.l2") - private val blockObserverScheduler = Schedulers.singleThread("block-observer-l2", reporter = { e => log.warn("Error in BlockObserver", e) }) - val globalScheduler: Scheduler = monix.execution.Scheduler.global - val eluScheduler: SchedulerService = Scheduler.singleThread("el-updater", reporter = { e => log.warn("Exception in ELUpdater", e) }) + private val payloadObserverScheduler = Schedulers.singleThread("block-observer-l2", reporter = { e => log.warn("Error in BlockObserver", e) }) + val globalScheduler: Scheduler = monix.execution.Scheduler.global + val eluScheduler: SchedulerService = Scheduler.singleThread("el-updater", reporter = { e => log.warn("Exception in ELUpdater", e) }) private val httpClientBackend = HttpClientSyncBackend() private val maybeAuthenticatedBackend = config.jwtSecretFile match { @@ -130,7 +130,7 @@ class ConsensusClientDependencies(context: ExtensionContext) extends AutoCloseab new ConcurrentHashMap[Channel, PeerInfo] ) - val blockObserver = new BlocksObserverImpl(allChannels, messageObserver.blocks, config.blockSyncRequestTimeout)(blockObserverScheduler) + val payloadObserver = new PayloadObserverImpl(allChannels, messageObserver.payloads, config.blockSyncRequestTimeout)(payloadObserverScheduler) override def close(): Unit = { log.info("Closing HTTP/Engine API") @@ -144,7 +144,7 @@ class ConsensusClientDependencies(context: ExtensionContext) extends AutoCloseab messageObserver.shutdown() log.info("Closing schedulers") - blockObserverScheduler.shutdown() + payloadObserverScheduler.shutdown() eluScheduler.shutdown() } } diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index 99f0f135..0fde81a3 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -34,7 +34,7 @@ import units.client.engine.EngineApiClient.PayloadId import units.client.engine.model.* import units.client.engine.model.Withdrawal.WithdrawalIndex import units.eth.{EmptyPayload, EthAddress, EthereumConstants} -import units.network.BlocksObserverImpl.BlockWithChannel +import units.network.PayloadObserverImpl.PayloadWithChannel import units.util.HexBytesConverter import units.util.HexBytesConverter.toHexNoPrefix @@ -50,7 +50,7 @@ class ELUpdater( config: ClientConfig, time: Time, wallet: Wallet, - requestBlockFromPeers: BlockHash => CancelableFuture[BlockWithChannel], + requestPayloadFromPeers: BlockHash => CancelableFuture[PayloadWithChannel], broadcastTx: Transaction => TracedResult[ValidationError, Boolean], scheduler: Scheduler, globalScheduler: Scheduler @@ -66,7 +66,7 @@ class ELUpdater( def consensusLayerChanged(): Unit = handleNextUpdate := scheduler.scheduleOnce(ClChangedProcessingDelay)(handleConsensusLayerChanged()) - def executionBlockReceived(block: NetworkBlock, ch: Channel): Unit = scheduler.execute { () => + def executionPayloadReceived(block: NetworkBlock, ch: Channel): Unit = scheduler.execute { () => logger.debug(s"New block ${block.hash}->${block.parentHash} (timestamp=${block.timestamp}, height=${block.height}) appeared") val now = time.correctedTime() / 1000 @@ -226,7 +226,7 @@ class ELUpdater( ) case _ => (for { - payloadJson <- engineApiClient.getPayloadJson(payloadId) + payloadJson <- engineApiClient.getPayload(payloadId) _ = logger.info(s"Forged payload $payloadId") latestValidHashOpt <- engineApiClient.applyNewPayload(payloadJson) latestValidHash <- Either.fromOption(latestValidHashOpt, ClientError("Latest valid hash not defined")) @@ -261,7 +261,7 @@ class ELUpdater( fixedFinalizedBlock = if (finalizedBlock.height > rollbackBlock.parentPayload.height) rollbackBlock.parentPayload else finalizedBlock _ <- confirmBlock(rollbackBlock.hash, fixedFinalizedBlock.hash) _ <- confirmBlock(target, fixedFinalizedBlock) - lastPayload <- engineApiClient.getLastPayload + lastPayload <- engineApiClient.getLatestBlock _ <- Either.cond( targetHash == lastPayload.hash, (), @@ -444,14 +444,14 @@ class ELUpdater( else { val finalizedBlock = chainContractClient.getFinalizedBlock logger.debug(s"Finalized block is ${finalizedBlock.hash}") - engineApiClient.getPayloadByHash(finalizedBlock.hash) match { + engineApiClient.getBlockByHash(finalizedBlock.hash) match { case Left(error) => logger.error(s"Could not load finalized block payload", error) case Right(Some(finalizedBlockPayload)) => logger.trace(s"Finalized block ${finalizedBlock.hash} is at height ${finalizedBlockPayload.height}") (for { newEpochInfo <- calculateEpochInfo mainChainInfo <- chainContractClient.getMainChainInfo.toRight("Can't get main chain info") - lastPayload <- engineApiClient.getLastPayload.leftMap(_.message) + lastPayload <- engineApiClient.getLatestBlock.leftMap(_.message) } yield { logger.trace(s"Following main chain ${mainChainInfo.id}") val fullValidationStatus = FullValidationStatus( @@ -475,7 +475,7 @@ class ELUpdater( ) case Right(None) => logger.trace(s"Finalized block ${finalizedBlock.hash} payload is not in EC, requesting block from peers") - setState("15", WaitingForSyncHead(finalizedBlock, requestAndProcessBlock(finalizedBlock.hash))) + setState("15", WaitingForSyncHead(finalizedBlock, requestAndProcessPayload(finalizedBlock.hash))) } } } @@ -531,7 +531,7 @@ class ELUpdater( findAltChain(fc.nodeChainInfo.id, lastValidBlock.hash) match { case Some(altChainInfo) => - engineApiClient.getPayloadByHash(finalizedBlock.hash) match { + engineApiClient.getBlockByHash(finalizedBlock.hash) match { case Right(Some(finalizedBlockPayload)) => followChainAndStartMining( updatedState.copy(chainStatus = FollowingChain(altChainInfo, None), returnToMainChainInfo = updatedReturnToMainChainInfo), @@ -639,7 +639,7 @@ class ELUpdater( val finalizedBlock = chainContractClient.getFinalizedBlock val options = chainContractClient.getOptions logger.debug(s"Finalized block is ${finalizedBlock.hash}") - engineApiClient.getPayloadByHash(finalizedBlock.hash) match { + engineApiClient.getBlockByHash(finalizedBlock.hash) match { case Left(error) => logger.error(s"Could not load finalized block payload", error) case Right(Some(finalizedBlockPayload)) => logger.trace(s"Finalized block ${finalizedBlock.hash} is at height ${finalizedBlockPayload.height}") @@ -711,7 +711,7 @@ class ELUpdater( requestMainChainBlock() case Right(None) => logger.trace(s"Finalized block ${finalizedBlock.hash} payload is not in EC, requesting block from peers") - setState("19", WaitingForSyncHead(finalizedBlock, requestAndProcessBlock(finalizedBlock.hash))) + setState("19", WaitingForSyncHead(finalizedBlock, requestAndProcessPayload(finalizedBlock.hash))) } } @@ -739,17 +739,17 @@ class ELUpdater( maybeRequestNextBlock(newState, finalizedBlock) } - private def requestBlock(contractBlock: ContractBlock): BlockRequestResult = { - logger.debug(s"Requesting block ${contractBlock.hash}") - engineApiClient.getPayloadByHash(contractBlock.hash) match { - case Right(Some(payload)) => BlockRequestResult.PayloadExists(payload) + private def requestPayload(contractBlock: ContractBlock): PayloadRequestResult = { + logger.debug(s"Requesting payload for block ${contractBlock.hash}") + engineApiClient.getBlockByHash(contractBlock.hash) match { + case Right(Some(payload)) => PayloadRequestResult.Exists(payload) case Right(None) => - requestAndProcessBlock(contractBlock.hash) - BlockRequestResult.Requested(contractBlock) + requestAndProcessPayload(contractBlock.hash) + PayloadRequestResult.Requested(contractBlock) case Left(err) => - logger.warn(s"Failed to get block ${contractBlock.hash} by hash: ${err.message}") - requestAndProcessBlock(contractBlock.hash) - BlockRequestResult.Requested(contractBlock) + logger.warn(s"Failed to get block ${contractBlock.hash} payload by hash: ${err.message}") + requestAndProcessPayload(contractBlock.hash) + PayloadRequestResult.Requested(contractBlock) } } @@ -758,8 +758,8 @@ class ELUpdater( case w: Working[ChainStatus] => w.returnToMainChainInfo.foreach { returnToMainChainInfo => if (w.mainChainInfo.id == returnToMainChainInfo.chainId) { - requestBlock(returnToMainChainInfo.missedBlock) match { - case BlockRequestResult.PayloadExists(payload) => + requestPayload(returnToMainChainInfo.missedBlock) match { + case PayloadRequestResult.Exists(payload) => logger.debug(s"Block ${returnToMainChainInfo.missedBlock.hash} payload exists at execution chain, trying to validate") validateAppliedBlock(returnToMainChainInfo.missedBlock, payload, w) match { case Right(updatedState) => @@ -773,7 +773,7 @@ class ELUpdater( case Left(err) => logger.debug(s"Missed block ${payload.hash} of main chain ${returnToMainChainInfo.chainId} validation error: ${err.message}") } - case BlockRequestResult.Requested(_) => + case PayloadRequestResult.Requested(_) => } } } @@ -781,10 +781,10 @@ class ELUpdater( } } - private def requestAndProcessBlock(hash: BlockHash): CancelableFuture[(Channel, NetworkBlock)] = { - requestBlockFromPeers(hash).andThen { - case Success((ch, block)) => executionBlockReceived(block, ch) - case Failure(exception) => logger.error(s"Error loading block $hash", exception) + private def requestAndProcessPayload(hash: BlockHash): CancelableFuture[(Channel, NetworkBlock)] = { + requestPayloadFromPeers(hash).andThen { + case Success((ch, payload)) => executionPayloadReceived(payload, ch) + case Failure(exception) => logger.error(s"Error loading block $hash", exception) }(globalScheduler) } @@ -798,7 +798,7 @@ class ELUpdater( ): Option[Working[FollowingChain]] = { @tailrec def findLastPayload(curBlock: ContractBlock): ExecutionPayload = { - engineApiClient.getPayloadByHash(curBlock.hash) match { + engineApiClient.getBlockByHash(curBlock.hash) match { case Right(Some(payload)) => payload case Right(_) => chainContractClient.getBlock(curBlock.parentHash) match { @@ -887,7 +887,7 @@ class ELUpdater( private def waitForSyncCompletion(target: ContractBlock): Unit = scheduler.scheduleOnce(5.seconds)(state match { case SyncingToFinalizedBlock(finalizedBlockHash) if finalizedBlockHash == target.hash => logger.debug(s"Checking if EL has synced to ${target.hash} on height ${target.height}") - engineApiClient.getLastPayload match { + engineApiClient.getLatestBlock match { case Left(error) => logger.error(s"Sync to ${target.hash} was not completed, error=${error.message}") setState("23", Starting) @@ -1129,8 +1129,8 @@ class ELUpdater( logger.error(s"Could not find child of ${prevState.lastPayload.hash} on contract: $error") prevState case Right(contractBlock) => - requestBlock(contractBlock) match { - case BlockRequestResult.PayloadExists(payload) => + requestPayload(contractBlock) match { + case PayloadRequestResult.Exists(payload) => logger.debug(s"Block ${contractBlock.hash} payload exists at EC chain, trying to confirm") confirmBlock(payload, finalizedBlock) match { case Right(_) => @@ -1144,7 +1144,7 @@ class ELUpdater( logger.error(s"Failed to confirm next block ${payload.hash}: ${err.message}") prevState } - case BlockRequestResult.Requested(contractBlock) => + case PayloadRequestResult.Requested(contractBlock) => val newState = prevState.copy(chainStatus = prevState.chainStatus.copy(nextExpectedBlock = Some(contractBlock))) setState("8", newState) newState @@ -1158,11 +1158,11 @@ class ELUpdater( private def mkRollbackBlock(rollbackTargetBlockId: BlockHash): JobResult[RollbackBlock] = for { targetBlockDataOpt <- chainContractClient.getBlock(rollbackTargetBlockId) match { - case None => engineApiClient.getPayloadByHash(rollbackTargetBlockId) + case None => engineApiClient.getBlockByHash(rollbackTargetBlockId) case x => Right(x) } targetBlockData <- Either.fromOption(targetBlockDataOpt, ClientError(s"Can't find block $rollbackTargetBlockId neither on a contract, nor in EC")) - parentPayloadOpt <- engineApiClient.getPayloadByHash(targetBlockData.parentHash) + parentPayloadOpt <- engineApiClient.getBlockByHash(targetBlockData.parentHash) parentPayload <- Either.fromOption(parentPayloadOpt, ClientError(s"Can't find block $rollbackTargetBlockId parent payload in execution client")) rollbackBlockOpt <- engineApiClient.applyNewPayload(EmptyPayload.mkExecutionPayloadJson(parentPayload)) rollbackBlock <- Either.fromOption(rollbackBlockOpt, ClientError("Rollback block hash is not defined as latest valid hash")) @@ -1175,7 +1175,7 @@ class ELUpdater( } private def getLastWithdrawalIndex(hash: BlockHash): JobResult[WithdrawalIndex] = - engineApiClient.getPayloadByHash(hash).flatMap { + engineApiClient.getBlockByHash(hash).flatMap { case None => Left(ClientError(s"Can't find block $hash payload on EC during withdrawal search")) case Some(payload) => payload.withdrawals.lastOption match { @@ -1390,7 +1390,7 @@ class ELUpdater( if (curBlock.height > curState.lastPayload.height) { loop(parentBlock, acc) } else { - engineApiClient.getPayloadByHash(curBlock.hash) match { + engineApiClient.getBlockByHash(curBlock.hash) match { case Right(Some(payload)) => loop(parentBlock, BlockForValidation(curBlock, payload) :: acc) case Right(None) => @@ -1471,11 +1471,11 @@ class ELUpdater( private def confirmBlock(blockData: CommonBlockData, finalizedBlockData: CommonBlockData): JobResult[PayloadStatus] = { val finalizedBlockHash = if (finalizedBlockData.height > blockData.height) blockData.hash else finalizedBlockData.hash - engineApiClient.forkChoiceUpdate(blockData.hash, finalizedBlockHash) + engineApiClient.forkChoiceUpdated(blockData.hash, finalizedBlockHash) } private def confirmBlock(hash: BlockHash, finalizedBlockHash: BlockHash): JobResult[PayloadStatus] = - engineApiClient.forkChoiceUpdate(hash, finalizedBlockHash) + engineApiClient.forkChoiceUpdated(hash, finalizedBlockHash) private def confirmBlockAndStartMining( lastPayload: ExecutionPayload, @@ -1487,7 +1487,7 @@ class ELUpdater( ): JobResult[PayloadId] = { val finalizedBlockHash = if (finalizedBlock.height > lastPayload.height) lastPayload.hash else finalizedBlock.hash engineApiClient - .forkChoiceUpdateWithPayloadId( + .forkChoiceUpdatedWithPayloadId( lastPayload.hash, finalizedBlockHash, unixEpochSeconds, @@ -1561,8 +1561,8 @@ object ELUpdater { } } - case class WaitingForSyncHead(target: ContractBlock, task: CancelableFuture[BlockWithChannel]) extends State - case class SyncingToFinalizedBlock(target: BlockHash) extends State + case class WaitingForSyncHead(target: ContractBlock, task: CancelableFuture[PayloadWithChannel]) extends State + case class SyncingToFinalizedBlock(target: BlockHash) extends State } private case class RollbackBlock(hash: BlockHash, parentPayload: ExecutionPayload) @@ -1574,10 +1574,10 @@ object ELUpdater { */ case class ReturnToMainChainInfo(missedBlock: ContractBlock, missedBlockParentPayload: ExecutionPayload, chainId: Long) - sealed trait BlockRequestResult - private object BlockRequestResult { - case class PayloadExists(payload: ExecutionPayload) extends BlockRequestResult - case class Requested(contractBlock: ContractBlock) extends BlockRequestResult + sealed trait PayloadRequestResult + private object PayloadRequestResult { + case class Exists(payload: ExecutionPayload) extends PayloadRequestResult + case class Requested(contractBlock: ContractBlock) extends PayloadRequestResult } private case class MiningData(payloadId: PayloadId, nextBlockUnixTs: Long, lastC2ETransferIndex: Long, lastElWithdrawalIndex: WithdrawalIndex) diff --git a/src/main/scala/units/ExecutionPayloadInfo.scala b/src/main/scala/units/ExecutionPayloadInfo.scala new file mode 100644 index 00000000..202a34e5 --- /dev/null +++ b/src/main/scala/units/ExecutionPayloadInfo.scala @@ -0,0 +1,6 @@ +package units + +import play.api.libs.json.JsObject +import units.client.engine.model.ExecutionPayload + +case class ExecutionPayloadInfo(payload: ExecutionPayload, payloadJson: JsObject) diff --git a/src/main/scala/units/NetworkBlock.scala b/src/main/scala/units/NetworkBlock.scala index 76b47fc5..b4f1789e 100644 --- a/src/main/scala/units/NetworkBlock.scala +++ b/src/main/scala/units/NetworkBlock.scala @@ -4,7 +4,7 @@ import cats.syntax.either.* import com.wavesplatform.account.PrivateKey import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto -import com.wavesplatform.crypto.{DigestLength, SignatureLength} +import com.wavesplatform.crypto.SignatureLength import org.web3j.abi.datatypes.generated.Uint256 import play.api.libs.json.{JsObject, Json} import units.client.CommonBlockData @@ -93,7 +93,4 @@ object NetworkBlock { } def apply(payload: JsObject): Either[ClientError, NetworkBlock] = apply(payload, Json.toBytes(payload), None) - - def validateReferenceLength(length: Int): Boolean = - length == DigestLength } diff --git a/src/main/scala/units/client/engine/EngineApiClient.scala b/src/main/scala/units/client/engine/EngineApiClient.scala index 3ad5b759..04b97a96 100644 --- a/src/main/scala/units/client/engine/EngineApiClient.scala +++ b/src/main/scala/units/client/engine/EngineApiClient.scala @@ -7,9 +7,9 @@ import units.eth.EthAddress import units.{BlockHash, JobResult} trait EngineApiClient { - def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[PayloadStatus] + def forkChoiceUpdated(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[PayloadStatus] - def forkChoiceUpdateWithPayloadId( + def forkChoiceUpdatedWithPayloadId( lastBlockHash: BlockHash, finalizedBlockHash: BlockHash, unixEpochSeconds: Long, @@ -18,17 +18,17 @@ trait EngineApiClient { withdrawals: Vector[Withdrawal] = Vector.empty ): JobResult[PayloadId] - def getPayloadJson(payloadId: PayloadId): JobResult[JsObject] + def getPayload(payloadId: PayloadId): JobResult[JsObject] def applyNewPayload(payloadJson: JsObject): JobResult[Option[BlockHash]] - def getPayloadBodyJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] + def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] - def getPayloadByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] + def getBlockByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] - def getPayloadByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] + def getBlockByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] - def getLastPayload: JobResult[ExecutionPayload] + def getLatestBlock: JobResult[ExecutionPayload] def getBlockJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] diff --git a/src/main/scala/units/client/engine/HttpEngineApiClient.scala b/src/main/scala/units/client/engine/HttpEngineApiClient.scala index aeab4cb8..03937d99 100644 --- a/src/main/scala/units/client/engine/HttpEngineApiClient.scala +++ b/src/main/scala/units/client/engine/HttpEngineApiClient.scala @@ -20,7 +20,7 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide val apiUrl: Uri = uri"${config.executionClientAddress}" - def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[PayloadStatus] = { + def forkChoiceUpdated(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[PayloadStatus] = { sendEngineRequest[ForkChoiceUpdatedRequest, ForkChoiceUpdatedResponse]( ForkChoiceUpdatedRequest(blockHash, finalizedBlockHash, None), BlockExecutionTimeout @@ -33,7 +33,7 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide } } - def forkChoiceUpdateWithPayloadId( + def forkChoiceUpdatedWithPayloadId( lastBlockHash: BlockHash, finalizedBlockHash: BlockHash, unixEpochSeconds: Long, @@ -60,7 +60,7 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide } } - def getPayloadJson(payloadId: PayloadId): JobResult[JsObject] = { + def getPayload(payloadId: PayloadId): JobResult[JsObject] = { sendEngineRequest[GetPayloadRequest, GetPayloadResponse](GetPayloadRequest(payloadId), NonBlockExecutionTimeout).map(_.executionPayload) } @@ -74,12 +74,12 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide } } - def getPayloadBodyJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = { + def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] = { sendEngineRequest[GetPayloadBodyByHash, JsArray](GetPayloadBodyByHash(hash), NonBlockExecutionTimeout) .map(_.value.headOption.flatMap(_.asOpt[JsObject])) } - def getPayloadByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] = { + def getBlockByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] = { for { json <- sendRequest[GetBlockByNumberRequest, JsObject](GetBlockByNumberRequest(number.str)) .leftMap(err => ClientError(s"Error getting payload by number $number: $err")) @@ -87,13 +87,13 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide } yield blockMeta } - def getPayloadByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] = { + def getBlockByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] = { sendRequest[GetBlockByHashRequest, ExecutionPayload](GetBlockByHashRequest(hash)) .leftMap(err => ClientError(s"Error getting payload by hash $hash: $err")) } - def getLastPayload: JobResult[ExecutionPayload] = for { - lastPayloadOpt <- getPayloadByNumber(BlockNumber.Latest) + def getLatestBlock: JobResult[ExecutionPayload] = for { + lastPayloadOpt <- getBlockByNumber(BlockNumber.Latest) lastPayload <- Either.fromOption(lastPayloadOpt, ClientError("Impossible: EC doesn't have payloads")) } yield lastPayload @@ -106,7 +106,7 @@ class HttpEngineApiClient(val config: ClientConfig, val backend: SttpBackend[Ide for { blockJsonOpt <- getBlockJsonByHash(hash) blockJson <- Either.fromOption(blockJsonOpt, ClientError("block not found")) - payloadBodyJsonOpt <- getPayloadBodyJsonByHash(hash) + payloadBodyJsonOpt <- getPayloadBodyByHash(hash) payloadBodyJson <- Either.fromOption(payloadBodyJsonOpt, ClientError("payload body not found")) } yield PayloadJsonData(blockJson, payloadBodyJson) } diff --git a/src/main/scala/units/client/engine/LoggedEngineApiClient.scala b/src/main/scala/units/client/engine/LoggedEngineApiClient.scala index c01ee0fb..e16dc796 100644 --- a/src/main/scala/units/client/engine/LoggedEngineApiClient.scala +++ b/src/main/scala/units/client/engine/LoggedEngineApiClient.scala @@ -15,10 +15,10 @@ import scala.util.chaining.scalaUtilChainingOps class LoggedEngineApiClient(underlying: EngineApiClient) extends EngineApiClient { protected val log: LoggerFacade = LoggerFacade(LoggerFactory.getLogger(underlying.getClass)) - override def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[PayloadStatus] = - wrap(s"forkChoiceUpdate($blockHash, f=$finalizedBlockHash)", underlying.forkChoiceUpdate(blockHash, finalizedBlockHash)) + override def forkChoiceUpdated(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[PayloadStatus] = + wrap(s"forkChoiceUpdated($blockHash, f=$finalizedBlockHash)", underlying.forkChoiceUpdated(blockHash, finalizedBlockHash)) - override def forkChoiceUpdateWithPayloadId( + override def forkChoiceUpdatedWithPayloadId( lastBlockHash: BlockHash, finalizedBlockHash: BlockHash, unixEpochSeconds: Long, @@ -26,31 +26,31 @@ class LoggedEngineApiClient(underlying: EngineApiClient) extends EngineApiClient prevRandao: String, withdrawals: Vector[Withdrawal] ): JobResult[PayloadId] = wrap( - s"forkChoiceUpdateWithPayloadId(l=$lastBlockHash, f=$finalizedBlockHash, ts=$unixEpochSeconds, m=$suggestedFeeRecipient, " + + s"forkChoiceUpdatedWithPayloadId(l=$lastBlockHash, f=$finalizedBlockHash, ts=$unixEpochSeconds, m=$suggestedFeeRecipient, " + s"r=$prevRandao, w={${withdrawals.mkString(", ")}}", - underlying.forkChoiceUpdateWithPayloadId(lastBlockHash, finalizedBlockHash, unixEpochSeconds, suggestedFeeRecipient, prevRandao, withdrawals) + underlying.forkChoiceUpdatedWithPayloadId(lastBlockHash, finalizedBlockHash, unixEpochSeconds, suggestedFeeRecipient, prevRandao, withdrawals) ) - override def getPayloadJson(payloadId: PayloadId): JobResult[JsObject] = - wrap(s"getPayloadJson($payloadId)", underlying.getPayloadJson(payloadId), filteredJsonStr) + override def getPayload(payloadId: PayloadId): JobResult[JsObject] = + wrap(s"getPayload($payloadId)", underlying.getPayload(payloadId), filteredJsonStr) override def applyNewPayload(payloadJson: JsObject): JobResult[Option[BlockHash]] = wrap(s"applyNewPayload(${filteredJsonStr(payloadJson)})", underlying.applyNewPayload(payloadJson), _.fold("None")(identity)) - override def getPayloadBodyJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = - wrap(s"getPayloadBodyJsonByHash($hash)", underlying.getPayloadBodyJsonByHash(hash), _.fold("None")(filteredJsonStr)) + override def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] = + wrap(s"getPayloadBodyByHash($hash)", underlying.getPayloadBodyByHash(hash), _.fold("None")(filteredJsonStr)) - override def getPayloadByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] = - wrap(s"getPayloadByNumber($number)", underlying.getPayloadByNumber(number), _.fold("None")(_.toString)) + override def getBlockByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] = + wrap(s"getBlockByNumber($number)", underlying.getBlockByNumber(number), _.fold("None")(_.toString)) - override def getPayloadByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] = - wrap(s"getPayloadByHash($hash)", underlying.getPayloadByHash(hash), _.fold("None")(_.toString)) + override def getBlockByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] = + wrap(s"getBlockByHash($hash)", underlying.getBlockByHash(hash), _.fold("None")(_.toString)) - override def getLastPayload: JobResult[ExecutionPayload] = - wrap("getLastPayload", underlying.getLastPayload) + override def getLatestBlock: JobResult[ExecutionPayload] = + wrap("getLatestBlock", underlying.getLatestBlock) override def getBlockJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = - wrap(s"getBlockByHashJson($hash)", underlying.getBlockJsonByHash(hash), _.fold("None")(filteredJsonStr)) + wrap(s"getBlockJsonByHash($hash)", underlying.getBlockJsonByHash(hash), _.fold("None")(filteredJsonStr)) override def getPayloadJsonDataByHash(hash: BlockHash): JobResult[PayloadJsonData] = { wrap( diff --git a/src/main/scala/units/network/BasicMessagesRepo.scala b/src/main/scala/units/network/BasicMessagesRepo.scala index 51febc1b..981afd66 100644 --- a/src/main/scala/units/network/BasicMessagesRepo.scala +++ b/src/main/scala/units/network/BasicMessagesRepo.scala @@ -1,12 +1,9 @@ package units.network import cats.syntax.either.* -import com.google.common.primitives.Bytes -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.crypto import com.wavesplatform.crypto.SignatureLength import units.util.HexBytesConverter.* -import units.{BlockHash, NetworkBlock} +import units.BlockHash import com.wavesplatform.network.message.Message.MessageCode import com.wavesplatform.network.message.{Message, MessageSpec} import com.wavesplatform.network.{InetSocketAddressSeqSpec, NetworkServer} @@ -36,42 +33,28 @@ object PeersSpec extends InetSocketAddressSeqSpec[KnownPeers] { override protected def wrap(addresses: Seq[InetSocketAddress]): KnownPeers = KnownPeers(addresses) } -object GetBlockL2Spec extends MessageSpec[GetBlock] { +object GetPayloadSpec extends MessageSpec[GetPayload] { override val messageCode: MessageCode = 3: Byte override val maxLength: Int = SignatureLength - override def serializeData(msg: GetBlock): Array[Byte] = toBytes(msg.hash) + override def serializeData(msg: GetPayload): Array[Byte] = + toBytes(msg.hash) - override def deserializeData(bytes: Array[Byte]): Try[GetBlock] = Try { - require( - NetworkBlock.validateReferenceLength(bytes.length), - s"Invalid hash length ${bytes.length} in GetBlock message, expecting ${crypto.DigestLength}" - ) - GetBlock(BlockHash(bytes)) - } + override def deserializeData(bytes: Array[Byte]): Try[GetPayload] = + Try(GetPayload(BlockHash(bytes))) } -object BlockSpec extends MessageSpec[NetworkBlock] { +object PayloadSpec extends MessageSpec[PayloadMessage] { override val messageCode: MessageCode = 4: Byte override val maxLength: Int = NetworkServer.MaxFrameLength - override def serializeData(block: NetworkBlock): Array[Byte] = { - val signatureBytes = block.signature.map(sig => Bytes.concat(Array(1.toByte), sig.arr)).getOrElse(Array(0.toByte)) - Bytes.concat(signatureBytes, block.payloadBytes) - } - - override def deserializeData(bytes: Array[Byte]): Try[NetworkBlock] = { - // We need a signature only for blocks those are not confirmed on the chain contract - val isWithSignature = bytes.headOption.contains(1.toByte) - val signature = if (isWithSignature) Some(ByteStr(bytes.slice(1, SignatureLength + 1))) else None - val payloadOffset = if (isWithSignature) SignatureLength + 1 else 1 - for { - _ <- Either.cond(signature.forall(_.size == SignatureLength), (), new RuntimeException("Invalid block signature size")).toTry - block <- NetworkBlock(bytes.drop(payloadOffset), signature).leftMap(err => new RuntimeException(err.message)).toTry - } yield block - } + override def serializeData(payloadMsg: PayloadMessage): Array[Byte] = + payloadMsg.toBytes + + override def deserializeData(bytes: Array[Byte]): Try[PayloadMessage] = + PayloadMessage.fromBytes(bytes).leftMap(err => new IllegalArgumentException(err)).toTry } object BasicMessagesRepo { @@ -80,8 +63,8 @@ object BasicMessagesRepo { val specs: Seq[Spec] = Seq( GetPeersSpec, PeersSpec, - GetBlockL2Spec, - BlockSpec + GetPayloadSpec, + PayloadSpec ) val specsByCodes: Map[Byte, Spec] = specs.map(s => s.messageCode -> s).toMap diff --git a/src/main/scala/units/network/BlocksObserver.scala b/src/main/scala/units/network/BlocksObserver.scala deleted file mode 100644 index 84b1c599..00000000 --- a/src/main/scala/units/network/BlocksObserver.scala +++ /dev/null @@ -1,15 +0,0 @@ -package units.network - -import units.network.BlocksObserverImpl.BlockWithChannel -import com.wavesplatform.network.ChannelObservable -import monix.eval.Task -import monix.execution.CancelableFuture -import units.{BlockHash, NetworkBlock} - -trait BlocksObserver { - def getBlockStream: ChannelObservable[NetworkBlock] - - def requestBlock(req: BlockHash): Task[BlockWithChannel] - - def loadBlock(req: BlockHash): CancelableFuture[BlockWithChannel] -} diff --git a/src/main/scala/units/network/HistoryReplier.scala b/src/main/scala/units/network/HistoryReplier.scala index b3cfcde1..2a2c3814 100644 --- a/src/main/scala/units/network/HistoryReplier.scala +++ b/src/main/scala/units/network/HistoryReplier.scala @@ -1,12 +1,13 @@ package units.network +import cats.syntax.either.* import com.wavesplatform.network.id import com.wavesplatform.utils.ScorexLogging import io.netty.channel.ChannelHandler.Sharable import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter} import monix.execution.Scheduler import units.client.engine.EngineApiClient -import units.{BlockHash, ClientError, NetworkBlock} +import units.{BlockHash, ClientError} import scala.concurrent.Future import scala.util.{Failure, Success} @@ -25,22 +26,22 @@ class HistoryReplier(engineApiClient: EngineApiClient)(implicit sc: Scheduler) e } override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = msg match { - case GetBlock(hash) => + case GetPayload(hash) => respondWith( ctx, - loadBlock(hash) + loadPayload(hash) .map { case Right(block) => - RawBytes(BlockSpec.messageCode, BlockSpec.serializeData(block)) + RawBytes(PayloadSpec.messageCode, PayloadSpec.serializeData(block)) case Left(err) => throw new NoSuchElementException(s"Error loading block $hash: $err") } ) case _ => super.channelRead(ctx, msg) } - private def loadBlock(hash: BlockHash): Future[Either[ClientError, NetworkBlock]] = Future { + private def loadPayload(hash: BlockHash): Future[Either[ClientError, PayloadMessage]] = Future { engineApiClient.getPayloadJsonDataByHash(hash).flatMap { payloadJsonData => - NetworkBlock(payloadJsonData.toPayloadJson) + PayloadMessage(payloadJsonData.toPayloadJson).leftMap(ClientError.apply) } } } diff --git a/src/main/scala/units/network/LegacyFrameCodec.scala b/src/main/scala/units/network/LegacyFrameCodec.scala index 1079ffc8..0d100d13 100644 --- a/src/main/scala/units/network/LegacyFrameCodec.scala +++ b/src/main/scala/units/network/LegacyFrameCodec.scala @@ -1,6 +1,5 @@ package units.network -import units.NetworkBlock import com.wavesplatform.network.BasicMessagesRepo.Spec import com.wavesplatform.network.LegacyFrameCodec.MessageRawData import com.wavesplatform.network.message.Message.MessageCode @@ -13,8 +12,8 @@ class LegacyFrameCodec(peerDatabase: PeerDatabase) extends LFC(peerDatabase) { override protected def messageToRawData(msg: Any): MessageRawData = { val rawBytes = (msg: @unchecked) match { - case rb: RawBytes => rb - case block: NetworkBlock => RawBytes.from(BlockSpec, block) + case rb: RawBytes => rb + case payloadMsg: PayloadMessage => RawBytes.from(PayloadSpec, payloadMsg) } MessageRawData(rawBytes.code, rawBytes.data) diff --git a/src/main/scala/units/network/Message.scala b/src/main/scala/units/network/Message.scala index 0daf8bf6..c0f452d4 100644 --- a/src/main/scala/units/network/Message.scala +++ b/src/main/scala/units/network/Message.scala @@ -12,7 +12,7 @@ case object GetPeers extends Message case class KnownPeers(peers: Seq[InetSocketAddress]) extends Message -case class GetBlock(hash: BlockHash) extends Message +case class GetPayload(hash: BlockHash) extends Message case class RawBytes(code: Byte, data: Array[Byte]) extends Message { override def toString: String = s"RawBytes($code, ${data.length} bytes)" diff --git a/src/main/scala/units/network/MessageCodec.scala b/src/main/scala/units/network/MessageCodec.scala index 0ec96c63..2194f649 100644 --- a/src/main/scala/units/network/MessageCodec.scala +++ b/src/main/scala/units/network/MessageCodec.scala @@ -21,7 +21,7 @@ class MessageCodec(peerDatabase: PeerDatabase) extends MessageToMessageCodec[Raw // With a spec case GetPeers => RawBytes.from(GetPeersSpec, GetPeers) case k: KnownPeers => RawBytes.from(PeersSpec, k) - case g: GetBlock => RawBytes.from(GetBlockL2Spec, g) + case g: GetPayload => RawBytes.from(GetPayloadSpec, g) case _ => throw new IllegalArgumentException(s"Can't send message $msg to $ctx (unsupported)") diff --git a/src/main/scala/units/network/MessageObserver.scala b/src/main/scala/units/network/MessageObserver.scala index f3de7fdb..cbe03c94 100644 --- a/src/main/scala/units/network/MessageObserver.scala +++ b/src/main/scala/units/network/MessageObserver.scala @@ -5,21 +5,20 @@ import io.netty.channel.ChannelHandler.Sharable import io.netty.channel.{Channel, ChannelHandlerContext, ChannelInboundHandlerAdapter} import monix.execution.schedulers.SchedulerService import monix.reactive.subjects.{ConcurrentSubject, Subject} -import units.NetworkBlock @Sharable class MessageObserver extends ChannelInboundHandlerAdapter { private implicit val scheduler: SchedulerService = Schedulers.fixedPool(2, "message-observer-l2") - val blocks: Subject[(Channel, NetworkBlock), (Channel, NetworkBlock)] = ConcurrentSubject.publish[(Channel, NetworkBlock)] + val payloads: Subject[(Channel, PayloadMessage), (Channel, PayloadMessage)] = ConcurrentSubject.publish[(Channel, PayloadMessage)] override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = msg match { - case b: NetworkBlock => blocks.onNext((ctx.channel(), b)) - case _ => super.channelRead(ctx, msg) + case b: PayloadMessage => payloads.onNext((ctx.channel(), b)) + case _ => super.channelRead(ctx, msg) } def shutdown(): Unit = { - blocks.onComplete() + payloads.onComplete() } } diff --git a/src/main/scala/units/network/PayloadMessage.scala b/src/main/scala/units/network/PayloadMessage.scala new file mode 100644 index 00000000..4b36718a --- /dev/null +++ b/src/main/scala/units/network/PayloadMessage.scala @@ -0,0 +1,89 @@ +package units.network + +import cats.syntax.either.* +import com.google.common.primitives.Bytes +import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.crypto.SignatureLength +import play.api.libs.json.{JsObject, Json} +import units.client.engine.model.{ExecutionPayload, Withdrawal} +import units.eth.EthAddress +import units.{BlockHash, ExecutionPayloadInfo} +import units.util.HexBytesConverter.* + +import scala.util.Try + +class PayloadMessage private ( + payloadJson: JsObject, + val hash: BlockHash, + val signature: Option[ByteStr] +) { + lazy val payload: Either[String, ExecutionPayloadInfo] = { + (for { + timestamp <- (payloadJson \ "timestamp").asOpt[String].map(toLong).toRight("timestamp not defined") + height <- (payloadJson \ "blockNumber").asOpt[String].map(toLong).toRight("height not defined") + parentHash <- (payloadJson \ "parentHash").asOpt[BlockHash].toRight("parent hash not defined") + stateRoot <- (payloadJson \ "stateRoot").asOpt[String].toRight("state root not defined") + feeRecipient <- (payloadJson \ "feeRecipient").asOpt[EthAddress].toRight("fee recipient not defined") + baseFeePerGas <- (payloadJson \ "baseFeePerGas").asOpt[String].map(toUint256).toRight("baseFeePerGas not defined") + gasLimit <- (payloadJson \ "gasLimit").asOpt[String].map(toLong).toRight("gasLimit not defined") + gasUsed <- (payloadJson \ "gasUsed").asOpt[String].map(toLong).toRight("gasUsed not defined") + prevRandao <- (payloadJson \ "prevRandao").asOpt[String].toRight("prevRandao not defined") + withdrawals <- (payloadJson \ "withdrawals").asOpt[Vector[Withdrawal]].toRight("withdrawals are not defined") + } yield { + ExecutionPayloadInfo( + ExecutionPayload( + hash, + parentHash, + stateRoot, + height, + timestamp, + feeRecipient, + baseFeePerGas, + gasLimit, + gasUsed, + prevRandao, + withdrawals + ), + payloadJson + ) + }).leftMap(err => s"Error creating payload for block $hash: $err") + } + + def toBytes: Array[Byte] = { + val signatureBytes = signature.map(sig => Bytes.concat(Array(1.toByte), sig.arr)).getOrElse(Array(0.toByte)) + Bytes.concat(signatureBytes, Json.toBytes(payloadJson)) + } +} + +object PayloadMessage { + def apply(payloadJson: JsObject): Either[String, PayloadMessage] = + apply(payloadJson, None) + + def apply(payloadJson: JsObject, hash: BlockHash, signature: Option[ByteStr]): PayloadMessage = + new PayloadMessage(payloadJson, hash, signature) + + def apply(payloadJson: JsObject, signature: Option[ByteStr]): Either[String, PayloadMessage] = + (payloadJson \ "blockHash") + .asOpt[BlockHash] + .toRight("Error creating payload: block hash not defined") + .map(PayloadMessage(payloadJson, _, signature)) + + def apply(payloadBytes: Array[Byte], signature: Option[ByteStr]): Either[String, PayloadMessage] = for { + payload <- Try(Json.parse(payloadBytes).as[JsObject]).toEither.leftMap(err => s"Payload bytes are not a valid JSON object: ${err.getMessage}") + block <- apply(payload, signature) + } yield block + + def fromBytes(bytes: Array[Byte]): Either[String, PayloadMessage] = { + val isWithSignature = bytes.headOption.contains(1.toByte) + val signature = if (isWithSignature) Some(ByteStr(bytes.slice(1, SignatureLength + 1))) else None + val payloadOffset = if (isWithSignature) SignatureLength + 1 else 1 + + for { + _ <- validateSignatureLength(signature) + pm <- apply(bytes.drop(payloadOffset), signature) + } yield pm + } + + private def validateSignatureLength(signature: Option[ByteStr]): Either[String, Unit] = + Either.cond(signature.forall(_.size == SignatureLength), (), "Invalid block signature size") +} diff --git a/src/main/scala/units/network/PayloadObserver.scala b/src/main/scala/units/network/PayloadObserver.scala new file mode 100644 index 00000000..9b00eeca --- /dev/null +++ b/src/main/scala/units/network/PayloadObserver.scala @@ -0,0 +1,15 @@ +package units.network + +import units.network.PayloadObserverImpl.PayloadWithChannel +import com.wavesplatform.network.ChannelObservable +import monix.eval.Task +import monix.execution.CancelableFuture +import units.BlockHash + +trait PayloadObserver { + def getPayloadStream: ChannelObservable[PayloadMessage] + + def requestPayload(req: BlockHash): Task[PayloadWithChannel] + + def loadPayload(req: BlockHash): CancelableFuture[PayloadWithChannel] +} diff --git a/src/main/scala/units/network/BlocksObserverImpl.scala b/src/main/scala/units/network/PayloadObserverImpl.scala similarity index 52% rename from src/main/scala/units/network/BlocksObserverImpl.scala rename to src/main/scala/units/network/PayloadObserverImpl.scala index 0c702341..efcd49a2 100644 --- a/src/main/scala/units/network/BlocksObserverImpl.scala +++ b/src/main/scala/units/network/PayloadObserverImpl.scala @@ -8,73 +8,73 @@ import io.netty.channel.group.DefaultChannelGroup import monix.eval.Task import monix.execution.{Cancelable, CancelableFuture, CancelablePromise, Scheduler} import monix.reactive.subjects.ConcurrentSubject -import units.network.BlocksObserverImpl.{BlockWithChannel, State} -import units.{BlockHash, NetworkBlock} +import units.network.PayloadObserverImpl.{PayloadWithChannel, State} +import units.BlockHash import java.time.Duration import scala.concurrent.duration.FiniteDuration import scala.jdk.CollectionConverters.* import scala.util.{Failure, Success} -class BlocksObserverImpl(allChannels: DefaultChannelGroup, blocks: ChannelObservable[NetworkBlock], syncTimeout: FiniteDuration)(implicit - sc: Scheduler -) extends BlocksObserver - with ScorexLogging { +class PayloadObserverImpl(allChannels: DefaultChannelGroup, payloads: ChannelObservable[PayloadMessage], syncTimeout: FiniteDuration)(implicit + sc: Scheduler +) extends PayloadObserver + with ScorexLogging { private var state: State = State.Idle(None) - private val blocksResult: ConcurrentSubject[BlockWithChannel, BlockWithChannel] = - ConcurrentSubject.publish[BlockWithChannel] + private val payloadsResult: ConcurrentSubject[PayloadWithChannel, PayloadWithChannel] = + ConcurrentSubject.publish[PayloadWithChannel] - def loadBlock(req: BlockHash): CancelableFuture[BlockWithChannel] = knownBlockCache.getIfPresent(req) match { - case null => - val p = CancelablePromise[BlockWithChannel]() - sc.execute { () => - val candidate = state match { - case State.LoadingBlock(_, nextAttempt, promise) => - nextAttempt.cancel() - promise.complete(Failure(new NoSuchElementException("Loading was canceled"))) - None - case State.Idle(candidate) => candidate - } - state = State.LoadingBlock(req, requestFromNextChannel(req, candidate, Set.empty).runToFuture, p) - } - p.future - case (ch, block) => - CancelablePromise.successful(ch -> block).future - } - - private val knownBlockCache = CacheBuilder + private val knownPayloadCache = CacheBuilder .newBuilder() .expireAfterWrite(Duration.ofMinutes(10)) .maximumSize(100) - .build[BlockHash, BlockWithChannel]() + .build[BlockHash, PayloadWithChannel]() - blocks - .foreach { case v@(ch, block) => + payloads + .foreach { case v @ (ch, pm) => state = state match { - case State.LoadingBlock(expectedHash, nextAttempt, p) if expectedHash == block.hash => + case State.LoadingPayload(expectedHash, nextAttempt, p) if expectedHash == pm.hash => nextAttempt.cancel() - p.complete(Success(ch -> block)) + p.complete(Success(ch -> pm)) State.Idle(Some(ch)) case other => other } - knownBlockCache.put(block.hash, v) - blocksResult.onNext(v) + knownPayloadCache.put(pm.hash, v) + payloadsResult.onNext(v) } - def getBlockStream: ChannelObservable[NetworkBlock] = blocksResult + def loadPayload(req: BlockHash): CancelableFuture[PayloadWithChannel] = knownPayloadCache.getIfPresent(req) match { + case null => + val p = CancelablePromise[PayloadWithChannel]() + sc.execute { () => + val candidate = state match { + case State.LoadingPayload(_, nextAttempt, promise) => + nextAttempt.cancel() + promise.complete(Failure(new NoSuchElementException("Loading was canceled"))) + None + case State.Idle(candidate) => candidate + } + state = State.LoadingPayload(req, requestFromNextChannel(req, candidate, Set.empty).runToFuture, p) + } + p.future + case (ch, pm) => + CancelablePromise.successful(ch -> pm).future + } + + def getPayloadStream: ChannelObservable[PayloadMessage] = payloadsResult - def requestBlock(req: BlockHash): Task[BlockWithChannel] = Task + def requestPayload(req: BlockHash): Task[PayloadWithChannel] = Task .defer { - log.info(s"Loading block $req") - knownBlockCache.getIfPresent(req) match { + log.info(s"Loading payload $req") + knownPayloadCache.getIfPresent(req) match { case null => - val p = CancelablePromise[BlockWithChannel]() + val p = CancelablePromise[PayloadWithChannel]() val candidate = state match { - case l: State.LoadingBlock => - log.trace(s"No longer waiting for block ${l.blockHash}, will load $req instead") + case l: State.LoadingPayload => + log.trace(s"No longer waiting for payload ${l.blockHash}, will load $req instead") l.nextAttempt.cancel() l.promise.future.cancel() None @@ -82,15 +82,15 @@ class BlocksObserverImpl(allChannels: DefaultChannelGroup, blocks: ChannelObserv candidate } - state = State.LoadingBlock( + state = State.LoadingPayload( req, requestFromNextChannel(req, candidate, Set.empty).runToFuture, p ) Task.fromCancelablePromise(p) - case (ch, block) => - Task.pure(ch -> block) + case (ch, pm) => + Task.pure(ch -> pm) } } .executeOn(sc) @@ -101,7 +101,7 @@ class BlocksObserverImpl(allChannels: DefaultChannelGroup, blocks: ChannelObserv log.trace(s"No channel to request $req") Set.empty[Channel] case Some(ch) => - ch.writeAndFlush(GetBlock(req)) + ch.writeAndFlush(GetPayload(req)) excluded ++ Set(ch) } }.flatMap(newExcluded => requestFromNextChannel(req, candidate, newExcluded).delayExecution(syncTimeout)) @@ -111,14 +111,14 @@ class BlocksObserverImpl(allChannels: DefaultChannelGroup, blocks: ChannelObserv } -object BlocksObserverImpl { +object PayloadObserverImpl { - type BlockWithChannel = (Channel, NetworkBlock) + type PayloadWithChannel = (Channel, PayloadMessage) sealed trait State object State { - case class LoadingBlock(blockHash: BlockHash, nextAttempt: Cancelable, promise: CancelablePromise[BlockWithChannel]) extends State + case class LoadingPayload(blockHash: BlockHash, nextAttempt: Cancelable, promise: CancelablePromise[PayloadWithChannel]) extends State case class Idle(pinnedChannel: Option[Channel]) extends State } diff --git a/src/main/scala/units/network/TrafficLogger.scala b/src/main/scala/units/network/TrafficLogger.scala index c22a836d..d7bb6cf0 100644 --- a/src/main/scala/units/network/TrafficLogger.scala +++ b/src/main/scala/units/network/TrafficLogger.scala @@ -2,7 +2,6 @@ package units.network import com.wavesplatform.network.{Handshake, HandshakeSpec, TrafficLogger as TL} import io.netty.channel.ChannelHandler.Sharable -import units.NetworkBlock import units.network.BasicMessagesRepo.specsByCodes @Sharable @@ -12,18 +11,18 @@ class TrafficLogger(settings: TL.Settings) extends TL(settings) { protected def codeOf(msg: AnyRef): Option[Byte] = { val aux: PartialFunction[AnyRef, Byte] = { - case x: RawBytes => x.code - case _: NetworkBlock => BlockSpec.messageCode - case x: Message => specsByClasses(x.getClass).messageCode - case _: Handshake => HandshakeSpec.messageCode + case x: RawBytes => x.code + case _: PayloadMessage => PayloadSpec.messageCode + case x: Message => specsByClasses(x.getClass).messageCode + case _: Handshake => HandshakeSpec.messageCode } aux.lift(msg) } protected def stringify(msg: Any): String = msg match { - case b: NetworkBlock => s"${b.hash}" + case pm: PayloadMessage => s"${pm.hash}" case RawBytes(code, data) => s"RawBytes(${specsByCodes(code).messageName}, ${data.length} bytes)" - case other => other.toString + case other => other.toString } } diff --git a/src/test/scala/units/ExtensionDomain.scala b/src/test/scala/units/ExtensionDomain.scala index 8729227d..b567cb36 100644 --- a/src/test/scala/units/ExtensionDomain.scala +++ b/src/test/scala/units/ExtensionDomain.scala @@ -44,7 +44,7 @@ import units.client.contract.HasConsensusLayerDappTxHelpers.EmptyE2CTransfersRoo import units.client.engine.model.{ExecutionPayload, TestPayloads} import units.client.{CommonBlockData, TestEcClients} import units.eth.{EthAddress, EthereumConstants, Gwei} -import units.network.TestBlocksObserver +import units.network.TestPayloadObserver import units.test.CustomMatchers import java.nio.charset.StandardCharsets @@ -90,7 +90,7 @@ class ExtensionDomain( val eluScheduler: TestScheduler = TestScheduler(ExecutionModel.AlwaysAsyncExecution) val elBlockStream: PublishSubject[(Channel, NetworkBlock)] = PublishSubject[(Channel, NetworkBlock)]() - val blockObserver: TestBlocksObserver = new TestBlocksObserver(elBlockStream) + val blockObserver: TestPayloadObserver = new TestPayloadObserver(elBlockStream) val neighbourChannel = new EmbeddedChannel() val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) diff --git a/src/test/scala/units/client/TestEcClients.scala b/src/test/scala/units/client/TestEcClients.scala index ba7e7fdb..8b87d0fc 100644 --- a/src/test/scala/units/client/TestEcClients.scala +++ b/src/test/scala/units/client/TestEcClients.scala @@ -64,7 +64,7 @@ class TestEcClients private ( val engineApi = new LoggedEngineApiClient( new EngineApiClient { - override def forkChoiceUpdate(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[PayloadStatus] = { + override def forkChoiceUpdated(blockHash: BlockHash, finalizedBlockHash: BlockHash): JobResult[PayloadStatus] = { knownBlocks.get().get(blockHash) match { case Some(cid) => currChainIdValue.set(cid) @@ -79,7 +79,7 @@ class TestEcClients private ( } }.asRight - override def forkChoiceUpdateWithPayloadId( + override def forkChoiceUpdatedWithPayloadId( lastBlockHash: BlockHash, finalizedBlockHash: BlockHash, unixEpochSeconds: Long, @@ -98,7 +98,7 @@ class TestEcClients private ( fb.payloadId.asRight } - override def getPayloadJson(payloadId: PayloadId): JobResult[JsObject] = + override def getPayload(payloadId: PayloadId): JobResult[JsObject] = forgingBlocks.transformAndExtract(_.withoutFirst { fb => fb.payloadId == payloadId }) match { case Some(fb) => TestPayloads.toPayloadJson(fb.testPayload, fb.testPayload.prevRandao).asRight case None => @@ -128,16 +128,16 @@ class TestEcClients private ( Some(newPayload.hash) }.asRight - override def getPayloadBodyJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = + override def getPayloadBodyByHash(hash: BlockHash): JobResult[Option[JsObject]] = notImplementedMethodJob("getPayloadBodyJsonByHash") - override def getPayloadByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] = + override def getBlockByNumber(number: BlockNumber): JobResult[Option[ExecutionPayload]] = number match { case BlockNumber.Latest => currChain.headOption.asRight case BlockNumber.Number(n) => currChain.find(_.height == n).asRight } - override def getPayloadByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] = { + override def getBlockByHash(hash: BlockHash): JobResult[Option[ExecutionPayload]] = { for { cid <- knownBlocks.get().get(hash) c <- chains.get().get(cid) @@ -148,7 +148,7 @@ class TestEcClients private ( override def getBlockJsonByHash(hash: BlockHash): JobResult[Option[JsObject]] = notImplementedMethodJob("getBlockJsonByHash") - override def getLastPayload: JobResult[ExecutionPayload] = currChain.head.asRight + override def getLatestBlock: JobResult[ExecutionPayload] = currChain.head.asRight override def getLogs(hash: BlockHash, address: EthAddress, topic: String): JobResult[List[GetLogsResponseEntry]] = { val request = GetLogsRequest(hash, address, List(topic)) diff --git a/src/test/scala/units/network/TestBlocksObserver.scala b/src/test/scala/units/network/TestPayloadObserver.scala similarity index 52% rename from src/test/scala/units/network/TestBlocksObserver.scala rename to src/test/scala/units/network/TestPayloadObserver.scala index 499884a3..5c317b0b 100644 --- a/src/test/scala/units/network/TestBlocksObserver.scala +++ b/src/test/scala/units/network/TestPayloadObserver.scala @@ -5,16 +5,16 @@ import com.wavesplatform.utils.ScorexLogging import io.netty.channel.Channel import monix.eval.Task import monix.execution.CancelableFuture -import units.network.BlocksObserverImpl.BlockWithChannel +import units.network.PayloadObserverImpl.PayloadWithChannel import units.{BlockHash, NetworkBlock} -class TestBlocksObserver(override val getBlockStream: ChannelObservable[NetworkBlock]) extends BlocksObserver with ScorexLogging { - override def loadBlock(req: BlockHash): CancelableFuture[(Channel, NetworkBlock)] = { +class TestPayloadObserver(override val getPayloadStream: ChannelObservable[NetworkBlock]) extends PayloadObserver with ScorexLogging { + override def loadPayload(req: BlockHash): CancelableFuture[(Channel, NetworkBlock)] = { log.debug(s"loadBlock($req)") CancelableFuture.never } - def requestBlock(req: BlockHash): Task[BlockWithChannel] = { + def requestPayload(req: BlockHash): Task[PayloadWithChannel] = { log.debug(s"requestBlock($req)") Task.never } From 5722acb9e35deddca3415d9d07617da427d777af Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Mon, 23 Sep 2024 17:20:07 +0300 Subject: [PATCH 05/14] Rename block classes (WIP) --- src/main/scala/units/ELUpdater.scala | 28 +++--- .../scala/units/network/MessageObserver.scala | 15 ++-- .../scala/units/network/PayloadMessage.scala | 6 +- .../scala/units/network/PayloadObserver.scala | 10 +-- .../units/network/PayloadObserverImpl.scala | 88 +++++++------------ .../scala/units/network/TrafficLogger.scala | 2 +- .../units/network/TestPayloadObserver.scala | 4 +- 7 files changed, 65 insertions(+), 88 deletions(-) diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index 0fde81a3..cacc73fe 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -34,7 +34,7 @@ import units.client.engine.EngineApiClient.PayloadId import units.client.engine.model.* import units.client.engine.model.Withdrawal.WithdrawalIndex import units.eth.{EmptyPayload, EthAddress, EthereumConstants} -import units.network.PayloadObserverImpl.PayloadWithChannel +import units.network.PayloadObserverImpl.PayloadInfoWithChannel import units.util.HexBytesConverter import units.util.HexBytesConverter.toHexNoPrefix @@ -43,17 +43,17 @@ import scala.concurrent.duration.* import scala.util.* class ELUpdater( - engineApiClient: EngineApiClient, - blockchain: Blockchain, - utx: UtxPool, - allChannels: DefaultChannelGroup, - config: ClientConfig, - time: Time, - wallet: Wallet, - requestPayloadFromPeers: BlockHash => CancelableFuture[PayloadWithChannel], - broadcastTx: Transaction => TracedResult[ValidationError, Boolean], - scheduler: Scheduler, - globalScheduler: Scheduler + engineApiClient: EngineApiClient, + blockchain: Blockchain, + utx: UtxPool, + allChannels: DefaultChannelGroup, + config: ClientConfig, + time: Time, + wallet: Wallet, + requestPayloadFromPeers: BlockHash => CancelableFuture[PayloadInfoWithChannel], + broadcastTx: Transaction => TracedResult[ValidationError, Boolean], + scheduler: Scheduler, + globalScheduler: Scheduler ) extends StrictLogging { import ELUpdater.* @@ -1561,8 +1561,8 @@ object ELUpdater { } } - case class WaitingForSyncHead(target: ContractBlock, task: CancelableFuture[PayloadWithChannel]) extends State - case class SyncingToFinalizedBlock(target: BlockHash) extends State + case class WaitingForSyncHead(target: ContractBlock, task: CancelableFuture[PayloadInfoWithChannel]) extends State + case class SyncingToFinalizedBlock(target: BlockHash) extends State } private case class RollbackBlock(hash: BlockHash, parentPayload: ExecutionPayload) diff --git a/src/main/scala/units/network/MessageObserver.scala b/src/main/scala/units/network/MessageObserver.scala index cbe03c94..9597680c 100644 --- a/src/main/scala/units/network/MessageObserver.scala +++ b/src/main/scala/units/network/MessageObserver.scala @@ -1,21 +1,26 @@ package units.network -import com.wavesplatform.utils.Schedulers +import com.wavesplatform.utils.{Schedulers, ScorexLogging} import io.netty.channel.ChannelHandler.Sharable import io.netty.channel.{Channel, ChannelHandlerContext, ChannelInboundHandlerAdapter} import monix.execution.schedulers.SchedulerService import monix.reactive.subjects.{ConcurrentSubject, Subject} +import units.ExecutionPayloadInfo @Sharable -class MessageObserver extends ChannelInboundHandlerAdapter { +class MessageObserver extends ChannelInboundHandlerAdapter with ScorexLogging { private implicit val scheduler: SchedulerService = Schedulers.fixedPool(2, "message-observer-l2") - val payloads: Subject[(Channel, PayloadMessage), (Channel, PayloadMessage)] = ConcurrentSubject.publish[(Channel, PayloadMessage)] + val payloads: Subject[(Channel, ExecutionPayloadInfo), (Channel, ExecutionPayloadInfo)] = ConcurrentSubject.publish[(Channel, ExecutionPayloadInfo)] override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = msg match { - case b: PayloadMessage => payloads.onNext((ctx.channel(), b)) - case _ => super.channelRead(ctx, msg) + case pm: PayloadMessage => + pm.payloadInfo match { + case Right(epi) => payloads.onNext((ctx.channel(), epi)) + case Left(err) => log.warn(err) + } + case _ => super.channelRead(ctx, msg) } def shutdown(): Unit = { diff --git a/src/main/scala/units/network/PayloadMessage.scala b/src/main/scala/units/network/PayloadMessage.scala index 4b36718a..d7cdd097 100644 --- a/src/main/scala/units/network/PayloadMessage.scala +++ b/src/main/scala/units/network/PayloadMessage.scala @@ -17,7 +17,7 @@ class PayloadMessage private ( val hash: BlockHash, val signature: Option[ByteStr] ) { - lazy val payload: Either[String, ExecutionPayloadInfo] = { + lazy val payloadInfo: Either[String, ExecutionPayloadInfo] = { (for { timestamp <- (payloadJson \ "timestamp").asOpt[String].map(toLong).toRight("timestamp not defined") height <- (payloadJson \ "blockNumber").asOpt[String].map(toLong).toRight("height not defined") @@ -46,7 +46,7 @@ class PayloadMessage private ( ), payloadJson ) - }).leftMap(err => s"Error creating payload for block $hash: $err") + }).leftMap(err => s"Error creating payload info for block $hash: $err") } def toBytes: Array[Byte] = { @@ -65,7 +65,7 @@ object PayloadMessage { def apply(payloadJson: JsObject, signature: Option[ByteStr]): Either[String, PayloadMessage] = (payloadJson \ "blockHash") .asOpt[BlockHash] - .toRight("Error creating payload: block hash not defined") + .toRight("Error creating payload message: block hash not defined") .map(PayloadMessage(payloadJson, _, signature)) def apply(payloadBytes: Array[Byte], signature: Option[ByteStr]): Either[String, PayloadMessage] = for { diff --git a/src/main/scala/units/network/PayloadObserver.scala b/src/main/scala/units/network/PayloadObserver.scala index 9b00eeca..1640975c 100644 --- a/src/main/scala/units/network/PayloadObserver.scala +++ b/src/main/scala/units/network/PayloadObserver.scala @@ -1,15 +1,13 @@ package units.network -import units.network.PayloadObserverImpl.PayloadWithChannel +import units.network.PayloadObserverImpl.PayloadInfoWithChannel import com.wavesplatform.network.ChannelObservable import monix.eval.Task import monix.execution.CancelableFuture -import units.BlockHash +import units.{BlockHash, ExecutionPayloadInfo} trait PayloadObserver { - def getPayloadStream: ChannelObservable[PayloadMessage] + def getPayloadStream: ChannelObservable[ExecutionPayloadInfo] - def requestPayload(req: BlockHash): Task[PayloadWithChannel] - - def loadPayload(req: BlockHash): CancelableFuture[PayloadWithChannel] + def loadPayload(req: BlockHash): CancelableFuture[PayloadInfoWithChannel] } diff --git a/src/main/scala/units/network/PayloadObserverImpl.scala b/src/main/scala/units/network/PayloadObserverImpl.scala index efcd49a2..7de42060 100644 --- a/src/main/scala/units/network/PayloadObserverImpl.scala +++ b/src/main/scala/units/network/PayloadObserverImpl.scala @@ -8,93 +8,67 @@ import io.netty.channel.group.DefaultChannelGroup import monix.eval.Task import monix.execution.{Cancelable, CancelableFuture, CancelablePromise, Scheduler} import monix.reactive.subjects.ConcurrentSubject -import units.network.PayloadObserverImpl.{PayloadWithChannel, State} -import units.BlockHash +import units.network.PayloadObserverImpl.{PayloadInfoWithChannel, State} +import units.{BlockHash, ExecutionPayloadInfo} import java.time.Duration import scala.concurrent.duration.FiniteDuration import scala.jdk.CollectionConverters.* import scala.util.{Failure, Success} -class PayloadObserverImpl(allChannels: DefaultChannelGroup, payloads: ChannelObservable[PayloadMessage], syncTimeout: FiniteDuration)(implicit +class PayloadObserverImpl(allChannels: DefaultChannelGroup, payloads: ChannelObservable[ExecutionPayloadInfo], syncTimeout: FiniteDuration)(implicit sc: Scheduler ) extends PayloadObserver with ScorexLogging { private var state: State = State.Idle(None) - private val payloadsResult: ConcurrentSubject[PayloadWithChannel, PayloadWithChannel] = - ConcurrentSubject.publish[PayloadWithChannel] + private val payloadsResult: ConcurrentSubject[PayloadInfoWithChannel, PayloadInfoWithChannel] = + ConcurrentSubject.publish[PayloadInfoWithChannel] private val knownPayloadCache = CacheBuilder .newBuilder() .expireAfterWrite(Duration.ofMinutes(10)) .maximumSize(100) - .build[BlockHash, PayloadWithChannel]() + .build[BlockHash, PayloadInfoWithChannel]() payloads - .foreach { case v @ (ch, pm) => + .foreach { case v @ (ch, epi) => state = state match { - case State.LoadingPayload(expectedHash, nextAttempt, p) if expectedHash == pm.hash => + case State.LoadingPayload(expectedHash, nextAttempt, p) if expectedHash == epi.payload.hash => nextAttempt.cancel() - p.complete(Success(ch -> pm)) + p.complete(Success(ch -> epi)) State.Idle(Some(ch)) case other => other } - knownPayloadCache.put(pm.hash, v) + knownPayloadCache.put(epi.payload.hash, v) payloadsResult.onNext(v) } - def loadPayload(req: BlockHash): CancelableFuture[PayloadWithChannel] = knownPayloadCache.getIfPresent(req) match { - case null => - val p = CancelablePromise[PayloadWithChannel]() - sc.execute { () => - val candidate = state match { - case State.LoadingPayload(_, nextAttempt, promise) => - nextAttempt.cancel() - promise.complete(Failure(new NoSuchElementException("Loading was canceled"))) - None - case State.Idle(candidate) => candidate - } - state = State.LoadingPayload(req, requestFromNextChannel(req, candidate, Set.empty).runToFuture, p) - } - p.future - case (ch, pm) => - CancelablePromise.successful(ch -> pm).future - } - - def getPayloadStream: ChannelObservable[PayloadMessage] = payloadsResult - - def requestPayload(req: BlockHash): Task[PayloadWithChannel] = Task - .defer { - log.info(s"Loading payload $req") - knownPayloadCache.getIfPresent(req) match { - case null => - val p = CancelablePromise[PayloadWithChannel]() - + def loadPayload(req: BlockHash): CancelableFuture[PayloadInfoWithChannel] = { + log.info(s"Loading payload $req") + knownPayloadCache.getIfPresent(req) match { + case null => + val p = CancelablePromise[PayloadInfoWithChannel]() + sc.execute { () => val candidate = state match { - case l: State.LoadingPayload => - log.trace(s"No longer waiting for payload ${l.blockHash}, will load $req instead") - l.nextAttempt.cancel() - l.promise.future.cancel() + case State.LoadingPayload(_, nextAttempt, promise) => + nextAttempt.cancel() + promise.complete(Failure(new NoSuchElementException("Loading was canceled"))) None - case State.Idle(candidate) => - candidate + case State.Idle(candidate) => candidate } - - state = State.LoadingPayload( - req, - requestFromNextChannel(req, candidate, Set.empty).runToFuture, - p - ) - - Task.fromCancelablePromise(p) - case (ch, pm) => - Task.pure(ch -> pm) - } + state = State.LoadingPayload(req, requestFromNextChannel(req, candidate, Set.empty).runToFuture, p) + } + p.future + case (ch, pm) => + CancelablePromise.successful(ch -> pm).future } - .executeOn(sc) + } + + def getPayloadStream: ChannelObservable[ExecutionPayloadInfo] = payloadsResult + // TODO: remove Task private def requestFromNextChannel(req: BlockHash, candidate: Option[Channel], excluded: Set[Channel]): Task[Unit] = Task { candidate.filterNot(excluded).orElse(nextOpenChannel(excluded)) match { case None => @@ -113,12 +87,12 @@ class PayloadObserverImpl(allChannels: DefaultChannelGroup, payloads: ChannelObs object PayloadObserverImpl { - type PayloadWithChannel = (Channel, PayloadMessage) + type PayloadInfoWithChannel = (Channel, ExecutionPayloadInfo) sealed trait State object State { - case class LoadingPayload(blockHash: BlockHash, nextAttempt: Cancelable, promise: CancelablePromise[PayloadWithChannel]) extends State + case class LoadingPayload(blockHash: BlockHash, nextAttempt: Cancelable, promise: CancelablePromise[PayloadInfoWithChannel]) extends State case class Idle(pinnedChannel: Option[Channel]) extends State } diff --git a/src/main/scala/units/network/TrafficLogger.scala b/src/main/scala/units/network/TrafficLogger.scala index d7bb6cf0..1867dff9 100644 --- a/src/main/scala/units/network/TrafficLogger.scala +++ b/src/main/scala/units/network/TrafficLogger.scala @@ -21,7 +21,7 @@ class TrafficLogger(settings: TL.Settings) extends TL(settings) { } protected def stringify(msg: Any): String = msg match { - case pm: PayloadMessage => s"${pm.hash}" + case pm: PayloadMessage => s"PayloadMessage(hash=${pm.hash})" case RawBytes(code, data) => s"RawBytes(${specsByCodes(code).messageName}, ${data.length} bytes)" case other => other.toString } diff --git a/src/test/scala/units/network/TestPayloadObserver.scala b/src/test/scala/units/network/TestPayloadObserver.scala index 5c317b0b..7256f246 100644 --- a/src/test/scala/units/network/TestPayloadObserver.scala +++ b/src/test/scala/units/network/TestPayloadObserver.scala @@ -5,7 +5,7 @@ import com.wavesplatform.utils.ScorexLogging import io.netty.channel.Channel import monix.eval.Task import monix.execution.CancelableFuture -import units.network.PayloadObserverImpl.PayloadWithChannel +import units.network.PayloadObserverImpl.PayloadInfoWithChannel import units.{BlockHash, NetworkBlock} class TestPayloadObserver(override val getPayloadStream: ChannelObservable[NetworkBlock]) extends PayloadObserver with ScorexLogging { @@ -14,7 +14,7 @@ class TestPayloadObserver(override val getPayloadStream: ChannelObservable[Netwo CancelableFuture.never } - def requestPayload(req: BlockHash): Task[PayloadWithChannel] = { + def requestPayload(req: BlockHash): Task[PayloadInfoWithChannel] = { log.debug(s"requestBlock($req)") Task.never } From 4ca859f2d76a377d5968f29a2c57fb575a3113f3 Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Tue, 24 Sep 2024 18:39:55 +0300 Subject: [PATCH 06/14] Rename block classes (WIP) --- src/main/scala/units/ELUpdater.scala | 146 +++++++++--------- .../scala/units/ExecutionPayloadInfo.scala | 3 +- src/main/scala/units/NetworkBlock.scala | 30 ++-- .../scala/units/client/CommonBlockData.scala | 2 +- .../units/client/contract/ContractBlock.scala | 18 +-- .../engine/model/ExecutionPayload.scala | 4 +- .../scala/units/network/PayloadMessage.scala | 10 +- .../scala/units/network/PayloadObserver.scala | 1 - .../units/BlockBriefValidationTestSuite.scala | 8 +- .../units/BlockFullValidationTestSuite.scala | 8 +- .../units/BlockIssuesForgingTestSuite.scala | 8 +- .../scala/units/E2CTransfersTestSuite.scala | 2 +- src/test/scala/units/ExtensionDomain.scala | 40 +++-- src/test/scala/units/TestPayloadBuilder.scala | 4 +- .../client/engine/model/TestPayloads.scala | 2 +- .../units/network/TestPayloadObserver.scala | 6 +- 16 files changed, 152 insertions(+), 140 deletions(-) diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index cacc73fe..1877065c 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -34,6 +34,7 @@ import units.client.engine.EngineApiClient.PayloadId import units.client.engine.model.* import units.client.engine.model.Withdrawal.WithdrawalIndex import units.eth.{EmptyPayload, EthAddress, EthereumConstants} +import units.network.PayloadMessage import units.network.PayloadObserverImpl.PayloadInfoWithChannel import units.util.HexBytesConverter import units.util.HexBytesConverter.toHexNoPrefix @@ -43,17 +44,17 @@ import scala.concurrent.duration.* import scala.util.* class ELUpdater( - engineApiClient: EngineApiClient, - blockchain: Blockchain, - utx: UtxPool, - allChannels: DefaultChannelGroup, - config: ClientConfig, - time: Time, - wallet: Wallet, - requestPayloadFromPeers: BlockHash => CancelableFuture[PayloadInfoWithChannel], - broadcastTx: Transaction => TracedResult[ValidationError, Boolean], - scheduler: Scheduler, - globalScheduler: Scheduler + engineApiClient: EngineApiClient, + blockchain: Blockchain, + utx: UtxPool, + allChannels: DefaultChannelGroup, + config: ClientConfig, + time: Time, + wallet: Wallet, + requestPayloadFromPeers: BlockHash => CancelableFuture[PayloadInfoWithChannel], + broadcastTx: Transaction => TracedResult[ValidationError, Boolean], + scheduler: Scheduler, + globalScheduler: Scheduler ) extends StrictLogging { import ELUpdater.* @@ -66,15 +67,16 @@ class ELUpdater( def consensusLayerChanged(): Unit = handleNextUpdate := scheduler.scheduleOnce(ClChangedProcessingDelay)(handleConsensusLayerChanged()) - def executionPayloadReceived(block: NetworkBlock, ch: Channel): Unit = scheduler.execute { () => - logger.debug(s"New block ${block.hash}->${block.parentHash} (timestamp=${block.timestamp}, height=${block.height}) appeared") + def executionPayloadReceived(epi: ExecutionPayloadInfo, ch: Channel): Unit = scheduler.execute { () => + val payload = epi.payload + logger.debug(s"New block ${payload.hash}->${payload.parentHash} (timestamp=${payload.timestamp}, height=${payload.height}) appeared") val now = time.correctedTime() / 1000 - if (block.timestamp - now <= MaxTimeDrift) { + if (payload.timestamp - now <= MaxTimeDrift) { state match { - case WaitingForSyncHead(target, _) if block.hash == target.hash => + case WaitingForSyncHead(target, _) if payload.hash == target.hash => val syncStarted = for { - _ <- engineApiClient.applyNewPayload(block.payloadJson) + _ <- engineApiClient.applyNewPayload(epi.payloadJson) fcuStatus <- confirmBlock(target, target) } yield fcuStatus @@ -88,26 +90,26 @@ class ELUpdater( waitForSyncCompletion(target) } case w @ Working(_, lastPayload, _, _, _, FollowingChain(nodeChainInfo, _), _, returnToMainChainInfo) - if block.parentHash == lastPayload.hash => - validateAndApply(block, ch, w, lastPayload, nodeChainInfo, returnToMainChainInfo) + if payload.parentHash == lastPayload.hash => + validateAndApply(epi, ch, w, lastPayload, nodeChainInfo, returnToMainChainInfo) case w: Working[ChainStatus] => w.returnToMainChainInfo match { - case Some(rInfo) if rInfo.missedBlock.hash == block.hash => + case Some(rInfo) if rInfo.missedBlock.hash == payload.hash => chainContractClient.getChainInfo(rInfo.chainId) match { case Some(chainInfo) if chainInfo.isMain => - validateAndApplyMissedBlock(block, ch, w, rInfo.missedBlock, rInfo.missedBlockParentPayload, chainInfo) + validateAndApplyMissedBlock(epi, ch, w, rInfo.missedBlock, rInfo.missedBlockParentPayload, chainInfo) case Some(_) => - logger.debug(s"Chain ${rInfo.chainId} is not main anymore, ignoring ${block.hash}") + logger.debug(s"Chain ${rInfo.chainId} is not main anymore, ignoring ${payload.hash}") case _ => - logger.error(s"Failed to get chain ${rInfo.chainId} info, ignoring ${block.hash}") + logger.error(s"Failed to get chain ${rInfo.chainId} info, ignoring ${payload.hash}") } - case _ => logger.debug(s"Expecting ${w.returnToMainChainInfo.fold("no block")(_.toString)}, ignoring unexpected ${block.hash}") + case _ => logger.debug(s"Expecting ${w.returnToMainChainInfo.fold("no block")(_.toString)}, ignoring unexpected ${payload.hash}") } case other => - logger.debug(s"$other: ignoring ${block.hash}") + logger.debug(s"$other: ignoring ${payload.hash}") } } else { - logger.debug(s"Block ${block.hash} is from future: timestamp=${block.timestamp}, now=$now, Δ${block.timestamp - now}s") + logger.debug(s"Block ${payload.hash} is from future: timestamp=${payload.timestamp}, now=$now, Δ${payload.timestamp - now}s") } } @@ -359,7 +361,7 @@ class ELUpdater( lastC2ETransferIndex, elWithdrawalIndexBefore, prevState.options, - Option.unless(parentPayload.height == EthereumConstants.GenesisBlockHeight)(parentPayload.minerRewardAddress) + Option.unless(parentPayload.height == EthereumConstants.GenesisBlockHeight)(parentPayload.feeRecipient) ) } yield { val newState = prevState.copy( @@ -781,10 +783,10 @@ class ELUpdater( } } - private def requestAndProcessPayload(hash: BlockHash): CancelableFuture[(Channel, NetworkBlock)] = { + private def requestAndProcessPayload(hash: BlockHash): CancelableFuture[(Channel, ExecutionPayloadInfo)] = { requestPayloadFromPeers(hash).andThen { - case Success((ch, payload)) => executionPayloadReceived(payload, ch) - case Failure(exception) => logger.error(s"Error loading block $hash", exception) + case Success((ch, epi)) => executionPayloadReceived(epi, ch) + case Failure(exception) => logger.error(s"Error loading block $hash payload", exception) }(globalScheduler) } @@ -941,22 +943,23 @@ class ELUpdater( ) } - private def validateMiner(block: NetworkBlock, epochInfo: Option[EpochInfo]): JobResult[Unit] = { + private def validateMiner(epi: ExecutionPayloadInfo, epochInfo: Option[EpochInfo]): JobResult[Unit] = { + val payload = epi.payload epochInfo match { case Some(epochMeta) => for { _ <- Either.cond( - block.minerRewardAddress == epochMeta.rewardAddress, + payload.feeRecipient == epochMeta.rewardAddress, (), - ClientError(s"block miner ${block.minerRewardAddress} doesn't equal to ${epochMeta.rewardAddress}") + ClientError(s"block miner ${payload.feeRecipient} doesn't equal to ${epochMeta.rewardAddress}") ) - signature <- Either.fromOption(block.signature, ClientError(s"signature not found")) + signature <- Either.fromOption(epi.signature, ClientError(s"signature not found")) publicKey <- Either.fromOption( - chainContractClient.getMinerPublicKey(block.minerRewardAddress), - ClientError(s"public key for block miner ${block.minerRewardAddress} not found") + chainContractClient.getMinerPublicKey(payload.feeRecipient), + ClientError(s"public key for block miner ${payload.feeRecipient} not found") ) _ <- Either.cond( - crypto.verify(signature, Json.toBytes(block.payloadJson), publicKey, checkWeakPk = true), + crypto.verify(signature, Json.toBytes(epi.payloadJson), publicKey, checkWeakPk = true), (), ClientError(s"invalid signature") ) @@ -965,27 +968,27 @@ class ELUpdater( } } - private def validateTimestamp(block: NetworkBlock, parentPayload: ExecutionPayload): JobResult[Unit] = { + private def validateTimestamp(payload: ExecutionPayload, parentPayload: ExecutionPayload): JobResult[Unit] = { val minAppendTs = parentPayload.timestamp + config.blockDelay.toSeconds Either.cond( - block.timestamp >= minAppendTs, + payload.timestamp >= minAppendTs, (), ClientError( - s"timestamp (${block.timestamp}) of appended block must be greater or equal $minAppendTs, " + - s"Δ${minAppendTs - block.timestamp}s" + s"timestamp (${payload.timestamp}) of appended block must be greater or equal $minAppendTs, " + + s"Δ${minAppendTs - payload.timestamp}s" ) ) } private def preValidateBlock( - block: NetworkBlock, + epi: ExecutionPayloadInfo, parentPayload: ExecutionPayload, epochInfo: Option[EpochInfo] ): JobResult[Unit] = { for { - _ <- validateTimestamp(block, parentPayload) - _ <- validateMiner(block, epochInfo) - _ <- engineApiClient.applyNewPayload(block.payloadJson) + _ <- validateTimestamp(epi.payload, parentPayload) + _ <- validateMiner(epi, epochInfo) + _ <- engineApiClient.applyNewPayload(epi.payloadJson) } yield () } @@ -1018,51 +1021,53 @@ class ELUpdater( } private def validateAndApplyMissedBlock( - block: NetworkBlock, + epi: ExecutionPayloadInfo, ch: Channel, prevState: Working[ChainStatus], contractBlock: ContractBlock, parentPayload: ExecutionPayload, nodeChainInfo: ChainInfo ): Unit = { - validateBlockFull(block, contractBlock, parentPayload, prevState) match { + val payload = epi.payload + validateBlockFull(epi, contractBlock, parentPayload, prevState) match { case Right(updatedState) => - logger.debug(s"Missed block ${block.hash} of main chain ${nodeChainInfo.id} was successfully validated") - broadcastAndConfirmBlock(block, ch, updatedState, nodeChainInfo, None) + logger.debug(s"Missed block ${payload.hash} of main chain ${nodeChainInfo.id} was successfully validated") + broadcastAndConfirmBlock(epi, ch, updatedState, nodeChainInfo, None) case Left(err) => - logger.debug(s"Missed block ${block.hash} of main chain ${nodeChainInfo.id} validation error: ${err.message}, ignoring block") + logger.debug(s"Missed block ${payload.hash} of main chain ${nodeChainInfo.id} validation error: ${err.message}, ignoring block") } } private def validateAndApply( - block: NetworkBlock, + epi: ExecutionPayloadInfo, ch: Channel, prevState: Working[ChainStatus], parentPayload: ExecutionPayload, nodeChainInfo: ChainInfo, returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Unit = { - chainContractClient.getBlock(block.hash) match { + val payload = epi.payload + chainContractClient.getBlock(payload.hash) match { case Some(contractBlock) if prevState.fullValidationStatus.lastValidatedBlock.hash == parentPayload.hash => // all blocks before current was fully validated, so we can perform full validation of this block - validateBlockFull(block, contractBlock, parentPayload, prevState) match { + validateBlockFull(epi, contractBlock, parentPayload, prevState) match { case Right(updatedState) => - logger.debug(s"Block ${block.hash} was successfully validated") - broadcastAndConfirmBlock(block, ch, updatedState, nodeChainInfo, returnToMainChainInfo) + logger.debug(s"Block ${payload.hash} was successfully validated") + broadcastAndConfirmBlock(epi, ch, updatedState, nodeChainInfo, returnToMainChainInfo) case Left(err) => - logger.debug(s"Block ${block.hash} validation error: ${err.message}") + logger.debug(s"Block ${payload.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(block, parentPayload, epochInfo) match { + preValidateBlock(epi, parentPayload, epochInfo) match { case Right(_) => - logger.debug(s"Block ${block.hash} was successfully partially validated") - broadcastAndConfirmBlock(block, ch, prevState, nodeChainInfo, returnToMainChainInfo) + logger.debug(s"Block ${payload.hash} was successfully partially validated") + broadcastAndConfirmBlock(epi, ch, prevState, nodeChainInfo, returnToMainChainInfo) case Left(err) => - logger.error(s"Block ${block.hash} prevalidation error: ${err.message}, ignoring block") + logger.error(s"Block ${payload.hash} prevalidation error: ${err.message}, ignoring block") } } } @@ -1095,17 +1100,18 @@ class ELUpdater( } private def broadcastAndConfirmBlock( - block: NetworkBlock, + epi: ExecutionPayloadInfo, ch: Channel, prevState: Working[ChainStatus], nodeChainInfo: ChainInfo, returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Unit = { - Try(allChannels.broadcast(block, Some(ch))).recover { err => - logger.error(s"Failed to broadcast block ${block.hash}: ${err.getMessage}") + val payload = epi.payload + Try(allChannels.broadcast(PayloadMessage(epi.payloadJson, epi.signature), Some(ch))).recover { err => + logger.error(s"Failed to broadcast block ${payload.hash} payload: ${err.getMessage}") } - confirmBlockAndFollowChain(block.toPayload, prevState, nodeChainInfo, returnToMainChainInfo) + confirmBlockAndFollowChain(payload, prevState, nodeChainInfo, returnToMainChainInfo) } private def findBlockChild(parent: BlockHash, lastBlockHash: BlockHash): Either[String, ContractBlock] = { @@ -1293,15 +1299,15 @@ class ELUpdater( } private def validateBlockFull( - block: NetworkBlock, + epi: ExecutionPayloadInfo, contractBlock: ContractBlock, parentPayload: ExecutionPayload, prevState: Working[ChainStatus] ): JobResult[Working[ChainStatus]] = { - logger.debug(s"Trying to do full validation of block ${block.hash}") + val payload = epi.payload + logger.debug(s"Trying to do full validation of block ${payload.hash}") for { - _ <- preValidateBlock(block, parentPayload, None) - payload = block.toPayload + _ <- preValidateBlock(epi, parentPayload, None) updatedState <- validateAppliedBlock(contractBlock, payload, prevState) } yield updatedState } @@ -1315,10 +1321,10 @@ class ELUpdater( val validationResult = for { _ <- Either.cond( - contractBlock.minerRewardAddress == payload.minerRewardAddress, + contractBlock.feeRecipient == payload.feeRecipient, (), ClientError( - s"Miner in block payload (${payload.minerRewardAddress}) should be equal to miner on contract (${contractBlock.minerRewardAddress})" + s"Miner in block payload (${payload.feeRecipient}) should be equal to miner on contract (${contractBlock.feeRecipient})" ) ) _ <- validateE2CTransfers(contractBlock, prevState.options.elBridgeAddress) @@ -1562,7 +1568,7 @@ object ELUpdater { } case class WaitingForSyncHead(target: ContractBlock, task: CancelableFuture[PayloadInfoWithChannel]) extends State - case class SyncingToFinalizedBlock(target: BlockHash) extends State + case class SyncingToFinalizedBlock(target: BlockHash) extends State } private case class RollbackBlock(hash: BlockHash, parentPayload: ExecutionPayload) diff --git a/src/main/scala/units/ExecutionPayloadInfo.scala b/src/main/scala/units/ExecutionPayloadInfo.scala index 202a34e5..fe0e9b40 100644 --- a/src/main/scala/units/ExecutionPayloadInfo.scala +++ b/src/main/scala/units/ExecutionPayloadInfo.scala @@ -1,6 +1,7 @@ package units +import com.wavesplatform.common.state.ByteStr import play.api.libs.json.JsObject import units.client.engine.model.ExecutionPayload -case class ExecutionPayloadInfo(payload: ExecutionPayload, payloadJson: JsObject) +case class ExecutionPayloadInfo(payload: ExecutionPayload, payloadJson: JsObject, signature: Option[ByteStr]) diff --git a/src/main/scala/units/NetworkBlock.scala b/src/main/scala/units/NetworkBlock.scala index b4f1789e..ab577151 100644 --- a/src/main/scala/units/NetworkBlock.scala +++ b/src/main/scala/units/NetworkBlock.scala @@ -14,20 +14,20 @@ import units.util.HexBytesConverter.* // TODO Refactor to eliminate a manual deserialization, e.g. (raw: JsonObject, parsed: ParsedBlockL2) class NetworkBlock private ( - val hash: BlockHash, - val timestamp: Long, // UNIX epoch seconds - val height: Long, - val parentHash: BlockHash, - val stateRoot: String, - val minerRewardAddress: EthAddress, - val baseFeePerGas: Uint256, - val gasLimit: Long, - val gasUsed: Long, - val prevRandao: String, - val withdrawals: Vector[Withdrawal], - val payloadBytes: Array[Byte], - val payloadJson: JsObject, - val signature: Option[ByteStr] + val hash: BlockHash, + val timestamp: Long, // UNIX epoch seconds + val height: Long, + val parentHash: BlockHash, + val stateRoot: String, + val feeRecipient: EthAddress, + val baseFeePerGas: Uint256, + val gasLimit: Long, + val gasUsed: Long, + val prevRandao: String, + val withdrawals: Vector[Withdrawal], + val payloadBytes: Array[Byte], + val payloadJson: JsObject, + val signature: Option[ByteStr] ) extends CommonBlockData { def isEpochFirstBlock: Boolean = withdrawals.nonEmpty @@ -37,7 +37,7 @@ class NetworkBlock private ( stateRoot = stateRoot, height = height, timestamp = timestamp, - minerRewardAddress = minerRewardAddress, + feeRecipient = feeRecipient, baseFeePerGas = baseFeePerGas, gasLimit = gasLimit, gasUsed = gasUsed, diff --git a/src/main/scala/units/client/CommonBlockData.scala b/src/main/scala/units/client/CommonBlockData.scala index bff4a673..696ff792 100644 --- a/src/main/scala/units/client/CommonBlockData.scala +++ b/src/main/scala/units/client/CommonBlockData.scala @@ -7,7 +7,7 @@ trait CommonBlockData { def hash: BlockHash def parentHash: BlockHash def height: Long - def minerRewardAddress: EthAddress + def feeRecipient: EthAddress def referencesGenesis: Boolean = height == EthereumConstants.GenesisBlockHeight + 1 } diff --git a/src/main/scala/units/client/contract/ContractBlock.scala b/src/main/scala/units/client/contract/ContractBlock.scala index 8d1b5140..d056c971 100644 --- a/src/main/scala/units/client/contract/ContractBlock.scala +++ b/src/main/scala/units/client/contract/ContractBlock.scala @@ -7,17 +7,17 @@ import units.eth.EthAddress import units.util.HexBytesConverter.toHex case class ContractBlock( - hash: BlockHash, - parentHash: BlockHash, - epoch: Int, - height: Long, - minerRewardAddress: EthAddress, - chainId: Long, - e2cTransfersRootHash: Digest, - lastC2ETransferIndex: Long + hash: BlockHash, + parentHash: BlockHash, + epoch: Int, + height: Long, + feeRecipient: EthAddress, + chainId: Long, + e2cTransfersRootHash: Digest, + lastC2ETransferIndex: Long ) extends CommonBlockData { override def toString: String = - s"ContractBlock($hash, p=$parentHash, e=$epoch, h=$height, m=$minerRewardAddress, c=$chainId, " + + s"ContractBlock($hash, p=$parentHash, e=$epoch, h=$height, m=$feeRecipient, c=$chainId, " + s"e2c=${if (e2cTransfersRootHash.isEmpty) "" else toHex(e2cTransfersRootHash)}, c2e=$lastC2ETransferIndex)" } diff --git a/src/main/scala/units/client/engine/model/ExecutionPayload.scala b/src/main/scala/units/client/engine/model/ExecutionPayload.scala index 27e517fe..b5916a39 100644 --- a/src/main/scala/units/client/engine/model/ExecutionPayload.scala +++ b/src/main/scala/units/client/engine/model/ExecutionPayload.scala @@ -19,7 +19,7 @@ case class ExecutionPayload( stateRoot: String, height: Long, timestamp: Long, - minerRewardAddress: EthAddress, + feeRecipient: EthAddress, baseFeePerGas: Uint256, gasLimit: Long, gasUsed: Long, @@ -27,7 +27,7 @@ case class ExecutionPayload( withdrawals: Vector[Withdrawal] ) extends CommonBlockData { override def toString: String = - s"ExecutionPayload($hash, p=$parentHash, h=$height, t=$timestamp, m=$minerRewardAddress, w={${withdrawals.mkString(", ")}})" + s"ExecutionPayload($hash, p=$parentHash, h=$height, t=$timestamp, m=$feeRecipient, w={${withdrawals.mkString(", ")}})" } object ExecutionPayload { diff --git a/src/main/scala/units/network/PayloadMessage.scala b/src/main/scala/units/network/PayloadMessage.scala index d7cdd097..0667bb83 100644 --- a/src/main/scala/units/network/PayloadMessage.scala +++ b/src/main/scala/units/network/PayloadMessage.scala @@ -2,7 +2,9 @@ package units.network import cats.syntax.either.* import com.google.common.primitives.Bytes +import com.wavesplatform.account.PrivateKey import com.wavesplatform.common.state.ByteStr +import com.wavesplatform.crypto import com.wavesplatform.crypto.SignatureLength import play.api.libs.json.{JsObject, Json} import units.client.engine.model.{ExecutionPayload, Withdrawal} @@ -44,7 +46,8 @@ class PayloadMessage private ( prevRandao, withdrawals ), - payloadJson + payloadJson, + signature ) }).leftMap(err => s"Error creating payload info for block $hash: $err") } @@ -73,6 +76,11 @@ object PayloadMessage { block <- apply(payload, signature) } yield block + def signed(payloadJson: JsObject, signer: PrivateKey): Either[String, PayloadMessage] = { + val signature = crypto.sign(signer, Json.toBytes(payloadJson)) + PayloadMessage(payloadJson, Some(signature)) + } + def fromBytes(bytes: Array[Byte]): Either[String, PayloadMessage] = { val isWithSignature = bytes.headOption.contains(1.toByte) val signature = if (isWithSignature) Some(ByteStr(bytes.slice(1, SignatureLength + 1))) else None diff --git a/src/main/scala/units/network/PayloadObserver.scala b/src/main/scala/units/network/PayloadObserver.scala index 1640975c..bc037920 100644 --- a/src/main/scala/units/network/PayloadObserver.scala +++ b/src/main/scala/units/network/PayloadObserver.scala @@ -2,7 +2,6 @@ package units.network import units.network.PayloadObserverImpl.PayloadInfoWithChannel import com.wavesplatform.network.ChannelObservable -import monix.eval.Task import monix.execution.CancelableFuture import units.{BlockHash, ExecutionPayloadInfo} diff --git a/src/test/scala/units/BlockBriefValidationTestSuite.scala b/src/test/scala/units/BlockBriefValidationTestSuite.scala index 19f576c8..1c6f4601 100644 --- a/src/test/scala/units/BlockBriefValidationTestSuite.scala +++ b/src/test/scala/units/BlockBriefValidationTestSuite.scala @@ -16,10 +16,10 @@ class BlockBriefValidationTestSuite extends BaseIntegrationTestSuite { val payload = d.createPayloadBuilder("0", miner).build() step(s"Receive network block ${payload.hash} with payload from a peer") - d.receiveNetworkBlock(payload, miner.account) + d.receivePayload(payload, miner.account) withClue("Brief block validation:") { d.triggerScheduledTasks() - d.pollSentNetworkBlock() match { + d.pollSentPayloadMessage() match { case Some(sent) => sent.hash shouldBe payload.hash case None => fail(s"${payload.hash} should not be ignored") } @@ -30,10 +30,10 @@ class BlockBriefValidationTestSuite extends BaseIntegrationTestSuite { val payload = d.createPayloadBuilder("0", minerRewardAddress = EthAddress.empty, parentPayload = d.genesisBlockPayload).build() step(s"Receive network block ${payload.hash} with payload from a peer") - d.receiveNetworkBlock(payload, miner.account) + d.receivePayload(payload, miner.account) withClue("Brief block validation:") { d.triggerScheduledTasks() - if (d.pollSentNetworkBlock().nonEmpty) fail(s"${payload.hash} should be ignored, because it is invalid by brief validation rules") + if (d.pollSentPayloadMessage().nonEmpty) fail(s"${payload.hash} should be ignored, because it is invalid by brief validation rules") } } } diff --git a/src/test/scala/units/BlockFullValidationTestSuite.scala b/src/test/scala/units/BlockFullValidationTestSuite.scala index bde3ceb6..d222aa12 100644 --- a/src/test/scala/units/BlockFullValidationTestSuite.scala +++ b/src/test/scala/units/BlockFullValidationTestSuite.scala @@ -28,7 +28,7 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { d.advanceConsensusLayerChanged() step(s"Receive network block ${payload.hash} with payload from a peer") - d.receiveNetworkBlock(payload, reliable.account) + d.receivePayload(payload, reliable.account) d.triggerScheduledTasks() step(s"Append a CL micro block with payload ${payload.hash} confirmation") @@ -56,7 +56,7 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { d.advanceConsensusLayerChanged() step(s"Receive network block ${payload.hash} with payload from a peer") - d.receiveNetworkBlock(payload, reliable.account) + d.receivePayload(payload, reliable.account) d.triggerScheduledTasks() step(s"Append a CL micro block with payload ${payload.hash} confirmation") @@ -103,7 +103,7 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { d.advanceConsensusLayerChanged() step(s"Receive network block ${payload2.hash} with payload2 from a peer") - d.receiveNetworkBlock(payload2, malfunction.account) + d.receivePayload(payload2, malfunction.account) d.triggerScheduledTasks() d.waitForCS[WaitForNewChain]("Forking") { cs => @@ -128,7 +128,7 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { "Different miners in CL and EL" in e2CTest( blockLogs = blockLogs, e2CTransfersRootHashHex = e2CTransfersRootHashHex, - badBlockPayloadPostProcessing = _.copy(minerRewardAddress = reliable.elRewardAddress) + badBlockPayloadPostProcessing = _.copy(feeRecipient = reliable.elRewardAddress) ) } } diff --git a/src/test/scala/units/BlockIssuesForgingTestSuite.scala b/src/test/scala/units/BlockIssuesForgingTestSuite.scala index 008dc9cc..e5e824a3 100644 --- a/src/test/scala/units/BlockIssuesForgingTestSuite.scala +++ b/src/test/scala/units/BlockIssuesForgingTestSuite.scala @@ -59,7 +59,7 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { } step(s"Receive network block ${payload2.hash} with payload2") - d.receiveNetworkBlock(payload2, otherMiner1.account, block2Epoch) + d.receivePayload(payload2, otherMiner1.account, block2Epoch) d.triggerScheduledTasks() d.ecClients.willForge(d.createPayloadBuilder("0-0-0", otherMiner1, payload2).rewardPrevMiner().build()) @@ -113,7 +113,7 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { s.nextExpectedBlock.map(_.hash).value shouldBe payload2.hash } - d.receiveNetworkBlock(payload2, otherMiner2.account, d.blockchain.height) + d.receivePayload(payload2, otherMiner2.account, d.blockchain.height) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe payload2.hash @@ -144,7 +144,7 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { } step(s"Receive network block ${payload3.hash} with payload3") - d.receiveNetworkBlock(payload3, thisMiner.account, block3Epoch) + d.receivePayload(payload3, thisMiner.account, block3Epoch) d.ecClients.willForge(d.createPayloadBuilder("0-1-1-1", thisMiner, payload3).rewardPrevMiner(2).build()) d.waitForCS[Mining]() { s => @@ -257,7 +257,7 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { s.nextExpectedBlock.map(_.hash).value shouldBe payload2.hash } - d.receiveNetworkBlock(payload2, otherMiner2.account, d.blockchain.height) + d.receivePayload(payload2, otherMiner2.account, d.blockchain.height) d.waitForCS[FollowingChain]() { s => s.nodeChainInfo.isMain shouldBe false s.nodeChainInfo.lastBlock.hash shouldBe payload2.hash diff --git a/src/test/scala/units/E2CTransfersTestSuite.scala b/src/test/scala/units/E2CTransfersTestSuite.scala index 9b0a869f..5093da6b 100644 --- a/src/test/scala/units/E2CTransfersTestSuite.scala +++ b/src/test/scala/units/E2CTransfersTestSuite.scala @@ -240,7 +240,7 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { // No root hash in extendMainChain tx d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, badPayload2)) // No root hash - d.receiveNetworkBlock(badPayload2, malfunction.account) + d.receivePayload(badPayload2, malfunction.account) d.advanceConsensusLayerChanged() d.waitForCS[WaitForNewChain]("State is expected") { s => diff --git a/src/test/scala/units/ExtensionDomain.scala b/src/test/scala/units/ExtensionDomain.scala index b567cb36..c9d7b43f 100644 --- a/src/test/scala/units/ExtensionDomain.scala +++ b/src/test/scala/units/ExtensionDomain.scala @@ -44,7 +44,7 @@ import units.client.contract.HasConsensusLayerDappTxHelpers.EmptyE2CTransfersRoo import units.client.engine.model.{ExecutionPayload, TestPayloads} import units.client.{CommonBlockData, TestEcClients} import units.eth.{EthAddress, EthereumConstants, Gwei} -import units.network.TestPayloadObserver +import units.network.{PayloadMessage, TestPayloadObserver} import units.test.CustomMatchers import java.nio.charset.StandardCharsets @@ -76,7 +76,7 @@ class ExtensionDomain( stateRoot = EthereumConstants.EmptyRootHashHex, height = 0, timestamp = testTime.getTimestamp() / 1000 - l2Config.blockDelay.toSeconds, - minerRewardAddress = EthAddress.empty, + feeRecipient = EthAddress.empty, baseFeePerGas = Uint256.DEFAULT, gasLimit = 0, gasUsed = 0, @@ -89,16 +89,16 @@ class ExtensionDomain( val globalScheduler: TestScheduler = TestScheduler(ExecutionModel.AlwaysAsyncExecution) val eluScheduler: TestScheduler = TestScheduler(ExecutionModel.AlwaysAsyncExecution) - val elBlockStream: PublishSubject[(Channel, NetworkBlock)] = PublishSubject[(Channel, NetworkBlock)]() - val blockObserver: TestPayloadObserver = new TestPayloadObserver(elBlockStream) + val payloadStream: PublishSubject[(Channel, ExecutionPayloadInfo)] = PublishSubject[(Channel, ExecutionPayloadInfo)]() + val blockObserver: TestPayloadObserver = new TestPayloadObserver(payloadStream) val neighbourChannel = new EmbeddedChannel() val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) allChannels.add(neighbourChannel) - def pollSentNetworkBlock(): Option[NetworkBlock] = Option(neighbourChannel.readOutbound[NetworkBlock]) - def receiveNetworkBlock(payload: ExecutionPayload, miner: SeedKeyPair, epochNumber: Int = blockchain.height): Unit = - receiveNetworkBlock(toNetworkBlock(payload, miner, epochNumber)) - def receiveNetworkBlock(incomingNetworkBlock: NetworkBlock): Unit = elBlockStream.onNext((new EmbeddedChannel(), incomingNetworkBlock)) + def pollSentPayloadMessage(): Option[PayloadMessage] = Option(neighbourChannel.readOutbound[PayloadMessage]) + def receivePayload(payload: ExecutionPayload, miner: SeedKeyPair, epochNumber: Int = blockchain.height): Unit = + receivePayload(toExecutionPayloadInfo(payload, miner, epochNumber)) + def receivePayload(incomingPayload: ExecutionPayloadInfo): Unit = payloadStream.onNext((new EmbeddedChannel(), incomingPayload)) val extensionContext: Context = new Context { override def settings: WavesSettings = self.settings @@ -169,19 +169,17 @@ class ExtensionDomain( f(is[CS](s.chainStatus)) } - def toNetworkBlock(payload: ExecutionPayload, miner: SeedKeyPair, epochNumber: Int): NetworkBlock = - NetworkBlock - .signed( - TestPayloads.toPayloadJson( - payload, - calculateRandao( - blockchain.vrf(epochNumber).getOrElse(throw new RuntimeException(s"VRF is empty for epoch $epochNumber")), - payload.parentHash - ) - ), - miner.privateKey + def toExecutionPayloadInfo(payload: ExecutionPayload, miner: SeedKeyPair, epochNumber: Int): ExecutionPayloadInfo = { + val payloadJson = TestPayloads.toPayloadJson( + payload, + calculateRandao( + blockchain.vrf(epochNumber).getOrElse(throw new RuntimeException(s"VRF is empty for epoch $epochNumber")), + payload.parentHash ) - .explicitGet() + ) + + PayloadMessage.signed(payloadJson, miner.privateKey).flatMap(_.payloadInfo).explicitGet() + } def forgeFromUtxPool(): Unit = { val (txsOpt, _, _) = utxPool.packUnconfirmed(MultiDimensionalMiningConstraint.Unlimited, None) @@ -349,7 +347,7 @@ class ExtensionDomain( TestPayloadBuilder(ecClients, elBridgeAddress, elMinerDefaultReward, l2Config.blockDelay, parentPayload = parentPayload).updatePayload( _.copy( hash = TestPayloadBuilder.createBlockHash(hashPath), - minerRewardAddress = minerRewardAddress, + feeRecipient = minerRewardAddress, prevRandao = ELUpdater.calculateRandao(blockchain.vrf(blockchain.height).get, parentPayload.hash) ) ) diff --git a/src/test/scala/units/TestPayloadBuilder.scala b/src/test/scala/units/TestPayloadBuilder.scala index 96460072..821b7f9f 100644 --- a/src/test/scala/units/TestPayloadBuilder.scala +++ b/src/test/scala/units/TestPayloadBuilder.scala @@ -20,7 +20,7 @@ class TestPayloadBuilder private ( this } - def rewardPrevMiner(elWithdrawalIndex: Int = 0): TestPayloadBuilder = rewardMiner(parentPayload.minerRewardAddress, elWithdrawalIndex) + def rewardPrevMiner(elWithdrawalIndex: Int = 0): TestPayloadBuilder = rewardMiner(parentPayload.feeRecipient, elWithdrawalIndex) def rewardMiner(minerRewardAddress: EthAddress, elWithdrawalIndex: Int = 0): TestPayloadBuilder = { payload = payload.copy(withdrawals = Vector(Withdrawal(elWithdrawalIndex, minerRewardAddress, elMinerDefaultReward))) @@ -52,7 +52,7 @@ object TestPayloadBuilder { stateRoot = EthereumConstants.EmptyRootHashHex, height = parentPayload.height + 1, timestamp = parentPayload.timestamp + blockDelay.toSeconds, - minerRewardAddress = EthAddress.empty, + feeRecipient = EthAddress.empty, baseFeePerGas = Uint256.DEFAULT, gasLimit = 0, gasUsed = 0, diff --git a/src/test/scala/units/client/engine/model/TestPayloads.scala b/src/test/scala/units/client/engine/model/TestPayloads.scala index b7718d5e..59eed980 100644 --- a/src/test/scala/units/client/engine/model/TestPayloads.scala +++ b/src/test/scala/units/client/engine/model/TestPayloads.scala @@ -16,7 +16,7 @@ object TestPayloads { "blockNumber" -> toHex(payload.height), "parentHash" -> payload.parentHash, "stateRoot" -> payload.stateRoot, - "feeRecipient" -> payload.minerRewardAddress, + "feeRecipient" -> payload.feeRecipient, "prevRandao" -> prevRandao, "baseFeePerGas" -> toHex(payload.baseFeePerGas), "gasLimit" -> toHex(payload.gasLimit), diff --git a/src/test/scala/units/network/TestPayloadObserver.scala b/src/test/scala/units/network/TestPayloadObserver.scala index 7256f246..06fd1e7d 100644 --- a/src/test/scala/units/network/TestPayloadObserver.scala +++ b/src/test/scala/units/network/TestPayloadObserver.scala @@ -6,10 +6,10 @@ import io.netty.channel.Channel import monix.eval.Task import monix.execution.CancelableFuture import units.network.PayloadObserverImpl.PayloadInfoWithChannel -import units.{BlockHash, NetworkBlock} +import units.{BlockHash, ExecutionPayloadInfo} -class TestPayloadObserver(override val getPayloadStream: ChannelObservable[NetworkBlock]) extends PayloadObserver with ScorexLogging { - override def loadPayload(req: BlockHash): CancelableFuture[(Channel, NetworkBlock)] = { +class TestPayloadObserver(override val getPayloadStream: ChannelObservable[ExecutionPayloadInfo]) extends PayloadObserver with ScorexLogging { + override def loadPayload(req: BlockHash): CancelableFuture[(Channel, ExecutionPayloadInfo)] = { log.debug(s"loadBlock($req)") CancelableFuture.never } From 7a9851be05bc20d80442c111d84166e78bede73c Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Wed, 25 Sep 2024 11:45:49 +0300 Subject: [PATCH 07/14] Fix broadcasted message --- src/main/scala/units/ELUpdater.scala | 8 ++++++-- .../scala/units/BlockBriefValidationTestSuite.scala | 10 +++++----- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index 1877065c..deb30d19 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -1107,8 +1107,12 @@ class ELUpdater( returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Unit = { val payload = epi.payload - Try(allChannels.broadcast(PayloadMessage(epi.payloadJson, epi.signature), Some(ch))).recover { err => - logger.error(s"Failed to broadcast block ${payload.hash} payload: ${err.getMessage}") + + (for { + pm <- PayloadMessage(epi.payloadJson, epi.signature) + _ <- Try(allChannels.broadcast(pm, Some(ch))).toEither.leftMap(_.getMessage) + } yield ()).recover { err => + logger.error(s"Failed to broadcast block ${payload.hash} payload: $err") } confirmBlockAndFollowChain(payload, prevState, nodeChainInfo, returnToMainChainInfo) diff --git a/src/test/scala/units/BlockBriefValidationTestSuite.scala b/src/test/scala/units/BlockBriefValidationTestSuite.scala index 1c6f4601..5dcd3ffd 100644 --- a/src/test/scala/units/BlockBriefValidationTestSuite.scala +++ b/src/test/scala/units/BlockBriefValidationTestSuite.scala @@ -11,13 +11,13 @@ class BlockBriefValidationTestSuite extends BaseIntegrationTestSuite { initialMiners = List(miner) ) - "Brief validation of network block" - { + "Brief validation of payload" - { "accepts if it is valid" in test { d => val payload = d.createPayloadBuilder("0", miner).build() - step(s"Receive network block ${payload.hash} with payload from a peer") + step(s"Receive block ${payload.hash} payload from a peer") d.receivePayload(payload, miner.account) - withClue("Brief block validation:") { + withClue("Brief payload validation:") { d.triggerScheduledTasks() d.pollSentPayloadMessage() match { case Some(sent) => sent.hash shouldBe payload.hash @@ -29,9 +29,9 @@ class BlockBriefValidationTestSuite extends BaseIntegrationTestSuite { "otherwise ignoring" in test { d => val payload = d.createPayloadBuilder("0", minerRewardAddress = EthAddress.empty, parentPayload = d.genesisBlockPayload).build() - step(s"Receive network block ${payload.hash} with payload from a peer") + step(s"Receive block ${payload.hash} payload from a peer") d.receivePayload(payload, miner.account) - withClue("Brief block validation:") { + withClue("Brief payload validation:") { d.triggerScheduledTasks() if (d.pollSentPayloadMessage().nonEmpty) fail(s"${payload.hash} should be ignored, because it is invalid by brief validation rules") } From 492970e6bfb6b0d3aea9254a72c57a3886954eab Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Wed, 25 Sep 2024 13:06:27 +0300 Subject: [PATCH 08/14] Fix broadcasted message --- src/main/scala/units/ELUpdater.scala | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index deb30d19..e147c994 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -233,16 +233,17 @@ class ELUpdater( latestValidHashOpt <- engineApiClient.applyNewPayload(payloadJson) latestValidHash <- Either.fromOption(latestValidHashOpt, ClientError("Latest valid hash not defined")) _ = logger.info(s"Applied payload $payloadId, block hash is $latestValidHash, timestamp = $timestamp") - newBlock <- NetworkBlock.signed(payloadJson, m.keyPair.privateKey) - _ = logger.debug(s"Broadcasting block ${newBlock.hash}") - _ <- Try(allChannels.broadcast(newBlock)).toEither.leftMap(err => - ClientError(s"Failed to broadcast block ${newBlock.hash}: ${err.toString}") + newPm <- PayloadMessage.signed(payloadJson, m.keyPair.privateKey).leftMap(ClientError.apply) + _ = logger.debug(s"Broadcasting block ${newPm.hash} payload") + _ <- Try(allChannels.broadcast(newPm)).toEither.leftMap(err => + ClientError(s"Failed to broadcast block ${newPm.hash} payload: ${err.toString}") ) - executionPayload = newBlock.toPayload - transfersRootHash <- getE2CTransfersRootHash(executionPayload.hash, chainContractOptions.elBridgeAddress) - funcCall <- contractFunction.toFunctionCall(executionPayload.hash, transfersRootHash, m.lastC2ETransferIndex) - _ <- callContract(funcCall, executionPayload, m.keyPair) - } yield executionPayload).fold( + payloadInfo <- newPm.payloadInfo.leftMap(ClientError.apply) + payload = payloadInfo.payload + transfersRootHash <- getE2CTransfersRootHash(payload.hash, chainContractOptions.elBridgeAddress) + funcCall <- contractFunction.toFunctionCall(payload.hash, transfersRootHash, m.lastC2ETransferIndex) + _ <- callContract(funcCall, payload, m.keyPair) + } yield payload).fold( err => logger.error(s"Failed to forge block for payloadId $payloadId at epoch ${epochInfo.number}: ${err.message}"), newPayload => scheduler.execute { () => tryToForgeNextBlock(epochInfo.number, newPayload, chainContractOptions) } ) From 9208c16d8a34e183353b7f1971ddb71792b2000e Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Wed, 25 Sep 2024 13:12:19 +0300 Subject: [PATCH 09/14] Remove NetworkBlock --- src/main/scala/units/NetworkBlock.scala | 96 ------------------- .../scala/units/client/TestEcClients.scala | 5 +- .../client/engine/model/TestPayloads.scala | 6 -- 3 files changed, 3 insertions(+), 104 deletions(-) delete mode 100644 src/main/scala/units/NetworkBlock.scala diff --git a/src/main/scala/units/NetworkBlock.scala b/src/main/scala/units/NetworkBlock.scala deleted file mode 100644 index ab577151..00000000 --- a/src/main/scala/units/NetworkBlock.scala +++ /dev/null @@ -1,96 +0,0 @@ -package units - -import cats.syntax.either.* -import com.wavesplatform.account.PrivateKey -import com.wavesplatform.common.state.ByteStr -import com.wavesplatform.crypto -import com.wavesplatform.crypto.SignatureLength -import org.web3j.abi.datatypes.generated.Uint256 -import play.api.libs.json.{JsObject, Json} -import units.client.CommonBlockData -import units.client.engine.model.{ExecutionPayload, Withdrawal} -import units.eth.EthAddress -import units.util.HexBytesConverter.* - -// TODO Refactor to eliminate a manual deserialization, e.g. (raw: JsonObject, parsed: ParsedBlockL2) -class NetworkBlock private ( - val hash: BlockHash, - val timestamp: Long, // UNIX epoch seconds - val height: Long, - val parentHash: BlockHash, - val stateRoot: String, - val feeRecipient: EthAddress, - val baseFeePerGas: Uint256, - val gasLimit: Long, - val gasUsed: Long, - val prevRandao: String, - val withdrawals: Vector[Withdrawal], - val payloadBytes: Array[Byte], - val payloadJson: JsObject, - val signature: Option[ByteStr] -) extends CommonBlockData { - def isEpochFirstBlock: Boolean = withdrawals.nonEmpty - - def toPayload: ExecutionPayload = ExecutionPayload( - hash = hash, - parentHash = parentHash, - stateRoot = stateRoot, - height = height, - timestamp = timestamp, - feeRecipient = feeRecipient, - baseFeePerGas = baseFeePerGas, - gasLimit = gasLimit, - gasUsed = gasUsed, - prevRandao = prevRandao, - withdrawals = withdrawals - ) - - override def toString: String = s"NetworkBlock($hash)" -} - -object NetworkBlock { - private def apply(payload: JsObject, payloadBytes: Array[Byte], signature: Option[ByteStr]): Either[ClientError, NetworkBlock] = { - // See BlockToPayloadMapper for all available fields - (for { - hash <- (payload \ "blockHash").asOpt[BlockHash].toRight("hash not defined") - timestamp <- (payload \ "timestamp").asOpt[String].map(toLong).toRight("timestamp not defined") - height <- (payload \ "blockNumber").asOpt[String].map(toLong).toRight("height not defined") - parentHash <- (payload \ "parentHash").asOpt[BlockHash].toRight("parent hash not defined") - stateRoot <- (payload \ "stateRoot").asOpt[String].toRight("state root not defined") - minerRewardAddress <- (payload \ "feeRecipient").asOpt[EthAddress].toRight("fee recipient not defined") - baseFeePerGas <- (payload \ "baseFeePerGas").asOpt[String].map(toUint256).toRight("baseFeePerGas not defined") - gasLimit <- (payload \ "gasLimit").asOpt[String].map(toLong).toRight("gasLimit not defined") - gasUsed <- (payload \ "gasUsed").asOpt[String].map(toLong).toRight("gasUsed not defined") - prevRandao <- (payload \ "prevRandao").asOpt[String].toRight("prevRandao not defined") - withdrawals <- (payload \ "withdrawals").asOpt[Vector[Withdrawal]].toRight("withdrawals are not defined") - _ <- Either.cond(signature.forall(_.size == SignatureLength), (), "invalid signature size") - } yield new NetworkBlock( - hash, - timestamp, - height, - parentHash, - stateRoot, - minerRewardAddress, - baseFeePerGas, - gasLimit, - gasUsed, - prevRandao, - withdrawals, - payloadBytes, - payload, - signature - )).leftMap(err => ClientError(s"Error creating NetworkBlock from payload ${new String(payloadBytes)}: $err at payload")) - } - - def apply(payloadBytes: Array[Byte], signature: Option[ByteStr]): Either[ClientError, NetworkBlock] = for { - payload <- Json.parse(payloadBytes).asOpt[JsObject].toRight(ClientError("Payload is not a valid JSON object")) - block <- apply(payload, payloadBytes, signature) - } yield block - - def signed(payload: JsObject, signer: PrivateKey): Either[ClientError, NetworkBlock] = { - val payloadBytes = Json.toBytes(payload) - NetworkBlock(payload, payloadBytes, Some(crypto.sign(signer, payloadBytes))) - } - - def apply(payload: JsObject): Either[ClientError, NetworkBlock] = apply(payload, Json.toBytes(payload), None) -} diff --git a/src/test/scala/units/client/TestEcClients.scala b/src/test/scala/units/client/TestEcClients.scala index 8b87d0fc..81adcae0 100644 --- a/src/test/scala/units/client/TestEcClients.scala +++ b/src/test/scala/units/client/TestEcClients.scala @@ -12,7 +12,8 @@ import units.client.engine.model.* import units.client.engine.{EngineApiClient, LoggedEngineApiClient} import units.collections.ListOps.* import units.eth.EthAddress -import units.{BlockHash, JobResult, NetworkBlock} +import units.network.PayloadMessage +import units.{BlockHash, JobResult} class TestEcClients private ( knownBlocks: Atomic[Map[BlockHash, ChainId]], @@ -108,7 +109,7 @@ class TestEcClients private ( } override def applyNewPayload(payloadJson: JsObject): JobResult[Option[BlockHash]] = { - val newPayload = NetworkBlock(payloadJson).explicitGet().toPayload + val newPayload = PayloadMessage(payloadJson).flatMap(_.payloadInfo).explicitGet().payload knownBlocks.get().get(newPayload.parentHash) match { case Some(cid) => val chain = chains.get()(cid) diff --git a/src/test/scala/units/client/engine/model/TestPayloads.scala b/src/test/scala/units/client/engine/model/TestPayloads.scala index 59eed980..1d66fcc6 100644 --- a/src/test/scala/units/client/engine/model/TestPayloads.scala +++ b/src/test/scala/units/client/engine/model/TestPayloads.scala @@ -1,15 +1,9 @@ package units.client.engine.model -import com.wavesplatform.account.SeedKeyPair -import com.wavesplatform.common.utils.EitherExt2 import play.api.libs.json.{JsObject, Json} -import units.NetworkBlock import units.util.HexBytesConverter.toHex object TestPayloads { - def toNetworkBlock(payload: ExecutionPayload, miner: SeedKeyPair, prevRandao: String): NetworkBlock = - NetworkBlock.signed(TestPayloads.toPayloadJson(payload, prevRandao), miner.privateKey).explicitGet() - def toPayloadJson(payload: ExecutionPayload, prevRandao: String): JsObject = Json.obj( "blockHash" -> payload.hash, "timestamp" -> toHex(payload.timestamp), From fa06b2b5806c4dbb5ba7c0f87f9b38e4db3533a0 Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Thu, 26 Sep 2024 18:15:04 +0300 Subject: [PATCH 10/14] Signature is on network level --- src/main/scala/units/ConsensusClient.scala | 7 +- src/main/scala/units/ELUpdater.scala | 50 +++------ .../scala/units/network/MessageObserver.scala | 10 +- .../scala/units/network/PayloadMessage.scala | 23 ++-- .../network/PayloadMessageWithChannel.scala | 5 + .../scala/units/network/PayloadObserver.scala | 13 ++- .../units/network/PayloadObserverImpl.scala | 105 +++++++++++++----- .../units/network/TestPayloadObserver.scala | 11 ++ 8 files changed, 139 insertions(+), 85 deletions(-) create mode 100644 src/main/scala/units/network/PayloadMessageWithChannel.scala diff --git a/src/main/scala/units/ConsensusClient.scala b/src/main/scala/units/ConsensusClient.scala index b210efce..551f5f72 100644 --- a/src/main/scala/units/ConsensusClient.scala +++ b/src/main/scala/units/ConsensusClient.scala @@ -28,7 +28,6 @@ class ConsensusClient( context: ExtensionContext, engineApiClient: EngineApiClient, payloadObserver: PayloadObserver, - allChannels: DefaultChannelGroup, globalScheduler: Scheduler, eluScheduler: Scheduler, ownedResources: AutoCloseable @@ -41,7 +40,6 @@ class ConsensusClient( context, deps.engineApiClient, deps.payloadObserver, - deps.allChannels, deps.globalScheduler, deps.eluScheduler, deps @@ -54,18 +52,17 @@ class ConsensusClient( engineApiClient, context.blockchain, context.utx, - allChannels, + payloadObserver, config, context.time, context.wallet, - payloadObserver.loadPayload, context.broadcastTransaction, eluScheduler, globalScheduler ) private val payloadsStreamCancelable: CancelableFuture[Unit] = - payloadObserver.getPayloadStream.foreach { case (ch, ep) => elu.executionPayloadReceived(ep, ch) }(globalScheduler) + payloadObserver.getPayloadStream.foreach(elu.executionPayloadReceived)(globalScheduler) override def start(): Unit = {} diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index e147c994..7190132d 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -9,7 +9,6 @@ import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto import com.wavesplatform.lang.ValidationError import com.wavesplatform.lang.v1.compiler.Terms.FUNCTION_CALL -import com.wavesplatform.network.ChannelGroupExt import com.wavesplatform.state.Blockchain import com.wavesplatform.state.diffs.FeeValidation.{FeeConstants, FeeUnit, ScriptExtraFee} import com.wavesplatform.state.diffs.TransactionDiffer.TransactionValidationError @@ -21,7 +20,6 @@ import com.wavesplatform.utils.{EthEncoding, Time, UnsupportedFeature, forceStop import com.wavesplatform.utx.UtxPool import com.wavesplatform.wallet.Wallet import io.netty.channel.Channel -import io.netty.channel.group.DefaultChannelGroup import monix.execution.cancelables.SerialCancelable import monix.execution.{CancelableFuture, Scheduler} import play.api.libs.json.* @@ -34,7 +32,7 @@ import units.client.engine.EngineApiClient.PayloadId import units.client.engine.model.* import units.client.engine.model.Withdrawal.WithdrawalIndex import units.eth.{EmptyPayload, EthAddress, EthereumConstants} -import units.network.PayloadMessage +import units.network.PayloadObserver import units.network.PayloadObserverImpl.PayloadInfoWithChannel import units.util.HexBytesConverter import units.util.HexBytesConverter.toHexNoPrefix @@ -47,11 +45,10 @@ class ELUpdater( engineApiClient: EngineApiClient, blockchain: Blockchain, utx: UtxPool, - allChannels: DefaultChannelGroup, + payloadObserver: PayloadObserver, config: ClientConfig, time: Time, wallet: Wallet, - requestPayloadFromPeers: BlockHash => CancelableFuture[PayloadInfoWithChannel], broadcastTx: Transaction => TracedResult[ValidationError, Boolean], scheduler: Scheduler, globalScheduler: Scheduler @@ -67,7 +64,7 @@ class ELUpdater( def consensusLayerChanged(): Unit = handleNextUpdate := scheduler.scheduleOnce(ClChangedProcessingDelay)(handleConsensusLayerChanged()) - def executionPayloadReceived(epi: ExecutionPayloadInfo, ch: Channel): Unit = scheduler.execute { () => + def executionPayloadReceived(epi: ExecutionPayloadInfo): Unit = scheduler.execute { () => val payload = epi.payload logger.debug(s"New block ${payload.hash}->${payload.parentHash} (timestamp=${payload.timestamp}, height=${payload.height}) appeared") @@ -91,13 +88,13 @@ class ELUpdater( } case w @ Working(_, lastPayload, _, _, _, FollowingChain(nodeChainInfo, _), _, returnToMainChainInfo) if payload.parentHash == lastPayload.hash => - validateAndApply(epi, ch, w, lastPayload, nodeChainInfo, returnToMainChainInfo) + validateAndApply(epi, w, lastPayload, nodeChainInfo, returnToMainChainInfo) case w: Working[ChainStatus] => w.returnToMainChainInfo match { case Some(rInfo) if rInfo.missedBlock.hash == payload.hash => chainContractClient.getChainInfo(rInfo.chainId) match { case Some(chainInfo) if chainInfo.isMain => - validateAndApplyMissedBlock(epi, ch, w, rInfo.missedBlock, rInfo.missedBlockParentPayload, chainInfo) + validateAndApplyMissedBlock(epi, w, rInfo.missedBlock, rInfo.missedBlockParentPayload, chainInfo) case Some(_) => logger.debug(s"Chain ${rInfo.chainId} is not main anymore, ignoring ${payload.hash}") case _ => @@ -233,11 +230,7 @@ class ELUpdater( latestValidHashOpt <- engineApiClient.applyNewPayload(payloadJson) latestValidHash <- Either.fromOption(latestValidHashOpt, ClientError("Latest valid hash not defined")) _ = logger.info(s"Applied payload $payloadId, block hash is $latestValidHash, timestamp = $timestamp") - newPm <- PayloadMessage.signed(payloadJson, m.keyPair.privateKey).leftMap(ClientError.apply) - _ = logger.debug(s"Broadcasting block ${newPm.hash} payload") - _ <- Try(allChannels.broadcast(newPm)).toEither.leftMap(err => - ClientError(s"Failed to broadcast block ${newPm.hash} payload: ${err.toString}") - ) + newPm <- payloadObserver.broadcastSigned(payloadJson, m.keyPair.privateKey).leftMap(ClientError.apply) payloadInfo <- newPm.payloadInfo.leftMap(ClientError.apply) payload = payloadInfo.payload transfersRootHash <- getE2CTransfersRootHash(payload.hash, chainContractOptions.elBridgeAddress) @@ -785,10 +778,12 @@ class ELUpdater( } private def requestAndProcessPayload(hash: BlockHash): CancelableFuture[(Channel, ExecutionPayloadInfo)] = { - requestPayloadFromPeers(hash).andThen { - case Success((ch, epi)) => executionPayloadReceived(epi, ch) - case Failure(exception) => logger.error(s"Error loading block $hash payload", exception) - }(globalScheduler) + payloadObserver + .loadPayload(hash) + .andThen { + case Success((ch, epi)) => executionPayloadReceived(epi) + case Failure(exception) => logger.error(s"Error loading block $hash payload", exception) + }(globalScheduler) } private def updateToFollowChain( @@ -1023,7 +1018,6 @@ class ELUpdater( private def validateAndApplyMissedBlock( epi: ExecutionPayloadInfo, - ch: Channel, prevState: Working[ChainStatus], contractBlock: ContractBlock, parentPayload: ExecutionPayload, @@ -1033,7 +1027,7 @@ class ELUpdater( validateBlockFull(epi, contractBlock, parentPayload, prevState) match { case Right(updatedState) => logger.debug(s"Missed block ${payload.hash} of main chain ${nodeChainInfo.id} was successfully validated") - broadcastAndConfirmBlock(epi, ch, updatedState, nodeChainInfo, None) + broadcastAndConfirmBlock(epi, updatedState, nodeChainInfo, None) case Left(err) => logger.debug(s"Missed block ${payload.hash} of main chain ${nodeChainInfo.id} validation error: ${err.message}, ignoring block") } @@ -1041,7 +1035,6 @@ class ELUpdater( private def validateAndApply( epi: ExecutionPayloadInfo, - ch: Channel, prevState: Working[ChainStatus], parentPayload: ExecutionPayload, nodeChainInfo: ChainInfo, @@ -1054,7 +1047,7 @@ class ELUpdater( validateBlockFull(epi, contractBlock, parentPayload, prevState) match { case Right(updatedState) => logger.debug(s"Block ${payload.hash} was successfully validated") - broadcastAndConfirmBlock(epi, ch, updatedState, nodeChainInfo, returnToMainChainInfo) + broadcastAndConfirmBlock(epi, updatedState, nodeChainInfo, returnToMainChainInfo) case Left(err) => logger.debug(s"Block ${payload.hash} validation error: ${err.message}") processInvalidBlock(contractBlock, prevState, Some(nodeChainInfo)) @@ -1066,7 +1059,7 @@ class ELUpdater( preValidateBlock(epi, parentPayload, epochInfo) match { case Right(_) => logger.debug(s"Block ${payload.hash} was successfully partially validated") - broadcastAndConfirmBlock(epi, ch, prevState, nodeChainInfo, returnToMainChainInfo) + broadcastAndConfirmBlock(epi, prevState, nodeChainInfo, returnToMainChainInfo) case Left(err) => logger.error(s"Block ${payload.hash} prevalidation error: ${err.message}, ignoring block") } @@ -1102,21 +1095,12 @@ class ELUpdater( private def broadcastAndConfirmBlock( epi: ExecutionPayloadInfo, - ch: Channel, prevState: Working[ChainStatus], nodeChainInfo: ChainInfo, returnToMainChainInfo: Option[ReturnToMainChainInfo] ): Unit = { - val payload = epi.payload - - (for { - pm <- PayloadMessage(epi.payloadJson, epi.signature) - _ <- Try(allChannels.broadcast(pm, Some(ch))).toEither.leftMap(_.getMessage) - } yield ()).recover { err => - logger.error(s"Failed to broadcast block ${payload.hash} payload: $err") - } - - confirmBlockAndFollowChain(payload, prevState, nodeChainInfo, returnToMainChainInfo) + payloadObserver.broadcast(epi.payload.hash) + confirmBlockAndFollowChain(epi.payload, prevState, nodeChainInfo, returnToMainChainInfo) } private def findBlockChild(parent: BlockHash, lastBlockHash: BlockHash): Either[String, ContractBlock] = { diff --git a/src/main/scala/units/network/MessageObserver.scala b/src/main/scala/units/network/MessageObserver.scala index 9597680c..8d2dda82 100644 --- a/src/main/scala/units/network/MessageObserver.scala +++ b/src/main/scala/units/network/MessageObserver.scala @@ -12,15 +12,11 @@ class MessageObserver extends ChannelInboundHandlerAdapter with ScorexLogging { private implicit val scheduler: SchedulerService = Schedulers.fixedPool(2, "message-observer-l2") - val payloads: Subject[(Channel, ExecutionPayloadInfo), (Channel, ExecutionPayloadInfo)] = ConcurrentSubject.publish[(Channel, ExecutionPayloadInfo)] + val payloads: Subject[PayloadMessageWithChannel, PayloadMessageWithChannel] = ConcurrentSubject.publish[PayloadMessageWithChannel] override def channelRead(ctx: ChannelHandlerContext, msg: AnyRef): Unit = msg match { - case pm: PayloadMessage => - pm.payloadInfo match { - case Right(epi) => payloads.onNext((ctx.channel(), epi)) - case Left(err) => log.warn(err) - } - case _ => super.channelRead(ctx, msg) + case pm: PayloadMessage => payloads.onNext(PayloadMessageWithChannel(pm, ctx.channel())) + case _ => super.channelRead(ctx, msg) } def shutdown(): Unit = { diff --git a/src/main/scala/units/network/PayloadMessage.scala b/src/main/scala/units/network/PayloadMessage.scala index 0667bb83..48387dad 100644 --- a/src/main/scala/units/network/PayloadMessage.scala +++ b/src/main/scala/units/network/PayloadMessage.scala @@ -2,7 +2,7 @@ package units.network import cats.syntax.either.* import com.google.common.primitives.Bytes -import com.wavesplatform.account.PrivateKey +import com.wavesplatform.account.{PrivateKey, PublicKey} import com.wavesplatform.common.state.ByteStr import com.wavesplatform.crypto import com.wavesplatform.crypto.SignatureLength @@ -17,8 +17,11 @@ import scala.util.Try class PayloadMessage private ( payloadJson: JsObject, val hash: BlockHash, + val feeRecipient: EthAddress, val signature: Option[ByteStr] ) { + lazy val jsonBytes: Array[Byte] = Json.toBytes(payloadJson) + lazy val payloadInfo: Either[String, ExecutionPayloadInfo] = { (for { timestamp <- (payloadJson \ "timestamp").asOpt[String].map(toLong).toRight("timestamp not defined") @@ -54,22 +57,26 @@ class PayloadMessage private ( def toBytes: Array[Byte] = { val signatureBytes = signature.map(sig => Bytes.concat(Array(1.toByte), sig.arr)).getOrElse(Array(0.toByte)) - Bytes.concat(signatureBytes, Json.toBytes(payloadJson)) + Bytes.concat(signatureBytes, jsonBytes) } + + def isSignatureValid(pk: PublicKey): Boolean = + signature.exists(crypto.verify(_, jsonBytes, pk, checkWeakPk = true)) } object PayloadMessage { def apply(payloadJson: JsObject): Either[String, PayloadMessage] = apply(payloadJson, None) - def apply(payloadJson: JsObject, hash: BlockHash, signature: Option[ByteStr]): PayloadMessage = - new PayloadMessage(payloadJson, hash, signature) + def apply(payloadJson: JsObject, hash: BlockHash, feeRecipient: EthAddress, signature: Option[ByteStr]): PayloadMessage = + new PayloadMessage(payloadJson, hash, feeRecipient, signature) def apply(payloadJson: JsObject, signature: Option[ByteStr]): Either[String, PayloadMessage] = - (payloadJson \ "blockHash") - .asOpt[BlockHash] - .toRight("Error creating payload message: block hash not defined") - .map(PayloadMessage(payloadJson, _, signature)) + (for { + hash <- (payloadJson \ "blockHash").asOpt[BlockHash].toRight("block hash not defined") + feeRecipient <- (payloadJson \ "feeRecipient").asOpt[EthAddress].toRight("fee recipient not defined") + } yield PayloadMessage(payloadJson, hash, feeRecipient, signature)) + .leftMap(err => s"Error creating payload message: $err") def apply(payloadBytes: Array[Byte], signature: Option[ByteStr]): Either[String, PayloadMessage] = for { payload <- Try(Json.parse(payloadBytes).as[JsObject]).toEither.leftMap(err => s"Payload bytes are not a valid JSON object: ${err.getMessage}") diff --git a/src/main/scala/units/network/PayloadMessageWithChannel.scala b/src/main/scala/units/network/PayloadMessageWithChannel.scala new file mode 100644 index 00000000..c96f7e05 --- /dev/null +++ b/src/main/scala/units/network/PayloadMessageWithChannel.scala @@ -0,0 +1,5 @@ +package units.network + +import io.netty.channel.Channel + +case class PayloadMessageWithChannel(pm: PayloadMessage, ch: Channel) diff --git a/src/main/scala/units/network/PayloadObserver.scala b/src/main/scala/units/network/PayloadObserver.scala index bc037920..cf9b56ee 100644 --- a/src/main/scala/units/network/PayloadObserver.scala +++ b/src/main/scala/units/network/PayloadObserver.scala @@ -1,12 +1,17 @@ package units.network -import units.network.PayloadObserverImpl.PayloadInfoWithChannel -import com.wavesplatform.network.ChannelObservable +import com.wavesplatform.account.PrivateKey import monix.execution.CancelableFuture +import monix.reactive.Observable +import play.api.libs.json.JsObject import units.{BlockHash, ExecutionPayloadInfo} trait PayloadObserver { - def getPayloadStream: ChannelObservable[ExecutionPayloadInfo] + def getPayloadStream: Observable[ExecutionPayloadInfo] - def loadPayload(req: BlockHash): CancelableFuture[PayloadInfoWithChannel] + def loadPayload(req: BlockHash): CancelableFuture[ExecutionPayloadInfo] + + def broadcastSigned(payloadJson: JsObject, signer: PrivateKey): Either[String, PayloadMessage] + + def broadcast(hash: BlockHash): Unit } diff --git a/src/main/scala/units/network/PayloadObserverImpl.scala b/src/main/scala/units/network/PayloadObserverImpl.scala index 7de42060..4310aba2 100644 --- a/src/main/scala/units/network/PayloadObserverImpl.scala +++ b/src/main/scala/units/network/PayloadObserverImpl.scala @@ -1,55 +1,86 @@ package units.network +import cats.syntax.either.* import com.google.common.cache.CacheBuilder -import com.wavesplatform.network.ChannelObservable +import com.wavesplatform.account.{PrivateKey, PublicKey} +import com.wavesplatform.network.ChannelGroupExt import com.wavesplatform.utils.ScorexLogging import io.netty.channel.Channel import io.netty.channel.group.DefaultChannelGroup import monix.eval.Task import monix.execution.{Cancelable, CancelableFuture, CancelablePromise, Scheduler} +import monix.reactive.Observable import monix.reactive.subjects.ConcurrentSubject -import units.network.PayloadObserverImpl.{PayloadInfoWithChannel, State} +import play.api.libs.json.JsObject +import units.eth.EthAddress +import units.network.PayloadObserverImpl.State import units.{BlockHash, ExecutionPayloadInfo} import java.time.Duration +import java.util.concurrent.ConcurrentHashMap import scala.concurrent.duration.FiniteDuration import scala.jdk.CollectionConverters.* -import scala.util.{Failure, Success} - -class PayloadObserverImpl(allChannels: DefaultChannelGroup, payloads: ChannelObservable[ExecutionPayloadInfo], syncTimeout: FiniteDuration)(implicit - sc: Scheduler -) extends PayloadObserver +import scala.util.{Failure, Success, Try} + +class PayloadObserverImpl( + allChannels: DefaultChannelGroup, + payloads: Observable[PayloadMessageWithChannel], + initialMinersKeys: Map[EthAddress, PublicKey], + syncTimeout: FiniteDuration +)(implicit sc: Scheduler) + extends PayloadObserver with ScorexLogging { - private var state: State = State.Idle(None) - private val payloadsResult: ConcurrentSubject[PayloadInfoWithChannel, PayloadInfoWithChannel] = - ConcurrentSubject.publish[PayloadInfoWithChannel] + private var state: State = State.Idle(None) + private var lastPayloadMessage: Option[PayloadMessageWithChannel] = None + + private val payloadsResult: ConcurrentSubject[ExecutionPayloadInfo, ExecutionPayloadInfo] = + ConcurrentSubject.publish[ExecutionPayloadInfo] + private val addrToPK: ConcurrentHashMap[EthAddress, PublicKey] = new ConcurrentHashMap[EthAddress, PublicKey](initialMinersKeys.asJava) private val knownPayloadCache = CacheBuilder .newBuilder() .expireAfterWrite(Duration.ofMinutes(10)) .maximumSize(100) - .build[BlockHash, PayloadInfoWithChannel]() + .build[BlockHash, ExecutionPayloadInfo]() payloads - .foreach { case v @ (ch, epi) => + .foreach { case PayloadMessageWithChannel(pm, ch) => state = state match { - case State.LoadingPayload(expectedHash, nextAttempt, p) if expectedHash == epi.payload.hash => - nextAttempt.cancel() - p.complete(Success(ch -> epi)) - State.Idle(Some(ch)) - case other => other + case State.LoadingPayload(expectedHash, nextAttempt, p) if expectedHash == pm.hash => + pm.payloadInfo match { + case Right(epi) => + nextAttempt.cancel() + p.complete(Success(epi)) + knownPayloadCache.put(pm.hash, epi) + payloadsResult.onNext(epi) + State.Idle(Some(ch)) + } + case other => + Option(addrToPK.get(pm.feeRecipient)) match { + case Some(pk) => + if (pm.isSignatureValid(pk)) { + pm.payloadInfo match { + case Right(epi) => + knownPayloadCache.put(pm.hash, epi) + payloadsResult.onNext(epi) + case Left(err) => log.debug(err) + } + } else { + log.debug(s"Invalid signature for payload ${pm.hash}") + } + case None => + log.debug(s"Payload ${pm.hash} fee recipient ${pm.feeRecipient} is unknown miner") + } + other } - - knownPayloadCache.put(epi.payload.hash, v) - payloadsResult.onNext(v) } - def loadPayload(req: BlockHash): CancelableFuture[PayloadInfoWithChannel] = { + def loadPayload(req: BlockHash): CancelableFuture[ExecutionPayloadInfo] = { log.info(s"Loading payload $req") - knownPayloadCache.getIfPresent(req) match { - case null => - val p = CancelablePromise[PayloadInfoWithChannel]() + Option(knownPayloadCache.getIfPresent(req)) match { + case None => + val p = CancelablePromise[ExecutionPayloadInfo]() sc.execute { () => val candidate = state match { case State.LoadingPayload(_, nextAttempt, promise) => @@ -61,12 +92,30 @@ class PayloadObserverImpl(allChannels: DefaultChannelGroup, payloads: ChannelObs state = State.LoadingPayload(req, requestFromNextChannel(req, candidate, Set.empty).runToFuture, p) } p.future - case (ch, pm) => - CancelablePromise.successful(ch -> pm).future + case Some(epi) => + CancelablePromise.successful(epi).future } } - def getPayloadStream: ChannelObservable[ExecutionPayloadInfo] = payloadsResult + def getPayloadStream: Observable[ExecutionPayloadInfo] = payloadsResult + + def broadcastSigned(payloadJson: JsObject, signer: PrivateKey): Either[String, PayloadMessage] = for { + pm <- PayloadMessage.signed(payloadJson, signer) + _ = log.debug(s"Broadcasting block ${pm.hash} payload") + _ <- Try(allChannels.broadcast(pm)).toEither.leftMap(err => s"Failed to broadcast block ${pm.hash} payload: ${err.toString}") + } yield pm + + override def broadcast(hash: BlockHash): Unit = { + (for { + payloadWithChannel <- lastPayloadMessage.toRight("No prepared for broadcast payload") + _ <- Either.cond(hash == payloadWithChannel.pm.hash, (), s"Payload for block $hash is not last received") + _ = log.debug(s"Broadcasting block ${payloadWithChannel.pm.hash} payload") + _ <- Try(allChannels.broadcast(payloadWithChannel.pm, Some(payloadWithChannel.ch))).toEither.leftMap(_.getMessage) + } yield ()).fold( + err => log.error(s"Failed to broadcast last received payload: $err"), + identity + ) + } // TODO: remove Task private def requestFromNextChannel(req: BlockHash, candidate: Option[Channel], excluded: Set[Channel]): Task[Unit] = Task { @@ -92,7 +141,7 @@ object PayloadObserverImpl { sealed trait State object State { - case class LoadingPayload(blockHash: BlockHash, nextAttempt: Cancelable, promise: CancelablePromise[PayloadInfoWithChannel]) extends State + case class LoadingPayload(blockHash: BlockHash, nextAttempt: Cancelable, promise: CancelablePromise[ExecutionPayloadInfo]) extends State case class Idle(pinnedChannel: Option[Channel]) extends State } diff --git a/src/test/scala/units/network/TestPayloadObserver.scala b/src/test/scala/units/network/TestPayloadObserver.scala index 06fd1e7d..11ed702f 100644 --- a/src/test/scala/units/network/TestPayloadObserver.scala +++ b/src/test/scala/units/network/TestPayloadObserver.scala @@ -1,10 +1,12 @@ package units.network +import com.wavesplatform.account.PrivateKey import com.wavesplatform.network.ChannelObservable import com.wavesplatform.utils.ScorexLogging import io.netty.channel.Channel import monix.eval.Task import monix.execution.CancelableFuture +import play.api.libs.json.JsObject import units.network.PayloadObserverImpl.PayloadInfoWithChannel import units.{BlockHash, ExecutionPayloadInfo} @@ -18,4 +20,13 @@ class TestPayloadObserver(override val getPayloadStream: ChannelObservable[Execu log.debug(s"requestBlock($req)") Task.never } + + override def broadcastSigned(payloadJson: JsObject, signer: PrivateKey): Either[String, PayloadMessage] = { + log.debug(s"broadcastSigned($payloadJson, $signer)") + PayloadMessage.signed(payloadJson, signer) + } + + override def broadcast(hash: BlockHash): Unit = { + log.debug(s"broadcast($hash)") + } } From ed0bed0e7bbc3a693c77440ea6b274016ad2a6db Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Fri, 27 Sep 2024 18:23:49 +0300 Subject: [PATCH 11/14] Signature is on network level --- src/main/scala/units/ConsensusClient.scala | 12 ++++- src/main/scala/units/ELUpdater.scala | 49 +++++++------------ .../scala/units/ExecutionPayloadInfo.scala | 3 +- .../client/contract/ChainContractClient.scala | 13 +++++ .../scala/units/network/MessageObserver.scala | 3 +- .../scala/units/network/PayloadMessage.scala | 3 +- .../scala/units/network/PayloadObserver.scala | 5 +- .../units/network/PayloadObserverImpl.scala | 24 ++++++--- src/test/scala/units/ExtensionDomain.scala | 24 ++++----- .../units/network/TestPayloadObserver.scala | 49 ++++++++++++++----- 10 files changed, 114 insertions(+), 71 deletions(-) diff --git a/src/main/scala/units/ConsensusClient.scala b/src/main/scala/units/ConsensusClient.scala index 551f5f72..cfc56148 100644 --- a/src/main/scala/units/ConsensusClient.scala +++ b/src/main/scala/units/ConsensusClient.scala @@ -16,6 +16,7 @@ import net.ceedubs.ficus.Ficus.* import org.slf4j.LoggerFactory import sttp.client3.HttpClientSyncBackend import units.client.JwtAuthenticationBackend +import units.client.contract.{ChainContractClient, ChainContractStateClient} import units.client.engine.{EngineApiClient, HttpEngineApiClient, LoggedEngineApiClient} import units.network.* @@ -27,6 +28,7 @@ class ConsensusClient( config: ClientConfig, context: ExtensionContext, engineApiClient: EngineApiClient, + chainContractClient: ChainContractClient, payloadObserver: PayloadObserver, globalScheduler: Scheduler, eluScheduler: Scheduler, @@ -39,6 +41,7 @@ class ConsensusClient( deps.config, context, deps.engineApiClient, + deps.chainContractClient, deps.payloadObserver, deps.globalScheduler, deps.eluScheduler, @@ -50,6 +53,7 @@ class ConsensusClient( private[units] val elu = new ELUpdater( engineApiClient, + chainContractClient, context.blockchain, context.utx, payloadObserver, @@ -115,6 +119,9 @@ class ConsensusClientDependencies(context: ExtensionContext) extends AutoCloseab val engineApiClient = new LoggedEngineApiClient(new HttpEngineApiClient(config, maybeAuthenticatedBackend)) + private val contractAddress = config.chainContractAddress + val chainContractClient = new ChainContractStateClient(contractAddress, context.blockchain) + val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) val peerDatabase = new PeerDatabaseImpl(config.network) val messageObserver = new MessageObserver() @@ -127,7 +134,10 @@ class ConsensusClientDependencies(context: ExtensionContext) extends AutoCloseab new ConcurrentHashMap[Channel, PeerInfo] ) - val payloadObserver = new PayloadObserverImpl(allChannels, messageObserver.payloads, config.blockSyncRequestTimeout)(payloadObserverScheduler) + val payloadObserver = + new PayloadObserverImpl(allChannels, messageObserver.payloads, chainContractClient.getMinersPks, config.blockSyncRequestTimeout)( + payloadObserverScheduler + ) override def close(): Unit = { log.info("Closing HTTP/Engine API") diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index 7190132d..7b6589a7 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -19,10 +19,8 @@ import com.wavesplatform.transaction.{Asset, Proofs, Transaction, TransactionSig import com.wavesplatform.utils.{EthEncoding, Time, UnsupportedFeature, forceStopApplication} import com.wavesplatform.utx.UtxPool import com.wavesplatform.wallet.Wallet -import io.netty.channel.Channel import monix.execution.cancelables.SerialCancelable import monix.execution.{CancelableFuture, Scheduler} -import play.api.libs.json.* import units.ELUpdater.State.* import units.ELUpdater.State.ChainStatus.{FollowingChain, Mining, WaitForNewChain} import units.client.CommonBlockData @@ -33,7 +31,6 @@ import units.client.engine.model.* import units.client.engine.model.Withdrawal.WithdrawalIndex import units.eth.{EmptyPayload, EthAddress, EthereumConstants} import units.network.PayloadObserver -import units.network.PayloadObserverImpl.PayloadInfoWithChannel import units.util.HexBytesConverter import units.util.HexBytesConverter.toHexNoPrefix @@ -43,6 +40,7 @@ import scala.util.* class ELUpdater( engineApiClient: EngineApiClient, + chainContractClient: ChainContractClient, blockchain: Blockchain, utx: UtxPool, payloadObserver: PayloadObserver, @@ -55,9 +53,7 @@ class ELUpdater( ) extends StrictLogging { import ELUpdater.* - private val handleNextUpdate = SerialCancelable() - private val contractAddress = config.chainContractAddress - private val chainContractClient = new ChainContractStateClient(contractAddress, blockchain) + private val handleNextUpdate = SerialCancelable() private[units] var state: State = Starting @@ -152,7 +148,7 @@ class ELUpdater( // Removing here, because we have these transactions in PP after the onProcessBlock trigger utx.getPriorityPool.foreach { pp => val staleTxs = pp.priorityTransactions.filter { - case tx: InvokeScriptTransaction => tx.dApp == contractAddress + case tx: InvokeScriptTransaction => tx.dApp == config.chainContractAddress case _ => false } @@ -169,7 +165,7 @@ class ELUpdater( val tx = InvokeScriptTransaction( TxVersion.V2, invoker.publicKey, - contractAddress, + config.chainContractAddress, Some(fc), Seq.empty, TxPositiveAmount.unsafeFrom(FeeConstants(TransactionType.InvokeScript) * FeeUnit + extraFee), @@ -178,7 +174,9 @@ class ELUpdater( Proofs.empty, blockchain.settings.addressSchemeCharacter.toByte ).signWith(invoker.privateKey) - logger.info(s"Invoking $contractAddress '${fc.function.funcName}' for block ${payload.hash}->${payload.parentHash}, txId=${tx.id()}") + logger.info( + s"Invoking ${config.chainContractAddress} '${fc.function.funcName}' for block ${payload.hash}->${payload.parentHash}, txId=${tx.id()}" + ) cleanPriorityPool() broadcastTx(tx).resultE match { @@ -477,6 +475,7 @@ class ELUpdater( } private def handleConsensusLayerChanged(): Unit = { + payloadObserver.updateMinerPublicKeys(chainContractClient.getMinersPks) state match { case Starting => updateStartingState() case w: Working[ChainStatus] => updateWorkingState(w) @@ -484,7 +483,7 @@ class ELUpdater( } } - private def findAltChain(prevChainId: Long, referenceBlock: BlockHash) = { + private def findAltChain(prevChainId: Long, referenceBlock: BlockHash): Option[ChainInfo] = { logger.debug(s"Trying to find alternative chain referencing $referenceBlock") val lastChainId = chainContractClient.getLastChainId @@ -777,11 +776,11 @@ class ELUpdater( } } - private def requestAndProcessPayload(hash: BlockHash): CancelableFuture[(Channel, ExecutionPayloadInfo)] = { + private def requestAndProcessPayload(hash: BlockHash): CancelableFuture[ExecutionPayloadInfo] = { payloadObserver .loadPayload(hash) .andThen { - case Success((ch, epi)) => executionPayloadReceived(epi) + case Success(epi) => executionPayloadReceived(epi) case Failure(exception) => logger.error(s"Error loading block $hash payload", exception) }(globalScheduler) } @@ -943,23 +942,11 @@ class ELUpdater( val payload = epi.payload epochInfo match { case Some(epochMeta) => - for { - _ <- Either.cond( - payload.feeRecipient == epochMeta.rewardAddress, - (), - ClientError(s"block miner ${payload.feeRecipient} doesn't equal to ${epochMeta.rewardAddress}") - ) - signature <- Either.fromOption(epi.signature, ClientError(s"signature not found")) - publicKey <- Either.fromOption( - chainContractClient.getMinerPublicKey(payload.feeRecipient), - ClientError(s"public key for block miner ${payload.feeRecipient} not found") - ) - _ <- Either.cond( - crypto.verify(signature, Json.toBytes(epi.payloadJson), publicKey, checkWeakPk = true), - (), - ClientError(s"invalid signature") - ) - } yield () + Either.cond( + payload.feeRecipient == epochMeta.rewardAddress, + (), + ClientError(s"block miner ${payload.feeRecipient} doesn't equal to ${epochMeta.rewardAddress}") + ) case _ => Either.unit } } @@ -1556,8 +1543,8 @@ object ELUpdater { } } - case class WaitingForSyncHead(target: ContractBlock, task: CancelableFuture[PayloadInfoWithChannel]) extends State - case class SyncingToFinalizedBlock(target: BlockHash) extends State + case class WaitingForSyncHead(target: ContractBlock, task: CancelableFuture[ExecutionPayloadInfo]) extends State + case class SyncingToFinalizedBlock(target: BlockHash) extends State } private case class RollbackBlock(hash: BlockHash, parentPayload: ExecutionPayload) diff --git a/src/main/scala/units/ExecutionPayloadInfo.scala b/src/main/scala/units/ExecutionPayloadInfo.scala index fe0e9b40..202a34e5 100644 --- a/src/main/scala/units/ExecutionPayloadInfo.scala +++ b/src/main/scala/units/ExecutionPayloadInfo.scala @@ -1,7 +1,6 @@ package units -import com.wavesplatform.common.state.ByteStr import play.api.libs.json.JsObject import units.client.engine.model.ExecutionPayload -case class ExecutionPayloadInfo(payload: ExecutionPayload, payloadJson: JsObject, signature: Option[ByteStr]) +case class ExecutionPayloadInfo(payload: ExecutionPayload, payloadJson: JsObject) diff --git a/src/main/scala/units/client/contract/ChainContractClient.scala b/src/main/scala/units/client/contract/ChainContractClient.scala index 8c328191..214d0752 100644 --- a/src/main/scala/units/client/contract/ChainContractClient.scala +++ b/src/main/scala/units/client/contract/ChainContractClient.scala @@ -54,6 +54,10 @@ trait ChainContractClient { case BinaryDataEntry(_, v) => EthAddress.unsafeFrom(v.arr) } + def getPublicKey(rewardAddress: EthAddress): Option[PublicKey] = { + getBinaryData(s"miner_${rewardAddress}_PK").map(PublicKey(_)) + } + def getBlock(hash: BlockHash): Option[ContractBlock] = getBinaryData(s"block_$hash").orElse(getBinaryData(s"blockMeta${clean(hash)}")).map { blockMeta => val bb = ByteBuffer.wrap(blockMeta.arr) @@ -212,6 +216,15 @@ trait ChainContractClient { def getNativeTransfers(fromIndex: Long, maxItems: Long): Vector[ContractTransfer] = (fromIndex until math.min(fromIndex + maxItems, getNativeTransfersCount)).map(requireNativeTransfer).toVector + def getMinersPks: Map[EthAddress, PublicKey] = { + getAllActualMiners.flatMap { addr => + for { + rewardAddress <- getElRewardAddress(addr) + publicKey <- getPublicKey(rewardAddress) + } yield rewardAddress -> publicKey + }.toMap + } + private def getNativeTransfersCount: Long = getLongData("nativeTransfersCount").getOrElse(0L) private def requireNativeTransfer(atIndex: Long): ContractTransfer = { diff --git a/src/main/scala/units/network/MessageObserver.scala b/src/main/scala/units/network/MessageObserver.scala index 8d2dda82..709e798a 100644 --- a/src/main/scala/units/network/MessageObserver.scala +++ b/src/main/scala/units/network/MessageObserver.scala @@ -2,10 +2,9 @@ package units.network import com.wavesplatform.utils.{Schedulers, ScorexLogging} import io.netty.channel.ChannelHandler.Sharable -import io.netty.channel.{Channel, ChannelHandlerContext, ChannelInboundHandlerAdapter} +import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter} import monix.execution.schedulers.SchedulerService import monix.reactive.subjects.{ConcurrentSubject, Subject} -import units.ExecutionPayloadInfo @Sharable class MessageObserver extends ChannelInboundHandlerAdapter with ScorexLogging { diff --git a/src/main/scala/units/network/PayloadMessage.scala b/src/main/scala/units/network/PayloadMessage.scala index 48387dad..41366f33 100644 --- a/src/main/scala/units/network/PayloadMessage.scala +++ b/src/main/scala/units/network/PayloadMessage.scala @@ -49,8 +49,7 @@ class PayloadMessage private ( prevRandao, withdrawals ), - payloadJson, - signature + payloadJson ) }).leftMap(err => s"Error creating payload info for block $hash: $err") } diff --git a/src/main/scala/units/network/PayloadObserver.scala b/src/main/scala/units/network/PayloadObserver.scala index cf9b56ee..e838153c 100644 --- a/src/main/scala/units/network/PayloadObserver.scala +++ b/src/main/scala/units/network/PayloadObserver.scala @@ -1,9 +1,10 @@ package units.network -import com.wavesplatform.account.PrivateKey +import com.wavesplatform.account.{PrivateKey, PublicKey} import monix.execution.CancelableFuture import monix.reactive.Observable import play.api.libs.json.JsObject +import units.eth.EthAddress import units.{BlockHash, ExecutionPayloadInfo} trait PayloadObserver { @@ -14,4 +15,6 @@ trait PayloadObserver { def broadcastSigned(payloadJson: JsObject, signer: PrivateKey): Either[String, PayloadMessage] def broadcast(hash: BlockHash): Unit + + def updateMinerPublicKeys(newKeys: Map[EthAddress, PublicKey]): Unit } diff --git a/src/main/scala/units/network/PayloadObserverImpl.scala b/src/main/scala/units/network/PayloadObserverImpl.scala index 4310aba2..c95125d1 100644 --- a/src/main/scala/units/network/PayloadObserverImpl.scala +++ b/src/main/scala/units/network/PayloadObserverImpl.scala @@ -31,8 +31,9 @@ class PayloadObserverImpl( extends PayloadObserver with ScorexLogging { - private var state: State = State.Idle(None) - private var lastPayloadMessage: Option[PayloadMessageWithChannel] = None + private var state: State = State.Idle(None) + private val lastPayloadMessages: ConcurrentHashMap[BlockHash, PayloadMessageWithChannel] = + new ConcurrentHashMap[BlockHash, PayloadMessageWithChannel]() private val payloadsResult: ConcurrentSubject[ExecutionPayloadInfo, ExecutionPayloadInfo] = ConcurrentSubject.publish[ExecutionPayloadInfo] @@ -45,23 +46,26 @@ class PayloadObserverImpl( .build[BlockHash, ExecutionPayloadInfo]() payloads - .foreach { case PayloadMessageWithChannel(pm, ch) => + .foreach { case v @ PayloadMessageWithChannel(pm, ch) => state = state match { case State.LoadingPayload(expectedHash, nextAttempt, p) if expectedHash == pm.hash => pm.payloadInfo match { case Right(epi) => nextAttempt.cancel() p.complete(Success(epi)) + lastPayloadMessages.put(pm.hash, v) knownPayloadCache.put(pm.hash, epi) payloadsResult.onNext(epi) - State.Idle(Some(ch)) + case Left(err) => log.debug(err) } + State.Idle(Some(ch)) case other => Option(addrToPK.get(pm.feeRecipient)) match { case Some(pk) => if (pm.isSignatureValid(pk)) { pm.payloadInfo match { case Right(epi) => + lastPayloadMessages.put(pm.hash, v) knownPayloadCache.put(pm.hash, epi) payloadsResult.onNext(epi) case Left(err) => log.debug(err) @@ -107,7 +111,7 @@ class PayloadObserverImpl( override def broadcast(hash: BlockHash): Unit = { (for { - payloadWithChannel <- lastPayloadMessage.toRight("No prepared for broadcast payload") + payloadWithChannel <- Option(lastPayloadMessages.get(hash)).toRight(s"No prepared for broadcast payload $hash") _ <- Either.cond(hash == payloadWithChannel.pm.hash, (), s"Payload for block $hash is not last received") _ = log.debug(s"Broadcasting block ${payloadWithChannel.pm.hash} payload") _ <- Try(allChannels.broadcast(payloadWithChannel.pm, Some(payloadWithChannel.ch))).toEither.leftMap(_.getMessage) @@ -115,6 +119,13 @@ class PayloadObserverImpl( err => log.error(s"Failed to broadcast last received payload: $err"), identity ) + + lastPayloadMessages.remove(hash) + } + + def updateMinerPublicKeys(newKeys: Map[EthAddress, PublicKey]): Unit = { + addrToPK.clear() + addrToPK.putAll(newKeys.asJava) } // TODO: remove Task @@ -135,9 +146,6 @@ class PayloadObserverImpl( } object PayloadObserverImpl { - - type PayloadInfoWithChannel = (Channel, ExecutionPayloadInfo) - sealed trait State object State { diff --git a/src/test/scala/units/ExtensionDomain.scala b/src/test/scala/units/ExtensionDomain.scala index c9d7b43f..72e60491 100644 --- a/src/test/scala/units/ExtensionDomain.scala +++ b/src/test/scala/units/ExtensionDomain.scala @@ -23,7 +23,6 @@ import com.wavesplatform.transaction.{DiscardedBlocks, Transaction} import com.wavesplatform.utils.{ScorexLogging, Time} import com.wavesplatform.utx.UtxPool import com.wavesplatform.wallet.Wallet -import io.netty.channel.Channel import io.netty.channel.embedded.EmbeddedChannel import io.netty.channel.group.DefaultChannelGroup import io.netty.util.concurrent.GlobalEventExecutor @@ -39,7 +38,7 @@ import play.api.libs.json.* import units.ELUpdater.* import units.ELUpdater.State.{ChainStatus, Working} import units.ExtensionDomain.* -import units.client.contract.HasConsensusLayerDappTxHelpers +import units.client.contract.{ChainContractStateClient, HasConsensusLayerDappTxHelpers} import units.client.contract.HasConsensusLayerDappTxHelpers.EmptyE2CTransfersRootHashHex import units.client.engine.model.{ExecutionPayload, TestPayloads} import units.client.{CommonBlockData, TestEcClients} @@ -89,16 +88,17 @@ class ExtensionDomain( val globalScheduler: TestScheduler = TestScheduler(ExecutionModel.AlwaysAsyncExecution) val eluScheduler: TestScheduler = TestScheduler(ExecutionModel.AlwaysAsyncExecution) - val payloadStream: PublishSubject[(Channel, ExecutionPayloadInfo)] = PublishSubject[(Channel, ExecutionPayloadInfo)]() - val blockObserver: TestPayloadObserver = new TestPayloadObserver(payloadStream) - val neighbourChannel = new EmbeddedChannel() val allChannels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE) allChannels.add(neighbourChannel) + + val payloadStream: PublishSubject[PayloadMessage] = PublishSubject[PayloadMessage]() + val payloadObserver: TestPayloadObserver = new TestPayloadObserver(payloadStream, allChannels) + def pollSentPayloadMessage(): Option[PayloadMessage] = Option(neighbourChannel.readOutbound[PayloadMessage]) def receivePayload(payload: ExecutionPayload, miner: SeedKeyPair, epochNumber: Int = blockchain.height): Unit = - receivePayload(toExecutionPayloadInfo(payload, miner, epochNumber)) - def receivePayload(incomingPayload: ExecutionPayloadInfo): Unit = payloadStream.onNext((new EmbeddedChannel(), incomingPayload)) + receivePayload(toPayloadMessage(payload, miner, epochNumber)) + def receivePayload(incomingPayload: PayloadMessage): Unit = payloadStream.onNext(incomingPayload) val extensionContext: Context = new Context { override def settings: WavesSettings = self.settings @@ -119,12 +119,14 @@ class ExtensionDomain( override def utxEvents: Observable[UtxEvent] = Observable.empty } + val chainContractClient = new ChainContractStateClient(chainContractAddress, blockchain) + val consensusClient: ConsensusClient = new ConsensusClient( l2Config, extensionContext, ecClients.engineApi, - blockObserver, - allChannels, + chainContractClient, + payloadObserver, globalScheduler, eluScheduler, () => {} @@ -169,7 +171,7 @@ class ExtensionDomain( f(is[CS](s.chainStatus)) } - def toExecutionPayloadInfo(payload: ExecutionPayload, miner: SeedKeyPair, epochNumber: Int): ExecutionPayloadInfo = { + def toPayloadMessage(payload: ExecutionPayload, miner: SeedKeyPair, epochNumber: Int): PayloadMessage = { val payloadJson = TestPayloads.toPayloadJson( payload, calculateRandao( @@ -178,7 +180,7 @@ class ExtensionDomain( ) ) - PayloadMessage.signed(payloadJson, miner.privateKey).flatMap(_.payloadInfo).explicitGet() + PayloadMessage.signed(payloadJson, miner.privateKey).explicitGet() } def forgeFromUtxPool(): Unit = { diff --git a/src/test/scala/units/network/TestPayloadObserver.scala b/src/test/scala/units/network/TestPayloadObserver.scala index 11ed702f..e8033cec 100644 --- a/src/test/scala/units/network/TestPayloadObserver.scala +++ b/src/test/scala/units/network/TestPayloadObserver.scala @@ -1,32 +1,55 @@ package units.network -import com.wavesplatform.account.PrivateKey -import com.wavesplatform.network.ChannelObservable +import com.wavesplatform.account.{PrivateKey, PublicKey} +import com.wavesplatform.network.ChannelGroupExt import com.wavesplatform.utils.ScorexLogging -import io.netty.channel.Channel -import monix.eval.Task +import io.netty.channel.group.DefaultChannelGroup import monix.execution.CancelableFuture +import monix.reactive.Observable import play.api.libs.json.JsObject -import units.network.PayloadObserverImpl.PayloadInfoWithChannel +import units.eth.EthAddress import units.{BlockHash, ExecutionPayloadInfo} -class TestPayloadObserver(override val getPayloadStream: ChannelObservable[ExecutionPayloadInfo]) extends PayloadObserver with ScorexLogging { - override def loadPayload(req: BlockHash): CancelableFuture[(Channel, ExecutionPayloadInfo)] = { +import java.util.concurrent.ConcurrentHashMap + +class TestPayloadObserver(messages: Observable[PayloadMessage], allChannels: DefaultChannelGroup) extends PayloadObserver with ScorexLogging { + + private val lastPayloadMessages: ConcurrentHashMap[BlockHash, PayloadMessage] = + new ConcurrentHashMap[BlockHash, PayloadMessage]() + + override def loadPayload(req: BlockHash): CancelableFuture[ExecutionPayloadInfo] = { log.debug(s"loadBlock($req)") CancelableFuture.never } - def requestPayload(req: BlockHash): Task[PayloadInfoWithChannel] = { - log.debug(s"requestBlock($req)") - Task.never - } - override def broadcastSigned(payloadJson: JsObject, signer: PrivateKey): Either[String, PayloadMessage] = { log.debug(s"broadcastSigned($payloadJson, $signer)") - PayloadMessage.signed(payloadJson, signer) + val pm = PayloadMessage.signed(payloadJson, signer) + allChannels.broadcast(pm) + pm } override def broadcast(hash: BlockHash): Unit = { log.debug(s"broadcast($hash)") + Option(lastPayloadMessages.get(hash)).foreach { pm => + allChannels.broadcast(pm) + } + lastPayloadMessages.remove(hash) + } + + override def updateMinerPublicKeys(newKeys: Map[EthAddress, PublicKey]): Unit = { + log.debug(s"updateMinerPublicKeys($newKeys)") + } + + override def getPayloadStream: Observable[ExecutionPayloadInfo] = { + log.debug("getPayloadStream") + messages.concatMapIterable { pm => + pm.payloadInfo match { + case Right(epi) => + lastPayloadMessages.put(pm.hash, pm) + List(epi) + case Left(_) => List.empty + } + } } } From 2c26c3ddc52a0dd11bc1689a7d2b5441811a5436 Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Fri, 27 Sep 2024 18:30:51 +0300 Subject: [PATCH 12/14] Review fix --- src/main/scala/units/ELUpdater.scala | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index 7b6589a7..5faf5f47 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -862,7 +862,7 @@ class ELUpdater( logger.error(s"Failed to get block $targetBlockHash meta at contract") None } - } else if (isLastPayloadOnFork(prevChainInfo, prevState.lastPayload)) { + } else if (isLastBlockOnFork(prevChainInfo, prevState.lastPayload)) { val updatedLastPayload = findLastPayload(prevChainInfo.lastBlock) rollbackAndFollowChain(updatedLastPayload, prevChainInfo, mainChainInfo, prevState.returnToMainChainInfo) } else { @@ -876,10 +876,10 @@ class ELUpdater( } } - private def isLastPayloadOnFork(chainInfo: ChainInfo, lastPayload: ExecutionPayload) = - chainInfo.lastBlock.height == lastPayload.height && chainInfo.lastBlock.hash != lastPayload.hash || - chainInfo.lastBlock.height > lastPayload.height && !chainContractClient.blockExists(lastPayload.hash) || - chainInfo.lastBlock.height < lastPayload.height + private def isLastBlockOnFork(chainInfo: ChainInfo, lastBlock: CommonBlockData) = + chainInfo.lastBlock.height == lastBlock.height && chainInfo.lastBlock.hash != lastBlock.hash || + chainInfo.lastBlock.height > lastBlock.height && !chainContractClient.blockExists(lastBlock.hash) || + chainInfo.lastBlock.height < lastBlock.height private def waitForSyncCompletion(target: ContractBlock): Unit = scheduler.scheduleOnce(5.seconds)(state match { case SyncingToFinalizedBlock(finalizedBlockHash) if finalizedBlockHash == target.hash => From 2ff6c0f0651983d0abff4234baf2ec81d9af64a7 Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Mon, 30 Sep 2024 16:58:36 +0300 Subject: [PATCH 13/14] Rename block mentions --- src/main/scala/units/ConsensusClient.scala | 2 +- src/main/scala/units/ELUpdater.scala | 93 +++++++------- .../scala/units/network/HistoryReplier.scala | 6 +- .../scala/units/network/PayloadMessage.scala | 8 +- .../scala/units/util/HexBytesConverter.scala | 4 - .../units/BlockFullValidationTestSuite.scala | 14 +- .../units/BlockIssuesForgingTestSuite.scala | 121 +++++++++--------- .../scala/units/E2CTransfersTestSuite.scala | 4 +- src/test/scala/units/ExtensionDomain.scala | 6 +- .../units/network/TestPayloadObserver.scala | 2 +- 10 files changed, 129 insertions(+), 131 deletions(-) diff --git a/src/main/scala/units/ConsensusClient.scala b/src/main/scala/units/ConsensusClient.scala index cfc56148..f93e3931 100644 --- a/src/main/scala/units/ConsensusClient.scala +++ b/src/main/scala/units/ConsensusClient.scala @@ -102,7 +102,7 @@ class ConsensusClientDependencies(context: ExtensionContext) extends AutoCloseab val config: ClientConfig = context.settings.config.as[ClientConfig]("waves.l2") - private val payloadObserverScheduler = Schedulers.singleThread("block-observer-l2", reporter = { e => log.warn("Error in BlockObserver", e) }) + private val payloadObserverScheduler = Schedulers.singleThread("payload-observer-l2", reporter = { e => log.warn("Error in PayloadObserver", e) }) val globalScheduler: Scheduler = monix.execution.Scheduler.global val eluScheduler: SchedulerService = Scheduler.singleThread("el-updater", reporter = { e => log.warn("Exception in ELUpdater", e) }) diff --git a/src/main/scala/units/ELUpdater.scala b/src/main/scala/units/ELUpdater.scala index 5faf5f47..1b08afd2 100644 --- a/src/main/scala/units/ELUpdater.scala +++ b/src/main/scala/units/ELUpdater.scala @@ -62,7 +62,7 @@ class ELUpdater( def executionPayloadReceived(epi: ExecutionPayloadInfo): Unit = scheduler.execute { () => val payload = epi.payload - logger.debug(s"New block ${payload.hash}->${payload.parentHash} (timestamp=${payload.timestamp}, height=${payload.height}) appeared") + logger.debug(s"New payload for block ${payload.hash}->${payload.parentHash} (timestamp=${payload.timestamp}, height=${payload.height}) appeared") val now = time.correctedTime() / 1000 if (payload.timestamp - now <= MaxTimeDrift) { @@ -90,19 +90,19 @@ class ELUpdater( case Some(rInfo) if rInfo.missedBlock.hash == payload.hash => chainContractClient.getChainInfo(rInfo.chainId) match { case Some(chainInfo) if chainInfo.isMain => - validateAndApplyMissedBlock(epi, w, rInfo.missedBlock, rInfo.missedBlockParentPayload, chainInfo) + validateAndApplyMissed(epi, w, rInfo.missedBlock, rInfo.missedBlockParentPayload, chainInfo) case Some(_) => logger.debug(s"Chain ${rInfo.chainId} is not main anymore, ignoring ${payload.hash}") case _ => logger.error(s"Failed to get chain ${rInfo.chainId} info, ignoring ${payload.hash}") } - case _ => logger.debug(s"Expecting ${w.returnToMainChainInfo.fold("no block")(_.toString)}, ignoring unexpected ${payload.hash}") + case _ => logger.debug(s"Expecting ${w.returnToMainChainInfo.fold("no block payload")(_.toString)}, ignoring unexpected ${payload.hash}") } case other => logger.debug(s"$other: ignoring ${payload.hash}") } } else { - logger.debug(s"Block ${payload.hash} is from future: timestamp=${payload.timestamp}, now=$now, Δ${payload.timestamp - now}s") + logger.debug(s"Payload for block ${payload.hash} is from future: timestamp=${payload.timestamp}, now=$now, Δ${payload.timestamp - now}s") } } @@ -453,7 +453,7 @@ class ELUpdater( lastElWithdrawalIndex = None ) val options = chainContractClient.getOptions - followChainAndRequestNextBlock( + followChainAndRequestNextBlockPayload( newEpochInfo, mainChainInfo, lastPayload, @@ -468,8 +468,8 @@ class ELUpdater( _ => () ) case Right(None) => - logger.trace(s"Finalized block ${finalizedBlock.hash} payload is not in EC, requesting block from peers") - setState("15", WaitingForSyncHead(finalizedBlock, requestAndProcessPayload(finalizedBlock.hash))) + logger.trace(s"Finalized block ${finalizedBlock.hash} payload is not in EC, requesting from peers") + setState("15", WaitingForSyncHead(finalizedBlock, requestAndProcessBlockPayload(finalizedBlock.hash))) } } } @@ -509,12 +509,12 @@ class ELUpdater( } } - private def requestBlocksAndStartMining(prevState: Working[FollowingChain]): Unit = { + private def requestPayloadsAndStartMining(prevState: Working[FollowingChain]): Unit = { def check(missedBlock: ContractBlock): Unit = { state match { case w @ Working(epochInfo, lastPayload, finalizedBlock, mainChainInfo, _, fc: FollowingChain, _, returnToMainChainInfo) 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") + logger.debug(s"Block ${missedBlock.hash} payload wasn't received for $WaitRequestedPayloadTimeout, need to switch to alternative chain") (for { lastValidBlock <- getAltChainReferenceBlock(fc.nodeChainInfo, missedBlock) updatedState <- rollbackTo(w, lastValidBlock, finalizedBlock) @@ -556,8 +556,8 @@ class ELUpdater( case w: Working[ChainStatus] => w.chainStatus match { case FollowingChain(_, Some(nextExpectedBlock)) => - logger.debug(s"Waiting for block $nextExpectedBlock from peers") - scheduler.scheduleOnce(WaitRequestedBlockTimeout) { + logger.debug(s"Waiting for block $nextExpectedBlock payload from peers") + scheduler.scheduleOnce(WaitRequestedPayloadTimeout) { if (blockchain.height == prevState.epochInfo.number) { check(missedBlock) } @@ -574,7 +574,7 @@ class ELUpdater( prevState.chainStatus.nextExpectedBlock match { case Some(missedBlock) => - scheduler.scheduleOnce(WaitRequestedBlockTimeout) { + scheduler.scheduleOnce(WaitRequestedPayloadTimeout) { if (blockchain.height == prevState.epochInfo.number) { check(missedBlock) } @@ -600,7 +600,7 @@ class ELUpdater( finalizedBlock, options ).foreach { newState => - requestBlocksAndStartMining(newState) + requestPayloadsAndStartMining(newState) } } @@ -703,14 +703,14 @@ class ELUpdater( } } validateAppliedBlocks() - requestMainChainBlock() + requestMainChainBlockPayload() case Right(None) => - logger.trace(s"Finalized block ${finalizedBlock.hash} payload is not in EC, requesting block from peers") - setState("19", WaitingForSyncHead(finalizedBlock, requestAndProcessPayload(finalizedBlock.hash))) + logger.trace(s"Finalized block ${finalizedBlock.hash} payload is not in EC, requesting from peers") + setState("19", WaitingForSyncHead(finalizedBlock, requestAndProcessBlockPayload(finalizedBlock.hash))) } } - private def followChainAndRequestNextBlock( + private def followChainAndRequestNextBlockPayload( epochInfo: EpochInfo, nodeChainInfo: ChainInfo, lastPayload: ExecutionPayload, @@ -731,29 +731,29 @@ class ELUpdater( returnToMainChainInfo ) setState("3", newState) - maybeRequestNextBlock(newState, finalizedBlock) + maybeRequestNextBlockPayload(newState, finalizedBlock) } - private def requestPayload(contractBlock: ContractBlock): PayloadRequestResult = { + private def requestBlockPayload(contractBlock: ContractBlock): PayloadRequestResult = { logger.debug(s"Requesting payload for block ${contractBlock.hash}") engineApiClient.getBlockByHash(contractBlock.hash) match { case Right(Some(payload)) => PayloadRequestResult.Exists(payload) case Right(None) => - requestAndProcessPayload(contractBlock.hash) + requestAndProcessBlockPayload(contractBlock.hash) PayloadRequestResult.Requested(contractBlock) case Left(err) => logger.warn(s"Failed to get block ${contractBlock.hash} payload by hash: ${err.message}") - requestAndProcessPayload(contractBlock.hash) + requestAndProcessBlockPayload(contractBlock.hash) PayloadRequestResult.Requested(contractBlock) } } - private def requestMainChainBlock(): Unit = { + private def requestMainChainBlockPayload(): Unit = { state match { case w: Working[ChainStatus] => w.returnToMainChainInfo.foreach { returnToMainChainInfo => if (w.mainChainInfo.id == returnToMainChainInfo.chainId) { - requestPayload(returnToMainChainInfo.missedBlock) match { + requestBlockPayload(returnToMainChainInfo.missedBlock) match { case PayloadRequestResult.Exists(payload) => logger.debug(s"Block ${returnToMainChainInfo.missedBlock.hash} payload exists at execution chain, trying to validate") validateAppliedBlock(returnToMainChainInfo.missedBlock, payload, w) match { @@ -776,7 +776,7 @@ class ELUpdater( } } - private def requestAndProcessPayload(hash: BlockHash): CancelableFuture[ExecutionPayloadInfo] = { + private def requestAndProcessBlockPayload(hash: BlockHash): CancelableFuture[ExecutionPayloadInfo] = { payloadObserver .loadPayload(hash) .andThen { @@ -828,7 +828,7 @@ class ELUpdater( returnToMainChainInfo.filter(rInfo => rInfo.chainId != prevChainId && mainChainInfo.id == rInfo.chainId) ) setState("16", newState) - maybeRequestNextBlock(newState, finalizedContractBlock) + maybeRequestNextBlockPayload(newState, finalizedContractBlock) } def rollbackAndFollowChain( @@ -903,7 +903,7 @@ class ELUpdater( lastValidatedBlock = target, lastElWithdrawalIndex = None ) - followChainAndRequestNextBlock( + followChainAndRequestNextBlockPayload( newEpochInfo, mainChainInfo, lastPayload, @@ -994,16 +994,16 @@ class ELUpdater( .toRight(ClientError(s"Can't find a last block $referenceBlockHash of epoch #${lastEpoch.prevEpoch} on contract")) } yield referenceBlock } else { - val blockId = nodeChainInfo.firstBlock.parentHash + val blockHash = nodeChainInfo.firstBlock.parentHash chainContractClient - .getBlock(blockId) + .getBlock(blockHash) .toRight( - ClientError(s"Parent block $blockId for first block ${nodeChainInfo.firstBlock.hash} of chain ${nodeChainInfo.id} not found at contract") + ClientError(s"Parent block $blockHash for first block ${nodeChainInfo.firstBlock.hash} of chain ${nodeChainInfo.id} not found at contract") ) } } - private def validateAndApplyMissedBlock( + private def validateAndApplyMissed( epi: ExecutionPayloadInfo, prevState: Working[ChainStatus], contractBlock: ContractBlock, @@ -1065,7 +1065,7 @@ class ELUpdater( err => logger.error(s"Can't confirm block ${payload.hash} of chain ${nodeChainInfo.id}: ${err.message}"), _ => { logger.info(s"Successfully confirmed block ${payload.hash} of chain ${nodeChainInfo.id}") - followChainAndRequestNextBlock( + followChainAndRequestNextBlockPayload( prevState.epochInfo, nodeChainInfo, payload, @@ -1103,15 +1103,15 @@ class ELUpdater( } @tailrec - private def maybeRequestNextBlock(prevState: Working[FollowingChain], finalizedBlock: ContractBlock): Working[FollowingChain] = { + private def maybeRequestNextBlockPayload(prevState: Working[FollowingChain], finalizedBlock: ContractBlock): Working[FollowingChain] = { if (prevState.lastPayload.height < prevState.chainStatus.nodeChainInfo.lastBlock.height) { - logger.debug(s"EC chain is not synced, trying to find next block to request") + logger.debug(s"EC chain is not synced, trying to find next block to request payload") findBlockChild(prevState.lastPayload.hash, prevState.chainStatus.nodeChainInfo.lastBlock.hash) match { case Left(error) => logger.error(s"Could not find child of ${prevState.lastPayload.hash} on contract: $error") prevState case Right(contractBlock) => - requestPayload(contractBlock) match { + requestBlockPayload(contractBlock) match { case PayloadRequestResult.Exists(payload) => logger.debug(s"Block ${contractBlock.hash} payload exists at EC chain, trying to confirm") confirmBlock(payload, finalizedBlock) match { @@ -1121,7 +1121,7 @@ class ELUpdater( chainStatus = FollowingChain(prevState.chainStatus.nodeChainInfo, None) ) setState("7", newState) - maybeRequestNextBlock(newState, finalizedBlock) + maybeRequestNextBlockPayload(newState, finalizedBlock) case Left(err) => logger.error(s"Failed to confirm next block ${payload.hash}: ${err.message}") prevState @@ -1133,19 +1133,22 @@ class ELUpdater( } } } else { - logger.trace(s"EC chain ${prevState.chainStatus.nodeChainInfo.id} is synced, no need to request blocks") + logger.trace(s"EC chain ${prevState.chainStatus.nodeChainInfo.id} is synced, no need to request block payloads") prevState } } - private def mkRollbackBlock(rollbackTargetBlockId: BlockHash): JobResult[RollbackBlock] = for { - targetBlockDataOpt <- chainContractClient.getBlock(rollbackTargetBlockId) match { - case None => engineApiClient.getBlockByHash(rollbackTargetBlockId) + private def mkRollbackBlock(rollbackTargetBlockHash: BlockHash): JobResult[RollbackBlock] = for { + targetBlockDataOpt <- chainContractClient.getBlock(rollbackTargetBlockHash) match { + case None => engineApiClient.getBlockByHash(rollbackTargetBlockHash) case x => Right(x) } - targetBlockData <- Either.fromOption(targetBlockDataOpt, ClientError(s"Can't find block $rollbackTargetBlockId neither on a contract, nor in EC")) + targetBlockData <- Either.fromOption( + targetBlockDataOpt, + ClientError(s"Can't find block $rollbackTargetBlockHash neither on a contract, nor in EC") + ) parentPayloadOpt <- engineApiClient.getBlockByHash(targetBlockData.parentHash) - parentPayload <- Either.fromOption(parentPayloadOpt, ClientError(s"Can't find block $rollbackTargetBlockId parent payload in execution client")) + parentPayload <- Either.fromOption(parentPayloadOpt, ClientError(s"Can't find block $rollbackTargetBlockHash parent payload in execution client")) rollbackBlockOpt <- engineApiClient.applyNewPayload(EmptyPayload.mkExecutionPayloadJson(parentPayload)) rollbackBlock <- Either.fromOption(rollbackBlockOpt, ClientError("Rollback block hash is not defined as latest valid hash")) } yield RollbackBlock(rollbackBlock, parentPayload) @@ -1186,7 +1189,7 @@ class ELUpdater( } } yield rootHash - private def skipFinalizedBlocksValidation(curState: Working[ChainStatus]) = { + private def skipFinalizedBlocksValidation(curState: Working[ChainStatus]): Working[ChainStatus] = { if (curState.finalizedBlock.height > curState.fullValidationStatus.lastValidatedBlock.height) { val newState = curState.copy(fullValidationStatus = FullValidationStatus(curState.finalizedBlock, None)) setState("4", newState) @@ -1497,7 +1500,7 @@ object ELUpdater { val WaitForReferenceConfirmInterval: FiniteDuration = 500.millis val ClChangedProcessingDelay: FiniteDuration = 50.millis val MiningRetryInterval: FiniteDuration = 5.seconds - val WaitRequestedBlockTimeout: FiniteDuration = 2.seconds + val WaitRequestedPayloadTimeout: FiniteDuration = 2.seconds case class EpochInfo(number: Int, miner: Address, rewardAddress: EthAddress, hitSource: ByteStr, prevEpochLastBlockHash: Option[BlockHash]) @@ -1551,8 +1554,8 @@ object ELUpdater { case class ChainSwitchInfo(prevChainId: Long, referenceBlock: ContractBlock) - /** 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. + /** We haven't received block payload of a previous epoch when started a mining on a new epoch. We can return to the main chain, if get a missed + * block payload. */ case class ReturnToMainChainInfo(missedBlock: ContractBlock, missedBlockParentPayload: ExecutionPayload, chainId: Long) diff --git a/src/main/scala/units/network/HistoryReplier.scala b/src/main/scala/units/network/HistoryReplier.scala index 2a2c3814..b7083d46 100644 --- a/src/main/scala/units/network/HistoryReplier.scala +++ b/src/main/scala/units/network/HistoryReplier.scala @@ -31,9 +31,9 @@ class HistoryReplier(engineApiClient: EngineApiClient)(implicit sc: Scheduler) e ctx, loadPayload(hash) .map { - case Right(block) => - RawBytes(PayloadSpec.messageCode, PayloadSpec.serializeData(block)) - case Left(err) => throw new NoSuchElementException(s"Error loading block $hash: $err") + case Right(payloadMsg) => + RawBytes(PayloadSpec.messageCode, PayloadSpec.serializeData(payloadMsg)) + case Left(err) => throw new NoSuchElementException(s"Error loading block $hash payload: $err") } ) case _ => super.channelRead(ctx, msg) diff --git a/src/main/scala/units/network/PayloadMessage.scala b/src/main/scala/units/network/PayloadMessage.scala index 41366f33..59705e69 100644 --- a/src/main/scala/units/network/PayloadMessage.scala +++ b/src/main/scala/units/network/PayloadMessage.scala @@ -78,9 +78,9 @@ object PayloadMessage { .leftMap(err => s"Error creating payload message: $err") def apply(payloadBytes: Array[Byte], signature: Option[ByteStr]): Either[String, PayloadMessage] = for { - payload <- Try(Json.parse(payloadBytes).as[JsObject]).toEither.leftMap(err => s"Payload bytes are not a valid JSON object: ${err.getMessage}") - block <- apply(payload, signature) - } yield block + payload <- Try(Json.parse(payloadBytes).as[JsObject]).toEither.leftMap(err => s"Payload bytes are not a valid JSON object: ${err.getMessage}") + payloadMsg <- apply(payload, signature) + } yield payloadMsg def signed(payloadJson: JsObject, signer: PrivateKey): Either[String, PayloadMessage] = { val signature = crypto.sign(signer, Json.toBytes(payloadJson)) @@ -99,5 +99,5 @@ object PayloadMessage { } private def validateSignatureLength(signature: Option[ByteStr]): Either[String, Unit] = - Either.cond(signature.forall(_.size == SignatureLength), (), "Invalid block signature size") + Either.cond(signature.forall(_.size == SignatureLength), (), "Invalid payload signature size") } diff --git a/src/main/scala/units/util/HexBytesConverter.scala b/src/main/scala/units/util/HexBytesConverter.scala index d939087d..ae7721a0 100644 --- a/src/main/scala/units/util/HexBytesConverter.scala +++ b/src/main/scala/units/util/HexBytesConverter.scala @@ -1,7 +1,6 @@ package units.util import com.wavesplatform.common.state.ByteStr -import units.BlockHash import org.web3j.abi.datatypes.generated.Uint256 import org.web3j.utils.Numeric @@ -9,9 +8,6 @@ import java.math.BigInteger object HexBytesConverter { - def toByteStr(hash: BlockHash): ByteStr = - ByteStr(toBytes(hash)) - def toInt(intHex: String): Int = Numeric.toBigInt(intHex).intValueExact() diff --git a/src/test/scala/units/BlockFullValidationTestSuite.scala b/src/test/scala/units/BlockFullValidationTestSuite.scala index d222aa12..65ab7b19 100644 --- a/src/test/scala/units/BlockFullValidationTestSuite.scala +++ b/src/test/scala/units/BlockFullValidationTestSuite.scala @@ -27,11 +27,11 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { val payload = d.createPayloadBuilder("0", reliable).buildAndSetLogs(blockLogs) d.advanceConsensusLayerChanged() - step(s"Receive network block ${payload.hash} with payload from a peer") + step(s"Receive block ${payload.hash} payload from a peer") d.receivePayload(payload, reliable.account) d.triggerScheduledTasks() - step(s"Append a CL micro block with payload ${payload.hash} confirmation") + step(s"Append a CL micro block with block ${payload.hash} confirmation") d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, payload)) d.advanceConsensusLayerChanged() @@ -55,11 +55,11 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { val payload = d.createPayloadBuilder("0", reliable).buildAndSetLogs(blockLogs) d.advanceConsensusLayerChanged() - step(s"Receive network block ${payload.hash} with payload from a peer") + step(s"Receive block ${payload.hash} payload from a peer") d.receivePayload(payload, reliable.account) d.triggerScheduledTasks() - step(s"Append a CL micro block with payload ${payload.hash} confirmation") + step(s"Append a CL micro block with block ${payload.hash} confirmation") d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, payload, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() @@ -98,11 +98,11 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { val payload2 = badBlockPayloadPostProcessing(d.createPayloadBuilder("0-0", malfunction, payload1).rewardPrevMiner().buildAndSetLogs(blockLogs)) - step(s"Append a CL micro block with payload2 ${payload2.hash} confirmation") + step(s"Append a CL micro block with block2 ${payload2.hash} confirmation") d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(malfunction.account, payload2, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() - step(s"Receive network block ${payload2.hash} with payload2 from a peer") + step(s"Receive block2 ${payload2.hash} payload2 from a peer") d.receivePayload(payload2, malfunction.account) d.triggerScheduledTasks() @@ -118,7 +118,7 @@ class BlockFullValidationTestSuite extends BaseIntegrationTestSuite { "Events from an unexpected EL bridge address" in { val fakeBridgeAddress = EthAddress.unsafeFrom("0x53481054Ad294207F6ed4B6C2E6EaE34E1Bb8704") - val block2Logs = transferEvents.map(x => getLogsResponseEntry(x).copy(address = fakeBridgeAddress)) + val block2Logs = transferEvents.map(x => getLogsResponseEntry(x).copy(address = fakeBridgeAddress)) e2CTest( blockLogs = block2Logs, e2CTransfersRootHashHex = e2CTransfersRootHashHex diff --git a/src/test/scala/units/BlockIssuesForgingTestSuite.scala b/src/test/scala/units/BlockIssuesForgingTestSuite.scala index e5e824a3..81b9a0c3 100644 --- a/src/test/scala/units/BlockIssuesForgingTestSuite.scala +++ b/src/test/scala/units/BlockIssuesForgingTestSuite.scala @@ -4,7 +4,7 @@ import com.wavesplatform.db.WithState.AddrWithBalance import com.wavesplatform.transaction.TxHelpers import com.wavesplatform.wallet.Wallet import units.ELUpdater.State.ChainStatus.{FollowingChain, Mining, WaitForNewChain} -import units.ELUpdater.WaitRequestedBlockTimeout +import units.ELUpdater.WaitRequestedPayloadTimeout import units.client.contract.HasConsensusLayerDappTxHelpers.defaultFees import units.client.engine.model.ExecutionPayload @@ -52,13 +52,13 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { f(d, payload2, block2Epoch) } - "EC-block comes within timeout - then we continue forging" in test { (d, payload2, block2Epoch) => - d.advanceElu(WaitRequestedBlockTimeout - 1.millis) + "Block payload comes within timeout - then we continue forging" in test { (d, payload2, block2Epoch) => + d.advanceElu(WaitRequestedPayloadTimeout - 1.millis) d.waitForCS[FollowingChain](s"Still waiting payload2 ${payload2.hash}") { s => s.nextExpectedBlock.map(_.hash).value shouldBe payload2.hash } - step(s"Receive network block ${payload2.hash} with payload2") + step(s"Receive block2 ${payload2.hash} payload2") d.receivePayload(payload2, otherMiner1.account, block2Epoch) d.triggerScheduledTasks() @@ -69,13 +69,13 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { } } - "Network block with payload doesn't come - then we start an alternative chain" in test { (d, _, _) => + "Block payload doesn't come - then we start an alternative chain" in test { (d, _, _) => d.waitForCS[WaitForNewChain](s"Switched to alternative chain") { _ => } } } "We're on the alternative chain and" - { - "Network block with payload comes within timeout - then we continue forging" in withExtensionDomain() { d => + "Block payload comes within timeout - then we continue forging" in withExtensionDomain() { d => step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with payload1") d.advanceNewBlocks(otherMiner1.address) val payload1 = d.createPayloadBuilder("0", otherMiner1).buildAndSetLogs() @@ -135,7 +135,7 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { step(s"Start a new epoch of thisMiner ${thisMiner.address}") d.advanceNewBlocks(thisMiner.address) d.advanceConsensusLayerChanged() - d.advanceElu(WaitRequestedBlockTimeout - 1.millis) + d.advanceElu(WaitRequestedPayloadTimeout - 1.millis) d.waitForCS[FollowingChain](s"Waiting payload3 ${payload3.hash}") { s => s.nodeChainInfo.isMain shouldBe false @@ -143,7 +143,7 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { s.nextExpectedBlock.map(_.hash).value shouldBe payload3.hash } - step(s"Receive network block ${payload3.hash} with payload3") + step(s"Receive block3 ${payload3.hash} payload3") d.receivePayload(payload3, thisMiner.account, block3Epoch) d.ecClients.willForge(d.createPayloadBuilder("0-1-1-1", thisMiner, payload3).rewardPrevMiner(2).build()) @@ -152,73 +152,72 @@ class BlockIssuesForgingTestSuite extends BaseIntegrationTestSuite { } } - "We mined before the alternative chain before and network block with payload doesn't come - then we still wait for it" in withExtensionDomain() { - d => - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with payload1") - d.advanceNewBlocks(otherMiner1.address) - val payload1 = d.createPayloadBuilder("0", otherMiner1).buildAndSetLogs() - d.ecClients.addKnown(payload1) - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, payload1)) + "We mined before the alternative chain before and block payload doesn't come - then we still wait for it" in withExtensionDomain() { d => + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with payload1") + d.advanceNewBlocks(otherMiner1.address) + val payload1 = d.createPayloadBuilder("0", otherMiner1).buildAndSetLogs() + d.ecClients.addKnown(payload1) + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, payload1)) - d.waitForCS[FollowingChain]() { s => - s.nodeChainInfo.isMain shouldBe true - s.nodeChainInfo.lastBlock.hash shouldBe payload1.hash - } + d.waitForCS[FollowingChain]() { s => + s.nodeChainInfo.isMain shouldBe true + s.nodeChainInfo.lastBlock.hash shouldBe payload1.hash + } - step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with badPayload2") - d.advanceNewBlocks(otherMiner1.address) - val badPayload2 = d.createPayloadBuilder("0-0", otherMiner1, payload1).rewardPrevMiner().buildAndSetLogs() - d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, badPayload2)) + step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with badPayload2") + d.advanceNewBlocks(otherMiner1.address) + val badPayload2 = d.createPayloadBuilder("0-0", otherMiner1, payload1).rewardPrevMiner().buildAndSetLogs() + d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(otherMiner1.account, badPayload2)) - d.waitForCS[FollowingChain]() { s => - s.nodeChainInfo.isMain shouldBe true - s.nodeChainInfo.lastBlock.hash shouldBe badPayload2.hash - } + d.waitForCS[FollowingChain]() { s => + s.nodeChainInfo.isMain shouldBe true + s.nodeChainInfo.lastBlock.hash shouldBe badPayload2.hash + } - step(s"Start a new epoch of thisMiner ${thisMiner.address} with alternative chain payload2") - d.advanceNewBlocks(thisMiner.address) - val payload2 = d.createPayloadBuilder("0-1", thisMiner, payload1).rewardPrevMiner().buildAndSetLogs() - d.ecClients.willForge(payload2) - d.ecClients.willForge(d.createPayloadBuilder("0-1-i", thisMiner, payload2).buildAndSetLogs()) + step(s"Start a new epoch of thisMiner ${thisMiner.address} with alternative chain payload2") + d.advanceNewBlocks(thisMiner.address) + val payload2 = d.createPayloadBuilder("0-1", thisMiner, payload1).rewardPrevMiner().buildAndSetLogs() + d.ecClients.willForge(payload2) + d.ecClients.willForge(d.createPayloadBuilder("0-1-i", thisMiner, payload2).buildAndSetLogs()) - d.waitForCS[Mining]() { s => - val ci = s.nodeChainInfo.left.value - ci.referenceBlock.hash shouldBe payload1.hash - } + d.waitForCS[Mining]() { s => + val ci = s.nodeChainInfo.left.value + ci.referenceBlock.hash shouldBe payload1.hash + } - d.advanceMining() - d.forgeFromUtxPool() + d.advanceMining() + d.forgeFromUtxPool() - d.waitForCS[Mining]() { s => - val ci = s.nodeChainInfo.value - ci.lastBlock.hash shouldBe payload2.hash - } + d.waitForCS[Mining]() { s => + val ci = s.nodeChainInfo.value + ci.lastBlock.hash shouldBe payload2.hash + } - step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with badPayload3") - d.advanceNewBlocks(otherMiner2.address) - val badPayload3 = d.createPayloadBuilder("0-1-1", otherMiner2, payload2).rewardMiner(otherMiner2.elRewardAddress, 1).buildAndSetLogs() - d.appendMicroBlockAndVerify(d.chainContract.extendAltChain(otherMiner2.account, badPayload3, chainId = 1)) + step(s"Continue an alternative chain by otherMiner2 ${otherMiner2.address} with badPayload3") + d.advanceNewBlocks(otherMiner2.address) + val badPayload3 = d.createPayloadBuilder("0-1-1", otherMiner2, payload2).rewardMiner(otherMiner2.elRewardAddress, 1).buildAndSetLogs() + d.appendMicroBlockAndVerify(d.chainContract.extendAltChain(otherMiner2.account, badPayload3, chainId = 1)) - d.waitForCS[FollowingChain]() { s => - s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe badPayload3.hash - s.nextExpectedBlock.map(_.hash).value shouldBe badPayload3.hash - } + d.waitForCS[FollowingChain]() { s => + s.nodeChainInfo.isMain shouldBe false + s.nodeChainInfo.lastBlock.hash shouldBe badPayload3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe badPayload3.hash + } - step(s"Continue an alternative chain by thisMiner ${thisMiner.address}") - d.advanceNewBlocks(thisMiner.address) + step(s"Continue an alternative chain by thisMiner ${thisMiner.address}") + d.advanceNewBlocks(thisMiner.address) - d.advanceWaitRequestedBlock() - d.advanceWaitRequestedBlock() + d.advanceWaitRequestedBlockPayload() + d.advanceWaitRequestedBlockPayload() - d.waitForCS[FollowingChain](s"Still wait for badPayload3 ${badPayload3.hash}") { s => - s.nodeChainInfo.isMain shouldBe false - s.nodeChainInfo.lastBlock.hash shouldBe badPayload3.hash - s.nextExpectedBlock.map(_.hash).value shouldBe badPayload3.hash - } + d.waitForCS[FollowingChain](s"Still wait for badPayload3 ${badPayload3.hash}") { s => + s.nodeChainInfo.isMain shouldBe false + s.nodeChainInfo.lastBlock.hash shouldBe badPayload3.hash + s.nextExpectedBlock.map(_.hash).value shouldBe badPayload3.hash + } } - "We haven't mined the alternative chain before and network block with payload doesn't come - then we wait for a new alternative chain" in + "We haven't mined the alternative chain before and block payload doesn't come - then we wait for a new alternative chain" in withExtensionDomain() { d => step(s"Start a new epoch of otherMiner1 ${otherMiner1.address} with payload1") d.advanceNewBlocks(otherMiner1.address) diff --git a/src/test/scala/units/E2CTransfersTestSuite.scala b/src/test/scala/units/E2CTransfersTestSuite.scala index 5093da6b..612acfcc 100644 --- a/src/test/scala/units/E2CTransfersTestSuite.scala +++ b/src/test/scala/units/E2CTransfersTestSuite.scala @@ -21,7 +21,7 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { private val transferReceiver = TxHelpers.secondSigner private val transfer = Bridge.ElSentNativeEvent(transferReceiver.toAddress, 1) private val transferEvent = getLogsResponseEntry(transfer) - private val blockLogs = List(transferEvent) + private val blockLogs = List(transferEvent) private val e2CTransfersRootHashHex = HexBytesConverter.toHex(Bridge.mkTransfersHash(blockLogs).explicitGet()) private val transferProofs = Bridge.mkTransferProofs(List(transfer), 0).reverse // Contract requires from bottom to top @@ -63,7 +63,7 @@ class E2CTransfersTestSuite extends BaseIntegrationTestSuite { tryWithdraw() should produce("not found for the contract address") - step("Append a CL micro block with payload confirmation") + step("Append a CL micro block with block confirmation") d.ecClients.addKnown(payload) d.appendMicroBlockAndVerify(d.chainContract.extendMainChain(reliable.account, payload, e2CTransfersRootHashHex)) d.advanceConsensusLayerChanged() diff --git a/src/test/scala/units/ExtensionDomain.scala b/src/test/scala/units/ExtensionDomain.scala index 72e60491..10959c0a 100644 --- a/src/test/scala/units/ExtensionDomain.scala +++ b/src/test/scala/units/ExtensionDomain.scala @@ -134,7 +134,7 @@ class ExtensionDomain( triggers = triggers.appended(consensusClient) val defaultMaxTimeout: FiniteDuration = - List(WaitForReferenceConfirmInterval, ClChangedProcessingDelay, MiningRetryInterval, WaitRequestedBlockTimeout).max + 1.millis + List(WaitForReferenceConfirmInterval, ClChangedProcessingDelay, MiningRetryInterval, WaitRequestedPayloadTimeout).max + 1.millis val defaultInterval: FiniteDuration = ClChangedProcessingDelay def waitForWorking( @@ -292,8 +292,8 @@ class ExtensionDomain( // See ELUpdater.consensusLayerChanged def advanceConsensusLayerChanged(): Unit = advanceElu(ELUpdater.ClChangedProcessingDelay, "advanceConsensusLayerChanged") - // See ELUpdater.requestBlocksAndStartMining - def advanceWaitRequestedBlock(): Unit = advanceElu(ELUpdater.WaitRequestedBlockTimeout, "advanceWaitRequestedBlock") + // See ELUpdater.requestPayloadsAndStartMining + def advanceWaitRequestedBlockPayload(): Unit = advanceElu(ELUpdater.WaitRequestedPayloadTimeout, "advanceWaitRequestedBlockPayload") def advanceMiningRetry(): Unit = advanceElu(ELUpdater.MiningRetryInterval, "advanceMiningRetry") diff --git a/src/test/scala/units/network/TestPayloadObserver.scala b/src/test/scala/units/network/TestPayloadObserver.scala index e8033cec..d9fc1186 100644 --- a/src/test/scala/units/network/TestPayloadObserver.scala +++ b/src/test/scala/units/network/TestPayloadObserver.scala @@ -18,7 +18,7 @@ class TestPayloadObserver(messages: Observable[PayloadMessage], allChannels: Def new ConcurrentHashMap[BlockHash, PayloadMessage]() override def loadPayload(req: BlockHash): CancelableFuture[ExecutionPayloadInfo] = { - log.debug(s"loadBlock($req)") + log.debug(s"loadPayload($req)") CancelableFuture.never } From 4d9b42e774d092916af1aaa61adbed0fd6ee4f3f Mon Sep 17 00:00:00 2001 From: Ivan Mashonskii Date: Thu, 3 Oct 2024 12:27:38 +0300 Subject: [PATCH 14/14] Review fixes --- .../units/client/contract/ContractBlock.scala | 16 ++++++++-------- .../scala/units/network/MessageObserver.scala | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/scala/units/client/contract/ContractBlock.scala b/src/main/scala/units/client/contract/ContractBlock.scala index d056c971..25379605 100644 --- a/src/main/scala/units/client/contract/ContractBlock.scala +++ b/src/main/scala/units/client/contract/ContractBlock.scala @@ -7,14 +7,14 @@ import units.eth.EthAddress import units.util.HexBytesConverter.toHex case class ContractBlock( - hash: BlockHash, - parentHash: BlockHash, - epoch: Int, - height: Long, - feeRecipient: EthAddress, - chainId: Long, - e2cTransfersRootHash: Digest, - lastC2ETransferIndex: Long + hash: BlockHash, + parentHash: BlockHash, + epoch: Int, + height: Long, + feeRecipient: EthAddress, + chainId: Long, + e2cTransfersRootHash: Digest, + lastC2ETransferIndex: Long ) extends CommonBlockData { override def toString: String = s"ContractBlock($hash, p=$parentHash, e=$epoch, h=$height, m=$feeRecipient, c=$chainId, " + diff --git a/src/main/scala/units/network/MessageObserver.scala b/src/main/scala/units/network/MessageObserver.scala index 709e798a..9b055f91 100644 --- a/src/main/scala/units/network/MessageObserver.scala +++ b/src/main/scala/units/network/MessageObserver.scala @@ -1,13 +1,13 @@ package units.network -import com.wavesplatform.utils.{Schedulers, ScorexLogging} +import com.wavesplatform.utils.Schedulers import io.netty.channel.ChannelHandler.Sharable import io.netty.channel.{ChannelHandlerContext, ChannelInboundHandlerAdapter} import monix.execution.schedulers.SchedulerService import monix.reactive.subjects.{ConcurrentSubject, Subject} @Sharable -class MessageObserver extends ChannelInboundHandlerAdapter with ScorexLogging { +class MessageObserver extends ChannelInboundHandlerAdapter { private implicit val scheduler: SchedulerService = Schedulers.fixedPool(2, "message-observer-l2")