Skip to content

Commit

Permalink
Merge pull request #73 from emeraldpay/feature/eip4844-transaction
Browse files Browse the repository at this point in the history
solution: RLP encoding for EIP-4844 transactions
  • Loading branch information
splix authored Mar 15, 2024
2 parents 2e6ae0d + bd4bb3f commit 0b06f99
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ public byte[] hash() {
}

public byte[] hash(Integer chainId) {
byte[] rlp = TransactionEncoder.DEFAULT.encode(this, false, chainId);
byte[] rlp = TransactionEncoder.DEFAULT.encodeStandard(this, false, chainId);

Keccak.Digest256 keccak = new Keccak.Digest256();
keccak.update(rlp);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public Transaction decode(byte[] raw) {
if (type == TransactionType.GAS_PRIORITY) {
return decodeGasPriority(raw);
}
if (type == TransactionType.BLOB) {
return decodeBlob(raw);
}
if (type == TransactionType.STANDARD) {
return decodeStandard(raw);
}
Expand Down Expand Up @@ -130,6 +133,24 @@ public TransactionWithGasPriority decodeGasPriority(byte[] raw) {
return tx;
}

public TransactionWithBlob decodeBlob(byte[] raw) {
// rlp([chain_id, nonce, max_priority_fee_per_gas, max_fee_per_gas, gas_limit, to, value, data, access_list, max_fee_per_blob_gas, blob_versioned_hashes, y_parity, r, s])
RlpReader rdr = startReader(raw, 1);
TransactionWithBlob tx = new TransactionWithBlob();
readChainId(rdr, tx);
readNonce(rdr, tx);
readPriorityGasPrice(rdr, tx);
readMaxGasPrice(rdr, tx);
readGasLimit(rdr, tx);
readBodyPart(rdr, tx);

readAccessList(rdr, tx);
readBlob(rdr, tx);
tryReadSignature(rdr, tx);
ensureFullyRead(rdr);
return tx;
}

private RlpReader startReader(byte[] raw, int position) {
RlpReader toprdr = new RlpReader(raw, position, raw.length - position);
if (toprdr.getType() != RlpType.LIST) {
Expand Down Expand Up @@ -210,6 +231,26 @@ private void readAccessList(RlpReader rdr, TransactionWithAccess tx) {
}
}

protected void readBlob(RlpReader rdr, TransactionWithBlob tx) {
// The field max_fee_per_blob_gas is a uint256
if (rdr.hasNext() && rdr.getType() == RlpType.BYTES) {
tx.setMaxFeePerBlobGas(new Wei(rdr.nextBigInt()));
} else {
throw new IllegalArgumentException("Transaction has invalid RLP encoding. Cannot extract: Max Fee Per Blob Gas");
}
// the field blob_versioned_hashes represents a list of hash outputs from kzg_to_versioned_hash.
if (rdr.hasNext() && rdr.getType() == RlpType.LIST) {
RlpReader blobVersionedHashesRdr = rdr.nextList();
List<Hex32> blobVersionedHashes = new ArrayList<>();
while (blobVersionedHashesRdr.hasNext()) {
blobVersionedHashes.add(Hex32.from(blobVersionedHashesRdr.next()));
}
tx.setBlobVersionedHashes(blobVersionedHashes);
} else {
throw new IllegalArgumentException("Transaction has invalid RLP encoding. Not a list: Blob Versioned Hashes");
}
}

protected void readDefinitionsPart(RlpReader rdr, Transaction tx) {
readNonce(rdr, tx);
readGasPrice(rdr, tx);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,20 @@ public byte[] encode(Transaction tx, boolean includeSignature) {
if (tx.getType() == TransactionType.GAS_PRIORITY) {
return encode((TransactionWithGasPriority) tx, includeSignature);
}
if (tx.getType() == TransactionType.BLOB) {
return encode((TransactionWithBlob) tx, includeSignature);
}
if (tx.getType() == TransactionType.ACCESS_LIST) {
return encode((TransactionWithAccess) tx, includeSignature);
}
if (tx.getType() == TransactionType.STANDARD) {
if (includeSignature) {
return encode(tx, true, null);
return encodeStandard(tx, true, null);
} else {
Signature signature = tx.getSignature();
if (signature.getType() == SignatureType.EIP155) {
int chainId = ((SignatureEIP155)signature).getChainId();
return encode(tx, false, chainId);
return encodeStandard(tx, false, chainId);
} else {
throw new IllegalStateException("Neither signature nor chainId specified");
}
Expand All @@ -48,7 +51,7 @@ public byte[] encode(Transaction tx, boolean includeSignature) {
throw new IllegalStateException("Unsupported transaction type: " + tx.getType());
}

public byte[] encode(Transaction tx, boolean includeSignature, Integer chainId) {
protected byte[] encodeStandard(Transaction tx, boolean includeSignature, Integer chainId) {
RlpWriter wrt = new RlpWriter();
wrt.startList()
.write(tx.getNonce())
Expand Down Expand Up @@ -90,7 +93,7 @@ public byte[] encode(Transaction tx, boolean includeSignature, Integer chainId)
return wrt.toByteArray();
}

protected void writeBody(RlpWriter wrt, TransactionWithAccess tx, boolean includeSignature) {
protected void writeBody(RlpWriter wrt, Transaction tx) {
if (tx.getTo() != null) {
wrt.write(tx.getTo().getBytes());
} else {
Expand All @@ -108,7 +111,9 @@ protected void writeBody(RlpWriter wrt, TransactionWithAccess tx, boolean includ
} else {
wrt.write(new byte[0]);
}
}

private static void writeAccessList(RlpWriter wrt, TransactionWithAccess tx) {
wrt.startList();
for (TransactionWithAccess.Access access: tx.getAccessList()) {
wrt.startList();
Expand All @@ -121,27 +126,35 @@ protected void writeBody(RlpWriter wrt, TransactionWithAccess tx, boolean includ
wrt.closeList();
}
wrt.closeList();
}

if (includeSignature) {
Signature signature = tx.getSignature();
if (signature == null) {
// just empty signature
wrt.write(0)
.write(0)
.write(0);
} else {
if (signature.getType() == SignatureType.EIP2930) {
int yParity = ((SignatureEIP2930)signature).getYParity();
if (yParity == 0) {
wrt.write(0);
} else {
wrt.write(Integer.valueOf(yParity).byteValue());
}
wrt.write(signature.getR());
wrt.write(signature.getS());
private static void writeBlob(RlpWriter wrt, TransactionWithBlob tx) {
wrt.write(tx.getMaxFeePerBlobGas().getAmount());
wrt.startList();
for (Hex32 hash: tx.getBlobVersionedHashes()) {
wrt.write(hash.getBytes());
}
wrt.closeList();
}

private static void writeSignature(RlpWriter wrt, Signature signature) {
if (signature == null) {
// just empty signature
wrt.write(0)
.write(0)
.write(0);
} else {
if (signature.getType() == SignatureType.EIP2930) {
int yParity = ((SignatureEIP2930)signature).getYParity();
if (yParity == 0) {
wrt.write(0);
} else {
throw new ClassCastException("Required signature " + SignatureEIP2930.class.getName() + " but have " + signature.getClass().getName());
wrt.write(Integer.valueOf(yParity).byteValue());
}
wrt.write(signature.getR());
wrt.write(signature.getS());
} else {
throw new ClassCastException("Required signature " + SignatureEIP2930.class.getName() + " but have " + signature.getClass().getName());
}
}
}
Expand All @@ -155,7 +168,11 @@ public byte[] encode(TransactionWithAccess tx, boolean includeSignature) {
.write(tx.getNonce())
.write(tx.getGasPrice().getAmount())
.write(tx.getGas());
writeBody(wrt, tx, includeSignature);
writeBody(wrt, tx);
writeAccessList(wrt, tx);
if (includeSignature) {
writeSignature(wrt, tx.getSignature());
}
wrt.closeList();
return buffer.toByteArray();
}
Expand All @@ -170,7 +187,31 @@ public byte[] encode(TransactionWithGasPriority tx, boolean includeSignature) {
.write(tx.getPriorityGasPrice().getAmount())
.write(tx.getMaxGasPrice().getAmount())
.write(tx.getGas());
writeBody(wrt, tx, includeSignature);
writeBody(wrt, tx);
writeAccessList(wrt, tx);
if (includeSignature) {
writeSignature(wrt, tx.getSignature());
}
wrt.closeList();
return buffer.toByteArray();
}

public byte[] encode(TransactionWithBlob tx, boolean includeSignature) {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
buffer.write(TransactionType.BLOB.getFlag());
RlpWriter wrt = new RlpWriter(buffer);
wrt.startList()
.write(tx.getChainId())
.write(tx.getNonce())
.write(tx.getPriorityGasPrice().getAmount())
.write(tx.getMaxGasPrice().getAmount())
.write(tx.getGas());
writeBody(wrt, tx);
writeAccessList(wrt, tx);
writeBlob(wrt, tx);
if (includeSignature) {
writeSignature(wrt, tx.getSignature());
}
wrt.closeList();
return buffer.toByteArray();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,13 @@ public enum TransactionType {
* Transaction with Gas Max and Priority prices.
* @see <a href="https://eips.ethereum.org/EIPS/eip-1559">EIP-1559</a>
*/
GAS_PRIORITY((byte)2);
GAS_PRIORITY((byte)2),

/**
* Blob transaction
* @see <a href="https://eips.ethereum.org/EIPS/eip-4844">EIP-4844</a>
*/
BLOB((byte)3);

private final byte flag;

Expand Down Expand Up @@ -63,6 +69,9 @@ public static TransactionType fromPrefix(byte prefix) {
if (u == 2) {
return TransactionType.GAS_PRIORITY;
}
if (u == 3) {
return TransactionType.BLOB;
}
if (u == 1) {
return TransactionType.ACCESS_LIST;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package io.emeraldpay.etherjar.tx;

import io.emeraldpay.etherjar.domain.Wei;
import io.emeraldpay.etherjar.hex.Hex32;
import org.bouncycastle.jcajce.provider.digest.Keccak;

import java.util.List;

/**
* Transaction with Blobs (EIP-2718)
*
* @see <a href="https://eips.ethereum.org/EIPS/eip-2718">EIP-2718</a>
*/
public class TransactionWithBlob extends TransactionWithGasPriority {

private Wei maxFeePerBlobGas;

/**
* Represents a list of hash outputs from kzg_to_versioned_hash
*/
private List<Hex32> blobVersionedHashes;

public Wei getMaxFeePerBlobGas() {
return maxFeePerBlobGas;
}

public void setMaxFeePerBlobGas(Wei maxFeePerBlobGas) {
this.maxFeePerBlobGas = maxFeePerBlobGas;
}

public List<Hex32> getBlobVersionedHashes() {
return blobVersionedHashes;
}

public void setBlobVersionedHashes(List<Hex32> blobVersionedHashes) {
this.blobVersionedHashes = blobVersionedHashes;
}

@Override
public TransactionType getType() {
return TransactionType.BLOB;
}

@Override
public byte[] hash() {
byte[] rlp = TransactionEncoder.DEFAULT.encode(this, false);
Keccak.Digest256 keccak = new Keccak.Digest256();
keccak.update(rlp);
return keccak.digest();
}
}
Loading

0 comments on commit 0b06f99

Please sign in to comment.