Skip to content

Commit

Permalink
Txparse subcommand implementation (#6268)
Browse files Browse the repository at this point in the history
* txparse subcommand

Signed-off-by: garyschulte <garyschulte@gmail.com>
Co-authored-by: Sally MacFarlane <macfarla.github@gmail.com>
  • Loading branch information
garyschulte and macfarla authored Dec 11, 2023
1 parent 1d3eb93 commit 9f8f121
Show file tree
Hide file tree
Showing 3 changed files with 201 additions and 0 deletions.
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 = "Parse input transactions and return the sender, or an error.",
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");
}
}

0 comments on commit 9f8f121

Please sign in to comment.