diff --git a/hildr-batcher/src/main/java/io/optimism/batcher/channel/ChannelImpl.java b/hildr-batcher/src/main/java/io/optimism/batcher/channel/ChannelImpl.java index ac54df72..b2b69cf6 100644 --- a/hildr-batcher/src/main/java/io/optimism/batcher/channel/ChannelImpl.java +++ b/hildr-batcher/src/main/java/io/optimism/batcher/channel/ChannelImpl.java @@ -8,6 +8,7 @@ import io.optimism.type.L1BlockInfo; import io.optimism.utilities.derive.stages.Frame; import io.optimism.utilities.derive.stages.SingularBatch; +import io.optimism.utilities.encoding.TxEncoder; import java.io.IOException; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; @@ -304,7 +305,7 @@ private Tuple2 blockToBatch(EthBlock.Block block) { if (DEPOSIT_TX_TYPE.equalsIgnoreCase(txObj.getType())) { continue; } - txDataList.add(txObj.getInput()); + txDataList.add(Numeric.toHexString(TxEncoder.encode(txObj))); } return new Tuple2( l1Info, diff --git a/hildr-batcher/src/main/java/io/optimism/batcher/loader/BlockLoader.java b/hildr-batcher/src/main/java/io/optimism/batcher/loader/BlockLoader.java index a8707b4c..0f3c898b 100644 --- a/hildr-batcher/src/main/java/io/optimism/batcher/loader/BlockLoader.java +++ b/hildr-batcher/src/main/java/io/optimism/batcher/loader/BlockLoader.java @@ -4,6 +4,7 @@ import io.optimism.batcher.exception.Web3jCallException; import io.optimism.batcher.telemetry.BatcherMetrics; import io.optimism.type.BlockId; +import io.optimism.type.Epoch; import io.optimism.type.Genesis; import io.optimism.type.L1BlockInfo; import io.optimism.type.L1BlockRef; @@ -148,7 +149,7 @@ Tuple2 calculateL2BlockRangeToStore() { } L2BlockRef l2BlockToBlockRef(final EthBlock.Block block, Genesis genesis) { - BlockId l1Origin; + Epoch l1Origin; BigInteger sequenceNumber; if (block.getNumber().equals(genesis.l2().number())) { if (!block.getHash().equals(genesis.l2().hash())) { @@ -172,7 +173,7 @@ L2BlockRef l2BlockToBlockRef(final EthBlock.Block block, Genesis genesis) { } final byte[] input = Numeric.hexStringToByteArray(tx.getInput()); L1BlockInfo info = L1BlockInfo.from(input); - l1Origin = info.toId(); + l1Origin = info.toEpoch(); sequenceNumber = info.sequenceNumber(); } return new L2BlockRef( diff --git a/hildr-node/src/main/java/io/optimism/cli/Cli.java b/hildr-node/src/main/java/io/optimism/cli/Cli.java index 4fb688d9..4d41bdde 100644 --- a/hildr-node/src/main/java/io/optimism/cli/Cli.java +++ b/hildr-node/src/main/java/io/optimism/cli/Cli.java @@ -210,6 +210,7 @@ private Config.CliConfig from(Cli cli) { StringUtils.trim(Cli.this.getJwtSecret()), cli.checkpointSyncUrl, cli.rpcPort, + cli.syncMode, cli.devnet); } } diff --git a/hildr-node/src/main/java/io/optimism/common/AttributesDepositedCall.java b/hildr-node/src/main/java/io/optimism/common/AttributesDepositedCall.java index 9f654b84..0e3f9cfa 100644 --- a/hildr-node/src/main/java/io/optimism/common/AttributesDepositedCall.java +++ b/hildr-node/src/main/java/io/optimism/common/AttributesDepositedCall.java @@ -1,5 +1,6 @@ package io.optimism.common; +import io.optimism.type.Epoch; import io.optimism.type.L1BlockInfo; import java.math.BigInteger; import org.apache.commons.lang3.StringUtils; @@ -33,6 +34,14 @@ public record AttributesDepositedCall( BigInteger blobBaseFeeScalar, BigInteger blobBaseFee) { + /** + * Create Epoch from attributes deposited call. + * @return the epoch + */ + public Epoch toEpoch() { + return new Epoch(number, hash, timestamp, sequenceNumber); + } + /** * Create AttributesDepositedCall from attributes deposited call. * diff --git a/hildr-node/src/main/java/io/optimism/common/BlockInfo.java b/hildr-node/src/main/java/io/optimism/common/BlockInfo.java index 6d0b0bce..f9c42d05 100644 --- a/hildr-node/src/main/java/io/optimism/common/BlockInfo.java +++ b/hildr-node/src/main/java/io/optimism/common/BlockInfo.java @@ -4,6 +4,7 @@ import java.math.BigInteger; import java.util.Objects; import org.web3j.protocol.core.methods.response.EthBlock.Block; +import org.web3j.utils.Numeric; /** * The type BlockInfo. @@ -19,6 +20,9 @@ */ public record BlockInfo(String hash, BigInteger number, String parentHash, BigInteger timestamp) { + public static final BlockInfo EMPTY = new BlockInfo( + Numeric.toHexString(new byte[32]), BigInteger.ZERO, Numeric.toHexString(new byte[32]), BigInteger.ZERO); + /** * From block info. * diff --git a/hildr-node/src/main/java/io/optimism/config/Config.java b/hildr-node/src/main/java/io/optimism/config/Config.java index 3a1e68a5..4dc932ba 100644 --- a/hildr-node/src/main/java/io/optimism/config/Config.java +++ b/hildr-node/src/main/java/io/optimism/config/Config.java @@ -8,7 +8,7 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import io.optimism.common.BlockInfo; -import io.optimism.common.Epoch; +import io.optimism.type.Epoch; import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; @@ -40,10 +40,11 @@ * @param l2RpcUrl L2 chain rpc url. * @param l2EngineUrl L2 engine API url. * @param jwtSecret L2 engine API jwt secret. - * @param chainConfig The chain config. + * @param checkpointSyncUrl The checkpoint sync url. * @param rpcPort The rpc port. * @param devnet The flag of devnet. - * @param checkpointSyncUrl The checkpoint sync url. + * @param syncMode The sync mode + * @param chainConfig The chain config. * @author grapebaba * @since 0.1.0 */ @@ -58,6 +59,7 @@ public record Config( String checkpointSyncUrl, Integer rpcPort, Boolean devnet, + SyncMode syncMode, ChainConfig chainConfig) { /** @@ -142,6 +144,7 @@ private static MapConfigSource getMapConfigSource() { * @param jwtSecret L2 engine API jwt secret. * @param checkpointSyncUrl The checkpoint sync url. * @param rpcPort The rpc port. + * @param syncMode The sync mode. * @param devnet The devnet flag. */ public record CliConfig( @@ -154,6 +157,7 @@ public record CliConfig( String jwtSecret, String checkpointSyncUrl, Integer rpcPort, + SyncMode syncMode, Boolean devnet) { /** @@ -190,6 +194,9 @@ public Map toConfigMap() { if (rpcPort != null) { map.put("config.rpcPort", rpcPort.toString()); } + if (syncMode != null) { + map.put("config.syncMode", syncMode.toString()); + } map.put("config.devnet", String.valueOf(devnet != null && devnet)); return map; } @@ -659,7 +666,19 @@ public enum SyncMode { /** * Checkpoint sync mode. */ - Checkpoint; + Checkpoint, + /** + * Execution layer sync mode. + */ + ExecutionLayer; + + /** + * is execution layer sync mode + * @return true if execution layer sync mode, otherwise false. + */ + public boolean isEl() { + return this == ExecutionLayer; + } /** * From sync mode. @@ -673,6 +692,7 @@ public static SyncMode from(String value) { case "challenge" -> Challenge; case "full" -> Full; case "checkpoint" -> Checkpoint; + case "execution-layer" -> ExecutionLayer; default -> throw new RuntimeException("invalid sync mode"); }; } diff --git a/hildr-node/src/main/java/io/optimism/derive/State.java b/hildr-node/src/main/java/io/optimism/derive/State.java index 3cc74553..33b58998 100644 --- a/hildr-node/src/main/java/io/optimism/derive/State.java +++ b/hildr-node/src/main/java/io/optimism/derive/State.java @@ -1,17 +1,17 @@ package io.optimism.derive; import io.optimism.common.BlockInfo; -import io.optimism.common.Epoch; import io.optimism.config.Config; import io.optimism.driver.HeadInfo; import io.optimism.driver.L1AttributesDepositedTxNotFoundException; import io.optimism.l1.L1Info; +import io.optimism.type.Epoch; import java.math.BigInteger; import java.util.Map.Entry; import java.util.TreeMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.StructuredTaskScope; -import java.util.function.Function; +import java.util.function.BiFunction; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,7 +36,7 @@ public class State { private final TreeMap> l2Refs; - private final Function> l2Fetcher; + private final BiFunction> l2Fetcher; private BlockInfo safeHead; @@ -62,7 +62,7 @@ public State( TreeMap l1Info, TreeMap l1Hashes, TreeMap> l2Refs, - Function> l2Fetcher, + BiFunction> l2Fetcher, BlockInfo safeHead, Epoch safeEpoch, BigInteger currentEpochNum, @@ -89,7 +89,7 @@ public State( */ public static State create( TreeMap> l2Refs, - Function> l2Fetcher, + BiFunction> l2Fetcher, BlockInfo finalizedHead, Epoch finalizedEpoch, Config config) { @@ -144,7 +144,7 @@ public Tuple2 l2Info(BigInteger timestamp) { return cache; } LOGGER.warn("L2 refs cache not contains, will fetch from geth: blockNum = {}", blockNum); - var res = l2Fetcher.apply(blockNum); + var res = l2Fetcher.apply(DefaultBlockParameter.valueOf(blockNum), true); this.l2Refs.put(res.component1().number(), res); return res; } diff --git a/hildr-node/src/main/java/io/optimism/derive/stages/Attributes.java b/hildr-node/src/main/java/io/optimism/derive/stages/Attributes.java index 56e18e74..4d9fdb37 100644 --- a/hildr-node/src/main/java/io/optimism/derive/stages/Attributes.java +++ b/hildr-node/src/main/java/io/optimism/derive/stages/Attributes.java @@ -2,7 +2,6 @@ import io.optimism.common.BlockInfo; import io.optimism.common.BlockNotIncludedException; -import io.optimism.common.Epoch; import io.optimism.config.Config; import io.optimism.config.Config.SystemAccounts; import io.optimism.derive.EctoneUpgradeTransactions; @@ -10,6 +9,7 @@ import io.optimism.derive.State; import io.optimism.engine.ExecutionPayload.PayloadAttributes; import io.optimism.l1.L1Info; +import io.optimism.type.Epoch; import io.optimism.utilities.derive.stages.Batch; import io.optimism.utilities.derive.stages.SingularBatch; import io.optimism.utilities.gas.GasCalculator; diff --git a/hildr-node/src/main/java/io/optimism/derive/stages/Batches.java b/hildr-node/src/main/java/io/optimism/derive/stages/Batches.java index 765611d9..5062beec 100644 --- a/hildr-node/src/main/java/io/optimism/derive/stages/Batches.java +++ b/hildr-node/src/main/java/io/optimism/derive/stages/Batches.java @@ -2,12 +2,12 @@ import com.google.common.collect.Lists; import io.optimism.common.BlockInfo; -import io.optimism.common.Epoch; import io.optimism.config.Config; import io.optimism.derive.PurgeableIterator; import io.optimism.derive.State; import io.optimism.derive.stages.Channels.Channel; import io.optimism.l1.L1Info; +import io.optimism.type.Epoch; import io.optimism.utilities.derive.stages.Batch; import io.optimism.utilities.derive.stages.BatchType; import io.optimism.utilities.derive.stages.IBatch; @@ -243,6 +243,7 @@ private BatchStatus singularBatchStatus(final Batch batchWrapper) { return BatchStatus.Future; } case -1 -> { + LOGGER.warn("invalid batch timestamp, excepted={}, actual={}", nextTimestamp, batch.getTimestamp()); return BatchStatus.Drop; } default -> {} @@ -336,7 +337,11 @@ private BatchStatus spanBatchStatus(final Batch batchWrapper) { // check batch timestamp if (spanEndTimestamp.compareTo(nextTimestamp) < 0) { - LOGGER.warn("past batch"); + LOGGER.warn( + "past batch: nextTimestamp = l2SafeHead({}) + blockTime({}), spanEndTimestamp({})", + l2SafeHead.timestamp(), + this.config.chainConfig().blockTime(), + spanEndTimestamp); return BatchStatus.Drop; } if (spanStartTimestamp.compareTo(nextTimestamp) > 0) { diff --git a/hildr-node/src/main/java/io/optimism/driver/Driver.java b/hildr-node/src/main/java/io/optimism/driver/Driver.java index 2e6d7dbf..fba4186f 100644 --- a/hildr-node/src/main/java/io/optimism/driver/Driver.java +++ b/hildr-node/src/main/java/io/optimism/driver/Driver.java @@ -8,7 +8,6 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.AbstractExecutionThreadService; import io.optimism.common.BlockInfo; -import io.optimism.common.Epoch; import io.optimism.common.HildrServiceExecutionException; import io.optimism.config.Config; import io.optimism.derive.Pipeline; @@ -25,12 +24,13 @@ import io.optimism.telemetry.InnerMetrics; import io.optimism.type.BlockId; import io.optimism.type.DepositTransaction; +import io.optimism.type.Epoch; import io.optimism.type.Genesis; import io.optimism.type.L1BlockInfo; import io.optimism.type.L2BlockRef; import io.optimism.type.RollupConfigResult; import io.optimism.type.SystemConfig; -import io.optimism.utilities.TxDecoder; +import io.optimism.utilities.encoding.TxDecoder; import io.optimism.utilities.rpc.Web3jProvider; import io.optimism.utilities.telemetry.TracerTaskWrapper; import java.math.BigInteger; @@ -38,6 +38,7 @@ import java.util.HashMap; import java.util.List; import java.util.Optional; +import java.util.TreeMap; import java.util.concurrent.Callable; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; @@ -47,6 +48,7 @@ import java.util.concurrent.StructuredTaskScope; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; import org.apache.commons.collections4.CollectionUtils; @@ -83,6 +85,8 @@ public class Driver extends AbstractExecutionThreadService { private List futureUnsafeBlocks; + private final BiFunction> l2Fetcher; + private final AtomicReference state; private final ChainWatcher chainWatcher; @@ -105,11 +109,14 @@ public class Driver extends AbstractExecutionThreadService { private final AtomicBoolean isP2PNetworkStarted; + private final AtomicBoolean isElsyncFinished; + /** * Instantiates a new Driver. * * @param engineDriver the engine driver * @param pipeline the pipeline + * @param l2Fetcher the L2 HeadInfo fetcher * @param state the state * @param chainWatcher the chain watcher * @param unsafeBlockQueue the unsafe block queue @@ -122,6 +129,7 @@ public class Driver extends AbstractExecutionThreadService { public Driver( EngineDriver engineDriver, Pipeline pipeline, + BiFunction> l2Fetcher, AtomicReference state, ChainWatcher chainWatcher, MessagePassingQueue unsafeBlockQueue, @@ -132,6 +140,7 @@ public Driver( this.engineDriver = engineDriver; this.rpcServer = rpcServer; this.pipeline = pipeline; + this.l2Fetcher = l2Fetcher; this.state = state; this.chainWatcher = chainWatcher; this.unsafeBlockQueue = unsafeBlockQueue; @@ -147,6 +156,7 @@ public Driver( rpcHandler.put(RpcMethod.OP_ROLLUP_CONFIG.getRpcMethodName(), unused -> this.getRollupConfig()); this.rpcServer.register(rpcHandler); this.isP2PNetworkStarted = new AtomicBoolean(false); + this.isElsyncFinished = new AtomicBoolean(false); } /** @@ -210,27 +220,14 @@ public static Driver from(Config config, CountDownLatch latch) l1StartBlock.compareTo(BigInteger.ZERO) < 0 ? BigInteger.ZERO : l1StartBlock, finalizedHead.number(), config); - - var l2Refs = io.optimism.derive.State.initL2Refs(finalizedHead.number(), config.chainConfig(), l2Provider); - var l2Fetcher = (Function>) blockNum -> { - try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { - StructuredTaskScope.Subtask blockTask = scope.fork(TracerTaskWrapper.wrap(() -> l2Provider - .ethGetBlockByNumber(DefaultBlockParameter.valueOf(blockNum), true) - .send())); - scope.join(); - scope.throwIfFailed(); - - var block = blockTask.get(); - if (block == null || block.getBlock() == null) { - return null; - } - final HeadInfo l2BlockInfo = HeadInfo.from(block.getBlock()); - return new Tuple2<>(l2BlockInfo.l2BlockInfo(), l2BlockInfo.l1Epoch()); - } catch (Exception e) { - LOGGER.error("failed to fetch L2 block", e); - return null; - } - }; + TreeMap> l2Refs; + if (config.syncMode().isEl()) { + finalizedHead = BlockInfo.EMPTY; + l2Refs = new TreeMap<>(); + } else { + l2Refs = io.optimism.derive.State.initL2Refs(finalizedHead.number(), config.chainConfig(), l2Provider); + } + var l2Fetcher = Driver.l2Fetcher(l2Provider); AtomicReference state = new AtomicReference<>( io.optimism.derive.State.create(l2Refs, l2Fetcher, finalizedHead, finalizedEpoch, config)); @@ -246,7 +243,16 @@ public static Driver from(Config config, CountDownLatch latch) l2Provider.shutdown(); return new Driver<>( - engineDriver, pipeline, state, watcher, unsafeBlockQueue, rpcServer, latch, config, opStackNetwork); + engineDriver, + pipeline, + l2Fetcher, + state, + watcher, + unsafeBlockQueue, + rpcServer, + latch, + config, + opStackNetwork); } /** @@ -273,9 +279,7 @@ public RollupConfigResult getRollupConfig() { curSysConfig.l1FeeScalar(), curSysConfig.gasLimit()); var latestGenesis = new Genesis( - new BlockId( - chainConfig.l1StartEpoch().hash(), - chainConfig.l1StartEpoch().number()), + chainConfig.l1StartEpoch(), new BlockId( chainConfig.l2Genesis().hash(), chainConfig.l2Genesis().number()), chainConfig.l2Genesis().timestamp(), @@ -304,6 +308,9 @@ public RollupConfigResult getRollupConfig() { * @return result of sync status. */ public SyncStatusResult getSyncStatus() { + if (this.engineDriver.isEngineSyncing()) { + return null; + } // CurrentL1 final var currentL1 = this.chainWatcher.getCurrentL1(); // CurrentL1Finalized @@ -364,7 +371,9 @@ protected void startUp() { LOGGER.error("driver start fatal error", e); throw new HildrServiceExecutionException(e); } - this.chainWatcher.start(); + if (!Driver.this.config.syncMode().isEl()) { + this.chainWatcher.start(); + } } @Override @@ -440,14 +449,16 @@ private void advanceSafeHead() throws ExecutionException, InterruptedException { if (l1InclusionBlock == null) { throw new InvalidAttributesException("attributes without inclusion block"); } - + if (Driver.this.engineDriver.isEngineSyncing()) { + LOGGER.info("engine is syncing, skipping payload"); + continue; + } BigInteger seqNumber = payloadAttributes.seqNumber(); if (seqNumber == null) { throw new InvalidAttributesException("attributes without seq number"); } Driver.this.engineDriver.handleAttributes(payloadAttributes); - LOGGER.info( "safe head updated: {} {}", Driver.this.engineDriver.getSafeHead().number(), @@ -473,27 +484,54 @@ private void advanceUnsafeHead() throws ExecutionException, InterruptedException for (ExecutionPayload payload = this.unsafeBlockQueue.poll(); payload != null; payload = this.unsafeBlockQueue.poll()) { - this.futureUnsafeBlocks.add(payload); + BigInteger unsafeBlockNum = payload.blockNumber(); + BigInteger syncedBlockNum = Driver.this.engineDriver.getUnsafeHead().number(); + if (syncedBlockNum.compareTo(BigInteger.ZERO) == 0) { + this.futureUnsafeBlocks.add(payload); + break; + } else if (this.engineDriver.isEngineSyncing() && unsafeBlockNum.compareTo(syncedBlockNum) > 0) { + this.futureUnsafeBlocks.add(payload); + } else if (unsafeBlockNum.compareTo(syncedBlockNum) > 0 + && unsafeBlockNum.subtract(syncedBlockNum).compareTo(BigInteger.valueOf(1024L)) < 0) { + this.futureUnsafeBlocks.add(payload); + } + } + if (this.futureUnsafeBlocks.isEmpty()) { + return; + } + LOGGER.debug("will handle future unsafe blocks: size={}", this.futureUnsafeBlocks.size()); + Optional nextUnsafePayload; + if (Driver.this.engineDriver.isEngineSyncing()) { + nextUnsafePayload = Optional.of(this.futureUnsafeBlocks.removeFirst()); + } else { + nextUnsafePayload = Iterables.tryFind(this.futureUnsafeBlocks, input -> input.parentHash() + .equalsIgnoreCase( + Driver.this.engineDriver.getUnsafeHead().hash())) + .toJavaUtil(); } - this.futureUnsafeBlocks = this.futureUnsafeBlocks.stream() - .filter(payload -> { - BigInteger unsafeBlockNum = payload.blockNumber(); - BigInteger syncedBlockNum = - Driver.this.engineDriver.getUnsafeHead().number(); - return unsafeBlockNum.compareTo(syncedBlockNum) > 0 - && unsafeBlockNum.subtract(syncedBlockNum).compareTo(BigInteger.valueOf(1024L)) < 0; - }) - .collect(Collectors.toList()); - - Optional nextUnsafePayload = Iterables.tryFind( - this.futureUnsafeBlocks, input -> input.parentHash() - .equalsIgnoreCase( - Driver.this.engineDriver.getUnsafeHead().hash())) - .toJavaUtil(); - - if (nextUnsafePayload.isPresent()) { + if (nextUnsafePayload.isEmpty()) { + return; + } + try { + LOGGER.debug( + "will handle unsafe payload block hash: {}", + nextUnsafePayload.get().blockHash()); this.engineDriver.handleUnsafePayload(nextUnsafePayload.get()); + } catch (ForkchoiceUpdateException | InvalidExecutionPayloadException e) { + if (!this.config.syncMode().isEl()) { + throw e; + } + // Ignore fork choice update exception during EL syncing + LOGGER.warn("Failed to insert unsafe payload for EL sync: ", e); + } + if (!this.config.syncMode().isEl() || this.engineDriver.isEngineSyncing()) { + return; + } + if (!this.isElsyncFinished.compareAndExchange(false, true)) { + LOGGER.info("execution layer syncing is done, restarting chain watcher."); + this.fetchAndUpdateFinalizedHead(); + this.restartChainWatcher(); } } @@ -528,25 +566,26 @@ private void handleNextBlockUpdate() { case BlockUpdate.Reorg ignored -> { LOGGER.warn("reorg detected, purging pipeline"); Driver.this.unfinalizedBlocks.clear(); - - Driver.this.chainWatcher.restart( - Driver.this.engineDriver.getFinalizedEpoch().number().subtract(this.channelTimeout), - Driver.this.engineDriver.getFinalizedHead().number()); - - Driver.this.state.getAndUpdate(state -> { - state.purge( - Driver.this.engineDriver.getFinalizedHead(), Driver.this.engineDriver.getFinalizedEpoch()); - return state; - }); - - Driver.this.pipeline.purge(); - Driver.this.engineDriver.reorg(); + Driver.this.restartChainWatcher(); } case BlockUpdate.FinalityUpdate num -> Driver.this.finalizedL1BlockNumber = num.get(); default -> throw new IllegalArgumentException("unknown block update type"); } } + private void restartChainWatcher() { + Driver.this.chainWatcher.restart( + Driver.this.engineDriver.getFinalizedEpoch().number().subtract(this.channelTimeout), + Driver.this.engineDriver.getFinalizedHead().number()); + + Driver.this.state.getAndUpdate(state -> { + state.purge(Driver.this.engineDriver.getFinalizedHead(), Driver.this.engineDriver.getFinalizedEpoch()); + return state; + }); + Driver.this.pipeline.purge(); + Driver.this.engineDriver.reorg(); + } + private void updateFinalized() { UnfinalizedBlock newFinalized = Iterables.getLast( this.unfinalizedBlocks.stream() @@ -577,7 +616,9 @@ private boolean synced() { } private void tryStartNetwork() { - if (this.synced() && this.opStackNetwork != null && !this.isP2PNetworkStarted.compareAndExchange(false, true)) { + if ((this.synced() || this.config.syncMode().isEl()) + && this.opStackNetwork != null + && !this.isP2PNetworkStarted.compareAndExchange(false, true)) { this.opStackNetwork.start(); } } @@ -603,7 +644,7 @@ private L2BlockRef unsafeL2SyncTarget() { * @return L2BlockRef instance */ public static L2BlockRef payloadToRef(ExecutionPayload payload, Config.ChainConfig genesis) { - BlockId l1Origin; + Epoch l1Origin; BigInteger sequenceNumber; if (payload.blockNumber().compareTo(genesis.l2Genesis().number()) == 0) { if (!payload.blockHash().equals(genesis.l2Genesis().hash())) { @@ -613,18 +654,17 @@ public static L2BlockRef payloadToRef(ExecutionPayload payload, Config.ChainConf payload.blockHash(), genesis.l2Genesis().hash())); } - l1Origin = new BlockId( - genesis.l1StartEpoch().hash(), genesis.l1StartEpoch().number()); + l1Origin = genesis.l1StartEpoch(); sequenceNumber = BigInteger.ZERO; } else { - if (payload.transactions().size() == 0) { + if (payload.transactions().isEmpty()) { throw new RuntimeException( String.format("l2 block is missing L1 info deposit tx, block hash: %s", payload.blockHash())); } DepositTransaction depositTx = TxDecoder.decodeToDeposit(payload.transactions().get(0)); L1BlockInfo info = L1BlockInfo.from(Numeric.hexStringToByteArray(depositTx.getData())); - l1Origin = new BlockId(info.blockHash(), info.number()); + l1Origin = info.toEpoch(); sequenceNumber = info.sequenceNumber(); } @@ -633,10 +673,45 @@ public static L2BlockRef payloadToRef(ExecutionPayload payload, Config.ChainConf payload.blockNumber(), payload.parentHash(), payload.timestamp(), - new BlockId(l1Origin.hash(), l1Origin.number()), + l1Origin, sequenceNumber); } + private void fetchAndUpdateFinalizedHead() { + DefaultBlockParameter blockParameter; + if (this.engineDriver.getFinalizedHead().number().compareTo(BigInteger.ZERO) == 0) { + blockParameter = FINALIZED; + } else { + blockParameter = DefaultBlockParameter.valueOf( + this.engineDriver.getFinalizedHead().number()); + } + Tuple2 finalizedHead = l2Fetcher.apply(blockParameter, true); + this.engineDriver.updateFinalized(finalizedHead.component1(), finalizedHead.component2()); + } + + private static BiFunction> l2Fetcher( + final Web3j l2Provider) { + return (blockParameter, returnFull) -> { + try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { + StructuredTaskScope.Subtask blockTask = scope.fork(TracerTaskWrapper.wrap(() -> l2Provider + .ethGetBlockByNumber(blockParameter, returnFull) + .send())); + scope.join(); + scope.throwIfFailed(); + + var block = blockTask.get(); + if (block == null || block.getBlock() == null) { + return null; + } + final HeadInfo l2BlockInfo = HeadInfo.from(block.getBlock()); + return new Tuple2<>(l2BlockInfo.l2BlockInfo(), l2BlockInfo.l1Epoch()); + } catch (Exception e) { + LOGGER.error("failed to fetch L2 block", e); + return null; + } + }; + } + /** * The type Unfinalized block. * diff --git a/hildr-node/src/main/java/io/optimism/driver/EngineDriver.java b/hildr-node/src/main/java/io/optimism/driver/EngineDriver.java index 71031d17..ef662a8c 100644 --- a/hildr-node/src/main/java/io/optimism/driver/EngineDriver.java +++ b/hildr-node/src/main/java/io/optimism/driver/EngineDriver.java @@ -1,7 +1,6 @@ package io.optimism.driver; import io.optimism.common.BlockInfo; -import io.optimism.common.Epoch; import io.optimism.config.Config; import io.optimism.engine.Engine; import io.optimism.engine.EngineApi; @@ -14,6 +13,9 @@ import io.optimism.engine.OpEthExecutionPayload; import io.optimism.engine.OpEthForkChoiceUpdate; import io.optimism.engine.OpEthPayloadStatus; +import io.optimism.type.Epoch; +import io.optimism.type.L2BlockRef; +import io.optimism.type.enums.SyncStatus; import io.optimism.utilities.telemetry.TracerTaskWrapper; import java.math.BigInteger; import java.util.List; @@ -26,6 +28,7 @@ import org.web3j.crypto.Hash; import org.web3j.protocol.Web3j; import org.web3j.protocol.core.DefaultBlockParameter; +import org.web3j.protocol.core.DefaultBlockParameterName; import org.web3j.protocol.core.methods.response.EthBlock; import org.web3j.protocol.core.methods.response.EthBlock.TransactionObject; @@ -39,11 +42,15 @@ public class EngineDriver { private static final Logger LOGGER = LoggerFactory.getLogger(EngineDriver.class); - private E engine; + private final E engine; - private Web3j l2Client; + private final Web3j l2Client; - private BigInteger blockTime; + private final BigInteger blockTime; + + private final boolean syncModeEl; + + private final Config.ChainConfig chainConfig; private BlockInfo unsafeHead; @@ -55,6 +62,7 @@ public class EngineDriver { private Epoch finalizedEpoch; + private SyncStatus syncStatus; /** * Instantiates a new Engine driver. * @@ -72,7 +80,10 @@ public EngineDriver(BlockInfo finalizedHead, Epoch finalizedEpoch, Web3j l2Clien this.safeHead = finalizedHead; this.safeEpoch = finalizedEpoch; this.l2Client = l2Client; + this.chainConfig = config.chainConfig(); this.blockTime = config.chainConfig().blockTime(); + this.syncModeEl = config.syncMode().isEl(); + this.syncStatus = syncModeEl ? SyncStatus.WillStartEL : SyncStatus.CL; } /** Stop. */ @@ -134,6 +145,14 @@ public Epoch getFinalizedEpoch() { return finalizedEpoch; } + /** + * check is engine syncing + * @return true if engine is syncing + */ + public boolean isEngineSyncing() { + return syncStatus.isEngineSyncing(); + } + /** * Handle attributes completable future. * @@ -142,6 +161,7 @@ public Epoch getFinalizedEpoch() { * @throws InterruptedException the interrupted exception */ public void handleAttributes(PayloadAttributes attributes) throws ExecutionException, InterruptedException { + EthBlock block = this.blockAt(attributes.timestamp()); if (block == null || block.getBlock() == null) { processAttributes(attributes); @@ -163,10 +183,44 @@ public void handleAttributes(PayloadAttributes attributes) throws ExecutionExcep * @throws InterruptedException the interrupted exception */ public void handleUnsafePayload(ExecutionPayload payload) throws ExecutionException, InterruptedException { + if (this.syncStatus == SyncStatus.WillStartEL) { + var l2Finalized = l2Client.ethGetBlockByNumber(DefaultBlockParameterName.FINALIZED, true) + .sendAsync() + .get(); + if (l2Finalized.hasError() && l2Finalized.getError().getMessage().contains("block not found")) { + this.syncStatus = SyncStatus.StartedEL; + LOGGER.info("Starting EL sync"); + } else if (this.chainConfig.l2Genesis().number().compareTo(BigInteger.ZERO) != 0 + && l2Finalized + .getBlock() + .getHash() + .equals(this.chainConfig.l2Genesis().hash())) { + this.syncStatus = SyncStatus.StartedEL; + LOGGER.info("Starting EL sync"); + } else { + this.syncStatus = SyncStatus.FinishedEL; + LOGGER.info("Skipping EL sync and going straight to CL sync because there is a finalized block"); + return; + } + } + this.pushPayload(payload); this.unsafeHead = BlockInfo.from(payload); + L2BlockRef l2BlockInfo = payload.toL2BlockInfo(this.chainConfig); + if (this.syncStatus == SyncStatus.FinishedELNotFinalized) { + BlockInfo l2NewHead = new BlockInfo( + l2BlockInfo.hash(), l2BlockInfo.number(), l2BlockInfo.parentHash(), l2BlockInfo.timestamp()); + Epoch l1NewEpoch = l2BlockInfo.l1origin(); + this.updateSafeHead(l2NewHead, l1NewEpoch, false); + this.updateFinalized(l2NewHead, l1NewEpoch); + } + this.updateForkchoice(); LOGGER.info("unsafe head updated: {} {}", this.unsafeHead.number(), this.unsafeHead.hash()); + if (this.syncStatus == SyncStatus.FinishedELNotFinalized) { + this.syncStatus = SyncStatus.FinishedEL; + LOGGER.info("EL sync finished"); + } } /** @@ -194,6 +248,10 @@ public void reorg() { * @throws InterruptedException the interrupted exception */ public boolean engineReady() throws InterruptedException { + if (this.syncModeEl) { + // Skip check if EL sync is enabled + return true; + } ForkchoiceState forkchoiceState = createForkchoiceState(); try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { @@ -257,6 +315,7 @@ private void updateSafeHead(BlockInfo newHead, Epoch newEpoch, boolean reorgUnsa this.safeEpoch = newEpoch; } if (reorgUnsafe || this.safeHead.number().compareTo(this.unsafeHead.number()) > 0) { + LOGGER.info("update unsafe head number({}) to new head({})", this.unsafeHead.number(), newHead.number()); this.unsafeHead = newHead; } } @@ -270,12 +329,28 @@ private void updateForkchoice() throws InterruptedException, ExecutionException scope.join(); scope.throwIfFailed(); - ForkChoiceUpdate forkChoiceUpdate = forkChoiceUpdateFuture.get().getForkChoiceUpdate(); - if (forkChoiceUpdate.payloadStatus().getStatus() != Status.VALID) { - throw new ForkchoiceUpdateException(String.format( - "could not accept new forkchoice: %s", - forkChoiceUpdate.payloadStatus().getValidationError())); + var forkChoiceUpdate = forkChoiceUpdateFuture.get(); + if (forkChoiceUpdate.hasError()) { + throw new ForkchoiceUpdateException("could not accept new forkchoice: %s" + .formatted(forkChoiceUpdate.getError().getMessage())); + } + var forkChoiceUpdateStatus = forkChoiceUpdate.getForkChoiceUpdate().payloadStatus(); + var updateStatus = forkChoiceUpdateStatus.getStatus(); + if (this.syncStatus.isEngineSyncing()) { + if (updateStatus == Status.VALID && this.syncStatus == SyncStatus.StartedEL) { + this.syncStatus = SyncStatus.FinishedELNotFinalized; + } + // Allow SYNCING if engine P2P sync is enabled + if (updateStatus == Status.INVALID || forkChoiceUpdateStatus.getStatus() == Status.INVALID_BLOCK_HASH) { + throw new ForkchoiceUpdateException(String.format( + "could not accept new forkchoice: %s", forkChoiceUpdateStatus.getValidationError())); + } + } else { + if (updateStatus != Status.VALID) { + throw new ForkchoiceUpdateException(String.format( + "could not accept new forkchoice: %s", forkChoiceUpdateStatus.getValidationError())); + } } } } @@ -287,10 +362,26 @@ private void pushPayload(final ExecutionPayload payload) throws InterruptedExcep scope.join(); scope.throwIfFailed(); - PayloadStatus payloadStatus = payloadStatusFuture.get().getPayloadStatus(); - - if (payloadStatus.getStatus() != Status.VALID && payloadStatus.getStatus() != Status.ACCEPTED) { - throw new InvalidExecutionPayloadException("the provided checkpoint payload is invalid"); + OpEthPayloadStatus payloadStatus = payloadStatusFuture.get(); + if (payloadStatus.hasError()) { + throw new InvalidExecutionPayloadException("the provided checkpoint payload is invalid:" + + payloadStatus.getError().getMessage()); + } + PayloadStatus status = payloadStatus.getPayloadStatus(); + if (syncModeEl) { + if (status.getStatus() == Status.VALID && this.syncStatus == SyncStatus.StartedEL) { + syncStatus = SyncStatus.FinishedELNotFinalized; + } + // Allow SYNCING and ACCEPTED if engine EL sync is enabled + if (status.getStatus() != Status.VALID + && status.getStatus() != Status.ACCEPTED + && status.getStatus() != Status.SYNCING) { + throw new InvalidExecutionPayloadException("the provided checkpoint payload is invalid"); + } + } else { + if (status.getStatus() != Status.VALID && status.getStatus() != Status.ACCEPTED) { + throw new InvalidExecutionPayloadException("the provided checkpoint payload is invalid"); + } } } } diff --git a/hildr-node/src/main/java/io/optimism/driver/HeadInfo.java b/hildr-node/src/main/java/io/optimism/driver/HeadInfo.java index 918c4d5b..8b557d0a 100644 --- a/hildr-node/src/main/java/io/optimism/driver/HeadInfo.java +++ b/hildr-node/src/main/java/io/optimism/driver/HeadInfo.java @@ -2,7 +2,7 @@ import io.optimism.common.AttributesDepositedCall; import io.optimism.common.BlockInfo; -import io.optimism.common.Epoch; +import io.optimism.type.Epoch; import java.math.BigInteger; import org.web3j.protocol.core.methods.response.EthBlock; import org.web3j.protocol.core.methods.response.EthBlock.TransactionObject; @@ -33,8 +33,6 @@ public static HeadInfo from(EthBlock.Block block) { String txCallData = ((TransactionObject) block.getTransactions().get(0)).getInput(); AttributesDepositedCall call = AttributesDepositedCall.from(txCallData); - Epoch epoch = Epoch.from(call); - - return new HeadInfo(blockInfo, epoch, call.sequenceNumber()); + return new HeadInfo(blockInfo, call.toEpoch(), call.sequenceNumber()); } } diff --git a/hildr-node/src/main/java/io/optimism/engine/EngineApi.java b/hildr-node/src/main/java/io/optimism/engine/EngineApi.java index fe26090f..c57a1fb4 100644 --- a/hildr-node/src/main/java/io/optimism/engine/EngineApi.java +++ b/hildr-node/src/main/java/io/optimism/engine/EngineApi.java @@ -6,6 +6,7 @@ import io.optimism.config.Config; import io.optimism.engine.ExecutionPayload.PayloadAttributes; import io.optimism.engine.ForkChoiceUpdate.ForkchoiceState; +import io.optimism.utilities.rpc.Web3jProvider; import java.io.IOException; import java.math.BigInteger; import java.security.Key; @@ -87,20 +88,20 @@ public EngineApi fromEnv(Config config) { if (StringUtils.isBlank(baseUrlParm)) { throw new RuntimeException( """ - ENGINE_API_URL environment variable not set. - Please set this to the base url of the engine api - """); + ENGINE_API_URL environment variable not set. + Please set this to the base url of the engine api + """); } String secretKey = System.getenv("JWT_SECRET"); if (StringUtils.isBlank(secretKey)) { throw new RuntimeException( """ - JWT_SECRET environment variable not set. - Please set this to the 256 bit hex-encoded secret key - used to authenticate with the engine api. - This should be the same as set in the `--auth.secret` - flag when executing go-ethereum. - """); + JWT_SECRET environment variable not set. + Please set this to the 256 bit hex-encoded secret key + used to authenticate with the engine api. + This should be the same as set in the `--auth.secret` + flag when executing go-ethereum. + """); } String baseUrlFormat = authUrlFromAddr(baseUrlParm, null); return new EngineApi(config, baseUrlFormat, secretKey); @@ -116,7 +117,7 @@ public EngineApi fromEnv(Config config) { public EngineApi(final Config config, final String baseUrl, final String secretStr) { this.config = config; this.key = Keys.hmacShaKeyFor(Numeric.hexStringToByteArray(secretStr)); - this.web3jService = new HttpService(baseUrl); + this.web3jService = (HttpService) Web3jProvider.create(baseUrl).component2(); } /** diff --git a/hildr-node/src/main/java/io/optimism/engine/ExecutionPayload.java b/hildr-node/src/main/java/io/optimism/engine/ExecutionPayload.java index 1f802933..1f785c70 100644 --- a/hildr-node/src/main/java/io/optimism/engine/ExecutionPayload.java +++ b/hildr-node/src/main/java/io/optimism/engine/ExecutionPayload.java @@ -1,13 +1,21 @@ package io.optimism.engine; -import io.optimism.common.Epoch; +import io.optimism.common.BlockInfo; +import io.optimism.config.Config; import io.optimism.network.ExecutionPayloadSSZ; +import io.optimism.type.DepositTransaction; +import io.optimism.type.Epoch; +import io.optimism.type.L1BlockInfo; +import io.optimism.type.L2BlockRef; +import io.optimism.type.enums.TxType; +import io.optimism.utilities.encoding.TxDecoder; +import io.optimism.utilities.encoding.TxEncoder; +import io.optimism.utilities.rpc.response.OpEthBlock; import java.math.BigInteger; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.web3j.protocol.core.methods.response.EthBlock; -import org.web3j.protocol.core.methods.response.EthBlock.TransactionObject; import org.web3j.utils.Numeric; /** @@ -54,6 +62,41 @@ public record ExecutionPayload( BigInteger excessBlobGas, String parentBeaconBlockRoot) { + /** + * Converts the ExecutionPayload to an L2BlockRef. + * @param config the chain config + * @return the L2BlockRef + */ + public L2BlockRef toL2BlockInfo(Config.ChainConfig config) { + final Epoch l1GenesisEpoch = config.l1StartEpoch(); + final BlockInfo l2GenesisInfo = config.l2Genesis(); + BigInteger seqNumber; + Epoch l1Origin; + if (this.blockNumber.compareTo(l2GenesisInfo.number()) == 0) { + if (!l2GenesisInfo.hash().equals(this.blockHash)) { + throw new IllegalArgumentException("expected L2 genesis hash to match L2 block at genesis block number " + + l2GenesisInfo.number() + + ": " + + this.blockHash + + " <> " + + l2GenesisInfo.hash()); + } + l1Origin = l1GenesisEpoch; + seqNumber = BigInteger.ZERO; + } else { + if (transactions == null || transactions.isEmpty()) { + throw new IllegalArgumentException( + "l2 block is missing L1 info deposit tx, block hash: " + this.blockHash); + } + String txData = transactions.getFirst(); + DepositTransaction depositTx = TxDecoder.decodeToDeposit(txData); + L1BlockInfo l1Info = L1BlockInfo.from(Numeric.hexStringToByteArray(depositTx.getData())); + l1Origin = l1Info.toEpoch(); + seqNumber = l1Info.sequenceNumber(); + } + return new L2BlockRef(this.blockHash, this.blockNumber, this.parentHash, this.timestamp, l1Origin, seqNumber); + } + /** * The type Execution payload res. * @@ -126,12 +169,20 @@ public ExecutionPayload toExecutionPayload(String parentBeaconBlockRoot) { /** * From execution payload. * - * @param block the block + * @param block the L2 block * @return the execution payload */ - public static ExecutionPayload from(EthBlock.Block block) { + public static ExecutionPayload fromL2Block(OpEthBlock.Block block, Config.ChainConfig config) { + boolean isSystemTx = block.getTimestamp().compareTo(config.regolithTime()) < 0; List encodedTxs = block.getTransactions().stream() - .map(tx -> ((TransactionObject) tx).getInput()) + .map(tx -> { + var txObj = ((OpEthBlock.TransactionObject) tx); + if (TxType.OPTIMISM_DEPOSIT.is(txObj.getType())) { + return Numeric.toHexString(TxEncoder.encodeDepositTx(txObj, isSystemTx)); + } else { + return Numeric.toHexString(TxEncoder.encode(txObj.toWeb3j())); + } + }) .collect(Collectors.toList()); return new ExecutionPayload( diff --git a/hildr-node/src/main/java/io/optimism/l1/BeaconBlobFetcher.java b/hildr-node/src/main/java/io/optimism/l1/BeaconBlobFetcher.java index 0c34aaf7..d576fe9c 100644 --- a/hildr-node/src/main/java/io/optimism/l1/BeaconBlobFetcher.java +++ b/hildr-node/src/main/java/io/optimism/l1/BeaconBlobFetcher.java @@ -15,6 +15,7 @@ import java.util.concurrent.StructuredTaskScope; import java.util.stream.Collectors; import okhttp3.Call; +import okhttp3.HttpUrl; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; @@ -75,6 +76,12 @@ public BeaconBlobFetcher(String beaconUrl) { * @param beaconArchiverUrl L1 beacon archiver client url */ public BeaconBlobFetcher(String beaconUrl, String beaconArchiverUrl) { + if (beaconUrl.endsWith("/")) { + beaconUrl = beaconUrl.replaceAll("/+$", ""); + } + if (beaconArchiverUrl.endsWith("/")) { + beaconArchiverUrl = beaconArchiverUrl.replaceAll("/+$", ""); + } this.genesisMethod = GENESIS_METHOD_FORMAT.formatted(beaconUrl); this.specMethod = SPEC_METHOD_FORMAT.formatted(beaconUrl); this.sidecarsMethod = SIDECARS_METHOD_PREFIX_FORMAT.formatted(beaconUrl); @@ -138,7 +145,7 @@ public List getBlobSidecards(String blockId, final List return res.getData(); } if (this.archiverSidecarsMethod != null) { - LOGGER.debug( + LOGGER.warn( "blob sidecars may be pruned, try blob archiver sidecars method: blockId = {}, indices = {}", blockId, indices); @@ -147,7 +154,7 @@ public List getBlobSidecards(String blockId, final List return archiverRes.getData(); } } else { - LOGGER.debug( + LOGGER.info( "blob archiver sidecars method is empty, skip retry: block Id = {}, indices = {}", blockId, indices); @@ -157,7 +164,7 @@ public List getBlobSidecards(String blockId, final List } private BeaconApiResponse> getBlobSidecars(String url) { - var req = new Request.Builder().get().url(url).build(); + var req = new Request.Builder().get().url(HttpUrl.parse(url)).build(); return this.send(req, new TypeReference>>() {}); } diff --git a/hildr-node/src/main/java/io/optimism/network/AbstractTopicHandler.java b/hildr-node/src/main/java/io/optimism/network/AbstractTopicHandler.java index fc5c6c0a..141dc1ab 100644 --- a/hildr-node/src/main/java/io/optimism/network/AbstractTopicHandler.java +++ b/hildr-node/src/main/java/io/optimism/network/AbstractTopicHandler.java @@ -362,7 +362,10 @@ protected void processMessage( case IGNORE -> LOGGER.debug("Ignoring message for topic: {}", topic); case SAVE_FOR_FUTURE -> LOGGER.debug("Deferring message for topic: {}", topic); case ACCEPT -> { - LOGGER.debug("Accepting message for topic: {}", topic); + LOGGER.debug( + "Accepting message for topic: {}, number: {}", + topic, + blockMessage.payloadEnvelop.executionPayload().blockNumber()); this.unsafeBlockQueue.offer(blockMessage.payloadEnvelop.executionPayload()); } default -> throw new UnsupportedOperationException( diff --git a/hildr-node/src/main/java/io/optimism/network/OpStackNetwork.java b/hildr-node/src/main/java/io/optimism/network/OpStackNetwork.java index 1a6feb8a..1eff8360 100644 --- a/hildr-node/src/main/java/io/optimism/network/OpStackNetwork.java +++ b/hildr-node/src/main/java/io/optimism/network/OpStackNetwork.java @@ -46,33 +46,50 @@ public class OpStackNetwork { private static final List BOOTNODES = List.of( "enr:-J64QBbwPjPLZ6IOOToOLsSjtFUjjzN66qmBZdUexpO32Klrc458Q24kbty2PdRaLacHM5z-cZQr8mjeQu3pik6jPSOGAYYFIqBfgmlkgnY0gmlwhDaRWFWHb3BzdGFja4SzlAUAiXNlY3AyNTZrMaECmeSnJh7zjKrDSPoNMGXoopeDF4hhpj5I0OsQUUt4u8uDdGNwgiQGg3VkcIIkBg", "enr:-J64QAlTCDa188Hl1OGv5_2Kj2nWCsvxMVc_rEnLtw7RPFbOfqUOV6khXT_PH6cC603I2ynY31rSQ8sI9gLeJbfFGaWGAYYFIrpdgmlkgnY0gmlwhANWgzCHb3BzdGFja4SzlAUAiXNlY3AyNTZrMaECkySjcg-2v0uWAsFsZZu43qNHppGr2D5F913Qqs5jDCGDdGNwgiQGg3VkcIIkBg", - "enr:-J24QGEzN4mJgLWNTUNwj7riVJ2ZjRLenOFccl2dbRFxHHOCCZx8SXWzgf-sLzrGs6QgqSFCvGXVgGPBkRkfOWlT1-iGAYe6Cu93gmlkgnY0gmlwhCJBEUSHb3BzdGFja4OkAwCJc2VjcDI1NmsxoQLuYIwaYOHg3CUQhCkS-RsSHmUd1b_x93-9yQ5ItS6udIN0Y3CCIyuDdWRwgiMr", - "enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg", - "enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg", - "enr:-Ku4QPn5eVhcoF1opaFEvg1b6JNFD2rqVkHQ8HApOKK61OIcIXD127bKWgAtbwI7pnxx6cDyk_nI88TrZKQaGMZj0q0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDayLMaJc2VjcDI1NmsxoQK2sBOLGcUb4AwuYzFuAVCaNHA-dy24UuEKkeFNgCVCsIN1ZHCCIyg", - "enr:-Ku4QEWzdnVtXc2Q0ZVigfCGggOVB2Vc1ZCPEc6j21NIFLODSJbvNaef1g4PxhPwl_3kax86YPheFUSLXPRs98vvYsoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDZBrP2Jc2VjcDI1NmsxoQM6jr8Rb1ktLEsVcKAPa08wCsKUmvoQ8khiOl_SLozf9IN1ZHCCIyg", + // + // "enr:-J24QGEzN4mJgLWNTUNwj7riVJ2ZjRLenOFccl2dbRFxHHOCCZx8SXWzgf-sLzrGs6QgqSFCvGXVgGPBkRkfOWlT1-iGAYe6Cu93gmlkgnY0gmlwhCJBEUSHb3BzdGFja4OkAwCJc2VjcDI1NmsxoQLuYIwaYOHg3CUQhCkS-RsSHmUd1b_x93-9yQ5ItS6udIN0Y3CCIyuDdWRwgiMr", + // + // "enr:-Ku4QHqVeJ8PPICcWk1vSn_XcSkjOkNiTg6Fmii5j6vUQgvzMc9L1goFnLKgXqBJspJjIsB91LTOleFmyWWrFVATGngBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAMRHkWJc2VjcDI1NmsxoQKLVXFOhp2uX6jeT0DvvDpPcU8FWMjQdR4wMuORMhpX24N1ZHCCIyg", + // + // "enr:-Ku4QG-2_Md3sZIAUebGYT6g0SMskIml77l6yR-M_JXc-UdNHCmHQeOiMLbylPejyJsdAPsTHJyjJB2sYGDLe0dn8uYBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhBLY-NyJc2VjcDI1NmsxoQORcM6e19T1T9gi7jxEZjk_sjVLGFscUNqAY9obgZaxbIN1ZHCCIyg", + // + // "enr:-Ku4QPn5eVhcoF1opaFEvg1b6JNFD2rqVkHQ8HApOKK61OIcIXD127bKWgAtbwI7pnxx6cDyk_nI88TrZKQaGMZj0q0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDayLMaJc2VjcDI1NmsxoQK2sBOLGcUb4AwuYzFuAVCaNHA-dy24UuEKkeFNgCVCsIN1ZHCCIyg", + // + // "enr:-Ku4QEWzdnVtXc2Q0ZVigfCGggOVB2Vc1ZCPEc6j21NIFLODSJbvNaef1g4PxhPwl_3kax86YPheFUSLXPRs98vvYsoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhDZBrP2Jc2VjcDI1NmsxoQM6jr8Rb1ktLEsVcKAPa08wCsKUmvoQ8khiOl_SLozf9IN1ZHCCIyg", // Base "enr:-J24QNz9lbrKbN4iSmmjtnr7SjUMk4zB7f1krHZcTZx-JRKZd0kA2gjufUROD6T3sOWDVDnFJRvqBBo62zuF-hYCohOGAYiOoEyEgmlkgnY0gmlwhAPniryHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQKNVFlCxh_B-716tTs-h1vMzZkSs1FTu_OYTNjgufplG4N0Y3CCJAaDdWRwgiQG", "enr:-J24QH-f1wt99sfpHy4c0QJM-NfmsIfmlLAMMcgZCUEgKG_BBYFc6FwYgaMJMQN5dsRBJApIok0jFn-9CS842lGpLmqGAYiOoDRAgmlkgnY0gmlwhLhIgb2Hb3BzdGFja4OFQgCJc2VjcDI1NmsxoQJ9FTIv8B9myn1MWaC_2lJ-sMoeCDkusCsk4BYHjjCq04N0Y3CCJAaDdWRwgiQG", "enr:-J24QDXyyxvQYsd0yfsN0cRr1lZ1N11zGTplMNlW4xNEc7LkPXh0NAJ9iSOVdRO95GPYAIc6xmyoCCG6_0JxdL3a0zaGAYiOoAjFgmlkgnY0gmlwhAPckbGHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQJwoS7tzwxqXSyFL7g0JM-KWVbgvjfB8JA__T7yY_cYboN0Y3CCJAaDdWRwgiQG", "enr:-J24QHmGyBwUZXIcsGYMaUqGGSl4CFdx9Tozu-vQCn5bHIQbR7On7dZbU61vYvfrJr30t0iahSqhc64J46MnUO2JvQaGAYiOoCKKgmlkgnY0gmlwhAPnCzSHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQINc4fSijfbNIiGhcgvwjsjxVFJHUstK9L1T8OTKUjgloN0Y3CCJAaDdWRwgiQG", - "enr:-J24QG3ypT4xSu0gjb5PABCmVxZqBjVw9ca7pvsI8jl4KATYAnxBmfkaIuEqy9sKvDHKuNCsy57WwK9wTt2aQgcaDDyGAYiOoGAXgmlkgnY0gmlwhDbGmZaHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQIeAK_--tcLEiu7HvoUlbV52MspE0uCocsx1f_rYvRenIN0Y3CCJAaDdWRwgiQG", + "enr:-J24QG3ypT4xSu0gjb5PABCmVxZqBjVw9ca7pvsI8jl4KATYAnxBmfkaIuEqy9sKvDHKuNCsy57WwK9wTt2aQgcaDDyGAYiOoGAXgmlkgnY0gmlwhDbGmZaHb3BzdGFja4OFQgCJc2VjcDI1NmsxoQIeAK_--tcLEiu7HvoUlbV52MspE0uCocsx1f_rYvRenIN0Y3CCJAaDdWRwgiQG" // Teku team (Consensys) - "enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2Gxb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNlY3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA", - "enr:-KG4QDyytgmE4f7AnvW-ZaUOIi9i79qX4JwjRAiXBZCU65wOfBu-3Nb5I7b_Rmg3KCOcZM_C3y5pg7EBU5XGrcLTduQEhGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQ2_DUbiXNlY3AyNTZrMaEDKnz_-ps3UUOfHWVYaskI5kWYO_vtYMGYCQRAR3gHDouDdGNwgiMog3VkcIIjKA", - // PegaSys Teku - "enr:-KG4QJRlj4pHagfNIm-Fsx9EVjW4rviuZYzle3tyddm2KAWMJBDGAhxfM2g-pDaaiwE8q19uvLSH4jyvWjypLMr3TIcEhGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNlY3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA", - "enr:-KG4QL-eqFoHy0cI31THvtZjpYUu_Jdw_MO7skQRJxY1g5HTN1A0epPCU6vi0gLGUgrzpU-ygeMSS8ewVxDpKfYmxMMGhGV0aDKQtTA_KgAAAAD__________4JpZIJ2NIJpcIQ2_DUbiXNlY3AyNTZrMaED8GJ2vzUqgL6-KD1xalo1CsmY4X1HaDnyl6Y_WayCo9GDdGNwgiMog3VkcIIjKA", - // Prysmatic Labs - "enr:-Ku4QImhMc1z8yCiNJ1TyUxdcfNucje3BGwEHzodEZUan8PherEo4sF7pPHPSIB1NNuSg5fZy7qFsjmUKs2ea1Whi0EBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQOVphkDqal4QzPMksc5wnpuC3gvSC8AfbFOnZY_On34wIN1ZHCCIyg", - "enr:-Ku4QP2xDnEtUXIjzJ_DhlCRN9SN99RYQPJL92TMlSv7U5C1YnYLjwOQHgZIUXw6c-BvRg2Yc2QsZxxoS_pPRVe0yK8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMeFF5GrS7UZpAH2Ly84aLK-TyvH-dRo0JM1i8yygH50YN1ZHCCJxA", - "enr:-Ku4QPp9z1W4tAO8Ber_NQierYaOStqhDqQdOPY3bB3jDgkjcbk6YrEnVYIiCBbTxuar3CzS528d2iE7TdJsrL-dEKoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMw5fqqkw2hHC4F5HZZDPsNmPdB1Gi8JPQK7pRc9XHh-oN1ZHCCKvg", - // Sigp Lighthouse - "enr:-Jq4QItoFUuug_n_qbYbU0OY04-np2wT8rUCauOOXNi0H3BWbDj-zbfZb7otA7jZ6flbBpx1LNZK2TDebZ9dEKx84LYBhGV0aDKQtTA_KgEAAAD__________4JpZIJ2NIJpcISsaa0ZiXNlY3AyNTZrMaEDHAD2JKYevx89W0CcFJFiskdcEzkH_Wdv9iW42qLK79ODdWRwgiMo", - "enr:-Jq4QN_YBsUOqQsty1OGvYv48PMaiEt1AzGD1NkYQHaxZoTyVGqMYXg0K9c0LPNWC9pkXmggApp8nygYLsQwScwAgfgBhGV0aDKQtTA_KgEAAAD__________4JpZIJ2NIJpcISLosQxiXNlY3AyNTZrMaEDBJj7_dLFACaxBfaI8KZTh_SSJUjhyAyfshimvSqo22WDdWRwgiMo", - // Nimbus - "enr:-LK4QA8FfhaAjlb_BXsXxSfiysR7R52Nhi9JBt4F8SPssu8hdE1BXQQEtVDC3qStCW60LSO7hEsVHv5zm8_6Vnjhcn0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAN4aBKJc2VjcDI1NmsxoQJerDhsJ-KxZ8sHySMOCmTO6sHM3iCFQ6VMvLTe948MyYN0Y3CCI4yDdWRwgiOM", - "enr:-LK4QKWrXTpV9T78hNG6s8AM6IO4XH9kFT91uZtFg1GcsJ6dKovDOr1jtAAFPnS2lvNltkOGA9k29BUN7lFh_sjuc9QBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhANAdd-Jc2VjcDI1NmsxoQLQa6ai7y9PMN5hpLe5HmiJSlYzMuzP7ZhwRiwHvqNXdoN0Y3CCI4yDdWRwgiOM"); + // + // "enr:-KG4QOtcP9X1FbIMOe17QNMKqDxCpm14jcX5tiOE4_TyMrFqbmhPZHK_ZPG2Gxb1GE2xdtodOfx9-cgvNtxnRyHEmC0ghGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNlY3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA", + // + // "enr:-KG4QDyytgmE4f7AnvW-ZaUOIi9i79qX4JwjRAiXBZCU65wOfBu-3Nb5I7b_Rmg3KCOcZM_C3y5pg7EBU5XGrcLTduQEhGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQ2_DUbiXNlY3AyNTZrMaEDKnz_-ps3UUOfHWVYaskI5kWYO_vtYMGYCQRAR3gHDouDdGNwgiMog3VkcIIjKA", + // // PegaSys Teku + // + // "enr:-KG4QJRlj4pHagfNIm-Fsx9EVjW4rviuZYzle3tyddm2KAWMJBDGAhxfM2g-pDaaiwE8q19uvLSH4jyvWjypLMr3TIcEhGV0aDKQ9aX9QgAAAAD__________4JpZIJ2NIJpcIQDE8KdiXNlY3AyNTZrMaEDhpehBDbZjM_L9ek699Y7vhUJ-eAdMyQW_Fil522Y0fODdGNwgiMog3VkcIIjKA", + // + // "enr:-KG4QL-eqFoHy0cI31THvtZjpYUu_Jdw_MO7skQRJxY1g5HTN1A0epPCU6vi0gLGUgrzpU-ygeMSS8ewVxDpKfYmxMMGhGV0aDKQtTA_KgAAAAD__________4JpZIJ2NIJpcIQ2_DUbiXNlY3AyNTZrMaED8GJ2vzUqgL6-KD1xalo1CsmY4X1HaDnyl6Y_WayCo9GDdGNwgiMog3VkcIIjKA", + // // Prysmatic Labs + // + // "enr:-Ku4QImhMc1z8yCiNJ1TyUxdcfNucje3BGwEHzodEZUan8PherEo4sF7pPHPSIB1NNuSg5fZy7qFsjmUKs2ea1Whi0EBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQOVphkDqal4QzPMksc5wnpuC3gvSC8AfbFOnZY_On34wIN1ZHCCIyg", + // + // "enr:-Ku4QP2xDnEtUXIjzJ_DhlCRN9SN99RYQPJL92TMlSv7U5C1YnYLjwOQHgZIUXw6c-BvRg2Yc2QsZxxoS_pPRVe0yK8Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMeFF5GrS7UZpAH2Ly84aLK-TyvH-dRo0JM1i8yygH50YN1ZHCCJxA", + // + // "enr:-Ku4QPp9z1W4tAO8Ber_NQierYaOStqhDqQdOPY3bB3jDgkjcbk6YrEnVYIiCBbTxuar3CzS528d2iE7TdJsrL-dEKoBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpD1pf1CAAAAAP__________gmlkgnY0gmlwhBLf22SJc2VjcDI1NmsxoQMw5fqqkw2hHC4F5HZZDPsNmPdB1Gi8JPQK7pRc9XHh-oN1ZHCCKvg", + // // Sigp Lighthouse + // + // "enr:-Jq4QItoFUuug_n_qbYbU0OY04-np2wT8rUCauOOXNi0H3BWbDj-zbfZb7otA7jZ6flbBpx1LNZK2TDebZ9dEKx84LYBhGV0aDKQtTA_KgEAAAD__________4JpZIJ2NIJpcISsaa0ZiXNlY3AyNTZrMaEDHAD2JKYevx89W0CcFJFiskdcEzkH_Wdv9iW42qLK79ODdWRwgiMo", + // + // "enr:-Jq4QN_YBsUOqQsty1OGvYv48PMaiEt1AzGD1NkYQHaxZoTyVGqMYXg0K9c0LPNWC9pkXmggApp8nygYLsQwScwAgfgBhGV0aDKQtTA_KgEAAAD__________4JpZIJ2NIJpcISLosQxiXNlY3AyNTZrMaEDBJj7_dLFACaxBfaI8KZTh_SSJUjhyAyfshimvSqo22WDdWRwgiMo", + // // Nimbus + // + // "enr:-LK4QA8FfhaAjlb_BXsXxSfiysR7R52Nhi9JBt4F8SPssu8hdE1BXQQEtVDC3qStCW60LSO7hEsVHv5zm8_6Vnjhcn0Bh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhAN4aBKJc2VjcDI1NmsxoQJerDhsJ-KxZ8sHySMOCmTO6sHM3iCFQ6VMvLTe948MyYN0Y3CCI4yDdWRwgiOM", + // + // "enr:-LK4QKWrXTpV9T78hNG6s8AM6IO4XH9kFT91uZtFg1GcsJ6dKovDOr1jtAAFPnS2lvNltkOGA9k29BUN7lFh_sjuc9QBh2F0dG5ldHOIAAAAAAAAAACEZXRoMpC1MD8qAAAAAP__________gmlkgnY0gmlwhANAdd-Jc2VjcDI1NmsxoQLQa6ai7y9PMN5hpLe5HmiJSlYzMuzP7ZhwRiwHvqNXdoN0Y3CCI4yDdWRwgiOM" + ); private final P2PNetwork p2pNetwork; private final List topicHandlers; diff --git a/hildr-node/src/main/java/io/optimism/runner/Runner.java b/hildr-node/src/main/java/io/optimism/runner/Runner.java index 212d6574..72b0a5db 100644 --- a/hildr-node/src/main/java/io/optimism/runner/Runner.java +++ b/hildr-node/src/main/java/io/optimism/runner/Runner.java @@ -14,10 +14,13 @@ import io.optimism.engine.ForkChoiceUpdate.ForkchoiceState; import io.optimism.engine.OpEthForkChoiceUpdate; import io.optimism.engine.OpEthPayloadStatus; +import io.optimism.utilities.rpc.Web3jProvider; +import io.optimism.utilities.rpc.response.OpEthBlock; import io.optimism.utilities.telemetry.TracerTaskWrapper; import java.math.BigInteger; import java.time.Duration; import java.util.Arrays; +import java.util.Collections; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; @@ -28,10 +31,12 @@ import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; import org.web3j.protocol.Web3j; +import org.web3j.protocol.Web3jService; import org.web3j.protocol.core.DefaultBlockParameter; +import org.web3j.protocol.core.Request; import org.web3j.protocol.core.methods.response.BooleanResponse; import org.web3j.protocol.core.methods.response.EthBlock; -import org.web3j.protocol.core.methods.response.EthBlock.TransactionObject; +import org.web3j.protocol.core.methods.response.EthBlockNumber; import org.web3j.protocol.http.HttpService; import org.web3j.tuples.generated.Tuple2; import org.web3j.utils.Numeric; @@ -106,7 +111,6 @@ public void waitReady() throws InterruptedException, ExecutionException { Thread.sleep(Duration.ofSeconds(3L)); } } - this.driver = Driver.from(this.config, this.latch); } /** @@ -148,12 +152,13 @@ public void challengeSync() { * * @throws InterruptedException the interrupted exception */ - public void fullSync() throws InterruptedException { + public void fullSync() throws InterruptedException, ExecutionException { LOGGER.info("starting full sync"); waitDriverRunning(); } - private void waitDriverRunning() throws InterruptedException { + private void waitDriverRunning() throws InterruptedException, ExecutionException { + this.driver = Driver.from(this.config, this.latch); this.startDriver(); } @@ -168,11 +173,12 @@ public void checkpointSync() throws ExecutionException, InterruptedException { if (StringUtils.isEmpty(this.config.checkpointSyncUrl())) { throw new SyncUrlMissingException("a checkpoint sync rpc url is required for checkpoint sync"); } - Web3j checkpointSyncUrl = Web3j.build(new HttpService(this.config.checkpointSyncUrl())); + Web3jService checkpointSyncUrl = + Web3jProvider.create(this.config.checkpointSyncUrl()).component2(); - EthBlock checkpointBlock = null; + OpEthBlock checkpointBlock = null; if (StringUtils.isNotEmpty(this.checkpointHash)) { - Tuple2 isEpochBoundary = isEpochBoundary(this.checkpointHash, checkpointSyncUrl); + Tuple2 isEpochBoundary = isEpochBoundary(this.checkpointHash, checkpointSyncUrl); if (isEpochBoundary.component1()) { checkpointBlock = isEpochBoundary.component2(); if (checkpointBlock == null) { @@ -185,17 +191,10 @@ public void checkpointSync() throws ExecutionException, InterruptedException { } } else { LOGGER.info("finding the latest epoch boundary to use as checkpoint"); - BigInteger blockNumber; - try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { - StructuredTaskScope.Subtask blockNumberFuture = scope.fork(TracerTaskWrapper.wrap( - () -> checkpointSyncUrl.ethBlockNumber().send().getBlockNumber())); - scope.join(); - scope.throwIfFailed(); - blockNumber = blockNumberFuture.get(); - } + BigInteger blockNumber = getEthBlockNumber(checkpointSyncUrl); while (isRunning() && !this.isShutdownTriggered) { - Tuple2 isEpochBoundary = + Tuple2 isEpochBoundary = isEpochBoundary(DefaultBlockParameter.valueOf(blockNumber), checkpointSyncUrl); if (isEpochBoundary.component1()) { checkpointBlock = isEpochBoundary.component2(); @@ -224,13 +223,49 @@ public void checkpointSync() throws ExecutionException, InterruptedException { scope.throwIfFailed(); l2CheckpointBlock = l2CheckpointBlockFuture.get(); } - if (l2CheckpointBlock != null) { + if (l2CheckpointBlock != null && l2CheckpointBlock.getBlock() != null) { LOGGER.warn("finalized head is above the checkpoint block"); - this.startDriver(); + ForkchoiceState forkchoiceState = ForkchoiceState.fromSingleHead(checkpointHash); + updateForkChoiceState(forkchoiceState); + waitDriverRunning(); return; } // this is a temporary fix to allow execution layer peering to work + addTrustedPeerToL2Engine(l2Provider); + + ExecutionPayload checkpointPayload = + ExecutionPayload.fromL2Block(checkpointBlock.getBlock(), this.config.chainConfig()); + ForkchoiceState forkchoiceState = ForkchoiceState.fromSingleHead(checkpointHash); + + newPayloadToCheckpoint(checkpointPayload); + updateForkChoiceState(forkchoiceState); + + LOGGER.info("syncing execution client to the checkpoint block..."); + while (isRunning() && !this.isShutdownTriggered) { + BigInteger blockNumber; + try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { + StructuredTaskScope.Subtask blockNumberFuture = scope.fork(TracerTaskWrapper.wrap( + () -> l2Provider.ethBlockNumber().send().getBlockNumber())); + + scope.join(); + scope.throwIfFailed(); + blockNumber = blockNumberFuture.get(); + } + if (blockNumber.compareTo(checkpointPayload.blockNumber()) >= 0) { + break; + } else { + Thread.sleep(Duration.ofSeconds(3L)); + } + } + + // after syncing to the checkpoint block, update the forkchoice state + updateForkChoiceState(forkchoiceState); + LOGGER.info("execution client successfully synced to the checkpoint block"); + waitDriverRunning(); + } + + private static void addTrustedPeerToL2Engine(Web3j l2Provider) throws InterruptedException, ExecutionException { // TODO: use a list of whitelisted bootnodes instead LOGGER.info("adding trusted peer to the execution layer"); try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { @@ -243,9 +278,41 @@ public void checkpointSync() throws ExecutionException, InterruptedException { throw new TrustedPeerAddedException("could not add peer"); } } + } - ExecutionPayload checkpointPayload = ExecutionPayload.from(checkpointBlock.getBlock()); + /** + * snap sync. + * + * @throws InterruptedException the interrupted exception + */ + public void executionLayerSync() throws InterruptedException, ExecutionException { + LOGGER.info("execution layer sync"); + waitDriverRunning(); + } + private void startDriver() throws InterruptedException { + driver.startAsync().awaitRunning(); + latch.await(); + } + + private BigInteger getEthBlockNumber(Web3jService web3jService) throws InterruptedException, ExecutionException { + try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { + StructuredTaskScope.Subtask blockNumberFuture = + scope.fork(TracerTaskWrapper.wrap(() -> new Request<>( + "eth_blockNumber", + Collections.emptyList(), + web3jService, + EthBlockNumber.class) + .send() + .getBlockNumber())); + scope.join(); + scope.throwIfFailed(); + return blockNumberFuture.get(); + } + } + + private void newPayloadToCheckpoint(ExecutionPayload checkpointPayload) + throws InterruptedException, ExecutionException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { StructuredTaskScope.Subtask payloadStatusFuture = scope.fork(TracerTaskWrapper.wrap(() -> engineApi.newPayload(checkpointPayload))); @@ -258,8 +325,10 @@ public void checkpointSync() throws ExecutionException, InterruptedException { throw new InvalidExecutionPayloadException("the provided checkpoint payload is invalid"); } } + } - ForkchoiceState forkchoiceState = ForkchoiceState.fromSingleHead(checkpointHash); + private void updateForkChoiceState(ForkchoiceState forkchoiceState) + throws InterruptedException, ExecutionException { try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { StructuredTaskScope.Subtask forkChoiceUpdateFuture = scope.fork(TracerTaskWrapper.wrap(() -> engineApi.forkchoiceUpdated(forkchoiceState, null))); @@ -275,40 +344,15 @@ public void checkpointSync() throws ExecutionException, InterruptedException { throw new ForkchoiceUpdateException("could not accept forkchoice, exiting"); } } - - LOGGER.info("syncing execution client to the checkpoint block..."); - while (isRunning() && !this.isShutdownTriggered) { - BigInteger blockNumber; - try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { - StructuredTaskScope.Subtask blockNumberFuture = scope.fork(TracerTaskWrapper.wrap( - () -> l2Provider.ethBlockNumber().send().getBlockNumber())); - - scope.join(); - scope.throwIfFailed(); - blockNumber = blockNumberFuture.get(); - } - if (blockNumber.compareTo(checkpointPayload.blockNumber()) >= 0) { - break; - } else { - Thread.sleep(Duration.ofSeconds(3L)); - } - } - - LOGGER.info("execution client successfully synced to the checkpoint block"); - waitDriverRunning(); - } - - private void startDriver() throws InterruptedException { - driver.startAsync().awaitRunning(); - latch.await(); } - private Tuple2 isEpochBoundary(String blockHash, Web3j checkpointSyncUrl) + private Tuple2 isEpochBoundary(String blockHash, Web3jService checkpointSyncUrl) throws InterruptedException, ExecutionException { - EthBlock block; + OpEthBlock block; try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { - StructuredTaskScope.Subtask blockFuture = scope.fork(TracerTaskWrapper.wrap( - () -> checkpointSyncUrl.ethGetBlockByHash(blockHash, true).send())); + StructuredTaskScope.Subtask blockFuture = scope.fork(TracerTaskWrapper.wrap(() -> new Request<>( + "eth_getBlockByHash", Arrays.asList(blockHash, true), checkpointSyncUrl, OpEthBlock.class) + .send())); scope.join(); scope.throwIfFailed(); block = blockFuture.get(); @@ -320,12 +364,17 @@ private Tuple2 isEpochBoundary(String blockHash, Web3j checkp return isBlockBoundary(block); } - private Tuple2 isEpochBoundary(DefaultBlockParameter blockParameter, Web3j checkpointSyncUrl) + private Tuple2 isEpochBoundary( + DefaultBlockParameter blockParameter, Web3jService checkpointSyncUrl) throws InterruptedException, ExecutionException { - EthBlock block; + OpEthBlock block; try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { - StructuredTaskScope.Subtask blockFuture = scope.fork(TracerTaskWrapper.wrap(() -> - checkpointSyncUrl.ethGetBlockByNumber(blockParameter, true).send())); + StructuredTaskScope.Subtask blockFuture = scope.fork(TracerTaskWrapper.wrap(() -> new Request<>( + "eth_getBlockByNumber", + Arrays.asList(blockParameter.getValue(), true), + checkpointSyncUrl, + OpEthBlock.class) + .send())); scope.join(); scope.throwIfFailed(); block = blockFuture.get(); @@ -337,9 +386,9 @@ private Tuple2 isEpochBoundary(DefaultBlockParameter blockPar return isBlockBoundary(block); } - @NotNull private Tuple2 isBlockBoundary(EthBlock block) { - String txInput = ((TransactionObject) block.getBlock().getTransactions().stream() - .filter(transactionResult -> ((TransactionObject) transactionResult) + @NotNull private Tuple2 isBlockBoundary(OpEthBlock block) { + String txInput = ((OpEthBlock.TransactionObject) block.getBlock().getTransactions().stream() + .filter(transactionResult -> ((OpEthBlock.TransactionObject) transactionResult) .getTo() .equalsIgnoreCase( SystemAccounts.defaultSystemAccounts().attributesPreDeploy())) @@ -347,8 +396,15 @@ private Tuple2 isEpochBoundary(DefaultBlockParameter blockPar .orElseThrow(() -> new TransactionNotFoundException( "could not find setL1BlockValues tx in the epoch boundary" + " search"))) .getInput(); - byte[] sequenceNumber = ArrayUtils.subarray(Numeric.hexStringToByteArray(txInput), 132, 164); - if (Arrays.equals(sequenceNumber, new byte[32])) { + byte[] sequenceNumber; + if (block.getBlock().getTimestamp().compareTo(this.config.chainConfig().ecotoneTime()) >= 0) { + // this is ecotone block, read sequence number from 12 to 20 + sequenceNumber = ArrayUtils.subarray(Numeric.hexStringToByteArray(txInput), 12, 20); + } else { + // this is ecotone block, read sequence number from 132 to 164 + sequenceNumber = ArrayUtils.subarray(Numeric.hexStringToByteArray(txInput), 132, 164); + } + if (Arrays.equals(sequenceNumber, new byte[32]) || Arrays.equals(sequenceNumber, new byte[8])) { return new Tuple2<>(true, block); } else { return new Tuple2<>(false, null); @@ -372,6 +428,7 @@ protected void run() throws Exception { case Challenge -> this.challengeSync(); case Full -> this.fullSync(); case Checkpoint -> this.checkpointSync(); + case ExecutionLayer -> this.executionLayerSync(); default -> throw new RuntimeException("unknown sync mode"); } } diff --git a/hildr-node/src/test/java/io/optimism/HildrTest.java b/hildr-node/src/test/java/io/optimism/HildrTest.java index aec95ab4..195110e4 100644 --- a/hildr-node/src/test/java/io/optimism/HildrTest.java +++ b/hildr-node/src/test/java/io/optimism/HildrTest.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.dataformat.toml.TomlMapper; +import io.optimism.config.Config; import io.optimism.config.Config.CliConfig; import org.junit.jupiter.api.Test; @@ -23,7 +24,8 @@ class HildrTest { */ @Test void appHasGreeting() throws JsonProcessingException { - CliConfig cliConfig = new CliConfig("test", "test", "test", "test", "test", "test", "test", null, null, false); + CliConfig cliConfig = new CliConfig( + "test", "test", "test", "test", "test", "test", "test", null, null, Config.SyncMode.Full, false); TomlMapper mapper = new TomlMapper(); String cliConfigStr = mapper.writerFor(CliConfig.class).writeValueAsString(cliConfig); diff --git a/hildr-node/src/test/java/io/optimism/TestConstants.java b/hildr-node/src/test/java/io/optimism/TestConstants.java index f56506c2..34c46e92 100644 --- a/hildr-node/src/test/java/io/optimism/TestConstants.java +++ b/hildr-node/src/test/java/io/optimism/TestConstants.java @@ -56,6 +56,7 @@ public static Config createConfig() { "testjwt", null, null, + Config.SyncMode.Full, false); return Config.create(null, cliConfig, Config.ChainConfig.optimismGoerli()); } diff --git a/hildr-node/src/test/java/io/optimism/common/EpochTest.java b/hildr-node/src/test/java/io/optimism/common/EpochTest.java index e640bcf3..9b75de46 100644 --- a/hildr-node/src/test/java/io/optimism/common/EpochTest.java +++ b/hildr-node/src/test/java/io/optimism/common/EpochTest.java @@ -2,6 +2,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import io.optimism.type.Epoch; import java.math.BigInteger; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -32,7 +33,7 @@ void from() { final BigInteger expectedTimestamp = BigInteger.valueOf(1682191440L); final AttributesDepositedCall call = AttributesDepositedCall.from(callData); - final Epoch epoch = Epoch.from(call); + final Epoch epoch = call.toEpoch(); assertEquals(expectedHash, epoch.hash()); assertEquals(expectedBlockNumber, epoch.number()); diff --git a/hildr-node/src/test/java/io/optimism/config/ConfigTest.java b/hildr-node/src/test/java/io/optimism/config/ConfigTest.java index 5045586f..cd180703 100644 --- a/hildr-node/src/test/java/io/optimism/config/ConfigTest.java +++ b/hildr-node/src/test/java/io/optimism/config/ConfigTest.java @@ -26,7 +26,8 @@ class ConfigTest { */ @Test void create() { - CliConfig cliConfig = new CliConfig(null, null, null, null, null, null, "testjwt", null, null, false); + CliConfig cliConfig = + new CliConfig(null, null, null, null, null, null, "testjwt", null, null, Config.SyncMode.Full, false); Config config = Config.create( Paths.get("src", "test", "resources", "test.toml"), cliConfig, ChainConfig.optimismGoerli()); assertEquals("https://example2.com", config.l2RpcUrl()); diff --git a/hildr-node/src/test/java/io/optimism/derive/stages/ChannelsTest.java b/hildr-node/src/test/java/io/optimism/derive/stages/ChannelsTest.java index 4c52e799..e7950dc1 100644 --- a/hildr-node/src/test/java/io/optimism/derive/stages/ChannelsTest.java +++ b/hildr-node/src/test/java/io/optimism/derive/stages/ChannelsTest.java @@ -116,7 +116,8 @@ void testReadChannelData() { } private Tuple2, MessagePassingQueue> createStage() { - Config config = new Config("", "", "", "", "", "", null, null, 9545, false, ChainConfig.optimismGoerli()); + Config config = new Config( + "", "", "", "", "", "", null, null, 9545, false, Config.SyncMode.Full, ChainConfig.optimismGoerli()); MessagePassingQueue transactionMessageMessagePassingQueue = new MpscGrowableArrayQueue<>(4096); Channels channels = diff --git a/hildr-node/src/test/java/io/optimism/driver/DriverTest.java b/hildr-node/src/test/java/io/optimism/driver/DriverTest.java index 1944355b..e9c26077 100644 --- a/hildr-node/src/test/java/io/optimism/driver/DriverTest.java +++ b/hildr-node/src/test/java/io/optimism/driver/DriverTest.java @@ -36,6 +36,7 @@ void testNewDriverFromFinalizedHead() throws IOException, ExecutionException, In "d195a64e08587a3f1560686448867220c2727550ce3e0c95c7200d0ade0f9167", l2rpc, null, + Config.SyncMode.Full, false); Config config = Config.create(null, cliConfig, ChainConfig.optimismGoerli()); diff --git a/hildr-node/src/test/java/io/optimism/engine/EngineApiTest.java b/hildr-node/src/test/java/io/optimism/engine/EngineApiTest.java index d49bcf25..9b1f8177 100644 --- a/hildr-node/src/test/java/io/optimism/engine/EngineApiTest.java +++ b/hildr-node/src/test/java/io/optimism/engine/EngineApiTest.java @@ -10,13 +10,13 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import io.optimism.TestConstants; -import io.optimism.common.Epoch; import io.optimism.engine.ExecutionPayload.ExecutionPayloadRes; import io.optimism.engine.ExecutionPayload.PayloadAttributes; import io.optimism.engine.ExecutionPayload.PayloadStatus; import io.optimism.engine.ExecutionPayload.Status; import io.optimism.engine.ForkChoiceUpdate.ForkChoiceUpdateRes; import io.optimism.engine.ForkChoiceUpdate.ForkchoiceState; +import io.optimism.type.Epoch; import java.io.IOException; import java.math.BigInteger; import java.security.Key; @@ -44,6 +44,7 @@ public class EngineApiTest { @BeforeAll static void setUp() throws IOException { + TestConstants.createConfig(); server = new MockWebServer(); server.start(8851); } diff --git a/hildr-node/src/test/java/io/optimism/rpc/RpcServerTest.java b/hildr-node/src/test/java/io/optimism/rpc/RpcServerTest.java index 1028e66e..c0554d2e 100644 --- a/hildr-node/src/test/java/io/optimism/rpc/RpcServerTest.java +++ b/hildr-node/src/test/java/io/optimism/rpc/RpcServerTest.java @@ -115,6 +115,7 @@ void testRpcServerRegister() throws IOException, InterruptedException { null, 9545, false, + Config.SyncMode.Full, Config.ChainConfig.optimism())); rpcServer.start(); HashMap rpcHandler = HashMap.newHashMap(1); diff --git a/hildr-utilities/src/main/java/io/optimism/type/BeaconBlockHeader.java b/hildr-utilities/src/main/java/io/optimism/type/BeaconBlockHeader.java index 32027e79..02766903 100644 --- a/hildr-utilities/src/main/java/io/optimism/type/BeaconBlockHeader.java +++ b/hildr-utilities/src/main/java/io/optimism/type/BeaconBlockHeader.java @@ -25,8 +25,19 @@ public class BeaconBlockHeader { @JsonAlias("body_root") private String bodyRoot; + /** + * Instantiates a new Beacon block header. + */ public BeaconBlockHeader() {} + /** + * Instantiates a new Beacon block header. + * @param slot the slot + * @param proposerIndex the proposer index + * @param parentRoot the parent root + * @param stateRoot the state root + * @param bodyRoot the body root + */ public BeaconBlockHeader(String slot, String proposerIndex, String parentRoot, String stateRoot, String bodyRoot) { this.slot = slot; this.proposerIndex = proposerIndex; @@ -35,42 +46,92 @@ public BeaconBlockHeader(String slot, String proposerIndex, String parentRoot, S this.bodyRoot = bodyRoot; } + /** + * Gets slot. + * + * @return the slot + */ public String getSlot() { return slot; } + /** + * Sets slot value. + * + * @param slot the slot + */ public void setSlot(String slot) { this.slot = slot; } + /** + * Gets proposer index. + * + * @return the proposer index + */ public String getProposerIndex() { return proposerIndex; } + /** + * Sets proposer index value. + * + * @param proposerIndex the proposer index + */ public void setProposerIndex(String proposerIndex) { this.proposerIndex = proposerIndex; } + /** + * Gets beacon parent root. + * + * @return the beacon parent root + */ public String getParentRoot() { return parentRoot; } + /** + * Sets parent root value. + * + * @param parentRoot the parent root + */ public void setParentRoot(String parentRoot) { this.parentRoot = parentRoot; } + /** + * Gets state root. + * + * @return the state root + */ public String getStateRoot() { return stateRoot; } + /** + * Sets state root value. + * + * @param stateRoot the state root + */ public void setStateRoot(String stateRoot) { this.stateRoot = stateRoot; } + /** + * Gets body root. + * + * @return the body root + */ public String getBodyRoot() { return bodyRoot; } + /** + * Sets body root value. + * + * @param bodyRoot the body root + */ public void setBodyRoot(String bodyRoot) { this.bodyRoot = bodyRoot; } diff --git a/hildr-utilities/src/main/java/io/optimism/type/BlobSidecar.java b/hildr-utilities/src/main/java/io/optimism/type/BlobSidecar.java index c059fcec..fffc2d98 100644 --- a/hildr-utilities/src/main/java/io/optimism/type/BlobSidecar.java +++ b/hildr-utilities/src/main/java/io/optimism/type/BlobSidecar.java @@ -41,6 +41,7 @@ public BlobSidecar() {} * @param signedBlockHeader signed blob block header info * @param kzgCommitment the kzg commitment info * @param kzgProof the kzg proofs + * @param kzgCommitmentInclusionProof the kzg commitment inclusion proofs */ public BlobSidecar( String index, @@ -57,54 +58,119 @@ public BlobSidecar( this.kzgCommitmentInclusionProof = kzgCommitmentInclusionProof; } + /** + * Gets index. + * + * @return the index + */ public String getIndex() { return index; } + /** + * Sets index value. + * + * @param index the index + */ public void setIndex(String index) { this.index = index; } + /** + * Gets blob. + * + * @return the blob + */ public String getBlob() { return blob; } + /** + * Sets blob value. + * + * @param blob the blob + */ public void setBlob(String blob) { this.blob = blob; } + /** + * Gets signed block header. + * + * @return the signed block header + */ public BeaconSignedBlockHeader getSignedBlockHeader() { return signedBlockHeader; } + /** + * Sets signed block header value. + * + * @param signedBlockHeader the signed block header + */ public void setSignedBlockHeader(BeaconSignedBlockHeader signedBlockHeader) { this.signedBlockHeader = signedBlockHeader; } + /** + * Gets kzg commitment. + * + * @return the kzg commitment + */ public String getKzgCommitment() { return kzgCommitment; } + /** + * Sets kzg commitment value. + * + * @param kzgCommitment the kzg commitment + */ public void setKzgCommitment(String kzgCommitment) { this.kzgCommitment = kzgCommitment; } + /** + * Gets kzg proof. + * + * @return the kzg proof + */ public String getKzgProof() { return kzgProof; } + /** + * Sets kzg proof value. + * + * @param kzgProof the kzg proof + */ public void setKzgProof(String kzgProof) { this.kzgProof = kzgProof; } + /** + * Gets kzg commitment inclusion proof. + * + * @return the kzg commitment inclusion proof + */ public List getKzgCommitmentInclusionProof() { return kzgCommitmentInclusionProof; } + /** + * Sets kzg commitment inclusion proof value. + * + * @param kzgCommitmentInclusionProof the kzg commitment inclusion proof + */ public void setKzgCommitmentInclusionProof(List kzgCommitmentInclusionProof) { this.kzgCommitmentInclusionProof = kzgCommitmentInclusionProof; } + /** + * Gets versioned hash. + * + * @return the versioned hash + */ public String getVersionedHash() { var hash = Hash.sha256(Numeric.hexStringToByteArray(this.kzgCommitment)); hash[0] = 1; diff --git a/hildr-utilities/src/main/java/io/optimism/type/DepositTransaction.java b/hildr-utilities/src/main/java/io/optimism/type/DepositTransaction.java index 65710716..ed94b92c 100644 --- a/hildr-utilities/src/main/java/io/optimism/type/DepositTransaction.java +++ b/hildr-utilities/src/main/java/io/optimism/type/DepositTransaction.java @@ -1,10 +1,16 @@ package io.optimism.type; import java.math.BigInteger; +import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.List; +import java.util.Objects; +import org.apache.commons.lang3.StringUtils; +import org.web3j.rlp.RlpEncoder; +import org.web3j.rlp.RlpList; import org.web3j.rlp.RlpString; import org.web3j.rlp.RlpType; +import org.web3j.utils.Numeric; /** * Class of DepositTransaction. @@ -168,16 +174,49 @@ public String getData() { * * @return the list */ - public List asRlpValues() { + public byte[] encode() { List result = new ArrayList<>(); - result.add(RlpString.create(getSourceHash())); - result.add(RlpString.create(getFrom())); - result.add(RlpString.create(getTo())); - result.add(RlpString.create(getMint())); - result.add(RlpString.create(getValue())); - result.add(RlpString.create(getGas())); - result.add(RlpString.create(isSystemTransaction() ? 1 : 0)); - result.add(RlpString.create(getData())); - return result; + + result.add(RlpString.create(Numeric.hexStringToByteArray(this.sourceHash))); + result.add(RlpString.create(Numeric.hexStringToByteArray(this.from))); + + if (StringUtils.isNotEmpty(this.to)) { + result.add(RlpString.create(Numeric.hexStringToByteArray(this.to))); + } else { + result.add(RlpString.create("")); + } + + result.add(RlpString.create(this.mint)); + result.add(RlpString.create(this.value)); + result.add(RlpString.create(this.gas)); + result.add(RlpString.create(this.isSystemTransaction ? 1L : 0L)); + result.add(RlpString.create(Numeric.hexStringToByteArray(this.data))); + + RlpList rlpList = new RlpList(result); + byte[] encoded = RlpEncoder.encode(rlpList); + + return ByteBuffer.allocate(encoded.length + 1) + .put((byte) 0x7e) + .put(encoded) + .array(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof DepositTransaction that)) return false; + return isSystemTransaction == that.isSystemTransaction + && Objects.equals(sourceHash, that.sourceHash) + && Objects.equals(from, that.from) + && Objects.equals(to, that.to) + && Objects.equals(mint, that.mint) + && Objects.equals(value, that.value) + && Objects.equals(gas, that.gas) + && Objects.equals(data, that.data); + } + + @Override + public int hashCode() { + return Objects.hash(sourceHash, from, to, mint, value, gas, isSystemTransaction, data); } } diff --git a/hildr-node/src/main/java/io/optimism/common/Epoch.java b/hildr-utilities/src/main/java/io/optimism/type/Epoch.java similarity index 74% rename from hildr-node/src/main/java/io/optimism/common/Epoch.java rename to hildr-utilities/src/main/java/io/optimism/type/Epoch.java index 1ad5986c..2083dcb3 100644 --- a/hildr-node/src/main/java/io/optimism/common/Epoch.java +++ b/hildr-utilities/src/main/java/io/optimism/type/Epoch.java @@ -1,6 +1,5 @@ -package io.optimism.common; +package io.optimism.type; -import io.optimism.type.L1BlockInfo; import java.math.BigInteger; /** @@ -15,16 +14,6 @@ */ public record Epoch(BigInteger number, String hash, BigInteger timestamp, BigInteger sequenceNumber) { - /** - * Create epoch from AttributesDepositedCall. - * - * @param call the hex call data - * @return the epoch - */ - public static Epoch from(AttributesDepositedCall call) { - return new Epoch(call.number(), call.hash(), call.timestamp(), call.sequenceNumber()); - } - /** * Create epoch from L1BlockInfo. * diff --git a/hildr-utilities/src/main/java/io/optimism/type/Genesis.java b/hildr-utilities/src/main/java/io/optimism/type/Genesis.java index 6703f4a3..8f93ede2 100644 --- a/hildr-utilities/src/main/java/io/optimism/type/Genesis.java +++ b/hildr-utilities/src/main/java/io/optimism/type/Genesis.java @@ -13,4 +13,4 @@ * @author thinkAfCod * @since 0.1.1 */ -public record Genesis(BlockId l1, BlockId l2, BigInteger l2Time, SystemConfig systemConfig) {} +public record Genesis(Epoch l1, BlockId l2, BigInteger l2Time, SystemConfig systemConfig) {} diff --git a/hildr-utilities/src/main/java/io/optimism/type/L1BlockInfo.java b/hildr-utilities/src/main/java/io/optimism/type/L1BlockInfo.java index 9ec24f93..39d1693f 100644 --- a/hildr-utilities/src/main/java/io/optimism/type/L1BlockInfo.java +++ b/hildr-utilities/src/main/java/io/optimism/type/L1BlockInfo.java @@ -18,6 +18,8 @@ * @param batcherAddr batcher address * @param l1FeeOverhead l1 fee overhead * @param l1FeeScalar l1 fee scalar + * @param blobBaseFeeScalar blob base fee scalar + * @param blobBaseFee blob base fee * @author thinkAfCod * @since 0.1.1 */ @@ -48,6 +50,14 @@ public record L1BlockInfo( public static final byte[] L1_INFO_ECOTONE_SIGN_BYTES = ArrayUtils.subarray(Hash.sha3(L1_INFO_ECOTONE_SIGNATURE.getBytes(StandardCharsets.UTF_8)), 0, 4); + /** + * Create Epoch from L1BlockInfo. + * @return the Epoch. + */ + public Epoch toEpoch() { + return new Epoch(number, blockHash, time, sequenceNumber); + } + /** * Parse tx data to L1BlockInfo. * diff --git a/hildr-utilities/src/main/java/io/optimism/type/L2BlockRef.java b/hildr-utilities/src/main/java/io/optimism/type/L2BlockRef.java index b085b33c..986aeb6e 100644 --- a/hildr-utilities/src/main/java/io/optimism/type/L2BlockRef.java +++ b/hildr-utilities/src/main/java/io/optimism/type/L2BlockRef.java @@ -21,7 +21,7 @@ public record L2BlockRef( BigInteger number, String parentHash, BigInteger timestamp, - BlockId l1origin, + Epoch l1origin, BigInteger sequenceNumber) { public static final L2BlockRef EMPTY = new L2BlockRef(null, null, null, null, null, null); @@ -48,7 +48,7 @@ public static L2BlockRef fromBlockAndL1Info(EthBlock.Block block, L1BlockInfo l1 block.getNumber(), block.getParentHash(), block.getTimestamp(), - new BlockId(l1Info.blockHash(), l1Info.number()), + l1Info.toEpoch(), l1Info.sequenceNumber()); } diff --git a/hildr-utilities/src/main/java/io/optimism/type/SpecConfig.java b/hildr-utilities/src/main/java/io/optimism/type/SpecConfig.java index f6c97002..ff805800 100644 --- a/hildr-utilities/src/main/java/io/optimism/type/SpecConfig.java +++ b/hildr-utilities/src/main/java/io/optimism/type/SpecConfig.java @@ -12,13 +12,22 @@ */ public class SpecConfig { + /** + * The seconds per slot. + */ @JsonAlias("SECONDS_PER_SLOT") public String secondsPerSlot; + /** + * The SpecConfig constructor. + */ public BigInteger getSecondsPerSlot() { return new BigInteger(secondsPerSlot); } + /** + * The SpecConfig constructor. + */ public void setSecondsPerSlot(String secondsPerSlot) { this.secondsPerSlot = secondsPerSlot; } diff --git a/hildr-utilities/src/main/java/io/optimism/type/enums/SyncStatus.java b/hildr-utilities/src/main/java/io/optimism/type/enums/SyncStatus.java new file mode 100644 index 00000000..72566717 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/type/enums/SyncStatus.java @@ -0,0 +1,54 @@ +package io.optimism.type.enums; + +/** + * Engine sync status enum. + * + * @author thinkAfCod + * @since 0.3.4 + */ +public enum SyncStatus { + /** + * Consensus layer sync mode initial status. + */ + CL(1), + /** + * Engine layer sync mode initial status. + */ + WillStartEL(2), + /** + * Engine layer sync mode started status. + */ + StartedEL(3), + /** + * Engine layer sync mode finished status, but not finalized. + */ + FinishedELNotFinalized(4), + /** + * Engine layer sync mode has finished status. + */ + FinishedEL(5); + + private final int code; + + SyncStatus(int code) { + this.code = code; + } + + /** + * Get the sync status code. + * + * @return the sync status code + */ + public int getCode() { + return code; + } + + /** + * Check if the engine is syncing. + * + * @return true if the engine is syncing, false otherwise + */ + public boolean isEngineSyncing() { + return code > 1 && code < 5; + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/type/enums/TxType.java b/hildr-utilities/src/main/java/io/optimism/type/enums/TxType.java new file mode 100644 index 00000000..616c575f --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/type/enums/TxType.java @@ -0,0 +1,41 @@ +package io.optimism.type.enums; + +import org.hyperledger.besu.datatypes.TransactionType; + +public enum TxType { + LEGACY(0x00, "0x0", TransactionType.FRONTIER), + /** Access list transaction type. */ + ACCESS_LIST(0x01, "0x1", TransactionType.ACCESS_LIST), + /** Eip1559 transaction type. */ + EIP1559(0x02, "0x2", TransactionType.EIP1559), + /** Blob transaction type. */ + BLOB(0x03, "0x3", TransactionType.BLOB), + /** Optimism Deposit transaction type. */ + OPTIMISM_DEPOSIT(0x7e, "0x7e", null); + + private final int typeValue; + private final String typeHexString; + private final TransactionType besuType; + + TxType(final int typeValue, final String typeHexString, final TransactionType txBesuType) { + this.typeValue = typeValue; + this.typeHexString = typeHexString; + this.besuType = txBesuType; + } + + public boolean is(final String type) { + return this.typeHexString.equalsIgnoreCase(type); + } + + public int getValue() { + return this.typeValue; + } + + public String getString() { + return this.typeHexString; + } + + public TransactionType getBesuType() { + return besuType; + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/UpgradeDepositSource.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/UpgradeDepositSource.java deleted file mode 100644 index 0dd9f93a..00000000 --- a/hildr-utilities/src/main/java/io/optimism/utilities/derive/UpgradeDepositSource.java +++ /dev/null @@ -1,35 +0,0 @@ -package io.optimism.utilities.derive; - -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import org.web3j.crypto.Hash; -import org.web3j.utils.Numeric; - -/** - * The UpgradeDepositSource class. - * @author thinkAfCod - * @since 0.2.7 - */ -public class UpgradeDepositSource { - - private static final BigInteger UPGRADE_DEPOSIT_SOURCE_DOMAIN = BigInteger.TWO; - - private final String intent; - - /** - * The UpgradeDepositSource constructor. - * @param intent The intent identifies the upgrade-tx uniquely, in a human-readable way. - */ - public UpgradeDepositSource(String intent) { - this.intent = intent; - } - - public String sourceHash() { - byte[] domainInput = new byte[32 * 2]; - byte[] paddedDomain = Numeric.toBytesPadded(UPGRADE_DEPOSIT_SOURCE_DOMAIN, 8); - System.arraycopy(paddedDomain, 0, domainInput, 24, 8); - byte[] intentHash = Hash.sha3(this.intent.getBytes(StandardCharsets.UTF_8)); - System.arraycopy(intentHash, 0, domainInput, 32, 32); - return Numeric.toHexString(Hash.sha3(domainInput)); - } -} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/TxDecoder.java b/hildr-utilities/src/main/java/io/optimism/utilities/encoding/TxDecoder.java similarity index 75% rename from hildr-utilities/src/main/java/io/optimism/utilities/TxDecoder.java rename to hildr-utilities/src/main/java/io/optimism/utilities/encoding/TxDecoder.java index bcb04eaf..665943a9 100644 --- a/hildr-utilities/src/main/java/io/optimism/utilities/TxDecoder.java +++ b/hildr-utilities/src/main/java/io/optimism/utilities/encoding/TxDecoder.java @@ -14,7 +14,7 @@ * specific language governing permissions and limitations under the License. */ -package io.optimism.utilities; +package io.optimism.utilities.encoding; import io.optimism.type.DepositTransaction; import java.math.BigInteger; @@ -52,14 +52,14 @@ public static DepositTransaction decodeToDeposit(final String hexTransaction) { final RlpList rlpList = RlpDecoder.decode(encodedTx); var values = ((RlpList) rlpList.getValues().getFirst()).getValues(); final String sourceHash = ((RlpString) values.getFirst()).asString(); - final String from = ((RlpString) values.getFirst()).asString(); - final String to = ((RlpString) values.getFirst()).asString(); - final BigInteger mint = ((RlpString) values.getFirst()).asPositiveBigInteger(); - final BigInteger value = ((RlpString) values.getFirst()).asPositiveBigInteger(); - final BigInteger gas = ((RlpString) values.getFirst()).asPositiveBigInteger(); + final String from = ((RlpString) values.get(1)).asString(); + final String to = ((RlpString) values.get(2)).asString(); + final BigInteger mint = ((RlpString) values.get(3)).asPositiveBigInteger(); + final BigInteger value = ((RlpString) values.get(4)).asPositiveBigInteger(); + final BigInteger gas = ((RlpString) values.get(5)).asPositiveBigInteger(); final boolean isSystemTx = - ((RlpString) values.getFirst()).asPositiveBigInteger().compareTo(BigInteger.ONE) == 0; - final String data = ((RlpString) values.getFirst()).asString(); + ((RlpString) values.get(6)).asPositiveBigInteger().compareTo(BigInteger.ONE) == 0; + final String data = ((RlpString) values.getLast()).asString(); return new DepositTransaction(sourceHash, from, to, mint, value, gas, isSystemTx, data); } } diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/encoding/TxEncoder.java b/hildr-utilities/src/main/java/io/optimism/utilities/encoding/TxEncoder.java new file mode 100644 index 00000000..4714e94d --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/encoding/TxEncoder.java @@ -0,0 +1,175 @@ +package io.optimism.utilities.encoding; + +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Suppliers; +import io.optimism.type.DepositTransaction; +import io.optimism.type.enums.TxType; +import io.optimism.utilities.rpc.response.OpEthBlock; +import java.math.BigInteger; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import org.apache.tuweni.bytes.Bytes; +import org.apache.tuweni.bytes.Bytes32; +import org.hyperledger.besu.crypto.SECPSignature; +import org.hyperledger.besu.crypto.SignatureAlgorithm; +import org.hyperledger.besu.crypto.SignatureAlgorithmFactory; +import org.hyperledger.besu.datatypes.AccessListEntry; +import org.hyperledger.besu.datatypes.Address; +import org.hyperledger.besu.datatypes.TransactionType; +import org.hyperledger.besu.datatypes.Wei; +import org.hyperledger.besu.ethereum.core.Transaction; +import org.hyperledger.besu.ethereum.core.encoding.EncodingContext; +import org.hyperledger.besu.ethereum.core.encoding.TransactionEncoder; +import org.web3j.protocol.core.methods.response.EthBlock; +import org.web3j.utils.Numeric; + +public class TxEncoder { + + private static final Supplier SIGNATURE_ALGORITHM = + Suppliers.memoize(SignatureAlgorithmFactory::getInstance); + + public static final BigInteger REPLAY_UNPROTECTED_V_BASE = BigInteger.valueOf(27); + public static final BigInteger REPLAY_UNPROTECTED_V_BASE_PLUS_1 = BigInteger.valueOf(28); + + public static final BigInteger REPLAY_PROTECTED_V_BASE = BigInteger.valueOf(35); + + // The v signature parameter starts at 36 because 1 is the first valid chainId so: + // chainId > 1 implies that 2 * chainId + V_BASE > 36. + public static final BigInteger REPLAY_PROTECTED_V_MIN = BigInteger.valueOf(36); + + private static final ObjectMapper mapper = new ObjectMapper(); + + static { + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + } + + public static byte[] encode(EthBlock.TransactionObject tx) { + if (TxType.OPTIMISM_DEPOSIT.is(tx.getType())) { + throw new IllegalArgumentException("this method not support deposit transaction"); + } + return TransactionEncoder.encodeOpaqueBytes(web3jTxToBesuTx(tx), EncodingContext.BLOCK_BODY) + .toArray(); + } + + public static DepositTransaction toDepositTx(OpEthBlock.TransactionObject tx, boolean isSystemTx) { + return new DepositTransaction( + tx.getSourceHash(), + tx.getFrom(), + tx.getTo(), + Numeric.toBigInt(tx.getMint()), + tx.getValue(), + tx.getGas(), + isSystemTx, + tx.getInput()); + } + + public static byte[] encodeDepositTx(OpEthBlock.TransactionObject tx, boolean isSystemTx) { + DepositTransaction depositTx = new DepositTransaction( + tx.getSourceHash(), + tx.getFrom(), + tx.getTo(), + Numeric.toBigInt(tx.getMint()), + tx.getValue(), + tx.getGas(), + isSystemTx, + tx.getInput()); + return depositTx.encode(); + } + + public static Transaction web3jTxToBesuTx(EthBlock.TransactionObject tx) { + if (TxType.LEGACY.is(tx.getType())) { + return toLegacyTx(tx); + } else if (TxType.EIP1559.is(tx.getType())) { + return toEIP1559Tx(tx); + } else if (TxType.ACCESS_LIST.is(tx.getType())) { + return toAccessListTx(tx); + } else { + throw new IllegalArgumentException("Unsupported transaction type: " + tx.getType()); + } + } + + private static Transaction toLegacyTx(EthBlock.TransactionObject tx) { + Transaction.Builder builder = Transaction.builder(); + builder.type(TxType.LEGACY.getBesuType()) + .chainId(BigInteger.valueOf(tx.getChainId())) + .nonce(tx.getNonce().longValue()) + .gasPrice(Wei.of(tx.getGasPrice())) + .gasLimit(tx.getGas().longValue()) + .to(Address.fromHexString(tx.getTo())) + .value(Wei.of(tx.getValue())) + .payload(Bytes.wrap(Numeric.hexStringToByteArray(tx.getInput()))); + byte recId = getRecIdFromLegacyTx(tx); + final BigInteger r = Numeric.toBigInt(tx.getR()); + final BigInteger s = Numeric.toBigInt(tx.getS()); + final SECPSignature signature = SIGNATURE_ALGORITHM.get().createSignature(r, s, recId); + builder.signature(signature); + return builder.build(); + } + + private static Transaction toEIP1559Tx(EthBlock.TransactionObject tx) { + Transaction.Builder builder = Transaction.builder(); + builder.type(TransactionType.EIP1559) + .chainId(BigInteger.valueOf(tx.getChainId())) + .nonce(tx.getNonce().longValue()) + .maxPriorityFeePerGas(Wei.of(tx.getMaxPriorityFeePerGas())) + .maxFeePerGas(Wei.of(tx.getMaxFeePerGas())) + .gasLimit(tx.getGas().longValue()) + .to(Address.fromHexString(tx.getTo())) + .value(Wei.of(tx.getValue())) + .payload(Bytes.wrap(Numeric.hexStringToByteArray(tx.getInput()))) + .accessList(tx.getAccessList().stream() + .map(accessListObj -> new AccessListEntry( + Address.fromHexString(accessListObj.getAddress()), + accessListObj.getStorageKeys().stream() + .map(storageKey -> Bytes32.wrap(Numeric.hexStringToByteArray(storageKey))) + .collect(Collectors.toList()))) + .toList()); + final byte recId = BigInteger.valueOf(tx.getV()).byteValue(); + final BigInteger r = Numeric.toBigInt(tx.getR()); + final BigInteger s = Numeric.toBigInt(tx.getS()); + final SECPSignature signature = SIGNATURE_ALGORITHM.get().createSignature(r, s, recId); + builder.signature(signature); + return builder.build(); + } + + private static Transaction toAccessListTx(EthBlock.TransactionObject tx) { + Transaction.Builder builder = Transaction.builder(); + builder.type(TransactionType.EIP1559) + .chainId(BigInteger.valueOf(tx.getChainId())) + .nonce(tx.getNonce().longValue()) + .gasPrice(Wei.of(tx.getGasPrice())) + .gasLimit(tx.getGas().longValue()) + .to(Address.fromHexString(tx.getTo())) + .value(Wei.of(tx.getValue())) + .payload(Bytes.wrap(Numeric.hexStringToByteArray(tx.getInput()))) + .accessList(tx.getAccessList().stream() + .map(accessListObj -> new AccessListEntry( + Address.fromHexString(accessListObj.getAddress()), + accessListObj.getStorageKeys().stream() + .map(storageKey -> Bytes32.wrap(Numeric.hexStringToByteArray(storageKey))) + .collect(Collectors.toList()))) + .toList()); + final byte recId = BigInteger.valueOf(tx.getV()).byteValue(); + final BigInteger r = Numeric.toBigInt(tx.getR()); + final BigInteger s = Numeric.toBigInt(tx.getS()); + final SECPSignature signature = SIGNATURE_ALGORITHM.get().createSignature(r, s, recId); + builder.signature(signature); + return builder.build(); + } + + private static byte getRecIdFromLegacyTx(EthBlock.TransactionObject tx) { + BigInteger v = BigInteger.valueOf(tx.getV()); + byte recId; + if (v.equals(REPLAY_UNPROTECTED_V_BASE) || v.equals(REPLAY_UNPROTECTED_V_BASE_PLUS_1)) { + recId = v.subtract(REPLAY_UNPROTECTED_V_BASE).byteValueExact(); + } else if (v.compareTo(REPLAY_PROTECTED_V_MIN) > 0) { + BigInteger chainId = v.subtract(REPLAY_PROTECTED_V_BASE).divide(BigInteger.TWO); + recId = v.subtract(BigInteger.TWO.multiply(chainId).add(REPLAY_PROTECTED_V_BASE)) + .byteValueExact(); + } else { + throw new RuntimeException(String.format("An unsupported encoded `v` value of %s was found", v)); + } + return recId; + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/gas/GasCalculator.java b/hildr-utilities/src/main/java/io/optimism/utilities/gas/GasCalculator.java index f53fc86c..a0d7788f 100644 --- a/hildr-utilities/src/main/java/io/optimism/utilities/gas/GasCalculator.java +++ b/hildr-utilities/src/main/java/io/optimism/utilities/gas/GasCalculator.java @@ -44,11 +44,11 @@ private GasCalculator() {} /** * The constant INIT_CODE_WORD_GAS. */ - public static final long INIT_CODE_WORD_GAS = 2L; + private static final long INIT_CODE_WORD_GAS = 2L; - public static final BigInteger MIN_BLOB_GAS_PRICE = BigInteger.ONE; + private static final BigInteger MIN_BLOB_GAS_PRICE = BigInteger.ONE; - public static final BigInteger BLOB_GAS_PRICE_UPDATE_FRACTION = new BigInteger("3338477"); + private static final BigInteger BLOB_GAS_PRICE_UPDATE_FRACTION = new BigInteger("3338477"); /** * Calculator gas fee but exclude effective of AccessList. diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/rpc/HttpClientProvider.java b/hildr-utilities/src/main/java/io/optimism/utilities/rpc/HttpClientProvider.java index 64cb1857..4ffcac61 100644 --- a/hildr-utilities/src/main/java/io/optimism/utilities/rpc/HttpClientProvider.java +++ b/hildr-utilities/src/main/java/io/optimism/utilities/rpc/HttpClientProvider.java @@ -24,7 +24,7 @@ private HttpClientProvider() {} */ public static OkHttpClient create() { OkHttpClient.Builder builder = new OkHttpClient.Builder(); - if (LOGGER.isDebugEnabled()) { + if (LOGGER.isTraceEnabled()) { builder.addInterceptor( new HttpLoggingInterceptor(LOGGER::debug).setLevel(HttpLoggingInterceptor.Level.BODY)); } diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/rpc/Web3jProvider.java b/hildr-utilities/src/main/java/io/optimism/utilities/rpc/Web3jProvider.java index 799b58f3..22f641b4 100644 --- a/hildr-utilities/src/main/java/io/optimism/utilities/rpc/Web3jProvider.java +++ b/hildr-utilities/src/main/java/io/optimism/utilities/rpc/Web3jProvider.java @@ -1,5 +1,6 @@ package io.optimism.utilities.rpc; +import ch.qos.logback.classic.Level; import java.net.ConnectException; import java.util.function.Consumer; import okhttp3.OkHttpClient; @@ -47,7 +48,7 @@ public static Tuple2 create(String url) { Web3jService web3Srv; if (Web3jProvider.isHttp(url)) { var okHttpClientBuilder = new OkHttpClient.Builder(); - if (LOGGER.isTraceEnabled()) { + if (LOGGER.isDebugEnabled()) { okHttpClientBuilder.addInterceptor( new HttpLoggingInterceptor(LOGGER::debug).setLevel(HttpLoggingInterceptor.Level.BODY)); } @@ -55,15 +56,14 @@ public static Tuple2 create(String url) { .addInterceptor(new RetryRateLimitInterceptor()) .build(); web3Srv = new HttpService(url, okHttpClient); + } else if (Web3jProvider.isWs(url)) { - final var web3finalSrv = new WebSocketService(url, true); var logger = LoggerFactory.getLogger("org.web3j.protocol.websocket"); if (logger instanceof ch.qos.logback.classic.Logger) { - var level = LOGGER.isTraceEnabled() - ? ch.qos.logback.classic.Level.TRACE - : ch.qos.logback.classic.Level.INFO; + var level = !LOGGER.isTraceEnabled() ? Level.INFO : Level.DEBUG; ((ch.qos.logback.classic.Logger) logger).setLevel(level); } + final var web3finalSrv = new WebSocketService(url, true); wsConnect(web3finalSrv); web3Srv = web3finalSrv; } else { diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/rpc/response/BeaconApiResponse.java b/hildr-utilities/src/main/java/io/optimism/utilities/rpc/response/BeaconApiResponse.java index 9ffbfda8..de56294e 100644 --- a/hildr-utilities/src/main/java/io/optimism/utilities/rpc/response/BeaconApiResponse.java +++ b/hildr-utilities/src/main/java/io/optimism/utilities/rpc/response/BeaconApiResponse.java @@ -7,9 +7,13 @@ * * @author thinkAfCod * @since 0.3.0 + * @param the beacon api response data type. */ public class BeaconApiResponse { + /** + * The response inner data. + */ public T data; /** diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/rpc/response/OpEthBlock.java b/hildr-utilities/src/main/java/io/optimism/utilities/rpc/response/OpEthBlock.java new file mode 100644 index 00000000..052aa027 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/rpc/response/OpEthBlock.java @@ -0,0 +1,893 @@ +package io.optimism.utilities.rpc.response; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonToken; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.io.IOException; +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import org.web3j.protocol.ObjectMapperFactory; +import org.web3j.protocol.core.Response; +import org.web3j.protocol.core.methods.response.AccessListObject; +import org.web3j.protocol.core.methods.response.EthBlock; +import org.web3j.utils.Numeric; + +/** + * Block object returned by: + * + *
    + *
  • eth_getBlockByHash + *
  • eth_getBlockByNumber + *
  • eth_getUncleByBlockHashAndIndex + *
  • eth_getUncleByBlockNumberAndIndex + *
+ * + *

See docs + * for further details. + * + *

See the following issue for + * details on additional Parity fields present in EthBlock. + */ +public class OpEthBlock extends Response { + + @Override + @JsonDeserialize(using = ResponseDeserialiser.class) + public void setResult(Block result) { + super.setResult(result); + } + + public Block getBlock() { + return getResult(); + } + + public static class Block { + private String number; + private String hash; + private String parentHash; + private String parentBeaconBlockRoot; + private String nonce; + private String sha3Uncles; + private String logsBloom; + private String transactionsRoot; + private String stateRoot; + private String receiptsRoot; + private String author; + private String miner; + private String mixHash; + private String difficulty; + private String totalDifficulty; + private String extraData; + private String size; + private String gasLimit; + private String gasUsed; + private String timestamp; + private List transactions; + private List uncles; + private List sealFields; + private String baseFeePerGas; + private String withdrawalsRoot; + private List withdrawals; + private String blobGasUsed; + private String excessBlobGas; + + public Block() {} + + public Block( + String number, + String hash, + String parentHash, + String parentBeaconBlockRoot, + String nonce, + String sha3Uncles, + String logsBloom, + String transactionsRoot, + String stateRoot, + String receiptsRoot, + String author, + String miner, + String mixHash, + String difficulty, + String totalDifficulty, + String extraData, + String size, + String gasLimit, + String gasUsed, + String timestamp, + List transactions, + List uncles, + List sealFields, + String baseFeePerGas, + String withdrawalsRoot, + List withdrawals, + String blobGasUsed, + String excessBlobGas) { + this.number = number; + this.hash = hash; + this.parentHash = parentHash; + this.parentBeaconBlockRoot = parentBeaconBlockRoot; + this.nonce = nonce; + this.sha3Uncles = sha3Uncles; + this.logsBloom = logsBloom; + this.transactionsRoot = transactionsRoot; + this.stateRoot = stateRoot; + this.receiptsRoot = receiptsRoot; + this.author = author; + this.miner = miner; + this.mixHash = mixHash; + this.difficulty = difficulty; + this.totalDifficulty = totalDifficulty; + this.extraData = extraData; + this.size = size; + this.gasLimit = gasLimit; + this.gasUsed = gasUsed; + this.timestamp = timestamp; + this.transactions = transactions; + this.uncles = uncles; + this.sealFields = sealFields; + this.baseFeePerGas = baseFeePerGas; + this.withdrawalsRoot = withdrawalsRoot; + this.withdrawals = withdrawals; + this.blobGasUsed = blobGasUsed; + this.excessBlobGas = excessBlobGas; + } + + public Block( + String number, + String hash, + String parentHash, + String nonce, + String sha3Uncles, + String logsBloom, + String transactionsRoot, + String stateRoot, + String receiptsRoot, + String author, + String miner, + String mixHash, + String difficulty, + String totalDifficulty, + String extraData, + String size, + String gasLimit, + String gasUsed, + String timestamp, + List transactions, + List uncles, + List sealFields, + String baseFeePerGas, + String withdrawalsRoot, + List withdrawals) { + this.number = number; + this.hash = hash; + this.parentHash = parentHash; + this.nonce = nonce; + this.sha3Uncles = sha3Uncles; + this.logsBloom = logsBloom; + this.transactionsRoot = transactionsRoot; + this.stateRoot = stateRoot; + this.receiptsRoot = receiptsRoot; + this.author = author; + this.miner = miner; + this.mixHash = mixHash; + this.difficulty = difficulty; + this.totalDifficulty = totalDifficulty; + this.extraData = extraData; + this.size = size; + this.gasLimit = gasLimit; + this.gasUsed = gasUsed; + this.timestamp = timestamp; + this.transactions = transactions; + this.uncles = uncles; + this.sealFields = sealFields; + this.baseFeePerGas = baseFeePerGas; + this.withdrawalsRoot = withdrawalsRoot; + this.withdrawals = withdrawals; + } + + public BigInteger getNumber() { + return Numeric.decodeQuantity(number); + } + + public String getNumberRaw() { + return number; + } + + public void setNumber(String number) { + this.number = number; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public String getParentHash() { + return parentHash; + } + + public void setParentHash(String parentHash) { + this.parentHash = parentHash; + } + + public String getParentBeaconBlockRoot() { + return parentBeaconBlockRoot; + } + + public void setParentBeaconBlockRoot(String parentBeaconBlockRoot) { + this.parentBeaconBlockRoot = parentBeaconBlockRoot; + } + + public BigInteger getNonce() { + return Numeric.decodeQuantity(nonce); + } + + public String getNonceRaw() { + return nonce; + } + + public void setNonce(String nonce) { + this.nonce = nonce; + } + + public String getSha3Uncles() { + return sha3Uncles; + } + + public void setSha3Uncles(String sha3Uncles) { + this.sha3Uncles = sha3Uncles; + } + + public String getLogsBloom() { + return logsBloom; + } + + public void setLogsBloom(String logsBloom) { + this.logsBloom = logsBloom; + } + + public String getTransactionsRoot() { + return transactionsRoot; + } + + public void setTransactionsRoot(String transactionsRoot) { + this.transactionsRoot = transactionsRoot; + } + + public String getStateRoot() { + return stateRoot; + } + + public void setStateRoot(String stateRoot) { + this.stateRoot = stateRoot; + } + + public String getReceiptsRoot() { + return receiptsRoot; + } + + public void setReceiptsRoot(String receiptsRoot) { + this.receiptsRoot = receiptsRoot; + } + + public String getAuthor() { + return author; + } + + public void setAuthor(String author) { + this.author = author; + } + + public String getMiner() { + return miner; + } + + public void setMiner(String miner) { + this.miner = miner; + } + + public String getMixHash() { + return mixHash; + } + + public void setMixHash(String mixHash) { + this.mixHash = mixHash; + } + + public BigInteger getDifficulty() { + return Numeric.decodeQuantity(difficulty); + } + + public String getDifficultyRaw() { + return difficulty; + } + + public void setDifficulty(String difficulty) { + this.difficulty = difficulty; + } + + public BigInteger getTotalDifficulty() { + return Numeric.decodeQuantity(totalDifficulty); + } + + public String getTotalDifficultyRaw() { + return totalDifficulty; + } + + public void setTotalDifficulty(String totalDifficulty) { + this.totalDifficulty = totalDifficulty; + } + + public String getExtraData() { + return extraData; + } + + public void setExtraData(String extraData) { + this.extraData = extraData; + } + + public BigInteger getSize() { + return size != null ? Numeric.decodeQuantity(size) : BigInteger.ZERO; + } + + public String getSizeRaw() { + return size; + } + + public void setSize(String size) { + this.size = size; + } + + public BigInteger getGasLimit() { + return Numeric.decodeQuantity(gasLimit); + } + + public String getGasLimitRaw() { + return gasLimit; + } + + public void setGasLimit(String gasLimit) { + this.gasLimit = gasLimit; + } + + public BigInteger getGasUsed() { + return Numeric.decodeQuantity(gasUsed); + } + + public String getGasUsedRaw() { + return gasUsed; + } + + public void setGasUsed(String gasUsed) { + this.gasUsed = gasUsed; + } + + public BigInteger getTimestamp() { + return Numeric.decodeQuantity(timestamp); + } + + public String getTimestampRaw() { + return timestamp; + } + + public void setTimestamp(String timestamp) { + this.timestamp = timestamp; + } + + public List getTransactions() { + return transactions; + } + + @JsonDeserialize(using = ResultTransactionDeserialiser.class) + public void setTransactions(List transactions) { + this.transactions = transactions; + } + + public List getUncles() { + return uncles; + } + + public void setUncles(List uncles) { + this.uncles = uncles; + } + + public List getSealFields() { + return sealFields; + } + + public void setSealFields(List sealFields) { + this.sealFields = sealFields; + } + + public BigInteger getBaseFeePerGas() { + return Numeric.decodeQuantity(baseFeePerGas); + } + + public void setBaseFeePerGas(String baseFeePerGas) { + this.baseFeePerGas = baseFeePerGas; + } + + public String getBaseFeePerGasRaw() { + return baseFeePerGas; + } + + public String getWithdrawalsRoot() { + return withdrawalsRoot; + } + + public void setWithdrawalsRoot(String withdrawalsRoot) { + this.withdrawalsRoot = withdrawalsRoot; + } + + public List getWithdrawals() { + return withdrawals; + } + + public void setWithdrawals(List withdrawals) { + this.withdrawals = withdrawals; + } + + public BigInteger getBlobGasUsed() { + if (blobGasUsed == null) return BigInteger.ZERO; + return Numeric.decodeQuantity(blobGasUsed); + } + + public String getBlobGasUsedRaw() { + if (blobGasUsed == null) return "0"; + return blobGasUsed; + } + + public void setBlobGasUsed(String blobGasUsed) { + this.blobGasUsed = blobGasUsed; + } + + public BigInteger getExcessBlobGas() { + if (excessBlobGas == null) return BigInteger.ZERO; + return Numeric.decodeQuantity(excessBlobGas); + } + + public String getExcessBlobGasRaw() { + if (excessBlobGas == null) return "0"; + return excessBlobGas; + } + + public void setExcessBlobGas(String excessBlobGas) { + this.excessBlobGas = excessBlobGas; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Block)) { + return false; + } + + Block block = (Block) o; + + if (getNumberRaw() != null ? !getNumberRaw().equals(block.getNumberRaw()) : block.getNumberRaw() != null) { + return false; + } + if (getHash() != null ? !getHash().equals(block.getHash()) : block.getHash() != null) { + return false; + } + if (getParentHash() != null + ? !getParentHash().equals(block.getParentHash()) + : block.getParentHash() != null) { + return false; + } + if (getParentBeaconBlockRoot() != null + ? !getParentBeaconBlockRoot().equals(block.getParentBeaconBlockRoot()) + : block.getParentBeaconBlockRoot() != null) { + return false; + } + if (getNonceRaw() != null ? !getNonceRaw().equals(block.getNonceRaw()) : block.getNonceRaw() != null) { + return false; + } + if (getSha3Uncles() != null + ? !getSha3Uncles().equals(block.getSha3Uncles()) + : block.getSha3Uncles() != null) { + return false; + } + if (getLogsBloom() != null ? !getLogsBloom().equals(block.getLogsBloom()) : block.getLogsBloom() != null) { + return false; + } + if (getTransactionsRoot() != null + ? !getTransactionsRoot().equals(block.getTransactionsRoot()) + : block.getTransactionsRoot() != null) { + return false; + } + if (getStateRoot() != null ? !getStateRoot().equals(block.getStateRoot()) : block.getStateRoot() != null) { + return false; + } + if (getReceiptsRoot() != null + ? !getReceiptsRoot().equals(block.getReceiptsRoot()) + : block.getReceiptsRoot() != null) { + return false; + } + if (getAuthor() != null ? !getAuthor().equals(block.getAuthor()) : block.getAuthor() != null) { + return false; + } + if (getMiner() != null ? !getMiner().equals(block.getMiner()) : block.getMiner() != null) { + return false; + } + if (getMixHash() != null ? !getMixHash().equals(block.getMixHash()) : block.getMixHash() != null) { + return false; + } + if (getDifficultyRaw() != null + ? !getDifficultyRaw().equals(block.getDifficultyRaw()) + : block.getDifficultyRaw() != null) { + return false; + } + if (getTotalDifficultyRaw() != null + ? !getTotalDifficultyRaw().equals(block.getTotalDifficultyRaw()) + : block.getTotalDifficultyRaw() != null) { + return false; + } + if (getExtraData() != null ? !getExtraData().equals(block.getExtraData()) : block.getExtraData() != null) { + return false; + } + if (getSizeRaw() != null ? !getSizeRaw().equals(block.getSizeRaw()) : block.getSizeRaw() != null) { + return false; + } + if (getGasLimitRaw() != null + ? !getGasLimitRaw().equals(block.getGasLimitRaw()) + : block.getGasLimitRaw() != null) { + return false; + } + if (getGasUsedRaw() != null + ? !getGasUsedRaw().equals(block.getGasUsedRaw()) + : block.getGasUsedRaw() != null) { + return false; + } + if (getTimestampRaw() != null + ? !getTimestampRaw().equals(block.getTimestampRaw()) + : block.getTimestampRaw() != null) { + return false; + } + if (getTransactions() != null + ? !getTransactions().equals(block.getTransactions()) + : block.getTransactions() != null) { + return false; + } + if (getUncles() != null ? !getUncles().equals(block.getUncles()) : block.getUncles() != null) { + return false; + } + + if (getBaseFeePerGasRaw() != null + ? !getBaseFeePerGasRaw().equals(block.getBaseFeePerGasRaw()) + : block.getBaseFeePerGasRaw() != null) { + return false; + } + + if (getSealFields() != null + ? !getSealFields().equals(block.getSealFields()) + : block.getSealFields() != null) { + return false; + } + + if (getBlobGasUsedRaw() != null + ? !getBlobGasUsedRaw().equals(block.getBlobGasUsedRaw()) + : block.getBlobGasUsedRaw() != null) { + return false; + } + + if (getExcessBlobGasRaw() != null + ? !getExcessBlobGasRaw().equals(block.getExcessBlobGasRaw()) + : block.getExcessBlobGasRaw() != null) { + return false; + } + + if (getWithdrawalsRoot() != null + ? !getWithdrawalsRoot().equals(block.getWithdrawalsRoot()) + : block.getWithdrawalsRoot() != null) { + return false; + } + + return getWithdrawals() != null + ? getWithdrawals().equals(block.getWithdrawals()) + : block.getWithdrawals() == null; + } + + @Override + public int hashCode() { + int result = getNumberRaw() != null ? getNumberRaw().hashCode() : 0; + result = 31 * result + (getHash() != null ? getHash().hashCode() : 0); + result = 31 * result + (getParentHash() != null ? getParentHash().hashCode() : 0); + result = 31 * result + + (getParentBeaconBlockRoot() != null + ? getParentBeaconBlockRoot().hashCode() + : 0); + result = 31 * result + (getNonceRaw() != null ? getNonceRaw().hashCode() : 0); + result = 31 * result + (getSha3Uncles() != null ? getSha3Uncles().hashCode() : 0); + result = 31 * result + (getLogsBloom() != null ? getLogsBloom().hashCode() : 0); + result = 31 * result + + (getTransactionsRoot() != null ? getTransactionsRoot().hashCode() : 0); + result = 31 * result + (getStateRoot() != null ? getStateRoot().hashCode() : 0); + result = + 31 * result + (getReceiptsRoot() != null ? getReceiptsRoot().hashCode() : 0); + result = 31 * result + (getAuthor() != null ? getAuthor().hashCode() : 0); + result = 31 * result + (getMiner() != null ? getMiner().hashCode() : 0); + result = 31 * result + (getMixHash() != null ? getMixHash().hashCode() : 0); + result = 31 * result + + (getDifficultyRaw() != null ? getDifficultyRaw().hashCode() : 0); + result = 31 * result + + (getTotalDifficultyRaw() != null ? getTotalDifficultyRaw().hashCode() : 0); + result = 31 * result + (getExtraData() != null ? getExtraData().hashCode() : 0); + result = 31 * result + (getSizeRaw() != null ? getSizeRaw().hashCode() : 0); + result = 31 * result + (getGasLimitRaw() != null ? getGasLimitRaw().hashCode() : 0); + result = 31 * result + (getGasUsedRaw() != null ? getGasUsedRaw().hashCode() : 0); + result = + 31 * result + (getTimestampRaw() != null ? getTimestampRaw().hashCode() : 0); + result = + 31 * result + (getTransactions() != null ? getTransactions().hashCode() : 0); + result = 31 * result + (getUncles() != null ? getUncles().hashCode() : 0); + result = 31 * result + (getSealFields() != null ? getSealFields().hashCode() : 0); + result = 31 * result + + (getBaseFeePerGasRaw() != null ? getBaseFeePerGasRaw().hashCode() : 0); + result = 31 * result + + (getWithdrawalsRoot() != null ? getWithdrawalsRoot().hashCode() : 0); + result = 31 * result + (getWithdrawals() != null ? getWithdrawals().hashCode() : 0); + result = 31 * result + + (getBlobGasUsedRaw() != null ? getBlobGasUsedRaw().hashCode() : 0); + result = 31 * result + + (getExcessBlobGasRaw() != null ? getExcessBlobGasRaw().hashCode() : 0); + return result; + } + } + + public interface TransactionResult { + T get(); + } + + public static class TransactionHash implements TransactionResult { + private String value; + + public TransactionHash() {} + + public TransactionHash(String value) { + this.value = value; + } + + @Override + public String get() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof TransactionHash)) { + return false; + } + + TransactionHash that = (TransactionHash) o; + + return value != null ? value.equals(that.value) : that.value == null; + } + + @Override + public int hashCode() { + return value != null ? value.hashCode() : 0; + } + } + + public static class TransactionObject extends OpTransaction implements TransactionResult { + public TransactionObject() {} + + public TransactionObject( + String hash, + String nonce, + String blockHash, + String blockNumber, + String chainId, + String transactionIndex, + String from, + String to, + String value, + String gasPrice, + String gas, + String input, + String creates, + String publicKey, + String raw, + String r, + String s, + long v, + String yParity, + String type, + String maxFeePerGas, + String maxPriorityFeePerGas, + List accessList, + String sourceHash, + String mint, + String depositReceiptVersion) { + super( + hash, + nonce, + blockHash, + blockNumber, + chainId, + transactionIndex, + from, + to, + value, + gas, + gasPrice, + input, + creates, + publicKey, + raw, + r, + s, + v, + yParity, + type, + maxFeePerGas, + maxPriorityFeePerGas, + accessList, + sourceHash, + mint, + depositReceiptVersion); + } + + public TransactionObject( + String hash, + String nonce, + String blockHash, + String blockNumber, + String chainId, + String transactionIndex, + String from, + String to, + String value, + String gasPrice, + String gas, + String input, + String creates, + String publicKey, + String raw, + String r, + String s, + long v, + String yParity, + String type, + String maxFeePerGas, + String maxPriorityFeePerGas, + List accessList, + String maxFeePerBlobGas, + List blobVersionedHashes, + String sourceHash, + String mint, + String depositReceiptVersion) { + super( + hash, + nonce, + blockHash, + blockNumber, + chainId, + transactionIndex, + from, + to, + value, + gas, + gasPrice, + input, + creates, + publicKey, + raw, + r, + s, + v, + yParity, + type, + maxFeePerGas, + maxPriorityFeePerGas, + accessList, + maxFeePerBlobGas, + blobVersionedHashes, + sourceHash, + mint, + depositReceiptVersion); + } + + /** + * Convert this transaction object to a web3j transaction object. + * Will ignore sourceHash, mint, and depositReceiptVersion fields. + * @return the web3j transaction instant + */ + public EthBlock.TransactionObject toWeb3j() { + return new EthBlock.TransactionObject( + getHash(), + getNonceRaw(), + getBlockHash(), + getBlockNumberRaw(), + getChainIdRaw(), + getTransactionIndexRaw(), + getFrom(), + getTo(), + getValueRaw(), + getGasPriceRaw(), + getGasRaw(), + getInput(), + getCreates(), + getPublicKey(), + getRaw(), + getR(), + getS(), + getV(), + getyParity(), + getType(), + getMaxFeePerGasRaw(), + getMaxPriorityFeePerGasRaw(), + getAccessList(), + getMaxFeePerBlobGasRaw(), + getBlobVersionedHashes()); + } + + @Override + public OpTransaction get() { + return this; + } + } + + public static class ResultTransactionDeserialiser extends JsonDeserializer> { + + private ObjectReader objectReader = ObjectMapperFactory.getObjectReader(); + + @Override + public List deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + + List transactionResults = new ArrayList<>(); + JsonToken nextToken = jsonParser.nextToken(); + + if (nextToken == JsonToken.START_OBJECT) { + Iterator transactionObjectIterator = + objectReader.readValues(jsonParser, TransactionObject.class); + while (transactionObjectIterator.hasNext()) { + transactionResults.add(transactionObjectIterator.next()); + } + } else if (nextToken == JsonToken.VALUE_STRING) { + jsonParser.getValueAsString(); + + Iterator transactionHashIterator = + objectReader.readValues(jsonParser, TransactionHash.class); + while (transactionHashIterator.hasNext()) { + transactionResults.add(transactionHashIterator.next()); + } + } + + return transactionResults; + } + } + + public static class ResponseDeserialiser extends JsonDeserializer { + + private ObjectReader objectReader = ObjectMapperFactory.getObjectReader(); + + @Override + public Block deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) + throws IOException { + if (jsonParser.getCurrentToken() != JsonToken.VALUE_NULL) { + return objectReader.readValue(jsonParser, Block.class); + } else { + return null; // null is wrapped by Optional in above getter + } + } + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/rpc/response/OpTransaction.java b/hildr-utilities/src/main/java/io/optimism/utilities/rpc/response/OpTransaction.java new file mode 100644 index 00000000..4cc13a22 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/rpc/response/OpTransaction.java @@ -0,0 +1,623 @@ +package io.optimism.utilities.rpc.response; + +import java.math.BigInteger; +import java.util.List; +import org.web3j.crypto.TransactionUtils; +import org.web3j.protocol.core.methods.response.AccessListObject; +import org.web3j.protocol.core.methods.response.EthBlock; +import org.web3j.protocol.core.methods.response.EthTransaction; +import org.web3j.utils.Numeric; + +/** Transaction object used by both {@link EthTransaction} and {@link EthBlock}. */ +public class OpTransaction { + + private String hash; + + private String nonce; + + private String blockHash; + + private String blockNumber; + + private String chainId; + + private String transactionIndex; + + private String from; + + private String to; + + private String value; + + private String gasPrice; + + private String gas; + + private String input; + + private String creates; + + private String publicKey; + + private String raw; + + private String r; + + private String s; + + private long v; // see https://github.com/web3j/web3j/issues/44 + + private String yParity; + + private String type; + + private String maxFeePerGas; + + private String maxPriorityFeePerGas; + + private List accessList; + + private String maxFeePerBlobGas; + + private List blobVersionedHashes; + + private String sourceHash; + + private String mint; + + private String depositReceiptVersion; + + public OpTransaction() {} + + public OpTransaction( + String hash, + String nonce, + String blockHash, + String blockNumber, + String chainId, + String transactionIndex, + String from, + String to, + String value, + String gas, + String gasPrice, + String input, + String creates, + String publicKey, + String raw, + String r, + String s, + long v, + String yParity, + String type, + String maxFeePerGas, + String maxPriorityFeePerGas, + List accessList, + String sourceHash, + String mint, + String depositReceiptVersion) { + this.hash = hash; + this.nonce = nonce; + this.blockHash = blockHash; + this.blockNumber = blockNumber; + this.chainId = chainId; + this.transactionIndex = transactionIndex; + this.from = from; + this.to = to; + this.value = value; + this.gasPrice = gasPrice; + this.gas = gas; + this.input = input; + this.creates = creates; + this.publicKey = publicKey; + this.raw = raw; + this.r = r; + this.s = s; + this.v = v; + this.yParity = yParity; + this.type = type; + this.maxFeePerGas = maxFeePerGas; + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + this.accessList = accessList; + this.sourceHash = sourceHash; + this.mint = mint; + this.depositReceiptVersion = depositReceiptVersion; + } + + public OpTransaction( + String hash, + String nonce, + String blockHash, + String blockNumber, + String chainId, + String transactionIndex, + String from, + String to, + String value, + String gas, + String gasPrice, + String input, + String creates, + String publicKey, + String raw, + String r, + String s, + long v, + String yParity, + String type, + String maxFeePerGas, + String maxPriorityFeePerGas, + List accessList, + String maxFeePerBlobGas, + List versionedHashes, + String sourceHash, + String mint, + String depositReceiptVersion) { + this.hash = hash; + this.nonce = nonce; + this.blockHash = blockHash; + this.blockNumber = blockNumber; + this.chainId = chainId; + this.transactionIndex = transactionIndex; + this.from = from; + this.to = to; + this.value = value; + this.gasPrice = gasPrice; + this.gas = gas; + this.input = input; + this.creates = creates; + this.publicKey = publicKey; + this.raw = raw; + this.r = r; + this.s = s; + this.v = v; + this.yParity = yParity; + this.type = type; + this.maxFeePerGas = maxFeePerGas; + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + this.accessList = accessList; + this.maxFeePerBlobGas = maxFeePerBlobGas; + this.blobVersionedHashes = versionedHashes; + this.sourceHash = sourceHash; + this.mint = mint; + this.depositReceiptVersion = depositReceiptVersion; + } + + public void setChainId(String chainId) { + this.chainId = chainId; + } + + public String getHash() { + return hash; + } + + public void setHash(String hash) { + this.hash = hash; + } + + public BigInteger getNonce() { + return Numeric.decodeQuantity(nonce); + } + + public void setNonce(String nonce) { + this.nonce = nonce; + } + + public String getNonceRaw() { + return nonce; + } + + public String getBlockHash() { + return blockHash; + } + + public void setBlockHash(String blockHash) { + this.blockHash = blockHash; + } + + public BigInteger getBlockNumber() { + return Numeric.decodeQuantity(blockNumber); + } + + public void setBlockNumber(String blockNumber) { + this.blockNumber = blockNumber; + } + + public String getBlockNumberRaw() { + return blockNumber; + } + + public BigInteger getTransactionIndex() { + return Numeric.decodeQuantity(transactionIndex); + } + + public void setTransactionIndex(String transactionIndex) { + this.transactionIndex = transactionIndex; + } + + public String getTransactionIndexRaw() { + return transactionIndex; + } + + public String getFrom() { + return from; + } + + public void setFrom(String from) { + this.from = from; + } + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + + public BigInteger getValue() { + return Numeric.decodeQuantity(value); + } + + public void setValue(String value) { + this.value = value; + } + + public String getValueRaw() { + return value; + } + + public BigInteger getGasPrice() { + return Numeric.decodeQuantity(gasPrice); + } + + public void setGasPrice(String gasPrice) { + this.gasPrice = gasPrice; + } + + public String getGasPriceRaw() { + return gasPrice; + } + + public BigInteger getGas() { + return Numeric.decodeQuantity(gas); + } + + public void setGas(String gas) { + this.gas = gas; + } + + public String getGasRaw() { + return gas; + } + + public String getInput() { + return input; + } + + public void setInput(String input) { + this.input = input; + } + + public String getCreates() { + return creates; + } + + public void setCreates(String creates) { + this.creates = creates; + } + + public String getPublicKey() { + return publicKey; + } + + public void setPublicKey(String publicKey) { + this.publicKey = publicKey; + } + + public String getRaw() { + return raw; + } + + public void setRaw(String raw) { + this.raw = raw; + } + + public String getR() { + return r; + } + + public void setR(String r) { + this.r = r; + } + + public String getS() { + return s; + } + + public void setS(String s) { + this.s = s; + } + + public long getV() { + return v; + } + + // Workaround until Geth & Parity return consistent values. At present + // Parity returns a byte value, Geth returns a hex-encoded string + // https://github.com/ethereum/go-ethereum/issues/3339 + public void setV(Object v) { + if (v instanceof String) { + // longValueExact() is not implemented on android 11 or later only on 12 so it was + // replaced with longValue. + this.v = Numeric.toBigInt((String) v).longValue(); + } else if (v instanceof Integer) { + this.v = ((Integer) v).longValue(); + } else { + this.v = (Long) v; + } + } + + public String getyParity() { + return yParity; + } + + public void setyParity(String yParity) { + this.yParity = yParity; + } + + public Long getChainId() { + if (chainId != null) { + return Numeric.decodeQuantity(chainId).longValue(); + } + + return TransactionUtils.deriveChainId(v); + } + + public String getChainIdRaw() { + return this.chainId; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public BigInteger getMaxFeePerGas() { + if (maxFeePerGas == null) return null; + return Numeric.decodeQuantity(maxFeePerGas); + } + + public String getMaxFeePerGasRaw() { + return maxFeePerGas; + } + + public void setMaxFeePerGas(String maxFeePerGas) { + this.maxFeePerGas = maxFeePerGas; + } + + public String getMaxPriorityFeePerGasRaw() { + return maxPriorityFeePerGas; + } + + public BigInteger getMaxPriorityFeePerGas() { + return Numeric.decodeQuantity(maxPriorityFeePerGas); + } + + public void setMaxPriorityFeePerGas(String maxPriorityFeePerGas) { + this.maxPriorityFeePerGas = maxPriorityFeePerGas; + } + + public List getAccessList() { + return accessList; + } + + public void setAccessList(List accessList) { + this.accessList = accessList; + } + + public String getMaxFeePerBlobGasRaw() { + return maxFeePerBlobGas; + } + + public BigInteger getMaxFeePerBlobGas() { + return Numeric.decodeQuantity(maxFeePerBlobGas); + } + + public void setMaxFeePerBlobGas(String maxFeePerBlobGas) { + this.maxFeePerBlobGas = maxFeePerBlobGas; + } + + public List getBlobVersionedHashes() { + return blobVersionedHashes; + } + + public void setBlobVersionedHashes(List blobVersionedHashes) { + this.blobVersionedHashes = blobVersionedHashes; + } + + public String getSourceHash() { + return sourceHash; + } + + public void setSourceHash(String sourceHash) { + this.sourceHash = sourceHash; + } + + public String getMint() { + return mint; + } + + public void setMint(String mint) { + this.mint = mint; + } + + public String getDepositReceiptVersion() { + return depositReceiptVersion; + } + + public void setDepositReceiptVersion(String depositReceiptVersion) { + this.depositReceiptVersion = depositReceiptVersion; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof OpTransaction)) { + return false; + } + + OpTransaction that = (OpTransaction) o; + + if (getV() != that.getV()) { + return false; + } + if (getHash() != null ? !getHash().equals(that.getHash()) : that.getHash() != null) { + return false; + } + if (getNonceRaw() != null ? !getNonceRaw().equals(that.getNonceRaw()) : that.getNonceRaw() != null) { + return false; + } + if (getBlockHash() != null ? !getBlockHash().equals(that.getBlockHash()) : that.getBlockHash() != null) { + return false; + } + + if (getChainIdRaw() != null ? !getChainIdRaw().equals(that.getChainIdRaw()) : that.getChainIdRaw() != null) { + return false; + } + + if (getBlockNumberRaw() != null + ? !getBlockNumberRaw().equals(that.getBlockNumberRaw()) + : that.getBlockNumberRaw() != null) { + return false; + } + if (getTransactionIndexRaw() != null + ? !getTransactionIndexRaw().equals(that.getTransactionIndexRaw()) + : that.getTransactionIndexRaw() != null) { + return false; + } + if (getFrom() != null ? !getFrom().equals(that.getFrom()) : that.getFrom() != null) { + return false; + } + if (getTo() != null ? !getTo().equals(that.getTo()) : that.getTo() != null) { + return false; + } + if (getValueRaw() != null ? !getValueRaw().equals(that.getValueRaw()) : that.getValueRaw() != null) { + return false; + } + if (getGasPriceRaw() != null + ? !getGasPriceRaw().equals(that.getGasPriceRaw()) + : that.getGasPriceRaw() != null) { + return false; + } + if (getGasRaw() != null ? !getGasRaw().equals(that.getGasRaw()) : that.getGasRaw() != null) { + return false; + } + if (getInput() != null ? !getInput().equals(that.getInput()) : that.getInput() != null) { + return false; + } + if (getCreates() != null ? !getCreates().equals(that.getCreates()) : that.getCreates() != null) { + return false; + } + if (getPublicKey() != null ? !getPublicKey().equals(that.getPublicKey()) : that.getPublicKey() != null) { + return false; + } + if (getRaw() != null ? !getRaw().equals(that.getRaw()) : that.getRaw() != null) { + return false; + } + if (getR() != null ? !getR().equals(that.getR()) : that.getR() != null) { + return false; + } + if (getyParity() != null ? !getyParity().equals(that.getyParity()) : that.getyParity() != null) { + return false; + } + if (getType() != null ? !getType().equals(that.getType()) : that.getType() != null) { + return false; + } + if (getMaxFeePerGasRaw() != null + ? !getMaxFeePerGasRaw().equals(that.getMaxFeePerGasRaw()) + : that.getMaxFeePerGasRaw() != null) { + return false; + } + if (getMaxPriorityFeePerGasRaw() != null + ? !getMaxPriorityFeePerGasRaw().equals(that.getMaxPriorityFeePerGasRaw()) + : that.getMaxPriorityFeePerGasRaw() != null) { + return false; + } + + if (getMaxFeePerBlobGasRaw() != null + ? !getMaxFeePerBlobGasRaw().equals(that.getMaxFeePerBlobGasRaw()) + : that.getMaxFeePerBlobGasRaw() != null) { + return false; + } + if (getBlobVersionedHashes() != null + ? !getBlobVersionedHashes().equals(that.getBlobVersionedHashes()) + : that.getBlobVersionedHashes() != null) { + return false; + } + if (getAccessList() != null ? !getAccessList().equals(that.getAccessList()) : that.getAccessList() != null) { + return false; + } + if (getSourceHash() != null ? !getSourceHash().equals(that.getSourceHash()) : that.getSourceHash() != null) { + return false; + } + if (getMint() != null ? !getMint().equals(that.getMint()) : that.getMint() != null) { + return false; + } + if (getDepositReceiptVersion() != null + ? !getDepositReceiptVersion().equals(that.getDepositReceiptVersion()) + : that.getDepositReceiptVersion() != null) { + return false; + } + return getS() != null ? getS().equals(that.getS()) : that.getS() == null; + } + + @Override + public int hashCode() { + int result = getHash() != null ? getHash().hashCode() : 0; + result = 31 * result + (getNonceRaw() != null ? getNonceRaw().hashCode() : 0); + result = 31 * result + (getBlockHash() != null ? getBlockHash().hashCode() : 0); + result = + 31 * result + (getBlockNumberRaw() != null ? getBlockNumberRaw().hashCode() : 0); + result = 31 * result + (getChainIdRaw() != null ? getChainIdRaw().hashCode() : 0); + result = 31 * result + + (getTransactionIndexRaw() != null ? getTransactionIndexRaw().hashCode() : 0); + result = 31 * result + (getFrom() != null ? getFrom().hashCode() : 0); + result = 31 * result + (getTo() != null ? getTo().hashCode() : 0); + result = 31 * result + (getValueRaw() != null ? getValueRaw().hashCode() : 0); + result = 31 * result + (getGasPriceRaw() != null ? getGasPriceRaw().hashCode() : 0); + result = 31 * result + (getGasRaw() != null ? getGasRaw().hashCode() : 0); + result = 31 * result + (getInput() != null ? getInput().hashCode() : 0); + result = 31 * result + (getCreates() != null ? getCreates().hashCode() : 0); + result = 31 * result + (getPublicKey() != null ? getPublicKey().hashCode() : 0); + result = 31 * result + (getRaw() != null ? getRaw().hashCode() : 0); + result = 31 * result + (getR() != null ? getR().hashCode() : 0); + result = 31 * result + (getS() != null ? getS().hashCode() : 0); + result = 31 * result + BigInteger.valueOf(getV()).hashCode(); + result = 31 * result + (getyParity() != null ? getyParity().hashCode() : 0); + result = 31 * result + (getType() != null ? getType().hashCode() : 0); + result = 31 * result + + (getMaxFeePerGasRaw() != null ? getMaxFeePerGasRaw().hashCode() : 0); + result = 31 * result + + (getMaxPriorityFeePerGasRaw() != null + ? getMaxPriorityFeePerGasRaw().hashCode() + : 0); + result = 31 * result + + (getMaxFeePerBlobGasRaw() != null ? getMaxFeePerBlobGasRaw().hashCode() : 0); + result = 31 * result + + (getBlobVersionedHashes() != null ? getBlobVersionedHashes().hashCode() : 0); + result = 31 * result + (getSourceHash() != null ? getSourceHash().hashCode() : 0); + result = 31 * result + (getMint() != null ? getMint().hashCode() : 0); + result = 31 * result + + (getDepositReceiptVersion() != null + ? getDepositReceiptVersion().hashCode() + : 0); + result = 31 * result + (getAccessList() != null ? getAccessList().hashCode() : 0); + return result; + } +} diff --git a/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/SpanBatchTest.java b/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/SpanBatchTest.java index 717cb1ab..5271b53b 100644 --- a/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/SpanBatchTest.java +++ b/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/SpanBatchTest.java @@ -7,7 +7,7 @@ import com.google.common.base.Charsets; import com.google.common.io.Resources; import io.netty.buffer.Unpooled; -import io.optimism.type.BlockId; +import io.optimism.type.Epoch; import io.optimism.type.L2BlockRef; import java.io.IOException; import java.math.BigInteger; @@ -318,7 +318,9 @@ void testSpanBatchDerive() throws IOException { RandomUtils.randomHex(), // random biginteger RandomUtils.randomBigInt(), - new BlockId(RandomUtils.randomHex(), RandomUtils.randomBigInt()), + new Epoch( + RandomUtils.randomBigInt(), RandomUtils.randomHex(), + RandomUtils.randomBigInt(), RandomUtils.randomBigInt()), RandomUtils.randomBigInt()); BigInteger genesisTimeStamp = diff --git a/hildr-utilities/src/test/java/io/optimism/utilities/encoding/TxEncoderTest.java b/hildr-utilities/src/test/java/io/optimism/utilities/encoding/TxEncoderTest.java new file mode 100644 index 00000000..3f87f69a --- /dev/null +++ b/hildr-utilities/src/test/java/io/optimism/utilities/encoding/TxEncoderTest.java @@ -0,0 +1,103 @@ +package io.optimism.utilities.encoding; + +import static org.junit.jupiter.api.Assertions.*; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Charsets; +import com.google.common.io.Resources; +import io.optimism.type.DepositTransaction; +import io.optimism.utilities.rpc.response.OpEthBlock; +import java.io.IOException; +import java.net.URL; +import java.util.List; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.web3j.crypto.Hash; +import org.web3j.protocol.core.methods.response.EthBlock; +import org.web3j.utils.Numeric; + +class TxEncoderTest { + + private static String blockJson; + + private static ObjectMapper mapper; + + @BeforeAll + static void setUp() throws IOException { + mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + URL url = Resources.getResource("op_block_json.txt"); + blockJson = Resources.toString(url, Charsets.UTF_8); + } + + @Test + void mapperOpEthBlock() throws IOException { + var opBlock = mapper.readValue(blockJson, OpEthBlock.class); + assertNotNull(opBlock); + assertNotNull(opBlock.getBlock().getTransactions()); + assertFalse(opBlock.getBlock().getTransactions().isEmpty()); + var txObj = (OpEthBlock.TransactionObject) + opBlock.getBlock().getTransactions().get(0); + assertEquals("0xb526f7d7fbabb79a10077d83d78d036fbb0e066f363b6a0434d009809c4dbe7a", txObj.getSourceHash()); + assertEquals("0x0", txObj.getMint()); + assertEquals("0x1", txObj.getDepositReceiptVersion()); + } + + @Test + void encodeFromEthBlock() throws JsonProcessingException { + var block = mapper.readValue(blockJson, EthBlock.class); + assertNotNull(block.getBlock()); + block.getBlock().getTransactions().removeFirst(); + assertEquals(2, block.getBlock().getTransactions().size()); + var txHashes = block.getBlock().getTransactions().stream() + .map(tx -> { + var txObj = (EthBlock.TransactionObject) tx; + return Numeric.toHexString(Hash.sha3(TxEncoder.encode(txObj))); + }) + .toList(); + assertNotNull(txHashes); + assertEquals(2, txHashes.size()); + assertEquals( + List.of( + "0x3ae14753cfc7dc0a7f03612c69c2d7586e2b1609b78823e44cf4c6c0286e6cfa", + "0x0401086b456ea2b20b1d1d2eec67e68be5bc91133e5fbc703ce9e9f61d0908d2"), + txHashes); + } + + @Test + void encodeFromOpEthBlock() throws JsonProcessingException { + var opBlock = mapper.readValue(blockJson, OpEthBlock.class); + assertNotNull(opBlock.getBlock()); + opBlock.getBlock().getTransactions().removeFirst(); + assertEquals(2, opBlock.getBlock().getTransactions().size()); + var txHashes = opBlock.getBlock().getTransactions().stream() + .map(tx -> { + var txObj = (OpEthBlock.TransactionObject) tx; + return Numeric.toHexString(Hash.sha3(TxEncoder.encode(txObj.toWeb3j()))); + }) + .toList(); + assertNotNull(txHashes); + assertEquals(2, txHashes.size()); + assertEquals( + List.of( + "0x3ae14753cfc7dc0a7f03612c69c2d7586e2b1609b78823e44cf4c6c0286e6cfa", + "0x0401086b456ea2b20b1d1d2eec67e68be5bc91133e5fbc703ce9e9f61d0908d2"), + txHashes); + } + + @Test + void encodeDepositTx() throws JsonProcessingException { + var opBlock = mapper.readValue(blockJson, OpEthBlock.class); + assertNotNull(opBlock.getBlock()); + var txObj = (OpEthBlock.TransactionObject) + opBlock.getBlock().getTransactions().removeFirst(); + DepositTransaction depositTx1 = TxEncoder.toDepositTx(txObj, false); + var txEncoded = TxEncoder.encodeDepositTx(txObj, false); + DepositTransaction depositTx2 = TxDecoder.decodeToDeposit(Numeric.toHexString(txEncoded)); + var txHash = Numeric.toHexString(Hash.sha3(txEncoded)); + assertEquals("0xc7e14eba295f2a5278d49ff9039b7faf7ac9dbccad0da8054d7f69985048b782", txHash); + assertEquals(depositTx1, depositTx2); + } +} diff --git a/hildr-utilities/src/test/resources/op_block_json.txt b/hildr-utilities/src/test/resources/op_block_json.txt new file mode 100644 index 00000000..da1f9328 --- /dev/null +++ b/hildr-utilities/src/test/resources/op_block_json.txt @@ -0,0 +1,93 @@ +{ + "jsonrpc": "2.0", + "id": 2, + "result": { + "baseFeePerGas": "0xfc", + "blobGasUsed": "0x0", + "difficulty": "0x0", + "excessBlobGas": "0x0", + "extraData": "0x", + "gasLimit": "0x1c9c380", + "gasUsed": "0x10f2fb", + "hash": "0x3b1bee7b7c9d9ef0214ec6aa92aee1993e46b58dd4e8e921e7567bae54cee96f", + "logsBloom": "0x0000000000000000000000040000000200000000200000200800040000000000000002000000004000000020004400800000000400000000000004000024280000010041000000000000000a000000000000000000040840200040200400000001000000020000000000000000000800000400200100004000004010000100000000004080000000000020800000010000000000000000000000000010100000022000000000000000000000000000000000000004020000000000000000004000000012080000000800810000000000000000040000000000000000100060000010200000000000000000000001000000000000000000000000000000000200", + "miner": "0x4200000000000000000000000000000000000011", + "mixHash": "0xcfc8ebe56ce3514da86872d11c4bee69e08b0ce22530bc5ad985fb40fbd1dc17", + "nonce": "0x0000000000000000", + "number": "0xa8d7e4", + "parentBeaconBlockRoot": "0x7ada272881c8b928ae6985be67436cdbee284718e592dd311b9e0c897b48f6b7", + "parentHash": "0x1a79f4cabca13c55580aa1ac091e598133c5dad842c85c2bd80d29c7ae8bb675", + "receiptsRoot": "0x7a3bb60a4a3b0469db343b8bc41979fb57b43897b43c82711905e0d750057adc", + "sha3Uncles": "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347", + "size": "0x792", + "stateRoot": "0xf14471561929363bfdda030f7370cdce0115d603970ffdbd8d89f2c1f31ec484", + "timestamp": "0x66288b74", + "totalDifficulty": "0x0", + "transactions": [ + { + "blockHash": "0x3b1bee7b7c9d9ef0214ec6aa92aee1993e46b58dd4e8e921e7567bae54cee96f", + "blockNumber": "0xa8d7e4", + "from": "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0001", + "gas": "0xf4240", + "gasPrice": "0x0", + "hash": "0xc7e14eba295f2a5278d49ff9039b7faf7ac9dbccad0da8054d7f69985048b782", + "input": "0x440a5e2000001db0000d273000000000000000030000000066288b28000000000057f71a000000000000000000000000000000000000000000000000000000000018a8ac00000000000000000000000000000000000000000000000000000008ae4cb18dd242e768970be28f048292fbcd78f492b4465022fac4c17dbeb12c5be37b80020000000000000000000000008f23bb38f531600e5d8fddaaec41f13fab46e98c", + "nonce": "0xa8d7e4", + "to": "0x4200000000000000000000000000000000000015", + "transactionIndex": "0x0", + "value": "0x0", + "type": "0x7e", + "v": "0x0", + "r": "0x0", + "s": "0x0", + "sourceHash": "0xb526f7d7fbabb79a10077d83d78d036fbb0e066f363b6a0434d009809c4dbe7a", + "mint": "0x0", + "depositReceiptVersion": "0x1" + }, + { + "blockHash": "0x3b1bee7b7c9d9ef0214ec6aa92aee1993e46b58dd4e8e921e7567bae54cee96f", + "blockNumber": "0xa8d7e4", + "from": "0x3325f5400dac097d94287c5b0b9dcb019eeaf150", + "gas": "0x155499", + "gasPrice": "0xf6a4e", + "hash": "0x3ae14753cfc7dc0a7f03612c69c2d7586e2b1609b78823e44cf4c6c0286e6cfa", + "input": "0x1bafde19000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000662bcf80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000662bcf800000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002625a000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000001245bc000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000003325f5400dac097d94287c5b0b9dcb019eeaf15000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000bce9", + "nonce": "0x18603", + "to": "0x2ce2af8e1a1a21775834e948fd7924687e5d1ece", + "transactionIndex": "0x1", + "value": "0x0", + "type": "0x0", + "chainId": "0xaa37dc", + "v": "0x1546fdc", + "r": "0x8d84f5d3e8221944d2df1142c819173f6b7ae1cd200e84daf0feade66b92b982", + "s": "0x60b7293abcc97241ce4a20ed829303440b7adc3ea98998d5d9ffb72f8dfc9584" + }, + { + "blockHash": "0x3b1bee7b7c9d9ef0214ec6aa92aee1993e46b58dd4e8e921e7567bae54cee96f", + "blockNumber": "0xa8d7e4", + "from": "0xfa99d3ab33ed6e7ceebb051de33dd24ec72839c4", + "gas": "0xf7a4", + "gasPrice": "0x1f8", + "maxFeePerGas": "0x1f8", + "maxPriorityFeePerGas": "0x1f8", + "hash": "0x0401086b456ea2b20b1d1d2eec67e68be5bc91133e5fbc703ce9e9f61d0908d2", + "input": "0x6c1162b208611706c579b8ece638decb282a712247f34fd714cd5c85a676676766d8cf460000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000194a708c1fd760000000000000000000000000000000000000000000000000000000000000000", + "nonce": "0x40e7e", + "to": "0xb92fb458a2a66dc70f32d4055d2fcf36483e6c3f", + "transactionIndex": "0x2", + "value": "0x0", + "type": "0x2", + "accessList": [], + "chainId": "0xaa37dc", + "v": "0x1", + "r": "0xb2754ea62ea0967cfa51a7f1d1a04f36e80ac517be68f0a71fd287d7e7904c0e", + "s": "0x7eb3f363e6faa403c6547992796565ff81d8c4e664a7cc02a5bb47d70dd8b042", + "yParity": "0x1" + } + ], + "transactionsRoot": "0xc5a00abdf7943006f71baa662116c32424ec4e758406588f2e949882b7348c35", + "uncles": [], + "withdrawals": [], + "withdrawalsRoot": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421" + } +} \ No newline at end of file