|
|
|
@ -22,6 +22,7 @@ import org.hyperledger.besu.datatypes.Hash; |
|
|
|
|
import org.hyperledger.besu.datatypes.Wei; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.Transaction; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.TransactionFilter; |
|
|
|
|
import org.hyperledger.besu.ethereum.core.encoding.ssz.TransactionNetworkPayload; |
|
|
|
|
import org.hyperledger.besu.ethereum.mainnet.feemarket.FeeMarket; |
|
|
|
|
import org.hyperledger.besu.ethereum.transaction.TransactionInvalidReason; |
|
|
|
|
import org.hyperledger.besu.evm.account.Account; |
|
|
|
@ -29,9 +30,15 @@ import org.hyperledger.besu.evm.gascalculator.GasCalculator; |
|
|
|
|
import org.hyperledger.besu.plugin.data.TransactionType; |
|
|
|
|
|
|
|
|
|
import java.math.BigInteger; |
|
|
|
|
import java.util.List; |
|
|
|
|
import java.util.Optional; |
|
|
|
|
import java.util.Set; |
|
|
|
|
|
|
|
|
|
import ethereum.ckzg4844.CKZG4844JNI; |
|
|
|
|
import org.apache.tuweni.bytes.Bytes; |
|
|
|
|
import org.apache.tuweni.bytes.Bytes32; |
|
|
|
|
import org.bouncycastle.crypto.digests.SHA256Digest; |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Validates a transaction based on Frontier protocol runtime requirements. |
|
|
|
|
* |
|
|
|
@ -40,6 +47,10 @@ import java.util.Set; |
|
|
|
|
*/ |
|
|
|
|
public class MainnetTransactionValidator { |
|
|
|
|
|
|
|
|
|
private final long MAX_DATA_GAS_PER_BLOCK = 524_288; // 2**19
|
|
|
|
|
private final long DATA_GAS_PER_BLOB = 131_072; // 2**17
|
|
|
|
|
private final byte BLOB_COMMITMENT_VERSION_KZG = 0x01; |
|
|
|
|
|
|
|
|
|
private final GasCalculator gasCalculator; |
|
|
|
|
private final FeeMarket feeMarket; |
|
|
|
|
|
|
|
|
@ -118,6 +129,14 @@ public class MainnetTransactionValidator { |
|
|
|
|
if (!signatureResult.isValid()) { |
|
|
|
|
return signatureResult; |
|
|
|
|
} |
|
|
|
|
if (transaction.getType().equals(TransactionType.BLOB) |
|
|
|
|
&& transaction.getBlobsWithCommitments().isPresent()) { |
|
|
|
|
final ValidationResult<TransactionInvalidReason> blobsResult = |
|
|
|
|
validateTransactionsBlobs(transaction); |
|
|
|
|
if (!blobsResult.isValid()) { |
|
|
|
|
return blobsResult; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (goQuorumCompatibilityMode && transaction.hasCostParams()) { |
|
|
|
|
return ValidationResult.invalid( |
|
|
|
@ -285,6 +304,107 @@ public class MainnetTransactionValidator { |
|
|
|
|
return ValidationResult.valid(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public ValidationResult<TransactionInvalidReason> validateTransactionsBlobs( |
|
|
|
|
final Transaction transaction) { |
|
|
|
|
|
|
|
|
|
if (transaction.getBlobsWithCommitments().isEmpty()) { |
|
|
|
|
return ValidationResult.invalid( |
|
|
|
|
TransactionInvalidReason.INVALID_BLOBS, |
|
|
|
|
"transaction blobs are empty, cannot verify without blobs"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Transaction.BlobsWithCommitments blobsWithCommitments = |
|
|
|
|
transaction.getBlobsWithCommitments().get(); |
|
|
|
|
|
|
|
|
|
if (blobsWithCommitments.blobs.getElements().size() |
|
|
|
|
> MAX_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB) { |
|
|
|
|
return ValidationResult.invalid( |
|
|
|
|
TransactionInvalidReason.INVALID_BLOBS, |
|
|
|
|
"Too many transaction blobs (" |
|
|
|
|
+ blobsWithCommitments.blobs.getElements().size() |
|
|
|
|
+ ") in transaction, max is " |
|
|
|
|
+ MAX_DATA_GAS_PER_BLOCK / DATA_GAS_PER_BLOB); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
if (blobsWithCommitments.blobs.getElements().size() |
|
|
|
|
!= blobsWithCommitments.kzgCommitments.getElements().size()) { |
|
|
|
|
return ValidationResult.invalid( |
|
|
|
|
TransactionInvalidReason.INVALID_BLOBS, |
|
|
|
|
"transaction blobs and commitments are not the same size"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
List<TransactionNetworkPayload.KZGCommitment> commitments = |
|
|
|
|
blobsWithCommitments.kzgCommitments.getElements(); |
|
|
|
|
for (TransactionNetworkPayload.KZGCommitment commitment : commitments) { |
|
|
|
|
if (commitment.getData().get(0) != BLOB_COMMITMENT_VERSION_KZG) { |
|
|
|
|
return ValidationResult.invalid( |
|
|
|
|
TransactionInvalidReason.INVALID_BLOBS, |
|
|
|
|
"transaction blobs commitment version is not supported"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (transaction.getVersionedHashes().isEmpty()) { |
|
|
|
|
return ValidationResult.invalid( |
|
|
|
|
TransactionInvalidReason.INVALID_BLOBS, |
|
|
|
|
"transaction versioned hashes are empty, cannot verify without versioned hashes"); |
|
|
|
|
} |
|
|
|
|
List<Hash> versionedHashes = transaction.getVersionedHashes().get(); |
|
|
|
|
|
|
|
|
|
for (int i = 0; i < versionedHashes.size(); i++) { |
|
|
|
|
TransactionNetworkPayload.KZGCommitment commitment = |
|
|
|
|
blobsWithCommitments.kzgCommitments.getElements().get(i); |
|
|
|
|
Hash versionedHash = versionedHashes.get(i); |
|
|
|
|
Hash calculatedVersionedHash = hashCommitment(commitment); |
|
|
|
|
if (!calculatedVersionedHash.equals(versionedHash)) { |
|
|
|
|
return ValidationResult.invalid( |
|
|
|
|
TransactionInvalidReason.INVALID_BLOBS, |
|
|
|
|
"transaction blobs commitment hash does not match commitment"); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Bytes blobs = |
|
|
|
|
blobsWithCommitments.blobs.getElements().stream() |
|
|
|
|
.map( |
|
|
|
|
blob -> |
|
|
|
|
blob.getElements().stream() |
|
|
|
|
.map(sszuInt256Wrapper -> (Bytes) sszuInt256Wrapper.getData().toBytes()) |
|
|
|
|
.reduce(Bytes::concatenate) |
|
|
|
|
.orElseThrow()) |
|
|
|
|
.reduce(Bytes::concatenate) |
|
|
|
|
.orElseThrow(); |
|
|
|
|
|
|
|
|
|
Bytes kzgCommitments = |
|
|
|
|
blobsWithCommitments.kzgCommitments.getElements().stream() |
|
|
|
|
.map(commitment -> commitment.getData()) |
|
|
|
|
.reduce(Bytes::concatenate) |
|
|
|
|
.orElseThrow(); |
|
|
|
|
|
|
|
|
|
boolean kzgVerification = |
|
|
|
|
CKZG4844JNI.verifyAggregateKzgProof( |
|
|
|
|
blobs.toArrayUnsafe(), |
|
|
|
|
kzgCommitments.toArrayUnsafe(), |
|
|
|
|
blobsWithCommitments.blobs.getElements().size(), |
|
|
|
|
blobsWithCommitments.kzgProof.getBytes().toArrayUnsafe()); |
|
|
|
|
if (!kzgVerification) { |
|
|
|
|
return ValidationResult.invalid( |
|
|
|
|
TransactionInvalidReason.INVALID_BLOBS, |
|
|
|
|
"transaction blobs kzg proof verification failed"); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return ValidationResult.valid(); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private Hash hashCommitment(final TransactionNetworkPayload.KZGCommitment commitment) { |
|
|
|
|
SHA256Digest digest = new SHA256Digest(); |
|
|
|
|
digest.update(commitment.getData().toArrayUnsafe(), 0, commitment.getData().size()); |
|
|
|
|
|
|
|
|
|
final byte[] dig = new byte[digest.getDigestSize()]; |
|
|
|
|
|
|
|
|
|
digest.doFinal(dig, 0); |
|
|
|
|
|
|
|
|
|
dig[0] = BLOB_COMMITMENT_VERSION_KZG; |
|
|
|
|
return Hash.wrap(Bytes32.wrap(dig)); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private boolean isSenderAllowed( |
|
|
|
|
final Transaction transaction, final TransactionValidationParams validationParams) { |
|
|
|
|
if (validationParams.checkLocalPermissions() || validationParams.checkOnchainPermissions()) { |
|
|
|
|