Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Txparse subcommand implementation #6268

Merged
merged 9 commits into from
Dec 11, 2023
Merged
Show file tree
Hide file tree
Changes from 8 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
3 changes: 3 additions & 0 deletions besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@
import org.hyperledger.besu.cli.subcommands.PasswordSubCommand;
import org.hyperledger.besu.cli.subcommands.PublicKeySubCommand;
import org.hyperledger.besu.cli.subcommands.RetestethSubCommand;
import org.hyperledger.besu.cli.subcommands.TxParseSubCommand;
import org.hyperledger.besu.cli.subcommands.ValidateConfigSubCommand;
import org.hyperledger.besu.cli.subcommands.blocks.BlocksSubCommand;
import org.hyperledger.besu.cli.subcommands.operator.OperatorSubCommand;
Expand Down Expand Up @@ -1493,6 +1494,8 @@ private void addSubCommands(final InputStream in) {
jsonBlockImporterFactory,
rlpBlockExporterFactory,
commandLine.getOut()));
commandLine.addSubcommand(
TxParseSubCommand.COMMAND_NAME, new TxParseSubCommand(commandLine.getOut()));
commandLine.addSubcommand(
PublicKeySubCommand.COMMAND_NAME, new PublicKeySubCommand(commandLine.getOut()));
commandLine.addSubcommand(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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.cli.subcommands;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.besu.cli.subcommands.TxParseSubCommand.COMMAND_NAME;

import org.hyperledger.besu.cli.util.VersionProvider;
import org.hyperledger.besu.crypto.SignatureAlgorithmFactory;
import org.hyperledger.besu.ethereum.core.encoding.EncodingContext;
import org.hyperledger.besu.ethereum.core.encoding.TransactionDecoder;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.stream.Stream;

import org.apache.tuweni.bytes.Bytes;
import picocli.CommandLine;

/**
* txparse sub command implementing txparse spec from
* https://github.com/holiman/txparse/tree/main/cmd/txparse
*/
@CommandLine.Command(
name = COMMAND_NAME,
description = "Execute an Ethereum State Test.",
garyschulte marked this conversation as resolved.
Show resolved Hide resolved
mixinStandardHelpOptions = true,
versionProvider = VersionProvider.class)
public class TxParseSubCommand implements Runnable {

/** The constant COMMAND_NAME. */
public static final String COMMAND_NAME = "txparse";

@SuppressWarnings({"FieldCanBeFinal", "FieldMayBeFinal"}) // PicoCLI requires non-final Strings.
@CommandLine.Option(
names = "--corpus-file",
arity = "1..1",
description = "file to read transaction data lines from, otherwise defaults to stdin")
private String corpusFile = null;

static final BigInteger halfCurveOrder =
SignatureAlgorithmFactory.getInstance().getHalfCurveOrder();
static final BigInteger chainId = new BigInteger("1", 10);

private final PrintWriter out;

/**
* Instantiates a new TxParse sub command.
*
* @param out the PrintWriter where validation results will be reported.
*/
public TxParseSubCommand(final PrintWriter out) {
this.out = out;
}

@SuppressWarnings("StreamResourceLeak")
Stream<String> fileStreamReader(final String filePath) {
try {
return Files.lines(Paths.get(filePath))
// skip comments
.filter(line -> !line.startsWith("#"))
// strip out non-alphanumeric characters
.map(line -> line.replaceAll("[^a-zA-Z0-9]", ""));
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}

@Override
public void run() {

Stream<String> txStream;
if (corpusFile != null) {
txStream = fileStreamReader(corpusFile);
} else {
txStream = new BufferedReader(new InputStreamReader(System.in, UTF_8)).lines();
}

txStream.forEach(
line -> {
try {
Bytes bytes = Bytes.fromHexStringLenient(line);
dump(bytes);
} catch (Exception ex) {
err(ex.getMessage());
}
});
}

void dump(final Bytes tx) {
try {
var transaction = TransactionDecoder.decodeOpaqueBytes(tx, EncodingContext.BLOCK_BODY);

// https://github.com/hyperledger/besu/blob/5fe49c60b30fe2954c7967e8475c3b3e9afecf35/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java#L252
if (transaction.getChainId().isPresent() && !transaction.getChainId().get().equals(chainId)) {
throw new Exception("wrong chain id");
}

// https://github.com/hyperledger/besu/blob/5fe49c60b30fe2954c7967e8475c3b3e9afecf35/ethereum/core/src/main/java/org/hyperledger/besu/ethereum/mainnet/MainnetTransactionValidator.java#L270
if (transaction.getS().compareTo(halfCurveOrder) > 0) {
throw new Exception("signature s out of range");
}
out.println(transaction.getSender());

} catch (Exception ex) {
err(ex.getMessage());
}
}

void err(final String message) {
out.println("err: " + message);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* Copyright Hyperledger Besu Contributors.
*
* 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.cli.subcommands;

import static java.nio.charset.StandardCharsets.UTF_8;
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;

import java.io.ByteArrayOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

import org.apache.tuweni.bytes.Bytes;
import org.junit.jupiter.api.Test;

public class TxParseSubCommandTest {

ByteArrayOutputStream baos = new ByteArrayOutputStream();
PrintWriter writer = new PrintWriter(new OutputStreamWriter(baos, UTF_8), true);
TxParseSubCommand txParseSubCommand = new TxParseSubCommand(writer);

@Test
public void smokeTest() {
txParseSubCommand.dump(
Bytes.fromHexString(
"0x02f875018363bc5a8477359400850c570bd200825208943893d427c770de9b92065da03a8c825f13498da28702fbf8ccf8530480c080a0dd49141ecf93eeeaed5f3499a6103d6a9778e24a37b1f5c6a336c60c8a078c15a0511b51a3050771f66c1b779210a46a51be521a2446a3f87fc656dcfd1782aa5e"));
String output = baos.toString(UTF_8);
assertThat(output).isEqualTo("0xeb2629a2734e272bcc07bda959863f316f4bd4cf\n");
}

@Test
public void assertInvalidRlp() {
txParseSubCommand.dump(
Bytes.fromHexString(
"0x03f90124f9011e010516058261a89403040500000000000000000000000000000000006383000000f8b6f859940000000000000000000000000000000074657374f842a00100000000000000000000000000000000000000000000000000000000000000a00200000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000074657374f842a00100000000000000000000000000000000000000000000000000000000000000a002000000000000000000000000000000000000000000000000000000000000000fc080a082f96cae43a3f2554cad899a6e9168a3cd613454f82e93488f9b4c1a998cc814a06b7519cd0e3159d6ce9bf811ff3ca4e9c5d7ac63fc52142370a0725e4c5273b2c0c0c0"));
String output = baos.toString(UTF_8);
assertThat(output).startsWith("err: ");
assertThat(output).contains("arbitrary precision scalar");
}

@Test
public void assertValidRlp() {
txParseSubCommand.dump(
Bytes.fromHexString(
"0x03f9013f010516058261a89403040500000000000000000000000000000000006383000000f8b6f859940000000000000000000000000000000074657374f842a00100000000000000000000000000000000000000000000000000000000000000a00200000000000000000000000000000000000000000000000000000000000000f859940000000000000000000000000000000074657374f842a00100000000000000000000000000000000000000000000000000000000000000a002000000000000000000000000000000000000000000000000000000000000000fe1a0010657f37554c781402a22917dee2f75def7ab966d7b770905398eba3c44401401a0d98465c5489a09933e6428657f1fc4461d49febd8556c1c7e7053ed0be49cc4fa02c0d67e40aed75f87ea640cc47064d79f4383e24dec283ac6eb81e19da46cc26"));
String output = baos.toString(UTF_8);
assertThat(output).isEqualTo("0x730aa72228b55236de378f5267dfc77723b1380c\n");
}

@Test
public void assertInvalidChainId() {
txParseSubCommand.dump(
Bytes.fromHexString(
"0xf901fc303083303030803030b901ae30303030303030303030433030303030303030303030303030303030303030303030303030303030203030413030303030303030303000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000038390030000000002800380000413159000021000000002b0000000000003800000000003800000000000000000000002b633279315a00633200303041374222610063416200325832325a002543323861795930314130383058435931317a633043304239623900310031254363384361000023433158253041380037000027285839005a007937000000623700002761002b5a003937622e000000006300007863410000002a2e320000000000005a0000000000000037242778002039006120412e6362002138300000002a313030303030303030303030373030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030303030a03030303030303030303030303030303030303030303030303030303030303030a03030303030303030303030303030303030303030303030303030303030303030"));
String output = baos.toString(UTF_8);
assertThat(output).isEqualTo("err: wrong chain id\n");
}
}
Loading