Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions ethereum/api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ dependencies {
api 'org.slf4j:slf4j-api'
api 'org.apache.logging.log4j:log4j-api'

annotationProcessor 'org.openjdk.jmh:jmh-generator-annprocess'

compileOnly 'org.jspecify:jspecify'

implementation project(':config')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results;

import java.util.Objects;
import java.util.concurrent.TimeUnit;

import org.apache.tuweni.units.bigints.UInt256;
import org.apache.tuweni.units.bigints.UInt256Value;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;

@State(Scope.Thread)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@OutputTimeUnit(value = TimeUnit.NANOSECONDS)
@BenchmarkMode(Mode.AverageTime)
public class QuantityLongToHexBenchmark {
private static final String HEX_PREFIX = "0x";
public static final String HEX_ZERO = "0x0";
private static final char[] HEX_DIGITS = "0123456789abcdef".toCharArray();

@Param({"0", "255", "65535", "1000000", "9007199254740991", "9223372036854775807"})
Comment thread
ahamlat marked this conversation as resolved.
public long value;

@Benchmark
public void current(final Blackhole blackhole) {
blackhole.consume(uint256ToHex(UInt256.fromHexString(Long.toHexString(value))));
}

@Benchmark
public void simpleHexString(final Blackhole blackhole) {
blackhole.consume(simpleHexString(value));
}

@Benchmark
public void directCharArray(final Blackhole blackhole) {
blackhole.consume(directCharArray(value));
}

@Benchmark
public void unrolled(final Blackhole blackhole) {
blackhole.consume(unrolled(value));
}

private static String simpleHexString(final long value) {
return HEX_PREFIX + Long.toHexString(value);
}

private static String unrolled(final long value) {
if (value == 0L) {
return HEX_ZERO;
}
final int leadingZeroNibbles = Long.numberOfLeadingZeros(value) >>> 2;
final char[] buf = new char[18];
buf[2] = HEX_DIGITS[(int) ((value >>> 60) & 0xF)];
buf[3] = HEX_DIGITS[(int) ((value >>> 56) & 0xF)];
buf[4] = HEX_DIGITS[(int) ((value >>> 52) & 0xF)];
buf[5] = HEX_DIGITS[(int) ((value >>> 48) & 0xF)];
buf[6] = HEX_DIGITS[(int) ((value >>> 44) & 0xF)];
buf[7] = HEX_DIGITS[(int) ((value >>> 40) & 0xF)];
buf[8] = HEX_DIGITS[(int) ((value >>> 36) & 0xF)];
buf[9] = HEX_DIGITS[(int) ((value >>> 32) & 0xF)];
buf[10] = HEX_DIGITS[(int) ((value >>> 28) & 0xF)];
buf[11] = HEX_DIGITS[(int) ((value >>> 24) & 0xF)];
buf[12] = HEX_DIGITS[(int) ((value >>> 20) & 0xF)];
buf[13] = HEX_DIGITS[(int) ((value >>> 16) & 0xF)];
buf[14] = HEX_DIGITS[(int) ((value >>> 12) & 0xF)];
buf[15] = HEX_DIGITS[(int) ((value >>> 8) & 0xF)];
buf[16] = HEX_DIGITS[(int) ((value >>> 4) & 0xF)];
buf[17] = HEX_DIGITS[(int) (value & 0xF)];
buf[leadingZeroNibbles] = '0';
buf[leadingZeroNibbles + 1] = 'x';
return new String(buf, leadingZeroNibbles, 18 - leadingZeroNibbles);
}

private static String directCharArray(final long value) {
if (value == 0L) {
return HEX_ZERO;
}
int nibbles = 16;
while (nibbles > 1 && ((value >>> ((nibbles - 1) * 4)) & 0xFL) == 0L) {
nibbles--;
}
final char[] buf = new char[2 + nibbles];
buf[0] = '0';
buf[1] = 'x';
for (int i = 0; i < nibbles; i++) {
buf[2 + i] = HEX_DIGITS[(int) ((value >>> ((nibbles - 1 - i) * 4)) & 0xFL)];
}
return new String(buf);
}

private static String uint256ToHex(final UInt256Value<?> value) {
return value == null ? null : formatMinimalValue(value.toMinimalBytes().toShortHexString());
}

private static String formatMinimalValue(final String hexValue) {
final String prefixedHexString = prefixHexNotation(hexValue);
return Objects.equals(prefixedHexString, HEX_PREFIX) ? HEX_ZERO : prefixedHexString;
Comment thread
ahamlat marked this conversation as resolved.
}

private static String prefixHexNotation(final String hexValue) {
return hexValue.startsWith(HEX_PREFIX) ? hexValue : HEX_PREFIX + hexValue;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,29 +44,29 @@ public static String create(final UInt64Value<?> value) {
}

public static String create(final int value) {
return uint256ToHex(UInt256.valueOf(value));
return HEX_PREFIX + Integer.toHexString(value);
Comment thread
ahamlat marked this conversation as resolved.
}

public static String create(final long value) {
return uint256ToHex(UInt256.fromHexString(Long.toHexString(value)));
return HEX_PREFIX + Long.toHexString(value);
Comment thread
ahamlat marked this conversation as resolved.
}

public static String create(final byte value) {
return HEX_PREFIX + Integer.toHexString(value);
Comment thread
ahamlat marked this conversation as resolved.
}

public static String create(final Bytes value) {
return create(value.toArrayUnsafe());
return uint256ToHex(UInt256.fromBytes(Bytes32.leftPad(value)));
}

public static String create(final byte[] value) {
return uint256ToHex(UInt256.fromBytes(Bytes32.leftPad(Bytes.wrap(value))));
return create(Bytes.wrap(value));
}

public static String create(final BigInteger value) {
return uint256ToHex(UInt256.valueOf(value));
}

public static String create(final byte value) {
return formatMinimalValue(Integer.toHexString(value));
}

/**
* Fixed-length bytes sequences and should be returned as hex strings zero-padded to the expected
* length.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
/*
* Copyright contributors to Besu.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
* an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
* specific language governing permissions and limitations under the License.
*
* SPDX-License-Identifier: Apache-2.0
*/
package org.hyperledger.besu.ethereum.api.jsonrpc.internal.results;

import static org.assertj.core.api.Assertions.assertThat;

import java.math.BigInteger;

import org.apache.tuweni.bytes.Bytes;
import org.apache.tuweni.units.bigints.UInt256;
import org.apache.tuweni.units.bigints.UInt64;
import org.junit.jupiter.api.Test;

public class QuantityTest {

// --- create(int) ---

@Test
public void createInt_zero() {
assertThat(Quantity.create(0)).isEqualTo("0x0");
}

@Test
public void createInt_one() {
assertThat(Quantity.create(1)).isEqualTo("0x1");
}

@Test
public void createInt_maxValue() {
assertThat(Quantity.create(Integer.MAX_VALUE))
.isEqualTo("0x" + Integer.toHexString(Integer.MAX_VALUE));
}

// --- create(long) ---

@Test
public void createLong_zero() {
assertThat(Quantity.create(0L)).isEqualTo("0x0");
}

@Test
public void createLong_one() {
assertThat(Quantity.create(1L)).isEqualTo("0x1");
}

@Test
public void createLong_maxValue() {
assertThat(Quantity.create(Long.MAX_VALUE)).isEqualTo("0x" + Long.toHexString(Long.MAX_VALUE));
}

// --- create(byte) ---

@Test
public void createByte_zero() {
assertThat(Quantity.create((byte) 0)).isEqualTo("0x0");
}

@Test
public void createByte_one() {
assertThat(Quantity.create((byte) 1)).isEqualTo("0x1");
}

@Test
public void createByte_maxPositive() {
assertThat(Quantity.create(Byte.MAX_VALUE)).isEqualTo("0x7f");
}

// --- create(Bytes) ---

@Test
public void createBytes_empty() {
assertThat(Quantity.create(Bytes.EMPTY)).isEqualTo("0x0");
}

@Test
public void createBytes_zero() {
assertThat(Quantity.create(Bytes.of(0x00))).isEqualTo("0x0");
}

@Test
public void createBytes_oneByte() {
assertThat(Quantity.create(Bytes.of(0x01))).isEqualTo("0x1");
}

@Test
public void createBytes_leadingZerosStripped() {
assertThat(Quantity.create(Bytes.fromHexString("0x000001"))).isEqualTo("0x1");
}

@Test
public void createBytes_multipleBytes() {
assertThat(Quantity.create(Bytes.fromHexString("0x0100"))).isEqualTo("0x100");
}

// --- create(byte[]) ---

@Test
public void createByteArray_zero() {
assertThat(Quantity.create(new byte[] {0x00})).isEqualTo("0x0");
}

@Test
public void createByteArray_leadingZerosStripped() {
assertThat(Quantity.create(new byte[] {0x00, 0x00, 0x01})).isEqualTo("0x1");
}

@Test
public void createByteArray_multipleBytes() {
assertThat(Quantity.create(new byte[] {0x01, 0x00})).isEqualTo("0x100");
}

// --- create(BigInteger) ---

@Test
public void createBigInteger_zero() {
assertThat(Quantity.create(BigInteger.ZERO)).isEqualTo("0x0");
}

@Test
public void createBigInteger_one() {
assertThat(Quantity.create(BigInteger.ONE)).isEqualTo("0x1");
}

@Test
public void createBigInteger_large() {
final BigInteger value = BigInteger.TWO.pow(128);
assertThat(Quantity.create(value)).isEqualTo("0x" + value.toString(16));
}

// --- create(UInt256Value) ---

@Test
public void createUInt256_null() {
assertThat(Quantity.create((UInt256) null)).isNull();
}

@Test
public void createUInt256_zero() {
assertThat(Quantity.create(UInt256.ZERO)).isEqualTo("0x0");
}

@Test
public void createUInt256_one() {
assertThat(Quantity.create(UInt256.ONE)).isEqualTo("0x1");
}

@Test
public void createUInt256_maxValue() {
assertThat(Quantity.create(UInt256.MAX_VALUE))
.startsWith("0x")
.hasSize(66); // 0x + 64 hex chars
}

// --- create(UInt64Value) ---

@Test
public void createUInt64_null() {
assertThat(Quantity.create((UInt64) null)).isEqualTo("0x0");
}

@Test
public void createUInt64_zero() {
assertThat(Quantity.create(UInt64.ZERO)).isEqualTo("0x0");
}

@Test
public void createUInt64_one() {
assertThat(Quantity.create(UInt64.ONE)).startsWith("0x");
}

// --- longToPaddedHex ---

@Test
public void longToPaddedHex_zero() {
assertThat(Quantity.longToPaddedHex(0L, 4)).isEqualTo("0x00000000");
}

@Test
public void longToPaddedHex_padded() {
assertThat(Quantity.longToPaddedHex(1L, 4)).isEqualTo("0x00000001");
}

@Test
public void longToPaddedHex_full() {
assertThat(Quantity.longToPaddedHex(0xdeadbeefL, 4)).isEqualTo("0xdeadbeef");
}

// --- isValid ---

@Test
public void isValid_withPrefix() {
assertThat(Quantity.isValid("0x1")).isTrue();
}

@Test
public void isValid_withoutPrefix() {
assertThat(Quantity.isValid("1")).isFalse();
}

@Test
public void isValid_zeroHex() {
assertThat(Quantity.isValid("0x0")).isTrue();
}
}
Loading