diff --git a/hildr-batcher/build.gradle b/hildr-batcher/build.gradle index 544f8c67..18f1edda 100644 --- a/hildr-batcher/build.gradle +++ b/hildr-batcher/build.gradle @@ -26,6 +26,9 @@ repositories { maven { url "https://artifacts.consensys.net/public/maven/maven/" } + maven { url "https://artifacts.consensys.net/public/maven/maven/" } + maven { url "https://jitpack.io" } + google() } application { diff --git a/hildr-utilities/build.gradle b/hildr-utilities/build.gradle index 3501d597..c329a53c 100644 --- a/hildr-utilities/build.gradle +++ b/hildr-utilities/build.gradle @@ -23,6 +23,9 @@ repositories { maven { url "https://artifacts.consensys.net/public/maven/maven/" } + maven { url "https://artifacts.consensys.net/public/maven/maven/" } + maven { url "https://jitpack.io" } + google() } java { @@ -75,11 +78,21 @@ dependencies { implementation 'ch.qos.logback:logback-core:1.4.7' implementation 'ch.qos.logback:logback-classic:1.4.7' + + implementation 'io.tmio:tuweni-rlp:2.4.2' + implementation 'org.bouncycastle:bcprov-jdk18on:1.76' implementation 'org.slf4j:slf4j-api:2.0.7' + implementation 'io.libp2p:jvm-libp2p:1.0.1-RELEASE' testImplementation platform('org.junit:junit-bom:5.9.1') testImplementation 'org.junit.jupiter:junit-jupiter' + testImplementation 'io.tmio:tuweni-ssz:2.4.2' + testImplementation 'io.tmio:tuweni-units:2.4.2' + testImplementation('io.tmio:tuweni-crypto:2.4.2'){ + exclude group: 'org.bouncycastle', module: 'bcprov-jdk15on' + } + errorprone("com.google.errorprone:error_prone_core:2.18.0") } diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/BatchType.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/BatchType.java new file mode 100644 index 00000000..5fd92940 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/BatchType.java @@ -0,0 +1,30 @@ +package io.optimism.utilities.derive.stages; + +public enum BatchType { + SINGULAR_BATCH_TYPE(0, "SingularBatchType"), + SPAN_BATCH_TYPE(1, "SpanBatchType"); + private final int code; + private final String name; + + BatchType(int code, String name) { + this.code = code; + this.name = name; + } + + public int getCode() { + return code; + } + + public String getName() { + return name; + } + + public static BatchType from(int code) { + for (BatchType batchType : BatchType.values()) { + if (batchType.getCode() == code) { + return batchType; + } + } + throw new IllegalArgumentException("Invalid BatchType code: " + code); + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/IBatch.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/IBatch.java new file mode 100644 index 00000000..8b60ba96 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/IBatch.java @@ -0,0 +1,18 @@ +package io.optimism.utilities.derive.stages; + +import java.math.BigInteger; + +/** + * Batch contains information to build one or multiple L2 blocks. + * Batcher converts L2 blocks into Batch and writes encoded bytes to Channel. + * Derivation pipeline decodes Batch from Channel, and converts to one or multiple payload attributes. + * + * @author zhouop0 + * @since 0.1.0 + */ +public interface IBatch { + + int getBatchType(); + + BigInteger getTimestamp(); +} 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 new file mode 100644 index 00000000..a8055c20 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/RawSpanBatch.java @@ -0,0 +1,3 @@ +package io.optimism.utilities.derive.stages; + +public record RawSpanBatch(SpanBatchPrefix spanbatchPrefix, SpanBatchPayload spanbatchPayload) {} 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 new file mode 100644 index 00000000..9c0da606 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SingularBatch.java @@ -0,0 +1,63 @@ +package io.optimism.utilities.derive.stages; + +import io.optimism.type.BlockId; +import java.math.BigInteger; +import java.util.List; +import java.util.stream.Collectors; +import org.web3j.rlp.RlpEncoder; +import org.web3j.rlp.RlpList; +import org.web3j.rlp.RlpString; +import org.web3j.rlp.RlpType; + +public record SingularBatch( + String parentHash, BigInteger epochNum, String epochHash, BigInteger timestamp, List transactions) + implements IBatch { + + public BlockId epoch() { + return new BlockId(epochHash(), epochNum()); + } + + 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))); + } + + /** + * Decode batch. + * + * @param rlp the rlp + * @return the batch + */ + public static SingularBatch decode(RlpList rlp) { + 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 SingularBatch(parentHash, epochNum, epochHash, timestamp, transactions); + } + + @Override + public int getBatchType() { + return BatchType.SINGULAR_BATCH_TYPE.getCode(); + } + + @Override + public BigInteger getTimestamp() { + return timestamp(); + } + + public BigInteger getEpochNum() { + return epochNum(); + } +} 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 new file mode 100644 index 00000000..34a040bb --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatch.java @@ -0,0 +1,107 @@ +package io.optimism.utilities.derive.stages; + +import java.math.BigInteger; +import java.util.List; + +/** + * SpanBatch is an implementation of Batch interface, + * containing the input to build a span of L2 blocks in derived form (SpanBatchElement) + * + * @author zhouop0 + * @since 0.1.0 + */ +public class SpanBatch implements IBatch { + + // First 20 bytes of the first block's parent hash + private String parentCheck; + // First 20 bytes of the last block's L1 origin hash + private String l1OriginCheck; + // List of block input in derived form + private List batches; + + public String getParentCheck() { + return parentCheck; + } + + public String getL1OriginCheck() { + return l1OriginCheck; + } + + public List getBatches() { + return batches; + } + + @Override + public int getBatchType() { + return BatchType.SPAN_BATCH_TYPE.getCode(); + } + + @Override + public BigInteger getTimestamp() { + return batches.getFirst().timestamp(); + } + + /** + * GetStartEpochNum returns epoch number(L1 origin block number) of the first block in the span. + */ + public BigInteger getStartEpochNum() { + return this.batches.getFirst().epochNum(); + } + + /** + * checks if the parentCheck matches the first 20 bytes of given hash, probably the current L2 safe head. + * @param hash the first 20 bytes of given hash. + * @return boolean. + */ + public boolean checkOriginHash(String hash) { + return this.l1OriginCheck.equals(hash); + } + + /** + * checks if the parentCheck matches the first 20 bytes of given hash, probably the current L2 safe head. + * @param hash the first 20 bytes of given hash. + * @return boolean. + */ + public boolean checkParentHash(String hash) { + return this.parentCheck.equals(hash); + } + + /** + * GetBlockEpochNum + * @param index batches index. + * @return the epoch number(L1 origin block number) of the block at the given index in the span. + */ + public BigInteger getBlockEpochNum(int index) { + return this.batches.get(index).epochNum(); + } + + /** + * GetBlockTimestamp + * @param index batches index. + * @return the timestamp of the block at the given index in the span. + */ + public BigInteger getBlockTimestamp(int index) { + return this.batches.get(index).timestamp(); + } + + /** + * GetBlockCount + * @return the number of blocks in the span. + */ + public int getBlockCount() { + return this.batches.size(); + } + + /** + * AppendSingularBatch appends a SingularBatch into the span batch + * updates l1OriginCheck or parentCheck if needed. + * + * @param singularBatch SingularBatch + */ + public void AppendSingularBatch(SingularBatch singularBatch) { + if (batches.size() == 0) { + this.parentCheck = singularBatch.parentHash().substring(0, 20); + } + this.batches.add(SpanBatchElement.singularBatchToElement(singularBatch)); // add the batch to the list + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchAccessListTxData.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchAccessListTxData.java new file mode 100644 index 00000000..6a15f36a --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchAccessListTxData.java @@ -0,0 +1,57 @@ +package io.optimism.utilities.derive.stages; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.web3j.crypto.AccessListObject; +import org.web3j.crypto.transaction.type.TransactionType; +import org.web3j.rlp.RlpEncoder; +import org.web3j.rlp.RlpList; +import org.web3j.rlp.RlpString; +import org.web3j.rlp.RlpType; + +/** + * EIP-2930. + * + */ +public record SpanBatchAccessListTxData( + BigInteger value, BigInteger gasPrice, String data, List accessList) + implements SpanBatchTxData { + + @Override + public byte txType() { + return TransactionType.EIP2930.getRlpType(); + } + + public byte[] encode() { + List rlpList = new ArrayList<>(); + for (AccessListObject access : accessList) { + rlpList.add(RlpString.create(access.getAddress())); + rlpList.add(new RlpList( + access.getStorageKeys().stream().map(RlpString::create).collect(Collectors.toList()))); + } + return RlpEncoder.encode(new RlpList( + RlpString.create(value()), + RlpString.create(gasPrice()), + RlpString.create(data()), + new RlpList(rlpList))); + } + + public static SpanBatchAccessListTxData decode(RlpList rlp) { + BigInteger value = ((RlpString) rlp.getValues().get(0)).asPositiveBigInteger(); + BigInteger gasPrice = ((RlpString) rlp.getValues().get(1)).asPositiveBigInteger(); + String data = ((RlpString) rlp.getValues().get(2)).asString(); + List accessObjList = new ArrayList<>(); + ((RlpList) rlp.getValues().get(3)).getValues().forEach(rlpType -> { + RlpList rlpList = (RlpList) rlpType; + String address = ((RlpString) rlpList.getValues().get(0)).asString(); + List storageKeys = ((RlpList) rlpList.getValues().get(1)) + .getValues().stream() + .map(stKey -> ((RlpString) stKey).asString()) + .collect(Collectors.toList()); + accessObjList.add(new AccessListObject(address, storageKeys)); + }); + return new SpanBatchAccessListTxData(value, gasPrice, data, accessObjList); + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchDynamicFeeTxData.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchDynamicFeeTxData.java new file mode 100644 index 00000000..d5cc4b97 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchDynamicFeeTxData.java @@ -0,0 +1,54 @@ +package io.optimism.utilities.derive.stages; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.web3j.crypto.AccessListObject; +import org.web3j.crypto.transaction.type.TransactionType; +import org.web3j.rlp.RlpEncoder; +import org.web3j.rlp.RlpList; +import org.web3j.rlp.RlpString; +import org.web3j.rlp.RlpType; + +public record SpanBatchDynamicFeeTxData( + BigInteger value, BigInteger gasTipCap, BigInteger gasFeeCap, String data, List accessList) + implements SpanBatchTxData { + @Override + public byte txType() { + return TransactionType.EIP1559.getRlpType(); + } + + public byte[] encode() { + List rlpList = new ArrayList<>(); + for (AccessListObject access : accessList) { + rlpList.add(RlpString.create(access.getAddress())); + rlpList.add(new RlpList( + access.getStorageKeys().stream().map(RlpString::create).collect(Collectors.toList()))); + } + return RlpEncoder.encode(new RlpList( + RlpString.create(value()), + RlpString.create(gasTipCap()), + RlpString.create(gasFeeCap()), + RlpString.create(data()), + new RlpList(rlpList))); + } + + public static SpanBatchDynamicFeeTxData decode(RlpList rlp) { + BigInteger value = ((RlpString) rlp.getValues().get(0)).asPositiveBigInteger(); + BigInteger gasTipCap = ((RlpString) rlp.getValues().get(1)).asPositiveBigInteger(); + BigInteger gasFeeCap = ((RlpString) rlp.getValues().get(2)).asPositiveBigInteger(); + String data = ((RlpString) rlp.getValues().get(2)).asString(); + List accessObjList = new ArrayList<>(); + ((RlpList) rlp.getValues().get(3)).getValues().forEach(rlpType -> { + RlpList rlpList = (RlpList) rlpType; + String address = ((RlpString) rlpList.getValues().get(0)).asString(); + List storageKeys = ((RlpList) rlpList.getValues().get(1)) + .getValues().stream() + .map(stKey -> ((RlpString) stKey).asString()) + .collect(Collectors.toList()); + accessObjList.add(new AccessListObject(address, storageKeys)); + }); + return new SpanBatchDynamicFeeTxData(value, gasTipCap, gasFeeCap, data, accessObjList); + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchElement.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchElement.java new file mode 100644 index 00000000..7ba0e588 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchElement.java @@ -0,0 +1,11 @@ +package io.optimism.utilities.derive.stages; + +import java.math.BigInteger; +import java.util.List; + +public record SpanBatchElement(BigInteger epochNum, BigInteger timestamp, List transactions) { + + public static SpanBatchElement singularBatchToElement(SingularBatch singularBatch) { + return new SpanBatchElement(singularBatch.epochNum(), singularBatch.timestamp(), singularBatch.transactions()); + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchLegacyTxData.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchLegacyTxData.java new file mode 100644 index 00000000..e7aba6f4 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchLegacyTxData.java @@ -0,0 +1,26 @@ +package io.optimism.utilities.derive.stages; + +import java.math.BigInteger; +import org.web3j.rlp.RlpEncoder; +import org.web3j.rlp.RlpList; +import org.web3j.rlp.RlpString; + +public record SpanBatchLegacyTxData(BigInteger value, BigInteger gasPrice, String data) implements SpanBatchTxData { + + @Override + public byte txType() { + return 0x00; + } + + public byte[] encode() { + return RlpEncoder.encode( + new RlpList(RlpString.create(value()), RlpString.create(gasPrice()), RlpString.create(data()))); + } + + public static SpanBatchLegacyTxData decode(RlpList rlp) { + BigInteger value = ((RlpString) rlp.getValues().get(0)).asPositiveBigInteger(); + BigInteger gasPrice = ((RlpString) rlp.getValues().get(1)).asPositiveBigInteger(); + String data = ((RlpString) rlp.getValues().get(2)).asString(); + return new SpanBatchLegacyTxData(value, gasPrice, data); + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchPayload.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchPayload.java new file mode 100644 index 00000000..896bf73f --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchPayload.java @@ -0,0 +1,7 @@ +package io.optimism.utilities.derive.stages; + +import java.math.BigInteger; +import java.util.List; + +public record SpanBatchPayload( + BigInteger blockCount, BigInteger originBits, List blockTxCounts, SpanBatchTxs txs) {} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchPrefix.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchPrefix.java new file mode 100644 index 00000000..7a2c99d8 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchPrefix.java @@ -0,0 +1,5 @@ +package io.optimism.utilities.derive.stages; + +import java.math.BigInteger; + +public record SpanBatchPrefix(BigInteger relTimestamp, BigInteger l1Origin, String parentCheck, String l1OriginCheck) {} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchSignature.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchSignature.java new file mode 100644 index 00000000..fc572d20 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchSignature.java @@ -0,0 +1,5 @@ +package io.optimism.utilities.derive.stages; + +import java.math.BigInteger; + +public record SpanBatchSignature(BigInteger v, BigInteger r, BigInteger s) {} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchTx.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchTx.java new file mode 100644 index 00000000..f51827a4 --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchTx.java @@ -0,0 +1,86 @@ +package io.optimism.utilities.derive.stages; + +import java.util.ArrayList; +import org.apache.commons.lang3.ArrayUtils; +import org.web3j.crypto.transaction.type.ITransaction; +import org.web3j.crypto.transaction.type.Transaction1559; +import org.web3j.crypto.transaction.type.Transaction2930; +import org.web3j.crypto.transaction.type.TransactionType; +import org.web3j.rlp.RlpDecoder; +import org.web3j.rlp.RlpList; + +public class SpanBatchTx { + private final SpanBatchTxData spanBatchTxData; + + protected SpanBatchTx(SpanBatchTxData spanBatchTxData) { + this.spanBatchTxData = spanBatchTxData; + } + + public byte txType() { + return this.spanBatchTxData.txType(); + } + + public static SpanBatchTx newSpanBatchTx(ITransaction tx) { + SpanBatchTxData spanBatchTxData; + switch (tx.getType()) { + case LEGACY: + spanBatchTxData = new SpanBatchLegacyTxData(tx.getValue(), tx.getGasPrice(), tx.getData()); + break; + case EIP1559: + Transaction1559 transaction1559 = (Transaction1559) tx; + spanBatchTxData = new SpanBatchDynamicFeeTxData( + transaction1559.getValue(), + transaction1559.getMaxPriorityFeePerGas(), + transaction1559.getMaxFeePerGas(), + transaction1559.getData(), + new ArrayList<>()); + break; + case EIP2930: + Transaction2930 transaction2930 = (Transaction2930) tx; + spanBatchTxData = new SpanBatchAccessListTxData( + transaction2930.getValue(), + transaction2930.getGasPrice(), + transaction2930.getData(), + transaction2930.getAccessList()); + break; + default: + throw new RuntimeException("invalid tx type:" + tx.getType()); + } + return new SpanBatchTx(spanBatchTxData); + } + + public byte[] marshalBinary() { + if (spanBatchTxData.txType() == 0) { + SpanBatchLegacyTxData spanBatchLegacyTxData = (SpanBatchLegacyTxData) this.spanBatchTxData; + return spanBatchLegacyTxData.encode(); + } + if (TransactionType.EIP1559.getRlpType() == spanBatchTxData.txType()) { + SpanBatchDynamicFeeTxData spanBatchDynamicFeeTxData = (SpanBatchDynamicFeeTxData) this.spanBatchTxData; + return spanBatchDynamicFeeTxData.encode(); + } + if (TransactionType.EIP2930.getRlpType() == spanBatchTxData.txType()) { + SpanBatchAccessListTxData spanBatchAccessListTxData = (SpanBatchAccessListTxData) this.spanBatchTxData; + return spanBatchAccessListTxData.encode(); + } + return null; + } + + public SpanBatchTxData unMarshalBinary(byte[] b) { + if (b.length <= 1) { + throw new RuntimeException("typed transaction too short"); + } + byte[] spanBatchData = ArrayUtils.subarray(b, 1, b.length); + RlpList rlpBatchData = + (RlpList) RlpDecoder.decode(spanBatchData).getValues().get(0); + if ((b[0] & 0xFF) > 0x7F) { + return SpanBatchLegacyTxData.decode(rlpBatchData); + } + if (TransactionType.EIP2930.getRlpType() == b[0]) { + return SpanBatchAccessListTxData.decode(rlpBatchData); + } + if (TransactionType.EIP1559.getRlpType() == b[0]) { + return SpanBatchDynamicFeeTxData.decode(rlpBatchData); + } + return null; + } +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchTxData.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchTxData.java new file mode 100644 index 00000000..1d53d9dc --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchTxData.java @@ -0,0 +1,6 @@ +package io.optimism.utilities.derive.stages; + +public interface SpanBatchTxData { + + byte txType(); +} diff --git a/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchTxs.java b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchTxs.java new file mode 100644 index 00000000..b36a8e4b --- /dev/null +++ b/hildr-utilities/src/main/java/io/optimism/utilities/derive/stages/SpanBatchTxs.java @@ -0,0 +1,442 @@ +package io.optimism.utilities.derive.stages; + +import static org.web3j.crypto.transaction.type.TransactionType.EIP1559; +import static org.web3j.crypto.transaction.type.TransactionType.EIP2930; +import static org.web3j.crypto.transaction.type.TransactionType.LEGACY; + +import io.libp2p.etc.types.ByteBufExtKt; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufUtil; +import io.netty.buffer.Unpooled; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import org.apache.tuweni.bytes.Bytes; +import org.web3j.crypto.Keys; +import org.web3j.crypto.Sign; +import org.web3j.crypto.SignedRawTransaction; +import org.web3j.crypto.TransactionDecoder; +import org.web3j.crypto.transaction.type.ITransaction; +import org.web3j.crypto.transaction.type.Transaction1559; +import org.web3j.crypto.transaction.type.Transaction2930; +import org.web3j.crypto.transaction.type.TransactionType; +import org.web3j.utils.Numeric; + +public class SpanBatchTxs { + + private Long totalBlockTxCount; + private BigInteger contractCreationBits; + private BigInteger yParityBits; + private List txSigs; + private List txNonces; + private List txGases; + private List txTos; + private List txDatas; + private List txTypes; + + public SpanBatchTxs() {} + + public SpanBatchTxs( + Long totalBlockTxCount, + BigInteger contractCreationBits, + BigInteger yParityBits, + List txSigs, + List txNonces, + List txGases, + List txTos, + List txDatas, + List txTypes) { + this.totalBlockTxCount = totalBlockTxCount; + this.contractCreationBits = contractCreationBits; + this.yParityBits = yParityBits; + this.txSigs = txSigs; + this.txNonces = txNonces; + this.txGases = txGases; + this.txTos = txTos; + this.txDatas = txDatas; + this.txTypes = txTypes; + } + + public byte[] encodeContractCreationBits() { + Long contractCreationBitBufferLen = this.totalBlockTxCount / 8; + if (this.totalBlockTxCount % 8 != 0) { + contractCreationBitBufferLen = contractCreationBitBufferLen + 1; + } + byte[] contractCreationBitBuffer = new byte[contractCreationBitBufferLen.intValue()]; + for (int i = 0; i < this.totalBlockTxCount; i += 8) { + int end = i + 8; + if (end < this.totalBlockTxCount.intValue()) { + end = this.totalBlockTxCount.intValue(); + } + int bits = 0; + for (int j = i; j < end; j++) { + if (this.contractCreationBits.testBit(j)) { + bits |= 1 << (j - i); + } + } + contractCreationBitBuffer[i / 8] = (byte) bits; + } + return contractCreationBitBuffer; + } + + /** + * contractCreationBits is bitlist right-padded to a multiple of 8 bits. + * + * @param contractCreationBit encoded contract creation bits. + */ + public void decodeContractCreationBits(byte[] contractCreationBit) { + Long contractCreationBitBufferLen = this.totalBlockTxCount / 8; + if (this.totalBlockTxCount % 8 != 0) { + contractCreationBitBufferLen = contractCreationBitBufferLen + 1; + } + if (contractCreationBitBufferLen > 10000000) { + throw new RuntimeException("span batch size limit reached"); + } + byte[] contractCreationBitBuffer = new byte[contractCreationBitBufferLen.intValue()]; + System.arraycopy(contractCreationBit, 0, contractCreationBitBuffer, 0, contractCreationBit.length); + BigInteger contractCreationbits = BigInteger.ZERO; + for (int i = 0; i < this.totalBlockTxCount; i += 8) { + int end = i + 8; + if (end < this.totalBlockTxCount.intValue()) { + end = this.totalBlockTxCount.intValue(); + } + byte bits = contractCreationBitBuffer[i / 8]; + for (int j = i; j < end; j++) { + int bit = (bits >> (j - i)) & 1; + if (bit != 0) { + contractCreationbits = contractCreationbits.setBit(j); + } + } + } + this.contractCreationBits = contractCreationbits; + } + + /** + * Counts the number of contract creations in the batch. + * + * @return the number of contract creations in the batch. + */ + public Long contractCreationCount() { + if (contractCreationBits == null) { + throw new RuntimeException("dev error: contract creation bits not set"); + } + Long result = 0L; + for (int i = 0; i < this.totalBlockTxCount; i++) { + if (contractCreationBits.testBit(i)) { + result++; + } + } + return result; + } + + public byte[] encodeYParityBits() { + Long yParityBitBufferLen = this.totalBlockTxCount / 8; + if (this.totalBlockTxCount % 8 != 0) { + yParityBitBufferLen++; + } + byte[] yParityBitBuffer = new byte[yParityBitBufferLen.intValue()]; + for (int i = 0; i < this.totalBlockTxCount; i += 8) { + int end = i + 8; + if (end < this.totalBlockTxCount.intValue()) { + end = this.totalBlockTxCount.intValue(); + } + int bits = 0; + for (int j = i; j < end; j++) { + if (yParityBits.testBit(j)) { + bits |= 1 << (j - i); + } + } + yParityBitBuffer[i / 8] = (byte) bits; + } + return yParityBitBuffer; + } + + public void decodeYParityBits(byte[] yParityBit) { + Long yParityBitBufferLen = this.totalBlockTxCount / 8; + if (this.totalBlockTxCount % 8 != 0) { + yParityBitBufferLen++; + } + if (yParityBitBufferLen > 10000000L) { + throw new RuntimeException("span batch size limit reached"); + } + + BigInteger yParityBits = BigInteger.ZERO; + for (int i = 0; i < this.totalBlockTxCount; i += 8) { + int end = i + 8; + if (end < this.totalBlockTxCount.intValue()) { + end = this.totalBlockTxCount.intValue(); + } + byte bits = yParityBit[i / 8]; + for (int j = i; j < end; j++) { + int bit = (bits >> (j - i)) & 1; + if (bit != 0) { + yParityBits = yParityBits.setBit(j); + } + } + } + this.yParityBits = yParityBits; + } + + public byte[] encodeTxSigsRS() { + ByteBuffer result = ByteBuffer.allocate(64 * this.totalBlockTxCount.intValue()); + for (SpanBatchSignature signature : txSigs) { + byte[] rBytes = Numeric.toBytesPadded(signature.r(), 32); + result.put(rBytes); + byte[] sBytes = Numeric.toBytesPadded(signature.s(), 32); + result.put(sBytes); + } + return result.array(); + } + + public void decodeTxSigsRS(byte[] txSigsBuffer) { + List txSigs = new ArrayList<>(); + for (int i = 0; i < this.totalBlockTxCount.intValue(); i++) { + byte[] r = new byte[32]; + System.arraycopy(txSigsBuffer, i * 64, r, 0, 32); + byte[] s = new byte[32]; + System.arraycopy(txSigsBuffer, 32 + i * 64, s, 0, 32); + BigInteger rInt = Numeric.toBigInt(r); + BigInteger sInt = Numeric.toBigInt(s); + txSigs.add(new SpanBatchSignature(BigInteger.ZERO, rInt, sInt)); + } + this.txSigs = txSigs; + } + + public Bytes encodeTxNonces() { + ByteBuf buffer = Unpooled.buffer(10); + for (BigInteger txNonce : txNonces) { + ByteBufExtKt.writeUvarint(buffer, txNonce.longValue()); + } + return Bytes.wrap(ByteBufUtil.getBytes(buffer)); + } + + public void decodeTxNonces(Bytes bytes) { + List txNonces = new ArrayList<>(); + ByteBuf buffer = Unpooled.wrappedBuffer(bytes.toArrayUnsafe()); + for (int i = 0; i < this.totalBlockTxCount.intValue(); i++) { + BigInteger txNonce = BigInteger.valueOf(ByteBufExtKt.readUvarint(buffer)); + txNonces.add(txNonce); + } + this.txNonces = txNonces; + } + + public Bytes encodeTxGases() { + ByteBuf buffer = Unpooled.buffer(10); + for (BigInteger txGas : txGases) { + ByteBufExtKt.writeUvarint(buffer, txGas.longValue()); + } + return Bytes.wrap(ByteBufUtil.getBytes(buffer)); + } + + public void decodeTxGases(Bytes bytes) { + List txGases = new ArrayList<>(); + ByteBuf buffer = Unpooled.wrappedBuffer(bytes.toArrayUnsafe()); + for (int i = 0; i < this.totalBlockTxCount.intValue(); i++) { + BigInteger txNonce = BigInteger.valueOf(ByteBufExtKt.readUvarint(buffer)); + txGases.add(txNonce); + } + this.txGases = txGases; + } + + public byte[] encodeTxTos() { + ByteBuffer result = ByteBuffer.allocate(20 * this.totalBlockTxCount.intValue()); + for (String txTo : txTos) { + byte[] addressBytes = Numeric.hexStringToByteArray(txTo.substring(2)); + result.put(addressBytes); + } + return result.array(); + } + + public void decodeTxTos(byte[] txTosBuffer) { + List txTos = new ArrayList<>(); + for (int i = 0; i < this.totalBlockTxCount.intValue(); i++) { + byte[] addBytes = new byte[20]; + System.arraycopy(txTosBuffer, i * 20, addBytes, 0, 20); + String address = Keys.toChecksumAddress(Numeric.toHexStringNoPrefix(addBytes)) + .toLowerCase(); + txTos.add(address); + } + this.txTos = txTos; + } + + // No Test + public void recoverV(BigInteger chainId) { + if (this.txTypes.size() != this.txSigs.size()) { + throw new RuntimeException("tx type length and tx sigs length mismatch"); + } + for (int i = 0; i < this.txTypes.size(); i++) { + BigInteger bit = this.yParityBits.testBit(i) ? BigInteger.ONE : BigInteger.ZERO; + BigInteger v; + int type = this.txTypes.get(i); + switch (type) { + case 0: + v = chainId.multiply(new BigInteger("2")) + .add(BigInteger.valueOf(35)) + .add(bit); + break; + case 1, 2: + v = bit; + break; + default: + throw new RuntimeException("invalid tx type:" + this.txTypes.get(i)); + } + SpanBatchSignature old = this.txSigs.get(i); + SpanBatchSignature newTxSig = new SpanBatchSignature(v, old.r(), old.s()); + this.txSigs.set(i, newTxSig); + } + } + + public static SpanBatchTxs newSpanBatchTxs(List txs, int chainId) { + Long totalBlockTxCount = Long.valueOf(txs.size()); + BigInteger contractCreationBits = BigInteger.ZERO; + BigInteger yParityBits = BigInteger.ZERO; + List txSigs = new ArrayList<>(); + List txTos = new ArrayList<>(); + List txNonces = new ArrayList<>(); + List txGases = new ArrayList<>(); + List txDatas = new ArrayList<>(); + List txTypes = new ArrayList<>(); + for (int idx = 0; idx < totalBlockTxCount.intValue(); idx++) { + String tx = txs.get(idx); + SignedRawTransaction rawTransaction = (SignedRawTransaction) TransactionDecoder.decode(tx); + ITransaction transaction = rawTransaction.getTransaction(); + if (EIP1559.equals(rawTransaction.getType())) { + Transaction1559 transaction1559 = (Transaction1559) transaction; + if (transaction1559.getChainId() != chainId) { + throw new RuntimeException("chainId mismatch. tx has chain ID:" + transaction1559.getChainId() + + ", expected:" + chainId + ", but expected chain ID:" + chainId); + } + } + if (EIP2930.equals(rawTransaction.getType())) { + Transaction2930 transaction2930 = (Transaction2930) transaction; + if (transaction2930.getChainId() != chainId) { + throw new RuntimeException("chainId mismatch. tx has chain ID:" + transaction2930.getChainId() + + ", expected:" + chainId + ", but expected chain ID:" + chainId); + } + } + + Sign.SignatureData signatureData = rawTransaction.getSignatureData(); + BigInteger v = new BigInteger(1, signatureData.getV()); + BigInteger r = new BigInteger(1, signatureData.getR()); + BigInteger s = new BigInteger(1, signatureData.getS()); + + SpanBatchSignature txSig = new SpanBatchSignature(v, r, s); + txSigs.add(txSig); + BigInteger contractCreationBit = BigInteger.ONE; + if (transaction.getTo() != null) { + txTos.add(transaction.getTo()); + } else { + contractCreationBit = contractCreationBit.setBit(idx); + } + BigInteger yParityBit = convertVToYParity(txSig.v(), transaction.getType()); + if (yParityBit.testBit(idx)) { + yParityBit = yParityBit.setBit(idx); + } + txNonces.add(transaction.getNonce()); + txGases.add(transaction.getGasLimit()); + SpanBatchTx stx = SpanBatchTx.newSpanBatchTx(transaction); + byte[] stxByte = stx.marshalBinary(); + txDatas.add(Numeric.toHexString(stxByte)); + if (transaction.getType() == null || transaction.getType().getRlpType() == null) { + txTypes.add(0); + } else { + String hex = Integer.toHexString(transaction.getType().getRlpType() & 0xFF); + txTypes.add(Integer.parseInt(hex, 10)); + } + } + return new SpanBatchTxs( + totalBlockTxCount, + contractCreationBits, + yParityBits, + txSigs, + txNonces, + txGases, + txTos, + txDatas, + txTypes); + } + + public static BigInteger convertVToYParity(BigInteger v, TransactionType txType) { + if (EIP1559 == txType || EIP2930 == txType) { + return v; + } + if (LEGACY == txType) { + int res = (v.intValue() - 35) & 1; + return BigInteger.valueOf(res); + } + return null; + } + + public Long getTotalBlockTxCount() { + return totalBlockTxCount; + } + + public void setTotalBlockTxCount(Long totalBlockTxCount) { + this.totalBlockTxCount = totalBlockTxCount; + } + + public BigInteger getContractCreationBits() { + return contractCreationBits; + } + + public void setContractCreationBits(BigInteger contractCreationBits) { + this.contractCreationBits = contractCreationBits; + } + + public BigInteger getyParityBits() { + return yParityBits; + } + + public void setyParityBits(BigInteger yParityBits) { + this.yParityBits = yParityBits; + } + + public List getTxSigs() { + return txSigs; + } + + public void setTxSigs(List txSigs) { + this.txSigs = txSigs; + } + + public List getTxNonces() { + return txNonces; + } + + public void setTxNonces(List txNonces) { + this.txNonces = txNonces; + } + + public List getTxGases() { + return txGases; + } + + public void setTxGases(List txGases) { + this.txGases = txGases; + } + + public List getTxTos() { + return txTos; + } + + public void setTxTos(List txTos) { + this.txTos = txTos; + } + + public List getTxDatas() { + return txDatas; + } + + public void setTxDatas(List txDatas) { + this.txDatas = txDatas; + } + + public List getTxTypes() { + return txTypes; + } + + public void setTxTypes(List txTypes) { + this.txTypes = txTypes; + } +} diff --git a/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/BatchTest.java b/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/BatchTest.java new file mode 100644 index 00000000..f5c00c14 --- /dev/null +++ b/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/BatchTest.java @@ -0,0 +1,156 @@ +package io.optimism.utilities.derive.stages; + +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import org.apache.tuweni.units.bigints.UInt64; +import org.web3j.crypto.Credentials; +import org.web3j.crypto.ECKeyPair; +import org.web3j.crypto.Keys; +import org.web3j.crypto.RawTransaction; +import org.web3j.crypto.TransactionEncoder; +import org.web3j.crypto.transaction.type.LegacyTransaction; +import org.web3j.utils.Numeric; + +public class BatchTest { + + public static RawSpanBatch randomRawSpanBatch(int chainId) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + Random random = new Random(); + BigInteger originBits = BigInteger.ZERO; + long blockCountLong = 1 + (random.nextInt() & 0xFFL); + BigInteger blockCount = BigInteger.valueOf(blockCountLong); + List blockTxCounts = new ArrayList<>(); + BigInteger totalBlockTxCounts = BigInteger.ZERO; + for (int i = 0; i < blockCount.intValue(); i++) { + BigInteger blockTxCount = BigInteger.valueOf(random.nextInt(16)); + blockTxCounts.add(blockTxCount); + totalBlockTxCounts = totalBlockTxCounts.add(blockTxCount); + } + ECKeyPair ecKeyPair = Keys.createEcKeyPair(); + Credentials credentials = Credentials.create(ecKeyPair); + List txs = new ArrayList<>(); + for (int i = 0; i < totalBlockTxCounts.intValue(); i++) { + String tx = randomTransaction(credentials, chainId, UInt64.random().toBigInteger()); + txs.add(tx); + } + SpanBatchTxs spanBatchTxs = SpanBatchTxs.newSpanBatchTxs(txs, chainId); + + SpanBatchPrefix spanBatchPrefix = new SpanBatchPrefix( + UInt64.random().toBigInteger(), + UInt64.random().toBigInteger(), + Numeric.toHexString(generateRandomData(20)), + Numeric.toHexString(generateRandomData(20))); + SpanBatchPayload spanBatchPayload = new SpanBatchPayload(blockCount, originBits, blockTxCounts, spanBatchTxs); + + return new RawSpanBatch(spanBatchPrefix, spanBatchPayload); + } + + public static SingularBatch randomSingularBatch(int txCount, int chainId) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + ECKeyPair ecKeyPair = Keys.createEcKeyPair(); + Credentials credentials = Credentials.create(ecKeyPair); + Random rng = new Random(); + BigInteger maxLimit = new BigInteger("300000000000"); + BigInteger randomBigInteger = new BigInteger(maxLimit.bitLength(), rng); + while (randomBigInteger.compareTo(maxLimit) >= 0) { + randomBigInteger = new BigInteger(maxLimit.bitLength(), rng); + } + List txsEncoded = new ArrayList<>(); + for (int i = 0; i < txCount; i++) { + String txEncode = randomTransaction(credentials, chainId, randomBigInteger); + txsEncoded.add(txEncode); + } + return new SingularBatch( + RandomUtils.randomHex(), + UInt64.random().toBigInteger(), + RandomUtils.randomHex(), + UInt64.random().toBigInteger(), + txsEncoded); + } + + public static String randomTransaction(Credentials credentials, int chainId, BigInteger baseFee) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + List list = Arrays.asList(0, 1, 2); + int index = new Random().nextInt(list.size()); + return switch (list.get(index)) { + case 0 -> randomLegacyTx(credentials); + case 1 -> randomAccessListTx(credentials, chainId); + case 2 -> randomDynamicFeeTxWithBaseFee(credentials, chainId, baseFee); + default -> throw new RuntimeException("invalid tx type"); + }; + } + + public static String randomLegacyTx(Credentials credentials) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + LegacyTransaction lt = buildTransaction(); + RawTransaction rawTransaction = RawTransaction.createTransaction( + lt.getNonce(), lt.getGasPrice(), lt.getGasLimit(), lt.getTo(), lt.getValue(), lt.getData()); + TransactionEncoder.signMessage(rawTransaction, credentials); + byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); + return Numeric.toHexString(signedMessage); + } + + public static String randomAccessListTx(Credentials credentials, int chainId) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + LegacyTransaction lt = buildTransaction(); + // 2930 + RawTransaction rawTransaction = RawTransaction.createTransaction( + chainId, + lt.getNonce(), + lt.getGasPrice(), + lt.getGasLimit(), + lt.getTo(), + lt.getValue(), + lt.getData(), + new ArrayList<>()); + TransactionEncoder.signMessage(rawTransaction, credentials); + byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); + return Numeric.toHexString(signedMessage); + } + + public static String randomDynamicFeeTxWithBaseFee(Credentials credentials, int chainId, BigInteger baseFee) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + LegacyTransaction lt = buildTransaction(); + Random random = new Random(); + BigInteger tip = BigInteger.valueOf(random.nextInt(10)).multiply(BigInteger.TEN.pow(9)); + BigInteger fee = baseFee.add(tip); + // 1559 + RawTransaction rawTransaction = RawTransaction.createTransaction( + chainId, lt.getNonce(), lt.getGasLimit(), lt.getTo(), lt.getValue(), lt.getData(), tip, fee); + TransactionEncoder.signMessage(rawTransaction, credentials); + byte[] signedMessage = TransactionEncoder.signMessage(rawTransaction, credentials); + return Numeric.toHexString(signedMessage); + } + + public static LegacyTransaction buildTransaction() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + Random random = new Random(); + BigInteger nonce = BigInteger.valueOf(random.nextInt(10000)); + BigInteger gasPrice = BigInteger.valueOf(random.nextInt(10000)); + BigInteger gas = BigInteger.valueOf(random.nextInt(1999999)).add(new BigInteger("21000")); + String to = randomAddress(); + BigInteger value = BigInteger.valueOf(random.nextInt(10)).multiply(BigInteger.TEN.pow(18)); + byte[] data = generateRandomData(10); + return new LegacyTransaction(nonce, gasPrice, gas, to, value, Numeric.toHexString(data)); + } + + public static String randomAddress() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + ECKeyPair ecKeyPair = Keys.createEcKeyPair(); + Credentials credentials = Credentials.create(ecKeyPair); + return credentials.getAddress(); + } + + public static byte[] generateRandomData(int size) { + Random rng = new Random(); + byte[] out = new byte[size]; + rng.nextBytes(out); + return out; + } +} diff --git a/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/RandomUtils.java b/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/RandomUtils.java new file mode 100644 index 00000000..d33e61bd --- /dev/null +++ b/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/RandomUtils.java @@ -0,0 +1,31 @@ +package io.optimism.utilities.derive.stages; + +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import org.web3j.utils.Numeric; + +public class RandomUtils { + + private static byte[] computeHash(byte[] input) { + try { + MessageDigest digest = MessageDigest.getInstance("SHA-256"); + return digest.digest(input); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + return null; + } + } + + public static String randomHex() { + // Create a SecureRandom object + SecureRandom rng = new SecureRandom(); + // Generate a random byte array + byte[] randomBytes = new byte[32]; + rng.nextBytes(randomBytes); + // Compute the hash of the random byte array + byte[] hash = computeHash(randomBytes); + // Convert the byte array to a hexadecimal string + return Numeric.toHexString(hash); + } +} diff --git a/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/SingularBatchTest.java b/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/SingularBatchTest.java new file mode 100644 index 00000000..8e10f7d5 --- /dev/null +++ b/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/SingularBatchTest.java @@ -0,0 +1,26 @@ +package io.optimism.utilities.derive.stages; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.Random; +import org.junit.jupiter.api.Test; + +public class SingularBatchTest { + + @Test + public void TestSingularBatchForBatchInterface() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + Random random = new Random(); + int txCount = 1 + random.nextInt(8); + int chainId = random.nextInt(1000); + + SingularBatch singularBatch = BatchTest.randomSingularBatch(txCount, chainId); + + assertEquals(singularBatch.getBatchType(), BatchType.SINGULAR_BATCH_TYPE.getCode()); + assertEquals(singularBatch.getTimestamp(), singularBatch.timestamp()); + assertEquals(singularBatch.epochNum(), singularBatch.getEpochNum()); + } +} diff --git a/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/SpanBatchTxsTest.java b/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/SpanBatchTxsTest.java new file mode 100644 index 00000000..172f7bf1 --- /dev/null +++ b/hildr-utilities/src/test/java/io/optimism/utilities/derive/stages/SpanBatchTxsTest.java @@ -0,0 +1,183 @@ +package io.optimism.utilities.derive.stages; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.math.BigInteger; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.util.List; +import java.util.Random; +import org.apache.tuweni.bytes.Bytes; +import org.junit.jupiter.api.Test; + +public class SpanBatchTxsTest { + + @Test + void TestSpanBatchTxsContractCreationBits() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + Random random = new Random(); + int chainId = random.nextInt(1000); + RawSpanBatch rawSpanBatch = BatchTest.randomRawSpanBatch(chainId); + BigInteger contractCreationBits = rawSpanBatch.spanbatchPayload().txs().getContractCreationBits(); + Long totalBlockTxCount = rawSpanBatch.spanbatchPayload().txs().getTotalBlockTxCount(); + + SpanBatchTxs sbt = new SpanBatchTxs(); + sbt.setContractCreationBits(contractCreationBits); + sbt.setTotalBlockTxCount(totalBlockTxCount); + + byte[] contractCreationBitsBuffer = sbt.encodeContractCreationBits(); + + Long contractCreationBitBufferLen = totalBlockTxCount / 8; + if (totalBlockTxCount % 8 != 0) { + contractCreationBitBufferLen++; + } + assertEquals(contractCreationBitBufferLen, contractCreationBitsBuffer.length); + + sbt.decodeContractCreationBits(contractCreationBitsBuffer); + + assertEquals(contractCreationBits, sbt.getContractCreationBits()); + } + + @Test + public void TestSpanBatchTxsContractCreationCount() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + Random random = new Random(); + int chainId = random.nextInt(1000); + RawSpanBatch rawSpanBatch = BatchTest.randomRawSpanBatch(chainId); + BigInteger contractCreationBits = rawSpanBatch.spanbatchPayload().txs().getContractCreationBits(); + Long contractCreationCount = rawSpanBatch.spanbatchPayload().txs().contractCreationCount(); + Long totalBlockTxCount = rawSpanBatch.spanbatchPayload().txs().getTotalBlockTxCount(); + + SpanBatchTxs sbt = new SpanBatchTxs(); + sbt.setContractCreationBits(contractCreationBits); + sbt.setTotalBlockTxCount(totalBlockTxCount); + + byte[] contractCreationBitsBuffer = sbt.encodeContractCreationBits(); + sbt.setContractCreationBits(null); + sbt.decodeContractCreationBits(contractCreationBitsBuffer); + + Long contractCreationCount2 = sbt.contractCreationCount(); + assertEquals(contractCreationCount, contractCreationCount2); + } + + @Test + public void TestSpanBatchTxsYParityBits() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + Random random = new Random(); + int chainId = random.nextInt(1000); + + RawSpanBatch rawSpanBatch = BatchTest.randomRawSpanBatch(chainId); + BigInteger yParityBits = rawSpanBatch.spanbatchPayload().txs().getyParityBits(); + Long totalBlockTxCount = rawSpanBatch.spanbatchPayload().txs().getTotalBlockTxCount(); + + SpanBatchTxs sbt = new SpanBatchTxs(); + sbt.setyParityBits(yParityBits); + sbt.setTotalBlockTxCount(totalBlockTxCount); + + byte[] yParityBitsBuffer = sbt.encodeYParityBits(); + Long yParityBitBufferLen = totalBlockTxCount / 8; + if (totalBlockTxCount % 8 != 0) { + yParityBitBufferLen++; + } + assertEquals(yParityBitBufferLen, yParityBitsBuffer.length); + + sbt.setyParityBits(null); + sbt.decodeYParityBits(yParityBitsBuffer); + assertEquals(yParityBits, sbt.getyParityBits()); + } + + @Test + public void TestSpanBatchTxsTxSigs() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + Random random = new Random(); + int chainId = random.nextInt(1000); + + RawSpanBatch rawSpanBatch = BatchTest.randomRawSpanBatch(chainId); + List txSigs = rawSpanBatch.spanbatchPayload().txs().getTxSigs(); + Long totalBlockTxCount = rawSpanBatch.spanbatchPayload().txs().getTotalBlockTxCount(); + + SpanBatchTxs sbt = new SpanBatchTxs(); + sbt.setTotalBlockTxCount(totalBlockTxCount); + + sbt.setTxSigs(txSigs); + + byte[] txSigsBuffer = sbt.encodeTxSigsRS(); + + assertEquals(totalBlockTxCount * 64, txSigsBuffer.length); + + sbt.setTxSigs(null); + + sbt.decodeTxSigsRS(txSigsBuffer); + + for (int i = 0; i < totalBlockTxCount; i++) { + assertEquals(txSigs.get(i).r(), sbt.getTxSigs().get(i).r()); + assertEquals(txSigs.get(i).s(), sbt.getTxSigs().get(i).s()); + } + } + + @Test + public void TestSpanBatchTxsTxNonces() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + Random random = new Random(); + int chainId = random.nextInt(1000); + + RawSpanBatch rawSpanBatch = BatchTest.randomRawSpanBatch(chainId); + List txNonces = rawSpanBatch.spanbatchPayload().txs().getTxNonces(); + Long totalBlockTxCount = rawSpanBatch.spanbatchPayload().txs().getTotalBlockTxCount(); + + SpanBatchTxs sbt = new SpanBatchTxs(); + sbt.setTotalBlockTxCount(totalBlockTxCount); + sbt.setTxNonces(txNonces); + + Bytes txNoncesBuffer = sbt.encodeTxNonces(); + sbt.setTxNonces(null); + sbt.decodeTxNonces(txNoncesBuffer); + + assertEquals(txNonces, sbt.getTxNonces()); + } + + @Test + public void TestSpanBatchTxsTxGases() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + Random random = new Random(); + int chainId = random.nextInt(1000); + + RawSpanBatch rawSpanBatch = BatchTest.randomRawSpanBatch(chainId); + List txGases = rawSpanBatch.spanbatchPayload().txs().getTxGases(); + Long totalBlockTxCount = rawSpanBatch.spanbatchPayload().txs().getTotalBlockTxCount(); + + SpanBatchTxs sbt = new SpanBatchTxs(); + sbt.setTotalBlockTxCount(totalBlockTxCount); + sbt.setTxGases(txGases); + Bytes txGasesBuffer = sbt.encodeTxGases(); + sbt.setTxGases(null); + sbt.decodeTxGases(txGasesBuffer); + assertEquals(txGases, sbt.getTxGases()); + } + + @Test + public void TestSpanBatchTxsTxTos() + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, NoSuchProviderException { + Random random = new Random(); + int chainId = random.nextInt(1000); + + RawSpanBatch rawSpanBatch = BatchTest.randomRawSpanBatch(chainId); + List txTos = rawSpanBatch.spanbatchPayload().txs().getTxTos(); + Long totalBlockTxCount = rawSpanBatch.spanbatchPayload().txs().getTotalBlockTxCount(); + BigInteger contractCreationBits = rawSpanBatch.spanbatchPayload().txs().getContractCreationBits(); + + SpanBatchTxs sbt = new SpanBatchTxs(); + sbt.setTxTos(txTos); + sbt.setContractCreationBits(contractCreationBits); + sbt.setTotalBlockTxCount(totalBlockTxCount); + + byte[] txTosBuffer = sbt.encodeTxTos(); + assertEquals(totalBlockTxCount * 20, txTosBuffer.length); + + sbt.setTxTos(null); + + sbt.decodeTxTos(txTosBuffer); + assertEquals(txTos, sbt.getTxTos()); + } +}