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 6d22812d..ac54df72 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 @@ -6,8 +6,8 @@ import io.optimism.batcher.telemetry.BatcherMetrics; import io.optimism.type.BlockId; import io.optimism.type.L1BlockInfo; -import io.optimism.utilities.derive.stages.Batch; import io.optimism.utilities.derive.stages.Frame; +import io.optimism.utilities.derive.stages.SingularBatch; import java.io.IOException; import java.math.BigInteger; import java.security.NoSuchAlgorithmException; @@ -106,9 +106,9 @@ public L1BlockInfo addBlock(final EthBlock.Block block) { if (this.isClose) { throw new ChannelException("channel already closed"); } - final Tuple2 l1InfoAndBatch = this.blockToBatch(block); + final Tuple2 l1InfoAndBatch = this.blockToBatch(block); final L1BlockInfo l1Info = l1InfoAndBatch.component1(); - final Batch batch = l1InfoAndBatch.component2(); + final SingularBatch batch = l1InfoAndBatch.component2(); try { this.addBatch(batch); this.blocks.add(block); @@ -286,7 +286,7 @@ public void close() { } @SuppressWarnings({"rawtypes", "unchecked"}) - private Tuple2 blockToBatch(EthBlock.Block block) { + private Tuple2 blockToBatch(EthBlock.Block block) { final List blockTxs = block.getTransactions(); if (blockTxs == null || blockTxs.isEmpty()) { throw new ChannelException(String.format("block %s has no transations", block.getHash())); @@ -308,16 +308,11 @@ private Tuple2 blockToBatch(EthBlock.Block block) { } return new Tuple2( l1Info, - new Batch( - block.getParentHash(), - l1Info.number(), - l1Info.blockHash(), - block.getTimestamp(), - txDataList, - null)); + new SingularBatch( + block.getParentHash(), l1Info.number(), l1Info.blockHash(), block.getTimestamp(), txDataList)); } - private int addBatch(Batch batch) { + private int addBatch(SingularBatch batch) { if (this.isClose) { throw new ChannelException("channel already closed"); } @@ -385,7 +380,7 @@ private Frame frame(final int maxSize) { return frame; } - private void updateSeqWindowTimeout(final Batch batch) { + private void updateSeqWindowTimeout(final SingularBatch batch) { var timeout = batch.epochNum().add(this.seqWindowTimeout); this.updateTimeout(timeout); } 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 5ab153af..95533a25 100644 --- a/hildr-node/src/main/java/io/optimism/config/Config.java +++ b/hildr-node/src/main/java/io/optimism/config/Config.java @@ -2,6 +2,7 @@ import static java.util.Map.entry; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategies; import com.fasterxml.jackson.databind.annotation.JsonNaming; @@ -180,6 +181,8 @@ public Map toConfigMap() { * The type ChainConfig. * * @param network The network name. + * @param l1ChainId The L1 chain id. + * @param l2ChainId The L2 chain id. * @param l1StartEpoch The L1 block referenced by the L2 chainConfig. * @param l2Genesis The L2 genesis block info. * @param systemConfig The initial system config value. @@ -192,10 +195,9 @@ public Map toConfigMap() { * @param maxSeqDrift Maximum timestamp drift. * @param regolithTime Timestamp of the regolith hardfork. * @param canyonTime Timestamp of the canyon hardfork. + * @param deltaTime Timestamp of the canyon hardfork. * @param blockTime Network blocktime. * @param l2Tol1MessagePasser L2 To L1 Message passer address. - * @param l1ChainId The L1 chain id. - * @param l2ChainId The L2 chain id. * @author grapebaba * @since 0.1.0 */ @@ -215,6 +217,7 @@ public record ChainConfig( BigInteger maxSeqDrift, BigInteger regolithTime, BigInteger canyonTime, + BigInteger deltaTime, BigInteger blockTime, String l2Tol1MessagePasser) { @@ -252,6 +255,7 @@ public static ChainConfig optimism() { BigInteger.valueOf(600L), BigInteger.ZERO, BigInteger.valueOf(1704992401L), + BigInteger.valueOf(-1L), BigInteger.valueOf(2L), "0x4200000000000000000000000000000000000016"); } @@ -290,6 +294,7 @@ public static ChainConfig base() { BigInteger.valueOf(600L), BigInteger.ZERO, BigInteger.valueOf(1704992401L), + BigInteger.valueOf(-1L), BigInteger.valueOf(2L), "0x4200000000000000000000000000000000000016"); } @@ -328,6 +333,7 @@ public static ChainConfig optimismGoerli() { BigInteger.valueOf(600L), BigInteger.valueOf(1679079600L), BigInteger.valueOf(1699981200L), + BigInteger.valueOf(1703116800L), BigInteger.valueOf(2L), "0xEF2ec5A5465f075E010BE70966a8667c94BCe15a"); } @@ -366,6 +372,7 @@ public static ChainConfig optimismSepolia() { BigInteger.valueOf(600L), BigInteger.ZERO, BigInteger.valueOf(1699981200L), + BigInteger.valueOf(1703203200L), BigInteger.valueOf(2L), "0x4200000000000000000000000000000000000016"); } @@ -404,6 +411,7 @@ public static ChainConfig baseGoerli() { BigInteger.valueOf(600L), BigInteger.valueOf(1683219600L), BigInteger.valueOf(1699981200L), + BigInteger.valueOf(-1L), BigInteger.valueOf(2L), "0x4200000000000000000000000000000000000016"); } @@ -442,6 +450,7 @@ public static ChainConfig baseSepolia() { BigInteger.valueOf(600L), BigInteger.ZERO, BigInteger.valueOf(1699981200L), + BigInteger.valueOf(-1L), BigInteger.valueOf(2L), "0x4200000000000000000000000000000000000016"); } @@ -454,6 +463,7 @@ public static ChainConfig baseSepolia() { */ public static ChainConfig fromJson(String filePath) { ObjectMapper mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); try { ExternalChainConfig externalChainConfig = mapper.readValue( Files.readString(Path.of(filePath), StandardCharsets.UTF_8), ExternalChainConfig.class); @@ -495,6 +505,7 @@ public static ChainConfig fromExternal(ExternalChainConfig external) { external.maxSequencerDrift, external.regolithTime, external.canyonTime == null ? BigInteger.valueOf(-1L) : external.canyonTime, + external.deltaTime == null ? BigInteger.valueOf(-1L) : external.deltaTime, external.blockTime, "0x4200000000000000000000000000000000000016"); } @@ -544,6 +555,7 @@ public Map toConfigMap() { entry("config.chainConfig.maxSeqDrift", this.maxSeqDrift.toString()), entry("config.chainConfig.regolithTime", this.regolithTime.toString()), entry("config.chainConfig.canyonTime", this.canyonTime.toString()), + entry("config.chainConfig.deltaTime", this.deltaTime.toString()), entry("config.chainConfig.blockTime", this.blockTime.toString()), entry("config.chainConfig.l2Tol1MessagePasser", this.l2Tol1MessagePasser)); } @@ -657,6 +669,7 @@ public String batcherHash() { * @param l2ChainId l2 chain id * @param regolithTime regolith time * @param canyonTime canyon time + * @param deltaTime delta time * @param batchInboxAddress batch inbox address * @param depositContractAddress deposit contract address * @param l1SystemConfigAddress l1 system config address @@ -673,6 +686,7 @@ public record ExternalChainConfig( BigInteger l2ChainId, BigInteger regolithTime, BigInteger canyonTime, + BigInteger deltaTime, String batchInboxAddress, String depositContractAddress, String l1SystemConfigAddress) {} 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 7bb380dc..07a64423 100644 --- a/hildr-node/src/main/java/io/optimism/derive/State.java +++ b/hildr-node/src/main/java/io/optimism/derive/State.java @@ -3,11 +3,18 @@ import io.optimism.common.BlockInfo; import io.optimism.common.Epoch; import io.optimism.config.Config; +import io.optimism.driver.HeadInfo; import io.optimism.l1.L1Info; import java.math.BigInteger; import java.util.Map.Entry; import java.util.TreeMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.StructuredTaskScope; import org.apache.commons.lang3.StringUtils; +import org.web3j.protocol.Web3j; +import org.web3j.protocol.core.DefaultBlockParameter; +import org.web3j.protocol.core.methods.response.EthBlock; +import org.web3j.tuples.generated.Tuple2; /** * The type State. @@ -21,6 +28,8 @@ public class State { private final TreeMap l1Hashes; + private final TreeMap> l2Refs; + private BlockInfo safeHead; private Epoch safeEpoch; @@ -34,6 +43,7 @@ public class State { * * @param l1Info the L1 info * @param l1Hashes the L1 hashes + * @param l2Refs the L2 block info references * @param safeHead the safe head * @param safeEpoch the safe epoch * @param currentEpochNum the current epoch num @@ -42,12 +52,14 @@ public class State { public State( TreeMap l1Info, TreeMap l1Hashes, + TreeMap> l2Refs, BlockInfo safeHead, Epoch safeEpoch, BigInteger currentEpochNum, Config config) { this.l1Info = l1Info; this.l1Hashes = l1Hashes; + this.l2Refs = l2Refs; this.safeHead = safeHead; this.safeEpoch = safeEpoch; this.currentEpochNum = currentEpochNum; @@ -57,13 +69,19 @@ public State( /** * Create state. * + * @param l2Refs the L2 block info references * @param finalizedHead the finalized head * @param finalizedEpoch the finalized epoch * @param config the config * @return the state */ - public static State create(BlockInfo finalizedHead, Epoch finalizedEpoch, Config config) { - return new State(new TreeMap<>(), new TreeMap<>(), finalizedHead, finalizedEpoch, BigInteger.ZERO, config); + public static State create( + TreeMap> l2Refs, + BlockInfo finalizedHead, + Epoch finalizedEpoch, + Config config) { + return new State( + new TreeMap<>(), new TreeMap<>(), l2Refs, finalizedHead, finalizedEpoch, BigInteger.ZERO, config); } /** @@ -90,6 +108,20 @@ public L1Info l1Info(BigInteger number) { return l1Info.get(l1Hashes.get(number)); } + /** + * Gets L2 block info and epoch by block timestamp. + * + * @param timestamp the number + * @return the tuple of L2 block info and epoch + */ + public Tuple2 l2Info(BigInteger timestamp) { + final BigInteger blockNum = timestamp + .subtract(config.chainConfig().l2Genesis().timestamp()) + .divide(config.chainConfig().blockTime()) + .add(config.chainConfig().l2Genesis().number()); + return this.l2Refs.get(blockNum); + } + /** * Epoch epoch. * @@ -146,6 +178,7 @@ public void purge(BlockInfo safeHead, Epoch safeEpoch) { this.l1Info.clear(); this.l1Hashes.clear(); this.currentEpochNum = BigInteger.ZERO; + this.updateSafeHead(safeHead, safeEpoch); } /** @@ -157,6 +190,7 @@ public void purge(BlockInfo safeHead, Epoch safeEpoch) { public void updateSafeHead(BlockInfo safeHead, Epoch safeEpoch) { this.safeHead = safeHead; this.safeEpoch = safeEpoch; + this.l2Refs.put(safeHead.number(), new Tuple2<>(safeHead, safeEpoch)); } /** @@ -225,5 +259,63 @@ private void prune() { this.l1Info.remove(blockNumAndHash.getValue()); this.l1Hashes.pollFirstEntry(); } + + pruneUntil = this.safeHead + .number() + .subtract(this.config + .chainConfig() + .maxSeqDrift() + .divide(this.config.chainConfig().blockTime())); + + Entry> blockRefEntry; + while ((blockRefEntry = this.l2Refs.firstEntry()) != null) { + if (blockRefEntry.getKey().compareTo(pruneUntil) >= 0) { + break; + } + this.l2Refs.pollFirstEntry(); + } + } + + /** + * Init L2 refs tree map. + * + * @param headNum the l2 head block number + * @param chainConfig the chain config + * @param l2Client the l2 web3j client + * @return the L2 refs tree map. + * @throws ExecutionException throws the ExecutionException when the Task has been failed + * @throws InterruptedException throws the InterruptedException when the thread has been interrupted + */ + public static TreeMap> initL2Refs( + BigInteger headNum, Config.ChainConfig chainConfig, Web3j l2Client) + throws ExecutionException, InterruptedException { + final BigInteger lookback = chainConfig.maxSeqDrift().divide(chainConfig.blockTime()); + BigInteger start; + if (headNum.compareTo(lookback) < 0) { + start = chainConfig.l2Genesis().number(); + } else { + start = headNum.subtract(lookback).max(chainConfig.l2Genesis().number()); + } + final TreeMap> l2Refs = new TreeMap<>(); + for (BigInteger i = start; i.compareTo(headNum) <= 0; i = i.add(BigInteger.ONE)) { + try (var scope = new StructuredTaskScope.ShutdownOnFailure()) { + var l2Num = i; + var blockTask = + scope.fork(() -> l2Client.ethGetBlockByNumber(DefaultBlockParameter.valueOf(l2Num), true) + .send() + .getBlock()); + scope.join(); + scope.throwIfFailed(); + EthBlock.Block block = blockTask.get(); + if (block == null) { + continue; + } + final HeadInfo l2BlockInfo = HeadInfo.from(block); + l2Refs.put( + l2BlockInfo.l2BlockInfo().number(), + new Tuple2<>(l2BlockInfo.l2BlockInfo(), l2BlockInfo.l1Epoch())); + } + } + return l2Refs; } } 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 7f37e2e3..51902e09 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 @@ -9,6 +9,7 @@ import io.optimism.engine.ExecutionPayload.PayloadAttributes; import io.optimism.l1.L1Info; import io.optimism.utilities.derive.stages.Batch; +import io.optimism.utilities.derive.stages.SingularBatch; import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.ArrayList; @@ -78,7 +79,8 @@ public PayloadAttributes next() { return batch != null ? this.deriveAttributes(batch) : null; } - private PayloadAttributes deriveAttributes(Batch batch) { + private PayloadAttributes deriveAttributes(Batch batchWrapper) { + SingularBatch batch = (SingularBatch) batchWrapper.batch(); LOGGER.debug("attributes derived from block {}", batch.epochNum()); LOGGER.debug("batch epoch hash {}", batch.epochHash()); @@ -91,7 +93,7 @@ private PayloadAttributes deriveAttributes(Batch batch) { batch.epochNum(), batch.epochHash(), l1Info.blockInfo().timestamp()); BigInteger timestamp = batch.timestamp(); - BigInteger l1InclusionBlock = batch.l1InclusionBlock(); + BigInteger l1InclusionBlock = batchWrapper.l1InclusionBlock(); BigInteger seqNumber = this.sequenceNumber; String prevRandao = l1Info.blockInfo().mixHash(); List transactions = this.deriveTransactions(batch, l1Info); @@ -120,7 +122,7 @@ private PayloadAttributes deriveAttributes(Batch batch) { seqNumber); } - private List deriveTransactions(Batch batch, L1Info l1Info) { + private List deriveTransactions(SingularBatch batch, L1Info l1Info) { List transactions = new ArrayList<>(); String attributesTx = this.deriveAttributesDeposited(l1Info, batch.timestamp()); 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 f3fca0fe..7a81a46e 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 @@ -7,22 +7,33 @@ 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.utilities.derive.stages.Batch; +import io.optimism.utilities.derive.stages.BatchType; +import io.optimism.utilities.derive.stages.IBatch; +import io.optimism.utilities.derive.stages.SingularBatch; +import io.optimism.utilities.derive.stages.SpanBatch; +import io.optimism.utilities.derive.stages.SpanBatchElement; import java.io.ByteArrayOutputStream; import java.math.BigInteger; +import java.util.ArrayList; import java.util.List; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.zip.DataFormatException; import java.util.zip.Inflater; import org.apache.commons.lang3.ArrayUtils; +import org.apache.tuweni.bytes.Bytes; +import org.jctools.queues.atomic.SpscAtomicArrayQueue; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.web3j.rlp.RlpDecoder; import org.web3j.rlp.RlpList; import org.web3j.rlp.RlpString; import org.web3j.rlp.RlpType; +import org.web3j.tuples.generated.Tuple2; /** * The type Batches. @@ -42,6 +53,8 @@ public class Batches> implements PurgeableI private final Config config; + private SpscAtomicArrayQueue nextSingularBatches; + /** * Instantiates a new Batches. * @@ -55,19 +68,26 @@ public Batches(TreeMap batches, I channelIterator, AtomicRefe this.channelIterator = channelIterator; this.state = state; this.config = config; + this.nextSingularBatches = new SpscAtomicArrayQueue<>(1024 * 64); } @Override public void purge() { this.channelIterator.purge(); this.batches.clear(); + this.nextSingularBatches.clear(); } @Override public Batch next() { + final var nextBatch = nextSingularBatches.poll(); + if (nextBatch != null) { + return nextBatch; + } Channel channel = this.channelIterator.next(); if (channel != null) { - decodeBatches(channel).forEach(batch -> this.batches.put(batch.timestamp(), batch)); + decodeBatches(this.config.chainConfig(), channel) + .forEach(batch -> this.batches.put(batch.batch().getTimestamp(), batch)); } Batch derivedBatch = null; @@ -96,32 +116,31 @@ public Batch next() { Batch batch = null; if (derivedBatch != null) { - batch = derivedBatch; - } else { - State state = this.state.get(); - - BigInteger currentL1Block = state.getCurrentEpochNum(); - BlockInfo safeHead = state.getSafeHead(); - Epoch epoch = state.getSafeEpoch(); - Epoch nextEpoch = state.epoch(epoch.number().add(BigInteger.ONE)); - BigInteger seqWindowSize = this.config.chainConfig().seqWindowSize(); - - if (nextEpoch != null) { - if (currentL1Block.compareTo(epoch.number().add(seqWindowSize)) > 0) { - BigInteger nextTimestamp = - safeHead.timestamp().add(this.config.chainConfig().blockTime()); - Epoch epochRes = nextTimestamp.compareTo(nextEpoch.timestamp()) < 0 ? epoch : nextEpoch; - batch = new Batch( - safeHead.parentHash(), - epochRes.number(), - epochRes.hash(), - nextTimestamp, - Lists.newArrayList(), - currentL1Block); - } + List singularBatches = this.getSingularBatches(derivedBatch.batch(), this.state.get()); + if (singularBatches.size() != 0) { + this.nextSingularBatches.addAll(singularBatches); + return this.nextSingularBatches.poll(); } } + State state = this.state.get(); + + BigInteger currentL1Block = state.getCurrentEpochNum(); + BlockInfo safeHead = state.getSafeHead(); + Epoch epoch = state.getSafeEpoch(); + Epoch nextEpoch = state.epoch(epoch.number().add(BigInteger.ONE)); + BigInteger seqWindowSize = this.config.chainConfig().seqWindowSize(); + + if (nextEpoch != null) { + if (currentL1Block.compareTo(epoch.number().add(seqWindowSize)) > 0) { + BigInteger nextTimestamp = + safeHead.timestamp().add(this.config.chainConfig().blockTime()); + Epoch epochRes = nextTimestamp.compareTo(nextEpoch.timestamp()) < 0 ? epoch : nextEpoch; + var singularBatch = new SingularBatch( + safeHead.parentHash(), epochRes.number(), epochRes.hash(), nextTimestamp, Lists.newArrayList()); + batch = new Batch(singularBatch, currentL1Block); + } + } return batch; } @@ -131,16 +150,29 @@ public Batch next() { * @param channel the channel * @return the list */ - public static List decodeBatches(Channel channel) { + public static List decodeBatches(final Config.ChainConfig chainConfig, final Channel channel) { byte[] channelData = decompressZlib(channel.data()); List batches = RlpDecoder.decode(channelData).getValues(); return batches.stream() .map(rlpType -> { - byte[] batchData = ArrayUtils.subarray( - ((RlpString) rlpType).getBytes(), 1, ((RlpString) rlpType).getBytes().length); - RlpList rlpBatchData = - (RlpList) RlpDecoder.decode(batchData).getValues().getFirst(); - return Batch.decode(rlpBatchData, channel.l1InclusionBlock()); + byte[] buffer = ((RlpString) rlpType).getBytes(); + byte batchType = buffer[0]; + byte[] batchData = ArrayUtils.subarray(buffer, 1, buffer.length); + + if (BatchType.SPAN_BATCH_TYPE.getCode() == ((int) batchType)) { + return Batch.decodeSpanBatch( + batchData, + chainConfig.blockTime(), + chainConfig.l2Genesis().timestamp(), + chainConfig.l2ChainId(), + channel.l1InclusionBlock()); + } else if (BatchType.SINGULAR_BATCH_TYPE.getCode() == ((int) batchType)) { + RlpList rlpBatchData = (RlpList) + RlpDecoder.decode(batchData).getValues().getFirst(); + return Batch.decodeSingularBatch(rlpBatchData, channel.l1InclusionBlock()); + } else { + throw new IllegalArgumentException("invalid batch type"); + } }) .collect(Collectors.toList()); } @@ -153,6 +185,9 @@ private static byte[] decompressZlib(byte[] data) { byte[] buffer = new byte[1024]; while (!inflater.finished()) { int count = inflater.inflate(buffer); + if (count == 0) { + break; + } outputStream.write(buffer, 0, count); } @@ -163,7 +198,18 @@ private static byte[] decompressZlib(byte[] data) { } @SuppressWarnings("WhitespaceAround") - private BatchStatus batchStatus(Batch batch) { + private BatchStatus batchStatus(final Batch batch) { + if (batch.batch() instanceof SingularBatch) { + return singularBatchStatus(batch); + } else if (batch.batch() instanceof SpanBatch) { + return spanBatchStatus(batch); + } else { + throw new IllegalStateException("unknown batch type"); + } + } + + private BatchStatus singularBatchStatus(final Batch batchWrapper) { + final SingularBatch batch = (SingularBatch) batchWrapper.batch(); State state = this.state.get(); Epoch epoch = state.getSafeEpoch(); Epoch nextEpoch = state.epoch(epoch.number().add(BigInteger.ONE)); @@ -172,7 +218,7 @@ private BatchStatus batchStatus(Batch batch) { head.timestamp().add(this.config.chainConfig().blockTime()); // check timestamp range - switch (batch.timestamp().compareTo(nextTimestamp)) { + switch (batch.getTimestamp().compareTo(nextTimestamp)) { case 1 -> { return BatchStatus.Future; } @@ -189,7 +235,8 @@ private BatchStatus batchStatus(Batch batch) { } // check the inclusion delay - if (batch.epochNum().add(this.config.chainConfig().seqWindowSize()).compareTo(batch.l1InclusionBlock()) < 0) { + if (batch.epochNum().add(this.config.chainConfig().seqWindowSize()).compareTo(batchWrapper.l1InclusionBlock()) + < 0) { LOGGER.warn("inclusion window elapsed"); return BatchStatus.Drop; } @@ -252,6 +299,209 @@ private BatchStatus batchStatus(Batch batch) { return BatchStatus.Accept; } + private BatchStatus spanBatchStatus(final Batch batchWrapper) { + final SpanBatch spanBatch = (SpanBatch) batchWrapper.batch(); + final State state = this.state.get(); + final Epoch epoch = state.getSafeEpoch(); + final Epoch nextEpoch = state.epoch(epoch.number().add(BigInteger.ONE)); + final BlockInfo l2SafeHead = state.getSafeHead(); + final BigInteger nextTimestamp = + l2SafeHead.timestamp().add(this.config.chainConfig().blockTime()); + + final BigInteger startEpochNum = spanBatch.getStartEpochNum(); + final BigInteger endEpochNum = spanBatch.getBlockEpochNum(spanBatch.getBlockCount() - 1); + + final BigInteger spanStartTimestamp = spanBatch.getTimestamp(); + final BigInteger spanEndTimestamp = spanBatch.getBlockTimestamp(spanBatch.getBlockCount() - 1); + + // check batch timestamp + if (spanEndTimestamp.compareTo(nextTimestamp) < 0) { + LOGGER.warn("past batch"); + return BatchStatus.Drop; + } + if (spanStartTimestamp.compareTo(nextTimestamp) > 0) { + return BatchStatus.Future; + } + + // check for delta activation + // startEpoch == (safeEpoch.number + 1) + final Epoch batchOrigin = startEpochNum.compareTo(epoch.number().add(BigInteger.ONE)) == 0 ? nextEpoch : epoch; + + if (batchOrigin == null) { + return BatchStatus.Undecided; + } + if (batchOrigin.timestamp().compareTo(this.config.chainConfig().deltaTime()) < 0) { + LOGGER.warn("epoch start time is before delta activation: epochStartTime=%d" + .formatted(batchOrigin.timestamp())); + return BatchStatus.Drop; + } + + // find previous l2 block + final BigInteger prevTimestamp = + spanStartTimestamp.subtract(this.config.chainConfig().blockTime()); + final var prevL2Info = state.l2Info(prevTimestamp); + if (prevL2Info == null) { + LOGGER.warn("previous l2 block not found: %d".formatted(prevTimestamp)); + return BatchStatus.Drop; + } + + final var prevL2Block = prevL2Info.component1(); + final var prevL2Epoch = prevL2Info.component2(); + + // check that block builds on existing chain + final String spanBatchParentCheck = spanBatch.getParentCheck().toHexString(); + if (!spanBatch.checkParentHash(Bytes.fromHexString(prevL2Block.hash()))) { + LOGGER.warn( + "batch parent check failed: batchParent={}; prevL2BlockHash={}", + spanBatchParentCheck, + prevL2Block.hash()); + return BatchStatus.Drop; + } + + if (startEpochNum.add(this.config.chainConfig().seqWindowSize()).compareTo(batchWrapper.l1InclusionBlock()) + < 0) { + LOGGER.warn( + "sequence window check failed: startEpochNum={} + seqWindowSize={} < l1InclusionBlock={}", + startEpochNum, + this.config.chainConfig().seqWindowSize(), + batchWrapper.l1InclusionBlock()); + return BatchStatus.Drop; + } + + if (startEpochNum.compareTo(prevL2Block.number().add(BigInteger.ONE)) > 0) { + LOGGER.warn("invalid start epoch number"); + return BatchStatus.Drop; + } + + final Epoch l1Origin = state.epoch(endEpochNum); + if (l1Origin == null) { + LOGGER.warn("l1 origin not found"); + return BatchStatus.Drop; + } + final String l1OriginCheck = spanBatch.getL1OriginCheck().toHexString(); + if (!spanBatch.checkOriginHash(Bytes.fromHexString(l1Origin.hash()))) { + LOGGER.warn("l1 origin check failed: l1OriginCheck={}; l1Origin={}", l1OriginCheck, l1Origin.hash()); + return BatchStatus.Drop; + } + + if (startEpochNum.compareTo(prevL2Epoch.number()) < 0) { + LOGGER.warn("invalid start epoch number"); + return BatchStatus.Drop; + } + + // check sequencer drift + final int blockCount = spanBatch.getBlockCount(); + for (int i = 0; i < blockCount; i++) { + final var blockTimestamp = spanBatch.getBlockTimestamp(i); + if (blockTimestamp.compareTo(l2SafeHead.timestamp()) <= 0) { + continue; + } + final BigInteger l1OriginNum = spanBatch.getBlockEpochNum(i); + final L1Info batchL1Origin = state.l1Info(l1OriginNum); + if (batchL1Origin == null) { + LOGGER.warn("l1 origin not found"); + return BatchStatus.Drop; + } + if (blockTimestamp.compareTo(batchL1Origin.blockInfo().timestamp()) < 0) { + LOGGER.warn("block timestamp is less than L1 origin timestamp"); + return BatchStatus.Drop; + } + final var max = batchL1Origin + .blockInfo() + .timestamp() + .add(this.config.chainConfig().maxSeqDrift()); + if (blockTimestamp.compareTo(max) > 0) { + if (!spanBatch.getBlockTransactions(i).isEmpty()) { + LOGGER.warn(String.format( + "batch exceeded sequencer time drift, sequencer must adopt new L1 origin to include transactions again: max=%d", + max)); + return BatchStatus.Drop; + } + boolean originAdvanced; + if (i == 0) { + originAdvanced = startEpochNum.compareTo(l2SafeHead.number().add(BigInteger.ONE)) == 0; + } else { + originAdvanced = spanBatch.getBlockEpochNum(i).compareTo(spanBatch.getBlockEpochNum(i - 1)) > 0; + } + + if (!originAdvanced) { + var batchNextEpoch = state.l1Info(l1OriginNum.add(BigInteger.ONE)); + if (batchNextEpoch != null) { + if (blockTimestamp.compareTo(batchNextEpoch.blockInfo().timestamp()) >= 0) { + LOGGER.warn( + "batch exceeded sequencer time drift without adopting next origin, and next L1 origin would have been valid"); + return BatchStatus.Drop; + } + } else { + return BatchStatus.Undecided; + } + } + } + + if (spanBatch.hasInvalidTransactions(i)) { + LOGGER.warn(String.format("invalid transaction: empty or deposits into batch data: txIndex=%d", i)); + return BatchStatus.Drop; + } + } + + // overlapped block checks + for (SpanBatchElement element : spanBatch.getBatches()) { + if (element.timestamp().compareTo(nextTimestamp) >= 0) { + continue; + } + Tuple2 info = state.l2Info(element.timestamp()); + if (info == null) { + LOGGER.warn("overlapped l2 block not found"); + return BatchStatus.Drop; + } + + if (element.epochNum().compareTo(info.component2().number()) != 0) { + LOGGER.warn("epoch mismatch in overlapped blocks"); + return BatchStatus.Drop; + } + } + return BatchStatus.Accept; + } + + /** + * Gets singular batche list from IBatch instance. + * + * @param batch IBatch instance + * @param state the state + * @return the list that inner batch data was singular batch + */ + public List getSingularBatches(final IBatch batch, final State state) { + Function f = singularBatch -> new Batch(singularBatch, state.getCurrentEpochNum()); + if (batch instanceof SingularBatch typedBatch) { + return List.of(f.apply(typedBatch)); + } else if (batch instanceof SpanBatch typedBatch) { + return this.toSingularBatches(typedBatch, state).stream().map(f).collect(Collectors.toList()); + } else { + throw new IllegalStateException("unknown batch type"); + } + } + + private List toSingularBatches(final SpanBatch batch, final State state) { + List singularBatches = new ArrayList<>(); + for (SpanBatchElement element : batch.getBatches()) { + if (element.timestamp().compareTo(state.getSafeHead().timestamp()) <= 0) { + continue; + } + SingularBatch singularBatch = new SingularBatch(); + singularBatch.setEpochNum(element.epochNum()); + singularBatch.setTimestamp(element.timestamp()); + singularBatch.setTransactions(element.transactions()); + + Epoch l1Origins = state.epoch(element.epochNum()); + if (l1Origins == null) { + throw new RuntimeException("cannot find origin for epochNum: %d".formatted(element.epochNum())); + } + singularBatch.setEpochHash(state.epoch(singularBatch.epochNum()).hash()); + singularBatches.add(singularBatch); + } + return singularBatches; + } + /** * Create batches. * 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 31fb7edf..f7c6540b 100644 --- a/hildr-node/src/main/java/io/optimism/driver/Driver.java +++ b/hildr-node/src/main/java/io/optimism/driver/Driver.java @@ -206,8 +206,9 @@ public static Driver from(Config config, CountDownLatch latch) finalizedHead.number(), config); + var l2Refs = io.optimism.derive.State.initL2Refs(finalizedHead.number(), config.chainConfig(), provider); AtomicReference state = - new AtomicReference<>(io.optimism.derive.State.create(finalizedHead, finalizedEpoch, config)); + new AtomicReference<>(io.optimism.derive.State.create(l2Refs, finalizedHead, finalizedEpoch, config)); EngineDriver engineDriver = new EngineDriver<>(finalizedHead, finalizedEpoch, provider, config); diff --git a/hildr-node/src/main/java/io/optimism/rpc/RpcServer.java b/hildr-node/src/main/java/io/optimism/rpc/RpcServer.java index ae4c7c15..f421526c 100644 --- a/hildr-node/src/main/java/io/optimism/rpc/RpcServer.java +++ b/hildr-node/src/main/java/io/optimism/rpc/RpcServer.java @@ -19,7 +19,6 @@ import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.core.http.HttpServerRequest; -import io.vertx.core.http.ServerWebSocket; import io.vertx.ext.web.Route; import io.vertx.ext.web.Router; import io.vertx.ext.web.handler.BodyHandler; @@ -68,7 +67,6 @@ public RpcServer(final Config config) { /** Start. */ public void start() { this.httpServer = vertx.createHttpServer(getHttpServerOptions(config)); - httpServer.webSocketHandler(webSocketHandler()); httpServer.connectionHandler(connectionHandler()); CompletableFuture future = new CompletableFuture<>(); @@ -133,11 +131,6 @@ private Handler connectionHandler() { }; } - private Handler webSocketHandler() { - Handler o = null; - return o; - } - private HttpServerOptions getHttpServerOptions(final Config config) { final HttpServerOptions httpServerOptions = new HttpServerOptions() .setHost("0.0.0.0") diff --git a/hildr-node/src/main/java/io/optimism/rpc/methods/OutputAtBlock.java b/hildr-node/src/main/java/io/optimism/rpc/methods/OutputAtBlock.java index cd1f2ce0..dcba6a6c 100644 --- a/hildr-node/src/main/java/io/optimism/rpc/methods/OutputAtBlock.java +++ b/hildr-node/src/main/java/io/optimism/rpc/methods/OutputAtBlock.java @@ -86,7 +86,7 @@ public JsonRpcResponse response(JsonRpcRequestContext context) { private String computeL2OutputRoot(EthBlock.Block block, String storageRoot) { var version = new byte[32]; - byte[] digestBytes = null; + byte[] digestBytes = new byte[0]; digestBytes = ArrayUtils.addAll(digestBytes, version); digestBytes = ArrayUtils.addAll(digestBytes, Numeric.hexStringToByteArray(block.getStateRoot())); digestBytes = ArrayUtils.addAll(digestBytes, Numeric.hexStringToByteArray(storageRoot)); diff --git a/hildr-node/src/test/java/io/optimism/derive/stages/BatchesTest.java b/hildr-node/src/test/java/io/optimism/derive/stages/BatchesTest.java index db8bd077..c766def4 100644 --- a/hildr-node/src/test/java/io/optimism/derive/stages/BatchesTest.java +++ b/hildr-node/src/test/java/io/optimism/derive/stages/BatchesTest.java @@ -2,13 +2,18 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import io.optimism.config.Config; import io.optimism.derive.stages.Channels.Channel; import io.optimism.utilities.derive.stages.Batch; +import io.optimism.utilities.derive.stages.SingularBatch; +import io.optimism.utilities.derive.stages.SpanBatch; import java.math.BigInteger; import java.util.List; +import org.bouncycastle.jcajce.provider.digest.Keccak; import org.bouncycastle.util.encoders.Hex; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.web3j.utils.Numeric; /** * The type BatchesTest. @@ -18,9 +23,11 @@ */ class BatchesTest { - /** Decode batches. */ + /** + * Decode singular batches. + */ @Test - @DisplayName("Test decode batches successfully") + @DisplayName("Test decode singular batches successfully") void decodeBatches() { String data = "78dad459793894fbdb7f9e19db20eb902dbb9086b410b2af2939b66c255bd60991" + "c8a133c6845276c9daa36c21bb3211932c8908591a6509132a3b1959decb" @@ -124,12 +131,49 @@ void decodeBatches() { + "4c665ca197cebff1c90e5484cc8a6cb2c5b1badab35aefa35c1384f0bb64" + "59061ad574c2f37f8bbbd2e8dff5f27f020000ffff8db46838"; Channel channel = new Channel(BigInteger.ONE, Hex.decode(data), BigInteger.ONE); - List batches = Batches.decodeBatches(channel); - System.out.println(batches); + List batches = Batches.decodeBatches(Config.ChainConfig.optimismSepolia(), channel); assertEquals(6, batches.size()); assertEquals( "0x9a6d7cf81309515caed98e08edffe9a467a71a707910474b5fde43e2a6fc6454", - batches.get(0).parentHash()); + ((SingularBatch) batches.get(0).batch()).parentHash()); + } + + /** + * Decode span batches. + */ + @Test + @DisplayName("Test decode span batches successfully") + void testDecodeSpanBatch() { + final Config.ChainConfig chainConfig = Config.ChainConfig.optimismSepolia(); + + String channelData = + "78dadac9f58b71c9d7edacb77bd6323dd823c8ffeb44c059dee7ffb405f9b68b2feb9a3ef3508cc78be9f9edab1ea8557c09e3b1e83cffc05f2a8445c09141c08145c0914580010e181930012332c588a68c114323238c603cffb8e3e20ecb8f4f0d365a15b4ffe09abf6ddad1b7755a79ac67ff39b7bb9ddf3c67ab929e46cd439bf56c7757a8f67dddd968dbf1fc647b4498f6929c0b75a5f2d5557d491b6293a37343b33f681e2c37ae551763b8fc8c598271c67aed7426ff8e2dd7170a31ffbdfce97bb5d9ed0b1dfb94efcb6eb5efdb1bfb7152f8c4b9ae321c5b73af7f12517f3ec15e6effd5f0ddae251cd7673eb65b5d26a1b1e5e68e4b328587b5e6dd56717fb93d6cb3d5ea07b7ffdc0c0af2f86ab8485c73cd3fef280316fe282d96b4be42fd9df28d562c77edecef9c923fe9f6a069a346c1b7b33e9cc76c3e46dc4bacfc191cd3c8afcbc12e52eeaa7c9127ed6412c70ebee6b52dbc825971322c5eaea9adfb6673a54fddf37696757ff4aafa433f6da3531b23988abba61d3ba7beeecbb40db56935f1e7661d3812798fb95131b69eefe68f25fbf7ee7dd870517a79b4cecf0bb73ac439d5a7b7942c3cdef156ac284f31467ba5e0b39a4d8f569c303bba2c52e1b8f98c0ce91d4a96b33ffcaa985c94b2c06ec781a0c9e9d3bc2670ef1429e09b782fb323d9692607dbe9a30589dbbb6e479efbbe72d62af9f038b605f38ced7d32266f751189ff6a68f2d4b63d94c5f88cf575f7cfbbc3e3fae64b5cdc7d4cadf8ebc24bb2894b657e733d78fb3e6d47dca4bdfc1d264c9d2562dfaff4396cb83cfd94c2dc7766cbd3d218fde61f12e6b9767ed36dc625138d6778f7187a28075597196a6d522f9ac9b8e60a77dc094daf395ec7175c0f63f1326a5f257762b172c517dfbdf6ce7ed7f518129fac14fa77d84140d9e2f92791a34b7e3d7f27a4e82c7c66fbf38589266a16d3a2db4eba4e0d7b646e98fdbdea9af4e3a7739a0acb5c53f65c70c24ca002361a978eee8e5a59adbce3c786730719839d1fce3e894d8c12bdc48a31fd64126c68e6777268e677cedbc9c4a2bf26538a011f60725ecb801f24e097665c40403fe7fefa0f719efb64a6f1b7ca591d5aaa36bfece6cb15dfc37ea65d6cf37fd3b971b6848de6dc1bd7debe378909b2bdd6afc061fd29fa6e59a3935dea85d34213658e093f3a776abee3b523ab2eb933771ee2f0718c8d55ce0fff7e4b4a3395fba9bd8949656292c2a18d5cb97dcfcfccaeba72f6d59b2f824df5f5ca6eff5f1db96e57b14fe370a9b0cca7aeca4e7d4b5b33a9b06496a936455325669e8b489e2c1e5bf5e55666cf0b57070f7585cf35d922eaf6a57f4d583f2e8d8e6cbf31b7f1d3c9d432b377166db5f61bf7695b6ed67cc4f2e58bc4d1a7b39fe79e63f1582adbac7831454fc322c952de71f9d463ff73b86ec5bcd0e5519176645bc29572fa7df1cf49d3df24ea2e10d00b9f1fdd2c3c4b32d0f3e8a6355bf57708142c6ae3e8e0ff97ae2fe0e9f1a09b5b488140f8317dbed5ba6f8acc3e09bb0299aae517394dea2eb96419548530587fbffde1a7c734b7a625d2193a179630bf3634942998f4517fd6c71b0155779c7f7ff9686daf705934ed00d38f9dedfc5a8b58ba2f30b44466e88308831f3b96186d67c845b6e8de5a7488c75550f328040d84141c60faf181bb59e0e45710def1242c523632b128a984814ae088bb4a55457efea747cf9ec61a2a7aaf7f74cc600b012d5c145a49483f37162f2715270f772f6f6ac097342f74698aa7dafab9714c563029fcc0c0a1f6dbc1049769bc0fb66d5e9ec230104933a9b8b86058c7d3ab866681ea0b4b362847edd3ecff7e22df3661dd5a9eb50c6c4e57171c5c67bebef4ec9e87d33bb9773f9e9f701a49a9492dd781dfb5075a6f58cfdb32d3edd0546dbd035167b8c4266d0c083cb22f5479fa8f6eae66c12d293b5a18577c48fd3355d363bdd5ef7cb6acc5fb7630cf3feda55f5678d57b87f786794f055d8eb1c5d23a8c7e08c91cf439e4237bd867c71da69d779876dd61dab794e5e73ef6090bf9272ce46f5fca3161217fcb69c923b7246ecc976407000000ffff"; + Channel channel = new Channel(BigInteger.ONE, Hex.decode(channelData), BigInteger.ZERO); + List batches = Batches.decodeBatches(chainConfig, channel); + assertEquals(1, batches.size()); + + final var batch = (SpanBatch) batches.get(0).batch(); + + long blockTxCountsSum = batch.getBatches().stream() + .mapToLong(e -> e.transactions().size()) + .sum(); + assertEquals(9, blockTxCountsSum); + + assertEquals(BigInteger.ZERO, batches.get(0).l1InclusionBlock()); + + batch.getBatches().forEach(element -> { + BigInteger unusedBlockNum = element.timestamp() + .subtract(chainConfig.l2Genesis().timestamp()) + .divide(BigInteger.TWO); + BigInteger unusedEpochNum = element.epochNum(); + System.out.println( + "block: %d, epoch: %d".formatted(unusedBlockNum.longValue(), unusedEpochNum.longValue())); + element.transactions().forEach(txStr -> { + var digest = new Keccak.Digest256(); + byte[] hash = digest.digest(Numeric.hexStringToByteArray(txStr)); + System.out.println(Numeric.toHexString(hash)); + }); + }); } } 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 655ea86a..6b7442f0 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 @@ -1,5 +1,6 @@ package io.optimism.derive.stages; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -10,12 +11,15 @@ import io.optimism.derive.stages.Channels.Channel; import io.optimism.utilities.derive.stages.Frame; import java.math.BigInteger; +import java.util.List; import java.util.Optional; +import org.bouncycastle.util.encoders.Hex; import org.jctools.queues.MessagePassingQueue; import org.jctools.queues.MpscGrowableArrayQueue; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.web3j.tuples.generated.Tuple2; +import org.web3j.utils.Numeric; /** * The type ChannelsTest. @@ -88,6 +92,29 @@ void testReadyChannelStillPending() { assertTrue(channelOpt.isEmpty()); } + @Test + @DisplayName("Test read channel data from batch tx successfully") + void testReadChannelData() { + String data = + "00656531d7fca1ad32740ea3adca85922a0000000005dc78dadac9f58b71c9d7edacb77bd6323dd823c8ffeb44c059dee7ffb405f9b68b2feb9a3ef3508cc78be9f9edab1ea8557c09e3b1e83cffc05f2a8445c09141c08145c0914580010e181930012332c588a68c114323238c603cffb8e3e20ecb8f4f0d365a15b4ffe09abf6ddad1b7755a79ac67ff39b7bb9ddf3c67ab929e46cd439bf56c7757a8f67dddd968dbf1fc647b4498f6929c0b75a5f2d5557d491b6293a37343b33f681e2c37ae551763b8fc8c598271c67aed7426ff8e2dd7170a31ffbdfce97bb5d9ed0b1dfb94efcb6eb5efdb1bfb7152f8c4b9ae321c5b73af7f12517f3ec15e6effd5f0ddae251cd7673eb65b5d26a1b1e5e68e4b328587b5e6dd56717fb93d6cb3d5ea07b7ffdc0c0af2f86ab8485c73cd3fef280316fe282d96b4be42fd9df28d562c77edecef9c923fe9f6a069a346c1b7b33e9cc76c3e46dc4bacfc191cd3c8afcbc12e52eeaa7c9127ed6412c70ebee6b52dbc825971322c5eaea9adfb6673a54fddf37696757ff4aafa433f6da3531b23988abba61d3ba7beeecbb40db56935f1e7661d3812798fb95131b69eefe68f25fbf7ee7dd870517a79b4cecf0bb73ac439d5a7b7942c3cdef156ac284f31467ba5e0b39a4d8f569c303bba2c52e1b8f98c0ce91d4a96b33ffcaa985c94b2c06ec781a0c9e9d3bc2670ef1429e09b782fb323d9692607dbe9a30589dbbb6e479efbbe72d62af9f038b605f38ced7d32266f751189ff6a68f2d4b63d94c5f88cf575f7cfbbc3e3fae64b5cdc7d4cadf8ebc24bb2894b657e733d78fb3e6d47dca4bdfc1d264c9d2562dfaff4396cb83cfd94c2dc7766cbd3d218fde61f12e6b9767ed36dc625138d6778f7187a28075597196a6d522f9ac9b8e60a77dc094daf395ec7175c0f63f1326a5f257762b172c517dfbdf6ce7ed7f518129fac14fa77d84140d9e2f92791a34b7e3d7f27a4e82c7c66fbf38589266a16d3a2db4eba4e0d7b646e98fdbdea9af4e3a7739a0acb5c53f65c70c24ca002361a978eee8e5a59adbce3c786730719839d1fce3e894d8c12bdc48a31fd64126c68e6777268e677cedbc9c4a2bf26538a011f60725ecb801f24e097665c40403fe7fefa0f719efb64a6f1b7ca591d5aaa36bfece6cb15dfc37ea65d6cf37fd3b971b6848de6dc1bd7debe378909b2bdd6afc061fd29fa6e59a3935dea85d34213658e093f3a776abee3b523ab2eb933771ee2f0718c8d55ce0fff7e4b4a3395fba9bd8949656292c2a18d5cb97dcfcfccaeba72f6d59b2f824df5f5ca6eff5f1db96e57b14fe370a9b0cca7aeca4e7d4b5b33a9b06496a936455325669e8b489e2c1e5bf5e55666cf0b57070f7585cf35d922eaf6a57f4d583f2e8d8e6cbf31b7f1d3c9d432b377166db5f61bf7695b6ed67cc4f2e58bc4d1a7b39fe79e63f1582adbac7831454fc322c952de71f9d463ff73b86ec5bcd0e5519176645bc29572fa7df1cf49d3df24ea2e10d00b9f1fdd2c3c4b32d0f3e8a6355bf57708142c6ae3e8e0ff97ae2fe0e9f1a09b5b488140f8317dbed5ba6f8acc3e09bb0299aae517394dea2eb96419548530587fbffde1a7c734b7a625d2193a179630bf3634942998f4517fd6c71b0155779c7f7ff9686daf705934ed00d38f9dedfc5a8b58ba2f30b44466e88308831f3b96186d67c845b6e8de5a7488c75550f328040d84141c60faf181bb59e0e45710def1242c523632b128a984814ae088bb4a55457efea747cf9ec61a2a7aaf7f74cc600b012d5c145a49483f37162f2715270f772f6f6ac097342f74698aa7dafab9714c563029fcc0c0a1f6dbc1049769bc0fb66d5e9ec230104933a9b8b86058c7d3ab866681ea0b4b362847edd3ecff7e22df3661dd5a9eb50c6c4e57171c5c67bebef4ec9e87d33bb9773f9e9f701a49a9492dd781dfb5075a6f58cfdb32d3edd0546dbd035167b8c4266d0c083cb22f5479fa8f6eae66c12d293b5a18577c48fd3355d363bdd5ef7cb6acc5fb7630cf3feda55f5678d57b87f786794f055d8eb1c5d23a8c7e08c91cf439e4237bd867c71da69d779876dd61dab794e5e73ef6090bf9272ce46f5fca3161217fcb69c923b7246ecc976407000000ffff01"; + byte[] dataBytes = Numeric.hexStringToByteArray(data); + BatcherTransactions.BatcherTransaction tx = + BatcherTransactions.BatcherTransaction.create(dataBytes, BigInteger.valueOf(10254359L)); + Channels.PendingChannel pendingChannel = + Channels.PendingChannel.create(tx.frames().get(0)); + List frames = tx.frames(); + for (int i = 1; i < frames.size(); i++) { + pendingChannel.pushFrame(frames.get(i)); + } + Channel parseChannel = Channel.from(pendingChannel); + + String channelData = + "78dadac9f58b71c9d7edacb77bd6323dd823c8ffeb44c059dee7ffb405f9b68b2feb9a3ef3508cc78be9f9edab1ea8557c09e3b1e83cffc05f2a8445c09141c08145c0914580010e181930012332c588a68c114323238c603cffb8e3e20ecb8f4f0d365a15b4ffe09abf6ddad1b7755a79ac67ff39b7bb9ddf3c67ab929e46cd439bf56c7757a8f67dddd968dbf1fc647b4498f6929c0b75a5f2d5557d491b6293a37343b33f681e2c37ae551763b8fc8c598271c67aed7426ff8e2dd7170a31ffbdfce97bb5d9ed0b1dfb94efcb6eb5efdb1bfb7152f8c4b9ae321c5b73af7f12517f3ec15e6effd5f0ddae251cd7673eb65b5d26a1b1e5e68e4b328587b5e6dd56717fb93d6cb3d5ea07b7ffdc0c0af2f86ab8485c73cd3fef280316fe282d96b4be42fd9df28d562c77edecef9c923fe9f6a069a346c1b7b33e9cc76c3e46dc4bacfc191cd3c8afcbc12e52eeaa7c9127ed6412c70ebee6b52dbc825971322c5eaea9adfb6673a54fddf37696757ff4aafa433f6da3531b23988abba61d3ba7beeecbb40db56935f1e7661d3812798fb95131b69eefe68f25fbf7ee7dd870517a79b4cecf0bb73ac439d5a7b7942c3cdef156ac284f31467ba5e0b39a4d8f569c303bba2c52e1b8f98c0ce91d4a96b33ffcaa985c94b2c06ec781a0c9e9d3bc2670ef1429e09b782fb323d9692607dbe9a30589dbbb6e479efbbe72d62af9f038b605f38ced7d32266f751189ff6a68f2d4b63d94c5f88cf575f7cfbbc3e3fae64b5cdc7d4cadf8ebc24bb2894b657e733d78fb3e6d47dca4bdfc1d264c9d2562dfaff4396cb83cfd94c2dc7766cbd3d218fde61f12e6b9767ed36dc625138d6778f7187a28075597196a6d522f9ac9b8e60a77dc094daf395ec7175c0f63f1326a5f257762b172c517dfbdf6ce7ed7f518129fac14fa77d84140d9e2f92791a34b7e3d7f27a4e82c7c66fbf38589266a16d3a2db4eba4e0d7b646e98fdbdea9af4e3a7739a0acb5c53f65c70c24ca002361a978eee8e5a59adbce3c786730719839d1fce3e894d8c12bdc48a31fd64126c68e6777268e677cedbc9c4a2bf26538a011f60725ecb801f24e097665c40403fe7fefa0f719efb64a6f1b7ca591d5aaa36bfece6cb15dfc37ea65d6cf37fd3b971b6848de6dc1bd7debe378909b2bdd6afc061fd29fa6e59a3935dea85d34213658e093f3a776abee3b523ab2eb933771ee2f0718c8d55ce0fff7e4b4a3395fba9bd8949656292c2a18d5cb97dcfcfccaeba72f6d59b2f824df5f5ca6eff5f1db96e57b14fe370a9b0cca7aeca4e7d4b5b33a9b06496a936455325669e8b489e2c1e5bf5e55666cf0b57070f7585cf35d922eaf6a57f4d583f2e8d8e6cbf31b7f1d3c9d432b377166db5f61bf7695b6ed67cc4f2e58bc4d1a7b39fe79e63f1582adbac7831454fc322c952de71f9d463ff73b86ec5bcd0e5519176645bc29572fa7df1cf49d3df24ea2e10d00b9f1fdd2c3c4b32d0f3e8a6355bf57708142c6ae3e8e0ff97ae2fe0e9f1a09b5b488140f8317dbed5ba6f8acc3e09bb0299aae517394dea2eb96419548530587fbffde1a7c734b7a625d2193a179630bf3634942998f4517fd6c71b0155779c7f7ff9686daf705934ed00d38f9dedfc5a8b58ba2f30b44466e88308831f3b96186d67c845b6e8de5a7488c75550f328040d84141c60faf181bb59e0e45710def1242c523632b128a984814ae088bb4a55457efea747cf9ec61a2a7aaf7f74cc600b012d5c145a49483f37162f2715270f772f6f6ac097342f74698aa7dafab9714c563029fcc0c0a1f6dbc1049769bc0fb66d5e9ec230104933a9b8b86058c7d3ab866681ea0b4b362847edd3ecff7e22df3661dd5a9eb50c6c4e57171c5c67bebef4ec9e87d33bb9773f9e9f701a49a9492dd781dfb5075a6f58cfdb32d3edd0546dbd035167b8c4266d0c083cb22f5479fa8f6eae66c12d293b5a18577c48fd3355d363bdd5ef7cb6acc5fb7630cf3feda55f5678d57b87f786794f055d8eb1c5d23a8c7e08c91cf439e4237bd867c71da69d779876dd61dab794e5e73ef6090bf9272ce46f5fca3161217fcb69c923b7246ecc976407000000ffff"; + Channel channel = new Channel(BigInteger.ONE, Hex.decode(channelData), BigInteger.ZERO); + + assertArrayEquals(channel.data(), parseChannel.data()); + } + private Tuple2, MessagePassingQueue> createStage() { Config config = new Config("", "", "", "", null, null, 9545, false, ChainConfig.optimismGoerli()); MessagePassingQueue transactionMessageMessagePassingQueue = diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/Batch.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/Batch.java index f9a0caf8..7ea071fb 100644 --- a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/Batch.java +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/Batch.java @@ -1,34 +1,27 @@ package io.optimism.utilities.derive.stages; +import io.netty.buffer.Unpooled; import java.math.BigInteger; -import java.util.List; -import java.util.stream.Collectors; -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; /** * The type Batch. * - * @param parentHash the parent hash - * @param epochNum the epoch num - * @param epochHash the epoch hash - * @param timestamp the timestamp - * @param transactions the transactions + * @param batch the batch * @param l1InclusionBlock L1 inclusion block * @author grapebaba - * @since 0.1.0 + * @since 0.2.4 */ -public record Batch( - String parentHash, - BigInteger epochNum, - String epochHash, - BigInteger timestamp, - List transactions, - BigInteger l1InclusionBlock) { +public record Batch(IBatch batch, BigInteger l1InclusionBlock) { + + /** + * Gets timestamp. + * + * @return the timestamp + */ + public BigInteger timestamp() { + return batch.getTimestamp(); + } /** * Encode batch. @@ -36,46 +29,46 @@ public record Batch( * @return encoded bytes by the batch */ public byte[] encode() { - List collect = transactions().stream() - .map(tx -> (RlpType) RlpString.create(tx)) - .collect(Collectors.toList()); - return RlpEncoder.encode(new RlpList( - RlpString.create(parentHash()), - RlpString.create(epochNum()), - RlpString.create(epochHash()), - RlpString.create(timestamp()), - new RlpList(collect))); + if (batch instanceof SingularBatch) { + var typedBatch = (SingularBatch) batch; + return typedBatch.encode(); + } else if (batch instanceof SpanBatch) { + throw new IllegalStateException("unsupport batch type: %s".formatted(batch.getBatchType())); + } else { + throw new IllegalStateException("unknown batch type"); + } } /** - * Decode batch. + * Decode singular batch. * * @param rlp the rlp * @param l1InclusionBlock L1 inclusion block * @return the batch */ - public static Batch decode(RlpList rlp, BigInteger l1InclusionBlock) { - String parentHash = ((RlpString) rlp.getValues().get(0)).asString(); - BigInteger epochNum = ((RlpString) rlp.getValues().get(1)).asPositiveBigInteger(); - String epochHash = ((RlpString) rlp.getValues().get(2)).asString(); - BigInteger timestamp = ((RlpString) rlp.getValues().get(3)).asPositiveBigInteger(); - List transactions = ((RlpList) rlp.getValues().get(4)) - .getValues().stream() - .map(rlpString -> ((RlpString) rlpString).asString()) - .collect(Collectors.toList()); - return new Batch(parentHash, epochNum, epochHash, timestamp, transactions, l1InclusionBlock); + public static Batch decodeSingularBatch(final RlpList rlp, final BigInteger l1InclusionBlock) { + return new Batch(SingularBatch.decode(rlp), l1InclusionBlock); } /** - * Has invalid transactions boolean. + * Decode span batch. * - * @return the boolean + * @param buf the span batch encoded bytes + * @param blockTime the block time + * @param l2genesisTimestamp L2 genesis timestamp + * @param l2ChainId the L2 chain id + * @param l1InclusionBlock L1 inclusion block + * @return the batch */ - public boolean hasInvalidTransactions() { - return this.transactions.stream() - .anyMatch(s -> StringUtils.isEmpty(s) - || (Numeric.containsHexPrefix("0x") - ? StringUtils.startsWithIgnoreCase(s, "0x7E") - : StringUtils.startsWithIgnoreCase(s, "7E"))); + public static Batch decodeSpanBatch( + final byte[] buf, + final BigInteger blockTime, + final BigInteger l2genesisTimestamp, + final BigInteger l2ChainId, + final BigInteger l1InclusionBlock) { + final RawSpanBatch rawSpanBatch = new RawSpanBatch(); + rawSpanBatch.decode(Unpooled.wrappedBuffer(buf)); + final SpanBatch spanBatch = rawSpanBatch.toSpanBatch(blockTime, l2genesisTimestamp, l2ChainId); + return new Batch(spanBatch, l1InclusionBlock); } } diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/RawSpanBatch.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/RawSpanBatch.java index 1fa7cf80..959c3a6b 100644 --- a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/RawSpanBatch.java +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/RawSpanBatch.java @@ -5,6 +5,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; +import org.apache.commons.lang3.ArrayUtils; import org.web3j.utils.Numeric; /** @@ -13,7 +14,7 @@ * @author grapebaba * @since 0.2.4 */ -public class RawSpanBatch implements IBatch { +public class RawSpanBatch { private SpanBatchPrefix spanbatchPrefix; private SpanBatchPayload spanbatchPayload; @@ -90,14 +91,13 @@ public String toString() { return "RawSpanBatch[spanbatchPrefix=%s, spanbatchPayload=%s]".formatted(spanbatchPrefix, spanbatchPayload); } - @Override - public BatchType getBatchType() { - return BatchType.SPAN_BATCH_TYPE; - } - - @Override - public BigInteger getTimestamp() { - return null; + /** + * Encode raw span batch. + * + * @return the encoded raw span batch bytes + */ + public byte[] encode() { + return ArrayUtils.addAll(this.spanbatchPrefix.encode(), this.spanbatchPayload.encode()); } /** diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SingularBatch.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SingularBatch.java index 4745a4fa..bfaaf893 100644 --- a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SingularBatch.java +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SingularBatch.java @@ -5,10 +5,12 @@ import java.util.List; import java.util.Objects; import java.util.stream.Collectors; +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; /** * The type SingularBatch. @@ -229,4 +231,17 @@ public String toString() { return "SingularBatch[parentHash=%s, epochNum=%s, epochHash=%s, timestamp=%s, transactions=%s]" .formatted(parentHash, epochNum, epochHash, timestamp, transactions); } + + /** + * Has invalid transactions boolean. + * + * @return the boolean + */ + public boolean hasInvalidTransactions() { + return this.transactions.stream() + .anyMatch(s -> StringUtils.isEmpty(s) + || (Numeric.containsHexPrefix(s) + ? StringUtils.startsWithIgnoreCase(s, "0x7E") + : StringUtils.startsWithIgnoreCase(s, "7E"))); + } } diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatch.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatch.java index 0e3faa85..b4427276 100644 --- a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatch.java +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatch.java @@ -10,6 +10,7 @@ import java.util.Comparator; import java.util.List; import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; import org.apache.tuweni.bytes.Bytes; import org.slf4j.Logger; import org.web3j.utils.Numeric; @@ -118,7 +119,7 @@ public BigInteger getStartEpochNum() { * @return boolean. boolean */ public boolean checkOriginHash(Bytes hash) { - return this.l1OriginCheck.equals(hash); + return this.l1OriginCheck.equals(hash.slice(0, this.l1OriginCheck.size())); } /** @@ -128,7 +129,7 @@ public boolean checkOriginHash(Bytes hash) { * @return boolean. boolean */ public boolean checkParentHash(Bytes hash) { - return this.parentCheck.equals(hash); + return this.parentCheck.equals(hash.slice(0, this.parentCheck.size())); } /** @@ -161,6 +162,20 @@ public List getBlockTransactions(int index) { return this.batches.get(index).transactions(); } + /** + * Has invalid transactions boolean. + * + * @param index the index + * @return the boolean + */ + public boolean hasInvalidTransactions(int index) { + return this.getBlockTransactions(index).stream() + .anyMatch(s -> StringUtils.isEmpty(s) + || (Numeric.containsHexPrefix(s) + ? StringUtils.startsWithIgnoreCase(s, "0x7E") + : StringUtils.startsWithIgnoreCase(s, "7E"))); + } + /** * GetBlockCount * @@ -176,7 +191,7 @@ public int getBlockCount() { * * @param singularBatch SingularBatch */ - public void AppendSingularBatch(SingularBatch singularBatch) { + public void appendSingularBatch(SingularBatch singularBatch) { if (batches.isEmpty()) { this.parentCheck = Bytes.fromHexStringLenient(singularBatch.parentHash().substring(0, 40)); @@ -375,7 +390,7 @@ public static SpanBatch newSpanBatch(List singularBatches) { } /** - * Derive span batch span batch. + * Derive raw span batch to span batch. * * @param batch the batch * @param blockTime the block time @@ -384,9 +399,8 @@ public static SpanBatch newSpanBatch(List singularBatches) { * @return the span batch */ public static SpanBatch deriveSpanBatch( - IBatch batch, BigInteger blockTime, BigInteger genesisTimestamp, BigInteger chainID) { - RawSpanBatch rawSpanBatch = (RawSpanBatch) batch; - return rawSpanBatch.toSpanBatch(blockTime, genesisTimestamp, chainID); + RawSpanBatch batch, BigInteger blockTime, BigInteger genesisTimestamp, BigInteger chainID) { + return batch.toSpanBatch(blockTime, genesisTimestamp, chainID); } /** @@ -418,7 +432,7 @@ public SpanBatchBuilder(BigInteger genesisTimestamp, BigInteger chainID) { * @param singularBatch the singular batch * @param seqNum the seq num */ - public void AppendSingularBatch(SingularBatch singularBatch, BigInteger seqNum) { + public void appendSingularBatch(SingularBatch singularBatch, BigInteger seqNum) { if (this.getBlockCount() == 0) { this.originChangedBit = 0; if (seqNum.compareTo(BigInteger.ZERO) == 0) { @@ -426,7 +440,7 @@ public void AppendSingularBatch(SingularBatch singularBatch, BigInteger seqNum) } } - this.spanBatch.AppendSingularBatch(singularBatch); + this.spanBatch.appendSingularBatch(singularBatch); } /** 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 1ab78f00..717cb1ab 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 @@ -53,9 +53,9 @@ void testSpanBatchForBatchInterface() throws IOException { assertEquals(spanBatch.getStartEpochNum(), singularBatches1.getFirst().getEpochNum()); assertTrue(spanBatch.checkOriginHash( - Bytes.fromHexString(singularBatches1.getLast().epochHash().substring(0, 40)))); + Bytes.fromHexString(singularBatches1.getLast().epochHash()))); assertTrue(spanBatch.checkParentHash( - Bytes.fromHexString(singularBatches1.getFirst().parentHash().substring(0, 40)))); + Bytes.fromHexString(singularBatches1.getFirst().parentHash()))); } /**