From e5cd007063e7e9ffb8f78d4b4c411551ff4425ca Mon Sep 17 00:00:00 2001 From: Volodymyr Kravets Date: Fri, 2 Aug 2024 19:17:14 +0300 Subject: [PATCH 1/5] perf: improve mem reading in Memory --- .../rsk/core/types/bytes/BoundaryUtils.java | 42 +++++++++++++ .../java/co/rsk/core/types/bytes/Bytes.java | 11 ---- .../co/rsk/core/types/bytes/BytesSlice.java | 63 +++++++++++-------- .../core/types/bytes/HexPrintableBytes.java | 22 ++++++- .../java/org/ethereum/crypto/HashUtil.java | 8 ++- .../ethereum/crypto/cryptohash/Digest.java | 6 +- .../crypto/cryptohash/DigestEngine.java | 15 +++-- .../main/java/org/ethereum/util/ByteUtil.java | 3 + .../main/java/org/ethereum/vm/CallCreate.java | 8 ++- .../src/main/java/org/ethereum/vm/VM.java | 6 +- .../java/org/ethereum/vm/program/Memory.java | 59 ++++++++++++++++- .../java/org/ethereum/vm/program/Program.java | 16 +++-- .../ethereum/vm/program/ProgramResult.java | 3 +- .../program/invoke/ProgramInvokeFactory.java | 3 +- .../invoke/ProgramInvokeFactoryImpl.java | 9 +-- .../vm/program/invoke/ProgramInvokeImpl.java | 36 ++++++----- .../java/co/rsk/core/BlockHeaderTest.java | 2 +- .../co/rsk/core/bc/BlockExecutorTest.java | 3 +- .../modules/trace/TraceTransformerTest.java | 3 +- .../TestProgramInvokeFactory.java | 6 +- .../ethereum/jsontestsuite/TestRunner.java | 4 +- .../java/org/ethereum/util/HashUtilTest.java | 5 +- 22 files changed, 241 insertions(+), 92 deletions(-) create mode 100644 rskj-core/src/main/java/co/rsk/core/types/bytes/BoundaryUtils.java diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/BoundaryUtils.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/BoundaryUtils.java new file mode 100644 index 00000000000..d2ffe8a7691 --- /dev/null +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/BoundaryUtils.java @@ -0,0 +1,42 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.core.types.bytes; + +public class BoundaryUtils { + + private BoundaryUtils() { /* hidden */ } + + public static void checkArraycopyParams(int srcLength, int srcPos, byte[] dest, int destPos, int length) { + if (length < 0) { + throw new IndexOutOfBoundsException("invalid 'length': " + length); + } + if (srcPos < 0 || srcPos + length > srcLength) { + throw new IndexOutOfBoundsException("invalid 'srcPos' and/or 'length': [" + srcPos + ";" + length + ")"); + } + if (destPos < 0 || destPos + length > dest.length) { + throw new IndexOutOfBoundsException("invalid 'destPos' and/or 'length': [" + destPos + ";" + length + ")"); + } + } + + public static void checkArrayIndexParam(int srcLength, int index) { + if (index < 0 || index >= srcLength) { + throw new IndexOutOfBoundsException("invalid index: " + index); + } + } +} diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/Bytes.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/Bytes.java index cb4c16d17d3..41605488d7e 100644 --- a/rskj-core/src/main/java/co/rsk/core/types/bytes/Bytes.java +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/Bytes.java @@ -18,7 +18,6 @@ package co.rsk.core.types.bytes; -import org.ethereum.util.ByteUtil; import org.ethereum.util.FastByteComparisons; import javax.annotation.Nonnull; @@ -143,16 +142,6 @@ public void arraycopy(int srcPos, byte[] dest, int destPos, int length) { System.arraycopy(byteArray, srcPos, dest, destPos, length); } - @Override - public String toHexString() { - return ByteUtil.toHexString(byteArray); - } - - @Override - public String toHexString(int off, int length) { - return ByteUtil.toHexString(byteArray, off, length); - } - @Nonnull @Override public byte[] asUnsafeByteArray() { diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java index ac3616fd8f0..2e140f65410 100644 --- a/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java @@ -128,6 +128,42 @@ default Bytes copyBytes() { default BytesSlice slice(int from, int to) { return new BytesSliceImpl(this, from, to); } + + static boolean equals(BytesSlice a, BytesSlice b) { + if (a == b) { + return true; + } + if (a == null || b == null) { + return false; + } + + int aLen = a.length(); + if (b.length() != aLen) { + return false; + } + + for (int i = 0; i < aLen; i++) { + if (a.byteAt(i) != b.byteAt(i)) { + return false; + } + } + + return true; + } + + static int hashCode(BytesSlice bytesSlice) { + if (bytesSlice == null) { + return 0; + } + + int result = 1; + int len = bytesSlice.length(); + for (int i = 0; i < len; i++) { + result = 31 * result + bytesSlice.byteAt(i); + } + + return result; + } } class BytesSliceImpl implements BytesSlice { @@ -161,39 +197,16 @@ public int length() { @Override public byte byteAt(int index) { - if (index < 0 || index >= length()) { - throw new IndexOutOfBoundsException("invalid index: " + index); - } + BoundaryUtils.checkArrayIndexParam(length(), index); return originBytes.byteAt(from + index); } @Override public void arraycopy(int srcPos, byte[] dest, int destPos, int length) { - if (length < 0) { - throw new IndexOutOfBoundsException("invalid 'length': " + length); - } - if (srcPos < 0 || srcPos + length > length()) { - throw new IndexOutOfBoundsException("invalid 'srcPos' and/or 'length': [" + srcPos + ";" + length + ")"); - } - if (destPos < 0 || destPos + length > dest.length) { - throw new IndexOutOfBoundsException("invalid 'destPos' and/or 'length': [" + destPos + ";" + length + ")"); - } + BoundaryUtils.checkArraycopyParams(length(), srcPos, dest, destPos, length); originBytes.arraycopy(this.from + srcPos, dest, destPos, length); } - @Override - public String toHexString(int off, int length) { - if (off < 0 || length < 0 || off + length > length()) { - throw new IndexOutOfBoundsException("invalid 'off' and/or 'length': " + off + "; " + length); - } - return originBytes.toHexString(from + off, length); - } - - @Override - public String toHexString() { - return toHexString(0, length()); - } - @Override public String toString() { return toPrintableString(); diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java index 3f91f918eda..d472f9f9240 100644 --- a/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java @@ -62,9 +62,27 @@ default String toJsonHexFormattedString() { return toPrintableString(SIMPLE_JSON_HEX_FORMATTER); } - String toHexString(int off, int length); + default String toHexString(int off, int length) { + return toHexStringV2(off, length); + } + + default String toHexString() { + return toHexString(0, length()); + } + + default String toHexStringV2(int off, int length) { + if (off < 0 || length < 0 || off + length > length()) { + throw new IndexOutOfBoundsException("invalid 'off' and/or 'length': " + off + "; " + length); + } - String toHexString(); + StringBuilder sb = new StringBuilder(length * 2); + for (int i = off; i < off + length; i++) { + byte b = byteAt(i); + sb.append(Character.forDigit((b >> 4) & 0xF, 16)); + sb.append(Character.forDigit((b & 0xF), 16)); + } + return sb.toString(); + } } class PrintableBytesHexFormatter implements PrintableBytes.Formatter { diff --git a/rskj-core/src/main/java/org/ethereum/crypto/HashUtil.java b/rskj-core/src/main/java/org/ethereum/crypto/HashUtil.java index aad79a2df3c..8350a22cbf5 100644 --- a/rskj-core/src/main/java/org/ethereum/crypto/HashUtil.java +++ b/rskj-core/src/main/java/org/ethereum/crypto/HashUtil.java @@ -20,6 +20,8 @@ package org.ethereum.crypto; import co.rsk.core.RskAddress; +import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.digests.RIPEMD160Digest; import org.ethereum.crypto.cryptohash.Keccak256; @@ -59,6 +61,10 @@ public static byte[] sha256(byte[] input) { } public static byte[] keccak256(byte[] input) { + return keccak256(Bytes.of(input)); + } + + public static byte[] keccak256(BytesSlice input) { Keccak256 digest = new Keccak256(); digest.update(input); return digest.digest(); @@ -125,7 +131,7 @@ public static byte[] calcNewAddr(byte[] addr, byte[] nonce) { * @param salt - salt to make different result addresses * @return new address */ - public static byte[] calcSaltAddr(RskAddress senderAddress, byte[] initCode, byte[] salt) { + public static byte[] calcSaltAddr(RskAddress senderAddress, BytesSlice initCode, byte[] salt) { // 0xff is of length 1 // keccak-256 of the address is of length 32 // Then we add the lengths of the senderAddress and the salt diff --git a/rskj-core/src/main/java/org/ethereum/crypto/cryptohash/Digest.java b/rskj-core/src/main/java/org/ethereum/crypto/cryptohash/Digest.java index cd90ab04fb5..45fdf680e92 100644 --- a/rskj-core/src/main/java/org/ethereum/crypto/cryptohash/Digest.java +++ b/rskj-core/src/main/java/org/ethereum/crypto/cryptohash/Digest.java @@ -21,6 +21,8 @@ package org.ethereum.crypto.cryptohash; +import co.rsk.core.types.bytes.BytesSlice; + /** *

This interface documents the API for a hash function. This * interface somewhat mimics the standard {@code @@ -88,7 +90,7 @@ public interface Digest { * * @param inbuf the data bytes */ - public void update(byte[] inbuf); + public void update(BytesSlice inbuf); /** * Insert some more bytes. @@ -97,7 +99,7 @@ public interface Digest { * @param off the data offset in {@code inbuf} * @param len the data length (in bytes) */ - public void update(byte[] inbuf, int off, int len); + public void update(BytesSlice inbuf, int off, int len); /** * Finalize the current hash computation and return the hash value diff --git a/rskj-core/src/main/java/org/ethereum/crypto/cryptohash/DigestEngine.java b/rskj-core/src/main/java/org/ethereum/crypto/cryptohash/DigestEngine.java index cbbd5ca56d7..21caf1930d4 100644 --- a/rskj-core/src/main/java/org/ethereum/crypto/cryptohash/DigestEngine.java +++ b/rskj-core/src/main/java/org/ethereum/crypto/cryptohash/DigestEngine.java @@ -21,6 +21,9 @@ package org.ethereum.crypto.cryptohash; +import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; + /** *

This class is a template which can be used to implement hash * functions. It takes care of some of the API, and also provides an @@ -141,7 +144,8 @@ public byte[] digest() /** @see org.ethereum.crypto.cryptohash.Digest */ public byte[] digest(byte[] input) { - update(input, 0, input.length); + BytesSlice bytesSlice = Bytes.of(input); + update(bytesSlice, 0, bytesSlice.length()); return digest(); } @@ -181,21 +185,20 @@ public void update(byte input) } /** @see org.ethereum.crypto.cryptohash.Digest */ - public void update(byte[] input) + public void update(BytesSlice input) { - update(input, 0, input.length); + update(input, 0, input.length()); } /** @see org.ethereum.crypto.cryptohash.Digest */ - public void update(byte[] input, int offset, int len) + public void update(BytesSlice input, int offset, int len) { while (len > 0) { int copyLen = blockLen - inputLen; if (copyLen > len) { copyLen = len; } - System.arraycopy(input, offset, inputBuf, inputLen, - copyLen); + input.arraycopy(offset, inputBuf, inputLen, copyLen); offset += copyLen; inputLen += copyLen; len -= copyLen; diff --git a/rskj-core/src/main/java/org/ethereum/util/ByteUtil.java b/rskj-core/src/main/java/org/ethereum/util/ByteUtil.java index ed4f2448868..69bab39773c 100644 --- a/rskj-core/src/main/java/org/ethereum/util/ByteUtil.java +++ b/rskj-core/src/main/java/org/ethereum/util/ByteUtil.java @@ -19,6 +19,8 @@ package org.ethereum.util; +import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; import org.bouncycastle.util.encoders.Hex; import org.ethereum.db.ByteArrayWrapper; @@ -38,6 +40,7 @@ private ByteUtil() { } public static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; + public static final BytesSlice EMPTY_BYTES_SLICE = Bytes.of(EMPTY_BYTE_ARRAY); private static final byte[] ZERO_BYTE_ARRAY = new byte[]{0}; /** diff --git a/rskj-core/src/main/java/org/ethereum/vm/CallCreate.java b/rskj-core/src/main/java/org/ethereum/vm/CallCreate.java index 622ca1e7c3d..eb8fd00ded5 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/CallCreate.java +++ b/rskj-core/src/main/java/org/ethereum/vm/CallCreate.java @@ -20,26 +20,28 @@ package org.ethereum.vm; +import co.rsk.core.types.bytes.BytesSlice; + /** * @author Roman Mandeleil * @since 03.07.2014 */ public class CallCreate { - final byte[] data; + final BytesSlice data; final byte[] destination; final long gasLimit; final byte[] value; - public CallCreate(byte[] data, byte[] destination, long gasLimit, byte[] value) { + public CallCreate(BytesSlice data, byte[] destination, long gasLimit, byte[] value) { this.data = data; this.destination = destination; this.gasLimit = gasLimit; this.value = value; } - public byte[] getData() { + public BytesSlice getData() { return data; } diff --git a/rskj-core/src/main/java/org/ethereum/vm/VM.java b/rskj-core/src/main/java/org/ethereum/vm/VM.java index 47246e2bbc2..54554327862 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/VM.java +++ b/rskj-core/src/main/java/org/ethereum/vm/VM.java @@ -22,6 +22,7 @@ import co.rsk.config.VmConfig; import co.rsk.core.RskAddress; import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; import co.rsk.crypto.Keccak256; import co.rsk.rpc.netty.ExecTimeoutContext; import org.bouncycastle.util.BigIntegers; @@ -628,7 +629,7 @@ protected void doSHA3() { // EXECUTION PHASE DataWord memOffsetData = program.stackPop(); DataWord lengthData = program.stackPop(); - byte[] buffer = program.memoryChunk(memOffsetData.intValue(), lengthData.intValue()); + BytesSlice buffer = program.memorySlice(memOffsetData.intValue(), lengthData.intValue()); byte[] encoded = HashUtil.keccak256(buffer); DataWord word = DataWord.valueOf(encoded); @@ -1192,8 +1193,7 @@ protected void doLOG(){ // Int32 address values guaranteed by previous MAX_MEMORY checks byte[] data = program.memoryChunk(memStart.intValue(), memOffset.intValue()); - LogInfo logInfo = - new LogInfo(address.getLast20Bytes(), topics, data); + LogInfo logInfo = new LogInfo(address.getLast20Bytes(), topics, data); if (isLogEnabled) { hint = logInfo.toString(); diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java b/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java index 32853f6d120..72ed443cb2f 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java @@ -19,6 +19,8 @@ package org.ethereum.vm.program; +import co.rsk.core.types.bytes.BoundaryUtils; +import co.rsk.core.types.bytes.BytesSlice; import org.ethereum.vm.DataWord; import org.ethereum.vm.program.listener.ProgramListener; import org.ethereum.vm.program.listener.ProgramListenerAware; @@ -29,8 +31,7 @@ import static java.lang.Math.ceil; import static java.lang.Math.min; import static java.lang.String.format; -import static org.ethereum.util.ByteUtil.EMPTY_BYTE_ARRAY; -import static org.ethereum.util.ByteUtil.oneByteToHexString; +import static org.ethereum.util.ByteUtil.*; public class Memory implements ProgramListenerAware { @@ -46,6 +47,16 @@ public void setTraceListener(ProgramListener traceListener) { this.traceListener = traceListener; } + public BytesSlice readSlice(int address, int size) { + if (size <= 0) { + return EMPTY_BYTES_SLICE; + } + + extend(address, size); + + return new MemorySlice(address, size); + } + public byte[] read(int address, int size) { if (size <= 0) { return EMPTY_BYTE_ARRAY; @@ -224,4 +235,48 @@ private void addChunks(int num) { chunks.add(new byte[CHUNK_SIZE]); } } + + private final class MemorySlice implements BytesSlice { + private final int address; + private final int size; + + MemorySlice(int address, int size) { + this.address = address; + this.size = size; + } + + @Override + public void arraycopy(int srcPos, byte[] dest, int destPos, int length) { + BoundaryUtils.checkArraycopyParams(length(), srcPos, dest, destPos, length); + + int chunkIndex = (address + srcPos) / CHUNK_SIZE; + int chunkOffset = (address + srcPos) % CHUNK_SIZE; + + int toGrab = length; + int start = destPos; + + while (toGrab > 0) { + int copied = grabMax(chunkIndex, chunkOffset, toGrab, dest, start); + + // read next chunk from the start + ++chunkIndex; + chunkOffset = 0; + + // mark remind + toGrab -= copied; + start += copied; + } + } + + @Override + public int length() { + return size; + } + + @Override + public byte byteAt(int index) { + BoundaryUtils.checkArrayIndexParam(length(), index); + return readByte(address + index); + } + } } diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java index e326c26a5cf..c61266c280a 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Program.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Program.java @@ -22,6 +22,7 @@ import co.rsk.core.Coin; import co.rsk.core.RskAddress; import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; import co.rsk.crypto.Keccak256; import co.rsk.pcc.NativeContract; import co.rsk.peg.Bridge; @@ -386,6 +387,10 @@ public DataWord memoryLoad(DataWord addr) { return memory.readWord(addr.intValue()); } + public BytesSlice memorySlice(int offset, int size) { + return memory.readSlice(offset, size); + } + public byte[] memoryChunk(int offset, int size) { return memory.read(offset, size); } @@ -467,7 +472,7 @@ public void createContract(DataWord value, DataWord memStart, DataWord memSize) @SuppressWarnings("ThrowableResultOfMethodCallIgnored") public void createContract2(DataWord value, DataWord memStart, DataWord memSize, DataWord salt) { RskAddress senderAddress = new RskAddress(getOwnerAddress()); - byte[] programCode = memoryChunk(memStart.intValue(), memSize.intValue()); + BytesSlice programCode = memorySlice(memStart.intValue(), memSize.intValue()); byte[] newAddressBytes = HashUtil.calcSaltAddr(senderAddress, programCode, salt.getData()); byte[] nonce = getStorage().getNonce(senderAddress).toByteArray(); @@ -503,7 +508,7 @@ private void createContract( RskAddress senderAddress, byte[] nonce, DataWord va if (byTestingSuite()) { // This keeps track of the contracts created for a test - getResult().addCallCreate(programCode, EMPTY_BYTE_ARRAY, + getResult().addCallCreate(Bytes.of(programCode), EMPTY_BYTE_ARRAY, gasLimit, value.getNoLeadZeroesData()); } @@ -752,7 +757,7 @@ public void callToAddress(MessageCall msg) { return; } - byte[] data = memoryChunk(msg.getInDataOffs().intValue(), msg.getInDataSize().intValue()); + BytesSlice data = memorySlice(msg.getInDataOffs().intValue(), msg.getInDataSize().intValue()); // FETCH THE SAVED STORAGE RskAddress codeAddress = new RskAddress(msg.getCodeAddress()); @@ -851,7 +856,7 @@ private boolean executeCode( Repository track, byte[] programCode, RskAddress senderAddress, - byte[] data) { + BytesSlice data) { returnDataBuffer = null; // reset return buffer right before the call ProgramResult childResult; @@ -1418,7 +1423,7 @@ public void callToPrecompiledAddress(MessageCall msg, PrecompiledContract contra if (byTestingSuite()) { // This keeps track of the calls created for a test - this.getResult().addCallCreate(data, + this.getResult().addCallCreate(Bytes.of(data), codeAddress.getBytes(), msg.getGas().longValueSafe(), msg.getEndowment().getNoLeadZeroesData()); @@ -1673,6 +1678,7 @@ public StackTooLargeException(String message) { /** * used mostly for testing reasons */ + @VisibleForTesting public byte[] getMemory() { return memory.read(0, memory.size()); } diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/ProgramResult.java b/rskj-core/src/main/java/org/ethereum/vm/program/ProgramResult.java index 370c301a7e2..7cdda0610e3 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/ProgramResult.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/ProgramResult.java @@ -18,6 +18,7 @@ */ package org.ethereum.vm.program; +import co.rsk.core.types.bytes.BytesSlice; import org.ethereum.core.SignatureCache; import org.ethereum.core.Transaction; import org.ethereum.vm.CallCreate; @@ -194,7 +195,7 @@ public List getCallCreateList() { return callCreateList; } - public void addCallCreate(byte[] data, byte[] destination, long gasLimit, byte[] value) { + public void addCallCreate(BytesSlice data, byte[] destination, long gasLimit, byte[] value) { getCallCreateList().add(new CallCreate(data, destination, gasLimit, value)); } diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactory.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactory.java index 63befefe62e..d59cbb28c84 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactory.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactory.java @@ -19,6 +19,7 @@ package org.ethereum.vm.program.invoke; import co.rsk.core.Coin; +import co.rsk.core.types.bytes.BytesSlice; import org.ethereum.core.Block; import org.ethereum.core.Repository; import org.ethereum.core.SignatureCache; @@ -39,7 +40,7 @@ ProgramInvoke createProgramInvoke(Transaction tx, int txindex, Block block, ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, DataWord callerAddress, DataWord inValue, long inGas, - Coin balanceInt, byte[] dataIn, + Coin balanceInt, BytesSlice dataIn, Repository repository, BlockStore blockStore, boolean isStaticCall, boolean byTestingSuite); } diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java index 258c3ecf416..7500d681bb7 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeFactoryImpl.java @@ -21,6 +21,7 @@ import co.rsk.core.Coin; import co.rsk.core.RskAddress; import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; import org.ethereum.core.Block; import org.ethereum.core.Repository; import org.ethereum.core.SignatureCache; @@ -138,7 +139,7 @@ public ProgramInvoke createProgramInvoke(Transaction tx, int txindex, Block bloc byte[] minGasPrice = minimumGasPrice != null ? minimumGasPrice.getBytes() : ByteUtil.EMPTY_BYTE_ARRAY; - return new ProgramInvokeImpl(addr.getBytes(), origin, caller, balance.getBytes(), txGasPrice.getBytes(), gas, callValue.getBytes(), data, + return new ProgramInvokeImpl(addr.getBytes(), origin, caller, balance.getBytes(), txGasPrice.getBytes(), gas, callValue.getBytes(), Bytes.of(data), lastHash, coinbase, timestamp, number, txindex,difficulty, gaslimit, minGasPrice, repository, blockStore); } @@ -150,7 +151,7 @@ public ProgramInvoke createProgramInvoke(Transaction tx, int txindex, Block bloc public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, DataWord callerAddress, DataWord inValue, long inGas, - Coin balanceInt, byte[] dataIn, + Coin balanceInt, BytesSlice dataIn, Repository repository, BlockStore blockStore, boolean isStaticCall, boolean byTestingSuite) { @@ -163,7 +164,7 @@ public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, Da long agas = inGas; DataWord callValue = inValue; - byte[] data = dataIn; + BytesSlice data = dataIn; DataWord lastHash = program.getPrevHash(); DataWord coinbase = program.getCoinbase(); DataWord timestamp = program.getTimestamp(); @@ -198,7 +199,7 @@ public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, Da txGasPrice.longValue(), agas, Bytes.of(callValue.getNoLeadZeroesData()), - data == null ? "" : ByteUtil.toHexString(data), + data == null ? "" : data.toHexString(), Bytes.of(lastHash.getData()), Bytes.of(coinbase.getLast20Bytes()), timestamp.longValue(), diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java index 4127dfa833b..41a565a8f71 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java @@ -19,6 +19,8 @@ package org.ethereum.vm.program.invoke; +import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; import org.ethereum.core.Repository; import org.ethereum.db.BlockStore; import org.ethereum.vm.DataWord; @@ -47,7 +49,7 @@ public class ProgramInvokeImpl implements ProgramInvoke { private final DataWord callValue; private long gas; - byte[] msgData; + BytesSlice msgData; /** * BLOCK env ** @@ -73,7 +75,7 @@ public class ProgramInvokeImpl implements ProgramInvoke { public ProgramInvokeImpl(DataWord address, DataWord origin, DataWord caller, DataWord balance, DataWord txGasPrice, long gas, - DataWord callValue, byte[] msgData, + DataWord callValue, BytesSlice msgData, DataWord lastHash, DataWord coinbase, DataWord timestamp, DataWord number, DataWord transactionIndex, DataWord difficulty, DataWord gaslimit, DataWord minimumGasPrice, Repository repository, int callDeep, BlockStore blockStore, @@ -114,7 +116,7 @@ public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] ba byte[] gaslimit, byte[] minimumGasPrice, Repository repository, BlockStore blockStore, boolean byTestingSuite) { - this(address, origin, caller, balance, txGasPrice, gas, callValue, msgData, lastHash, coinbase, + this(address, origin, caller, balance, txGasPrice, gas, callValue, Bytes.of(msgData), lastHash, coinbase, timestamp, number, transactionIndex, difficulty, gaslimit, minimumGasPrice, repository, blockStore); this.byTestingSuite = byTestingSuite; @@ -122,7 +124,7 @@ public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] ba public ProgramInvokeImpl(byte[] address, byte[] origin, byte[] caller, byte[] balance, - byte[] txGasPrice, byte[] gas, byte[] callValue, byte[] msgData, + byte[] txGasPrice, byte[] gas, byte[] callValue, BytesSlice msgData, byte[] lastHash, byte[] coinbase, long timestamp, long number, int transactionIndex, byte[] difficulty, byte[] gaslimit, byte[] minimumGasPrice, Repository repository, BlockStore blockStore) { @@ -209,16 +211,16 @@ public DataWord getDataValue(DataWord indexData) { int index = tempIndex.intValue(); // possible overflow is caught below int size = 32; // maximum datavalue size - if (msgData == null || index >= msgData.length + if (msgData == null || index >= msgData.length() || tempIndex.compareTo(maxMsgData) == 1) { return DataWord.ZERO; } - if (index + size > msgData.length) { - size = msgData.length - index; + if (index + size > msgData.length()) { + size = msgData.length() - index; } byte[] data = new byte[32]; - System.arraycopy(msgData, index, data, 0, size); + msgData.arraycopy(index, data, 0, size); return DataWord.valueOf(data); } @@ -226,10 +228,10 @@ public DataWord getDataValue(DataWord indexData) { @Override public DataWord getDataSize() { - if (msgData == null || msgData.length == 0) { + if (msgData == null || msgData.length() == 0) { return DataWord.ZERO; } - int size = msgData.length; + int size = msgData.length(); return DataWord.valueOf(size); } @@ -245,14 +247,14 @@ public byte[] getDataCopy(DataWord offsetData, DataWord lengthData) { if (msgData == null) { return data; } - if (offset > msgData.length) { + if (offset > msgData.length()) { return data; } - if (offset + length > msgData.length) { - length = msgData.length - offset; + if (offset + length > msgData.length()) { + length = msgData.length() - offset; } - System.arraycopy(msgData, offset, data, 0, length); + msgData.arraycopy(offset, data, 0, length); return data; } @@ -388,7 +390,7 @@ public boolean equals(Object o) { if (minimumGasPrice != null ? !minimumGasPrice.equals(that.minimumGasPrice) : that.minimumGasPrice != null) { return false; } - if (!Arrays.equals(msgData, that.msgData)) { + if (!BytesSlice.equals(msgData, that.msgData)) { return false; } if (number != null ? !number.equals(that.number) : that.number != null) { @@ -416,7 +418,7 @@ public boolean equals(Object o) { @Override public int hashCode() { int result = Objects.hash(address, origin, caller, balance, txGasPrice, callValue, gas, prevHash, coinbase, timestamp, number, difficulty, gaslimit, minimumGasPrice, storage, repository, byTransaction, byTestingSuite); - result = 31 * result + Arrays.hashCode(msgData); + result = 31 * result + BytesSlice.hashCode(msgData); return result; } @@ -430,7 +432,7 @@ public String toString() { ", gas=" + gas + ", txGasPrice=" + txGasPrice + ", callValue=" + callValue + - ", msgData=" + Arrays.toString(msgData) + + ", msgData=" + msgData + ", prevHash=" + prevHash + ", coinbase=" + coinbase + ", timestamp=" + timestamp + diff --git a/rskj-core/src/test/java/co/rsk/core/BlockHeaderTest.java b/rskj-core/src/test/java/co/rsk/core/BlockHeaderTest.java index 05c7d41ca3c..2a478392b42 100644 --- a/rskj-core/src/test/java/co/rsk/core/BlockHeaderTest.java +++ b/rskj-core/src/test/java/co/rsk/core/BlockHeaderTest.java @@ -359,7 +359,7 @@ void getHashShouldReuseCalculatedValue() { header.getHash(); } - hashUtilMocked.verify(() -> HashUtil.keccak256(ArgumentMatchers.any())); + hashUtilMocked.verify(() -> HashUtil.keccak256((byte[]) ArgumentMatchers.any())); } } diff --git a/rskj-core/src/test/java/co/rsk/core/bc/BlockExecutorTest.java b/rskj-core/src/test/java/co/rsk/core/bc/BlockExecutorTest.java index 16ae34501ea..fcb213c0b94 100644 --- a/rskj-core/src/test/java/co/rsk/core/bc/BlockExecutorTest.java +++ b/rskj-core/src/test/java/co/rsk/core/bc/BlockExecutorTest.java @@ -24,6 +24,7 @@ import co.rsk.core.Coin; import co.rsk.core.RskAddress; import co.rsk.core.TransactionExecutorFactory; +import co.rsk.core.types.bytes.Bytes; import co.rsk.db.*; import co.rsk.peg.BridgeSupportFactory; import co.rsk.peg.BtcBlockStoreWithCache.Factory; @@ -1443,7 +1444,7 @@ private static Transaction createStrangeTransaction( private static byte[] sha3(byte[] input) { Keccak256 digest = new Keccak256(); - digest.update(input); + digest.update(Bytes.of(input)); return digest.digest(); } diff --git a/rskj-core/src/test/java/co/rsk/rpc/modules/trace/TraceTransformerTest.java b/rskj-core/src/test/java/co/rsk/rpc/modules/trace/TraceTransformerTest.java index da578fc7499..26e3612a050 100644 --- a/rskj-core/src/test/java/co/rsk/rpc/modules/trace/TraceTransformerTest.java +++ b/rskj-core/src/test/java/co/rsk/rpc/modules/trace/TraceTransformerTest.java @@ -18,6 +18,7 @@ package co.rsk.rpc.modules.trace; +import co.rsk.core.types.bytes.Bytes; import org.ethereum.vm.DataWord; import org.ethereum.vm.program.invoke.ProgramInvoke; import org.ethereum.vm.program.invoke.ProgramInvokeImpl; @@ -42,7 +43,7 @@ void getActionFromInvokeData() { null, gas, callValue, - data, + Bytes.of(data), null, null, null, null, null, null, null, null, null, 0, null, false, false); diff --git a/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java b/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java index 3af60cfbe7c..bacd665bb6a 100644 --- a/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java +++ b/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestProgramInvokeFactory.java @@ -20,6 +20,8 @@ import co.rsk.core.Coin; import co.rsk.core.RskAddress; +import co.rsk.core.types.bytes.Bytes; +import co.rsk.core.types.bytes.BytesSlice; import org.ethereum.core.Block; import org.ethereum.core.Repository; import org.ethereum.core.SignatureCache; @@ -53,7 +55,7 @@ public ProgramInvoke createProgramInvoke(Transaction tx, int txindex, Block bloc @Override public ProgramInvoke createProgramInvoke(Program program, DataWord toAddress, DataWord callerAddress, DataWord inValue, long inGas, - Coin balanceInt, byte[] dataIn, + Coin balanceInt, BytesSlice dataIn, Repository repository, BlockStore blockStore, boolean isStaticCall, boolean byTestingSuite) { return null; @@ -113,7 +115,7 @@ private ProgramInvoke generalInvoke(Transaction tx, int txindex, Block block, Re Coin minimumGasPrice = block.getMinimumGasPrice(); return new ProgramInvokeImpl(addr.getBytes(), origin.getBytes(), caller.getBytes(), balance.getBytes(), - txGasPrice.getBytes(), gas, callValue.getBytes(), data, lastHash, coinbase, + txGasPrice.getBytes(), gas, callValue.getBytes(), Bytes.of(data), lastHash, coinbase, timestamp, number, txindex, difficulty, gaslimit, minimumGasPrice.getBytes(), repository, blockStore); } diff --git a/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestRunner.java b/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestRunner.java index 1109dcec96c..32c8af7b1a2 100644 --- a/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestRunner.java +++ b/rskj-core/src/test/java/org/ethereum/jsontestsuite/TestRunner.java @@ -558,13 +558,13 @@ public List runTestCase(TestingCase testCase) { boolean assertData = Arrays.equals( expectedCallCreate.getData(), - resultCallCreate.getData()); + resultCallCreate.getData().copyArray()); if (!assertData) { String output = String.format("Call/Create data is different. Expected: [ %s ], result: [ %s ]", ByteUtil.toHexString(expectedCallCreate.getData()), - ByteUtil.toHexString(resultCallCreate.getData())); + resultCallCreate.getData().toHexString()); logger.info(output); results.add(output); } diff --git a/rskj-core/src/test/java/org/ethereum/util/HashUtilTest.java b/rskj-core/src/test/java/org/ethereum/util/HashUtilTest.java index 83ee72e22f5..1dd7019a00c 100644 --- a/rskj-core/src/test/java/org/ethereum/util/HashUtilTest.java +++ b/rskj-core/src/test/java/org/ethereum/util/HashUtilTest.java @@ -20,6 +20,7 @@ package org.ethereum.util; import co.rsk.core.RskAddress; +import co.rsk.core.types.bytes.Bytes; import org.bouncycastle.util.encoders.Hex; import org.ethereum.crypto.HashUtil; import org.junit.jupiter.api.Assertions; @@ -199,9 +200,9 @@ void testToPrintableHash_NullPointerExceptionForNull() { private void runTestCalSaltAddr(String address, String saltString, String code, String expected){ RskAddress r = new RskAddress(address); byte[] salt = Hex.decode(saltString); - byte[] init_code = Hex.decode(code); + byte[] initCode = Hex.decode(code); - String result = ByteUtil.toHexString(HashUtil.calcSaltAddr(r,init_code,salt)); + String result = ByteUtil.toHexString(HashUtil.calcSaltAddr(r, Bytes.of(initCode), salt)); assertEquals(expected.toUpperCase(), result.toUpperCase()); } } From bfbd22a59f3a44740fd1b2dc8f6b0c3e223811fc Mon Sep 17 00:00:00 2001 From: Volodymyr Kravets Date: Mon, 5 Aug 2024 14:55:24 +0300 Subject: [PATCH 2/5] perf: fix sonar complaints --- .../src/main/java/org/ethereum/vm/CallCreate.java | 10 ++++------ .../src/main/java/org/ethereum/vm/program/Memory.java | 2 +- .../ethereum/vm/program/invoke/ProgramInvokeImpl.java | 7 +++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/rskj-core/src/main/java/org/ethereum/vm/CallCreate.java b/rskj-core/src/main/java/org/ethereum/vm/CallCreate.java index eb8fd00ded5..78589947a43 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/CallCreate.java +++ b/rskj-core/src/main/java/org/ethereum/vm/CallCreate.java @@ -17,7 +17,6 @@ * along with this program. If not, see . */ - package org.ethereum.vm; import co.rsk.core.types.bytes.BytesSlice; @@ -28,11 +27,10 @@ */ public class CallCreate { - final BytesSlice data; - final byte[] destination; - final long gasLimit; - final byte[] value; - + private final BytesSlice data; + private final byte[] destination; + private final long gasLimit; + private final byte[] value; public CallCreate(BytesSlice data, byte[] destination, long gasLimit, byte[] value) { this.data = data; diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java b/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java index 72ed443cb2f..ea4c62907d1 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java @@ -38,7 +38,7 @@ public class Memory implements ProgramListenerAware { private static final int CHUNK_SIZE = 1024; private static final int WORD_SIZE = 32; - private List chunks = new LinkedList<>(); + private final List chunks = new LinkedList<>(); private int softSize; private ProgramListener traceListener; diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java index 41a565a8f71..aa46d259ffa 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java @@ -37,7 +37,7 @@ */ public class ProgramInvokeImpl implements ProgramInvoke { - private BlockStore blockStore; + private final BlockStore blockStore; /** * TRANSACTION env ** */ @@ -47,9 +47,8 @@ public class ProgramInvokeImpl implements ProgramInvoke { private final DataWord balance; private final DataWord txGasPrice; private final DataWord callValue; - private long gas; - - BytesSlice msgData; + private final long gas; + private final BytesSlice msgData; /** * BLOCK env ** From 71f6ea78e87c28988acd42f9c0a4a28742cf1f3a Mon Sep 17 00:00:00 2001 From: Volodymyr Kravets Date: Mon, 30 Sep 2024 12:34:36 +0300 Subject: [PATCH 3/5] perf: add jmh test; add java doc --- rskj-core/build.gradle | 1 + .../co/rsk/jmh/utils/BenchmarkByteUtil.java | 62 +++++++++++++++++++ .../java/co/rsk/jmh/utils/plan/DataPlan.java | 56 +++++++++++++++++ .../core/types/bytes/HexPrintableBytes.java | 24 +++++-- .../vm/program/invoke/ProgramInvokeImpl.java | 1 - 5 files changed, 139 insertions(+), 5 deletions(-) create mode 100644 rskj-core/src/jmh/java/co/rsk/jmh/utils/BenchmarkByteUtil.java create mode 100644 rskj-core/src/jmh/java/co/rsk/jmh/utils/plan/DataPlan.java diff --git a/rskj-core/build.gradle b/rskj-core/build.gradle index 8deb7a17fdc..9c5b0c8975c 100644 --- a/rskj-core/build.gradle +++ b/rskj-core/build.gradle @@ -194,6 +194,7 @@ ext { // WARN: consider different setups and sure to not use GPG elements without checksums or without signature file // in the remote repository (see https://github.com/gradle/gradle/security/advisories/GHSA-j6wc-xfg8-jx2j) dependencies { + jmhImplementation project(path: ':rskj-core') jmhImplementation "${jmhLibs.jmhCore}" jmhImplementation "${jmhLibs.web3jCore}" jmhImplementation "${libs.slf4jApiLib}" diff --git a/rskj-core/src/jmh/java/co/rsk/jmh/utils/BenchmarkByteUtil.java b/rskj-core/src/jmh/java/co/rsk/jmh/utils/BenchmarkByteUtil.java new file mode 100644 index 00000000000..755f3e6fb7a --- /dev/null +++ b/rskj-core/src/jmh/java/co/rsk/jmh/utils/BenchmarkByteUtil.java @@ -0,0 +1,62 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.jmh.utils; + +import co.rsk.core.types.bytes.Bytes; +import co.rsk.jmh.utils.plan.DataPlan; +import org.openjdk.jmh.annotations.*; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.RunnerException; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +@BenchmarkMode({Mode.Throughput}) +@Warmup(iterations = 1, time = 5 /* secs */) +@Measurement(iterations = 3, time = 5 /* secs */) +@OutputTimeUnit(TimeUnit.MILLISECONDS) +public class BenchmarkByteUtil { + + @Benchmark + public void toHexString_Bouncycastle(DataPlan plan) { + byte[] data = plan.getData(); + int off = plan.getNextRand(data.length); + int len = plan.getNextRand(data.length - off); + org.bouncycastle.util.encoders.Hex.toHexString(data, off, len); + } + + @Benchmark + public void toHexString_V2(DataPlan plan) { + Bytes bytes = plan.getBytes(); + int bytesLen = bytes.length(); + int off = plan.getNextRand(bytesLen); + int len = plan.getNextRand(bytesLen - off); + bytes.toHexString(off, len); + } + + public static void main(String[] args) throws RunnerException { + Options opt = new OptionsBuilder() + .include(BenchmarkByteUtil.class.getName()) + .forks(2) + .build(); + + new Runner(opt).run(); + } +} diff --git a/rskj-core/src/jmh/java/co/rsk/jmh/utils/plan/DataPlan.java b/rskj-core/src/jmh/java/co/rsk/jmh/utils/plan/DataPlan.java new file mode 100644 index 00000000000..a9c5993f74e --- /dev/null +++ b/rskj-core/src/jmh/java/co/rsk/jmh/utils/plan/DataPlan.java @@ -0,0 +1,56 @@ +/* + * This file is part of RskJ + * Copyright (C) 2024 RSK Labs Ltd. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package co.rsk.jmh.utils.plan; + +import co.rsk.core.types.bytes.Bytes; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; + +import java.util.Random; + +@State(Scope.Benchmark) +public class DataPlan { + + private final byte[] data = new byte[1024]; + + private Bytes bytes; + + private Random random; + + @Setup(Level.Trial) + public void doSetup() { + random = new Random(111); + random.nextBytes(data); + bytes = Bytes.of(data); + } + + public byte[] getData() { + return data; + } + + public Bytes getBytes() { + return bytes; + } + + public int getNextRand(int bound) { + return random.nextInt(bound); + } +} diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java index d472f9f9240..aa7e179ce6e 100644 --- a/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java @@ -18,6 +18,8 @@ package co.rsk.core.types.bytes; +import org.ethereum.util.ByteUtil; + import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.Objects; @@ -70,13 +72,27 @@ default String toHexString() { return toHexString(0, length()); } - default String toHexStringV2(int off, int length) { - if (off < 0 || length < 0 || off + length > length()) { - throw new IndexOutOfBoundsException("invalid 'off' and/or 'length': " + off + "; " + length); + /** + * This is a bit optimized version of {@link ByteUtil#toHexString(byte[], int, int)}, + * which does not use a third-party library. + * + * @param offset the start index of the bytes to be converted to hexadecimal. + * It must be non-negative and less than the length of the bytes. + * Otherwise, an {@link IndexOutOfBoundsException} will be thrown. + * @param length the number of bytes to be converted to hexadecimal. + * It must be non-negative and less than the length of the bytes. + * Otherwise, an {@link IndexOutOfBoundsException} will be thrown. + * + * @return the hexadecimal representation of the bytes in the range of {@code offset} and {@code length}. + */ + default String toHexStringV2(int offset, int length) { + int endIndex = offset + length; + if (offset < 0 || length < 0 || endIndex > length()) { + throw new IndexOutOfBoundsException("invalid 'offset' and/or 'length': " + offset + "; " + length); } StringBuilder sb = new StringBuilder(length * 2); - for (int i = off; i < off + length; i++) { + for (int i = offset; i < endIndex; i++) { byte b = byteAt(i); sb.append(Character.forDigit((b >> 4) & 0xF, 16)); sb.append(Character.forDigit((b & 0xF), 16)); diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java index aa46d259ffa..485fd97a5d1 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/invoke/ProgramInvokeImpl.java @@ -27,7 +27,6 @@ import org.ethereum.vm.program.Program; import java.math.BigInteger; -import java.util.Arrays; import java.util.Map; import java.util.Objects; From e1d6fd42e9cc262e090699072be865c8f97a0be9 Mon Sep 17 00:00:00 2001 From: Volodymyr Kravets Date: Wed, 9 Oct 2024 11:57:43 +0300 Subject: [PATCH 4/5] perf: add overflow check --- .../main/java/co/rsk/core/types/bytes/BoundaryUtils.java | 4 ++-- .../src/main/java/co/rsk/core/types/bytes/BytesSlice.java | 4 ++-- .../java/co/rsk/core/types/bytes/HexPrintableBytes.java | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/BoundaryUtils.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/BoundaryUtils.java index d2ffe8a7691..d56e610b44f 100644 --- a/rskj-core/src/main/java/co/rsk/core/types/bytes/BoundaryUtils.java +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/BoundaryUtils.java @@ -26,10 +26,10 @@ public static void checkArraycopyParams(int srcLength, int srcPos, byte[] dest, if (length < 0) { throw new IndexOutOfBoundsException("invalid 'length': " + length); } - if (srcPos < 0 || srcPos + length > srcLength) { + if (srcPos < 0 || Long.sum(srcPos, length) > srcLength) { throw new IndexOutOfBoundsException("invalid 'srcPos' and/or 'length': [" + srcPos + ";" + length + ")"); } - if (destPos < 0 || destPos + length > dest.length) { + if (destPos < 0 || Long.sum(destPos, length) > dest.length) { throw new IndexOutOfBoundsException("invalid 'destPos' and/or 'length': [" + destPos + ";" + length + ")"); } } diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java index 2e140f65410..82d89122753 100644 --- a/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/BytesSlice.java @@ -104,10 +104,10 @@ default byte[] copyArrayOfRange(int from, int to) { if (from < 0 || from > length()) { throw new IndexOutOfBoundsException("invalid 'from': " + from); } - int newLength = to - from; - if (newLength < 0) { + if (to < from) { throw new IllegalArgumentException(from + " > " + to); } + int newLength = to - from; byte[] copy = new byte[newLength]; arraycopy(from, copy, 0, Math.min(length() - from, newLength)); return copy; diff --git a/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java b/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java index aa7e179ce6e..de8dfb1e697 100644 --- a/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java +++ b/rskj-core/src/main/java/co/rsk/core/types/bytes/HexPrintableBytes.java @@ -86,11 +86,11 @@ default String toHexString() { * @return the hexadecimal representation of the bytes in the range of {@code offset} and {@code length}. */ default String toHexStringV2(int offset, int length) { - int endIndex = offset + length; - if (offset < 0 || length < 0 || endIndex > length()) { + if (offset < 0 || length < 0 || Long.sum(offset, length) > length()) { throw new IndexOutOfBoundsException("invalid 'offset' and/or 'length': " + offset + "; " + length); } + int endIndex = offset + length; StringBuilder sb = new StringBuilder(length * 2); for (int i = offset; i < endIndex; i++) { byte b = byteAt(i); @@ -106,7 +106,7 @@ class PrintableBytesHexFormatter implements PrintableBytes.Formatter bytesLen) { + if (off < 0 || length < 0 || Long.sum(off, length) > bytesLen) { throw new IndexOutOfBoundsException("invalid 'off' and/or 'length': " + off + "; " + length); } From ff944f28f480c84c23d566f2f426443a348153ce Mon Sep 17 00:00:00 2001 From: Volodymyr Kravets Date: Thu, 10 Oct 2024 12:02:39 +0300 Subject: [PATCH 5/5] perf: add write versioning check; add tests --- .../java/org/ethereum/vm/program/Memory.java | 21 +++++++++-- .../test/java/org/ethereum/vm/MemoryTest.java | 35 ++++++++++++++++--- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java b/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java index ea4c62907d1..0d9afa9bfed 100644 --- a/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java +++ b/rskj-core/src/main/java/org/ethereum/vm/program/Memory.java @@ -39,7 +39,10 @@ public class Memory implements ProgramListenerAware { private static final int WORD_SIZE = 32; private final List chunks = new LinkedList<>(); + private int softSize; + private int version; // versioning for memory changes + private ProgramListener traceListener; @Override @@ -54,7 +57,7 @@ public BytesSlice readSlice(int address, int size) { extend(address, size); - return new MemorySlice(address, size); + return new MemorySlice(address, size, version); } public byte[] read(int address, int size) { @@ -87,6 +90,8 @@ public byte[] read(int address, int size) { } public void write(int address, byte[] data, int dataSize, boolean limited) { + version++; + if (data.length < dataSize) { dataSize = data.length; } @@ -123,7 +128,6 @@ public void write(int address, byte[] data, int dataSize, boolean limited) { } } - public void extendAndWrite(int address, int allocSize, byte[] data) { extend(address, allocSize); write(address, data, data.length, false); @@ -239,15 +243,18 @@ private void addChunks(int num) { private final class MemorySlice implements BytesSlice { private final int address; private final int size; + private final int memVersion; - MemorySlice(int address, int size) { + MemorySlice(int address, int size, int memVersion) { this.address = address; this.size = size; + this.memVersion = memVersion; } @Override public void arraycopy(int srcPos, byte[] dest, int destPos, int length) { BoundaryUtils.checkArraycopyParams(length(), srcPos, dest, destPos, length); + checkVersion(); int chunkIndex = (address + srcPos) / CHUNK_SIZE; int chunkOffset = (address + srcPos) % CHUNK_SIZE; @@ -276,7 +283,15 @@ public int length() { @Override public byte byteAt(int index) { BoundaryUtils.checkArrayIndexParam(length(), index); + checkVersion(); + return readByte(address + index); } + + private void checkVersion() { + if (this.memVersion != Memory.this.version) { + throw new IllegalStateException("Memory was changed during slice lifetime"); + } + } } } diff --git a/rskj-core/src/test/java/org/ethereum/vm/MemoryTest.java b/rskj-core/src/test/java/org/ethereum/vm/MemoryTest.java index 89a68ad2b70..611efff84f4 100644 --- a/rskj-core/src/test/java/org/ethereum/vm/MemoryTest.java +++ b/rskj-core/src/test/java/org/ethereum/vm/MemoryTest.java @@ -19,16 +19,15 @@ package org.ethereum.vm; +import co.rsk.core.types.bytes.BytesSlice; +import org.bouncycastle.util.encoders.Hex; import org.ethereum.vm.program.Memory; import org.junit.jupiter.api.Test; -import org.bouncycastle.util.encoders.Hex; import java.util.Arrays; import static java.lang.Math.ceil; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.*; class MemoryTest { @@ -530,6 +529,34 @@ void memoryWriteLimited_3(){ assertEquals(10, zero); } + @Test + void memorySliceMatchesWithWritten() { + Memory memory = new Memory(); + memory.write(0, new byte[]{1, 2, 3, 4}, 4, false); + BytesSlice memSlice = memory.readSlice(0, 4); + + assertArrayEquals(new byte[]{1, 2, 3, 4}, memSlice.copyArray()); + assertArrayEquals(memory.read(0, 4), memSlice.copyArray()); + } + + @Test + void memorySliceMatchesWithWrittenAndExpansion() { + Memory memory = new Memory(); + memory.write(0, new byte[]{1, 2, 3, 4}, 4, false); + BytesSlice memSlice = memory.readSlice(0, 8); + assertArrayEquals(new byte[]{1, 2, 3, 4, 0, 0, 0, 0}, memSlice.copyArray()); + assertArrayEquals(memory.read(0, 8), memSlice.copyArray()); + } + @Test + void memoryWriteInvalidatesSlice() { + Memory memory = new Memory(); + memory.write(0, new byte[]{1, 2, 3, 4}, 4, false); + BytesSlice memSlice = memory.readSlice(0, 4); + memory.write(0, new byte[]{4, 3, 2, 1}, 4, false); + + assertThrows(IllegalStateException.class, () -> memSlice.byteAt(0), "Memory was changed during slice lifetime"); + assertThrows(IllegalStateException.class, () -> memSlice.arraycopy(0, new byte[4], 0, 4), "Memory was changed during slice lifetime"); + } }