Skip to content

[ETCM-1095] eip2930 receipt #1103

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

Merged
merged 2 commits into from
Sep 8, 2021
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
18 changes: 15 additions & 3 deletions src/main/scala/io/iohk/ethereum/db/storage/ReceiptStorage.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import akka.util.ByteString
import boopickle.Default.Pickle
import boopickle.Default.Unpickle
import boopickle.DefaultBasic._
import boopickle.Pickler

import io.iohk.ethereum.crypto.ECDSASignature
import io.iohk.ethereum.db.dataSource.DataSource
import io.iohk.ethereum.db.storage.ReceiptStorage._
import io.iohk.ethereum.domain.Address
Expand Down Expand Up @@ -63,11 +65,21 @@ object ReceiptStorage {
TxLogEntry(address, topics, data)
}(entry => (entry.loggerAddress, entry.logTopics, entry.data))

implicit val receiptPickler: Pickler[Receipt] =
transformPickler[Receipt, (TransactionOutcome, BigInt, ByteString, Seq[TxLogEntry])] {
case (state, gas, filter, logs) => new Receipt(state, gas, filter, logs)
implicit val legacyReceiptPickler: Pickler[LegacyReceipt] =
transformPickler[LegacyReceipt, (TransactionOutcome, BigInt, ByteString, Seq[TxLogEntry])] {
case (state, gas, filter, logs) => LegacyReceipt(state, gas, filter, logs)
} { receipt =>
(receipt.postTransactionStateHash, receipt.cumulativeGasUsed, receipt.logsBloomFilter, receipt.logs)
}

implicit val type01ReceiptPickler: Pickler[Type01Receipt] =
transformPickler[Type01Receipt, (TransactionOutcome, BigInt, ByteString, Seq[TxLogEntry])] {
case (state, gas, filter, logs) => Type01Receipt(LegacyReceipt(state, gas, filter, logs))
} { receipt =>
(receipt.postTransactionStateHash, receipt.cumulativeGasUsed, receipt.logsBloomFilter, receipt.logs)
}

implicit val receiptPickler: Pickler[Receipt] = compositePickler[Receipt]
.addConcreteType[LegacyReceipt]
.addConcreteType[Type01Receipt]
}
48 changes: 42 additions & 6 deletions src/main/scala/io/iohk/ethereum/domain/Receipt.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,51 @@ import org.bouncycastle.util.encoders.Hex

import io.iohk.ethereum.mpt.ByteArraySerializable

sealed trait Receipt {
def postTransactionStateHash: TransactionOutcome
def cumulativeGasUsed: BigInt
def logsBloomFilter: ByteString
def logs: Seq[TxLogEntry]
}

// shared structure for EIP-2930, EIP-1559
abstract class TypedLegacyReceipt(transactionTypeId: Byte, val delegateReceipt: LegacyReceipt) extends Receipt {
def postTransactionStateHash: TransactionOutcome = delegateReceipt.postTransactionStateHash
def cumulativeGasUsed: BigInt = delegateReceipt.cumulativeGasUsed
def logsBloomFilter: ByteString = delegateReceipt.logsBloomFilter
def logs: Seq[TxLogEntry] = delegateReceipt.logs
}

object Receipt {

val byteArraySerializable: ByteArraySerializable[Receipt] = new ByteArraySerializable[Receipt] {

import io.iohk.ethereum.network.p2p.messages.ETH63.ReceiptImplicits._

override def fromBytes(bytes: Array[Byte]): Receipt = bytes.toReceipt

override def toBytes(input: Receipt): Array[Byte] = input.toBytes
}
}

object LegacyReceipt {
def withHashOutcome(
postTransactionStateHash: ByteString,
cumulativeGasUsed: BigInt,
logsBloomFilter: ByteString,
logs: Seq[TxLogEntry]
): Receipt =
Receipt(HashOutcome(postTransactionStateHash), cumulativeGasUsed, logsBloomFilter, logs)
): LegacyReceipt =
LegacyReceipt(HashOutcome(postTransactionStateHash), cumulativeGasUsed, logsBloomFilter, logs)
}

object Type01Receipt {
def withHashOutcome(
postTransactionStateHash: ByteString,
cumulativeGasUsed: BigInt,
logsBloomFilter: ByteString,
logs: Seq[TxLogEntry]
): Type01Receipt =
Type01Receipt(LegacyReceipt.withHashOutcome(postTransactionStateHash, cumulativeGasUsed, logsBloomFilter, logs))
}

/** @param postTransactionStateHash For blocks where block.number >= byzantium-block-number (from config),
Expand All @@ -35,24 +62,33 @@ object Receipt {
*
* More description: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-658.md
*/
case class Receipt(
case class LegacyReceipt(
postTransactionStateHash: TransactionOutcome,
cumulativeGasUsed: BigInt,
logsBloomFilter: ByteString,
logs: Seq[TxLogEntry]
) {
override def toString: String = {
) extends Receipt {
def toPrettyString(prefix: String): String = {
val stateHash = postTransactionStateHash match {
case HashOutcome(hash) => hash.toArray[Byte]
case SuccessOutcome => Array(1.toByte)
case _ => Array(0.toByte)
}

s"Receipt{ " +
s"${prefix}{ " +
s"postTransactionStateHash: ${Hex.toHexString(stateHash)}, " +
s"cumulativeGasUsed: $cumulativeGasUsed, " +
s"logsBloomFilter: ${Hex.toHexString(logsBloomFilter.toArray[Byte])}, " +
s"logs: $logs" +
s"}"
}

override def toString: String = toPrettyString("LegacyReceipt")
}

/** EIP-2930 receipt for Transaction type 1
* @param legacyReceipt
*/
case class Type01Receipt(legacyReceipt: LegacyReceipt) extends TypedLegacyReceipt(Transaction.Type01, legacyReceipt) {
override def toString: String = legacyReceipt.toPrettyString("Type01Receipt")
}
25 changes: 19 additions & 6 deletions src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -311,12 +311,25 @@ class BlockPreparator(
HashOutcome(newWorld.stateRootHash)
}

val receipt = Receipt(
postTransactionStateHash = transactionOutcome,
cumulativeGasUsed = acumGas + gasUsed,
logsBloomFilter = BloomFilter.create(logs),
logs = logs
)
val receipt = stx.tx match {
case _: LegacyTransaction =>
LegacyReceipt(
postTransactionStateHash = transactionOutcome,
cumulativeGasUsed = acumGas + gasUsed,
logsBloomFilter = BloomFilter.create(logs),
logs = logs
)

case _: TransactionWithAccessList =>
Type01Receipt(
LegacyReceipt(
postTransactionStateHash = transactionOutcome,
cumulativeGasUsed = acumGas + gasUsed,
logsBloomFilter = BloomFilter.create(logs),
logs = logs
)
)
}

log.debug(s"Receipt generated for tx ${stx.hash.toHex}, $receipt")

Expand Down
35 changes: 28 additions & 7 deletions src/main/scala/io/iohk/ethereum/network/p2p/messages/ETH63.scala
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,12 @@ object ETH63 {
case SuccessOutcome => 1.toByte
case _ => 0.toByte
}
RLPList(stateHash, cumulativeGasUsed, logsBloomFilter, RLPList(logs.map(_.toRLPEncodable): _*))
val legacyRLPReceipt =
RLPList(stateHash, cumulativeGasUsed, logsBloomFilter, RLPList(logs.map(_.toRLPEncodable): _*))
receipt match {
case _: LegacyReceipt => legacyRLPReceipt
case _: Type01Receipt => PrefixedRLPEncodable(Transaction.Type01, legacyRLPReceipt)
}
}
}

Expand All @@ -180,25 +185,39 @@ object ETH63 {
}

implicit class ReceiptDec(val bytes: Array[Byte]) extends AnyVal {
def toReceipt: Receipt = ReceiptRLPEncodableDec(rawDecode(bytes)).toReceipt
import BaseETH6XMessages.TypedTransaction._

def toReceipt: Receipt = {
val first = bytes(0)
(first match {
case Transaction.Type01 => PrefixedRLPEncodable(Transaction.Type01, rawDecode(bytes.tail))
case _ => rawDecode(bytes)
}).toReceipt
}

def toReceipts: Seq[Receipt] = rawDecode(bytes) match {
case RLPList(items @ _*) => items.map(_.toReceipt)
case RLPList(items @ _*) => items.toTypedRLPEncodables.map(_.toReceipt)
case _ => throw new RuntimeException("Cannot decode Receipts")
}
}

implicit class ReceiptRLPEncodableDec(val rlpEncodeable: RLPEncodeable) extends AnyVal {
def toReceipt: Receipt = rlpEncodeable match {

def toLegacyReceipt: LegacyReceipt = rlpEncodeable match {
case RLPList(postTransactionStateHash, cumulativeGasUsed, logsBloomFilter, logs: RLPList) =>
val stateHash = postTransactionStateHash match {
case RLPValue(bytes) if bytes.length > 1 => HashOutcome(ByteString(bytes))
case RLPValue(bytes) if bytes.length == 1 && bytes.head == 1 => SuccessOutcome
case _ => FailureOutcome
}
Receipt(stateHash, cumulativeGasUsed, logsBloomFilter, logs.items.map(_.toTxLogEntry))
LegacyReceipt(stateHash, cumulativeGasUsed, logsBloomFilter, logs.items.map(_.toTxLogEntry))
case _ => throw new RuntimeException("Cannot decode Receipt")
}

def toReceipt: Receipt = rlpEncodeable match {
case PrefixedRLPEncodable(Transaction.Type01, legacyReceipt) => Type01Receipt(legacyReceipt.toLegacyReceipt)
case other => other.toLegacyReceipt
}
}
}

Expand All @@ -217,10 +236,12 @@ object ETH63 {

implicit class ReceiptsDec(val bytes: Array[Byte]) extends AnyVal {
import ReceiptImplicits._
import BaseETH6XMessages.TypedTransaction._

def toReceipts: Receipts = rawDecode(bytes) match {
case rlpList: RLPList => Receipts(rlpList.items.collect { case r: RLPList => r.items.map(_.toReceipt) })
case _ => throw new RuntimeException("Cannot decode Receipts")
case rlpList: RLPList =>
Receipts(rlpList.items.collect { case r: RLPList => r.items.toTypedRLPEncodables.map(_.toReceipt) })
case _ => throw new RuntimeException("Cannot decode Receipts")
}
}
}
Expand Down
27 changes: 16 additions & 11 deletions src/test/scala/io/iohk/ethereum/ObjectGenerators.scala
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,33 @@ trait ObjectGenerators {
arrayList <- Gen.nonEmptyListOf(byteArrayOfNItemsGen(size))
} yield byteStringList.zip(arrayList)

def receiptGen(): Gen[Receipt] = for {
def receiptGen: Gen[Receipt] =
Gen.oneOf(legacyReceiptGen, type01ReceiptGen)

def legacyReceiptGen: Gen[LegacyReceipt] = for {
postTransactionStateHash <- byteArrayOfNItemsGen(32)
cumulativeGasUsed <- bigIntGen
logsBloomFilter <- byteArrayOfNItemsGen(256)
} yield Receipt.withHashOutcome(
} yield LegacyReceipt.withHashOutcome(
postTransactionStateHash = ByteString(postTransactionStateHash),
cumulativeGasUsed = cumulativeGasUsed,
logsBloomFilter = ByteString(logsBloomFilter),
logs = Seq()
)

def type01ReceiptGen: Gen[Type01Receipt] = legacyReceiptGen.map(Type01Receipt(_))

def addressGen: Gen[Address] = byteArrayOfNItemsGen(20).map(Address(_))

def accessListItemGen(): Gen[AccessListItem] = for {
def accessListItemGen: Gen[AccessListItem] = for {
address <- addressGen
storageKeys <- Gen.listOf(bigIntGen)
} yield AccessListItem(address, storageKeys)

def transactionGen(): Gen[Transaction] =
Gen.oneOf(legacyTransactionGen(), typedTransactionGen())
def transactionGen: Gen[Transaction] =
Gen.oneOf(legacyTransactionGen, typedTransactionGen)

def legacyTransactionGen(): Gen[LegacyTransaction] = for {
def legacyTransactionGen: Gen[LegacyTransaction] = for {
nonce <- bigIntGen
gasPrice <- bigIntGen
gasLimit <- bigIntGen
Expand All @@ -106,15 +111,15 @@ trait ObjectGenerators {
payload
)

def typedTransactionGen(): Gen[TransactionWithAccessList] = for {
def typedTransactionGen: Gen[TransactionWithAccessList] = for {
chainId <- bigIntGen
nonce <- bigIntGen
gasPrice <- bigIntGen
gasLimit <- bigIntGen
receivingAddress <- addressGen
value <- bigIntGen
payload <- byteStringOfLengthNGen(256)
accessList <- Gen.listOf(accessListItemGen())
accessList <- Gen.listOf(accessListItemGen)
} yield TransactionWithAccessList(
chainId,
nonce,
Expand All @@ -126,7 +131,7 @@ trait ObjectGenerators {
accessList
)

def receiptsGen(n: Int): Gen[Seq[Seq[Receipt]]] = Gen.listOfN(n, Gen.listOf(receiptGen()))
def receiptsGen(n: Int): Gen[Seq[Seq[Receipt]]] = Gen.listOfN(n, Gen.listOf(receiptGen))

def branchNodeGen: Gen[BranchNode] = for {
children <- Gen
Expand Down Expand Up @@ -167,7 +172,7 @@ trait ObjectGenerators {

def signedTxSeqGen(length: Int, secureRandom: SecureRandom, chainId: Option[Byte]): Gen[Seq[SignedTransaction]] = {
val senderKeys = crypto.generateKeyPair(secureRandom)
val txsSeqGen = Gen.listOfN(length, transactionGen())
val txsSeqGen = Gen.listOfN(length, transactionGen)
txsSeqGen.map { txs =>
txs.map { tx =>
SignedTransaction.sign(tx, senderKeys, chainId)
Expand All @@ -178,7 +183,7 @@ trait ObjectGenerators {
def signedTxGen(secureRandom: SecureRandom, chainId: Option[Byte]): Gen[SignedTransaction] = {
val senderKeys = crypto.generateKeyPair(secureRandom)
for {
tx <- transactionGen()
tx <- transactionGen
} yield SignedTransaction.sign(tx, senderKeys, chainId)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,28 +179,28 @@ class StdBlockValidatorSpec extends AnyFlatSpec with Matchers with SecureRandomB
.header

val validReceipts: Seq[Receipt] = Seq(
Receipt.withHashOutcome(
LegacyReceipt.withHashOutcome(
postTransactionStateHash =
ByteString(Hex.decode("ce0ac687bb90d457b6573d74e4a25ea7c012fee329eb386dbef161c847f9842d")),
cumulativeGasUsed = 21000,
logsBloomFilter = ByteString(Hex.decode("0" * 512)),
logs = Seq[TxLogEntry]()
),
Receipt.withHashOutcome(
LegacyReceipt.withHashOutcome(
postTransactionStateHash =
ByteString(Hex.decode("b927d361126302acaa1fa5e93d0b7e349e278231fe2fc2846bfd54f50377f20a")),
cumulativeGasUsed = 42000,
logsBloomFilter = ByteString(Hex.decode("0" * 512)),
logs = Seq[TxLogEntry]()
),
Receipt.withHashOutcome(
LegacyReceipt.withHashOutcome(
postTransactionStateHash =
ByteString(Hex.decode("1e913d6bdd412d71292173d7908f8792adcf958b84c89575bc871a1decaee56d")),
cumulativeGasUsed = 63000,
logsBloomFilter = ByteString(Hex.decode("0" * 512)),
logs = Seq[TxLogEntry]()
),
Receipt.withHashOutcome(
LegacyReceipt.withHashOutcome(
postTransactionStateHash =
ByteString(Hex.decode("0c6e052bc83482bafaccffc4217adad49f3a9533c69c820966d75ed0154091e6")),
cumulativeGasUsed = 84000,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,20 @@ class SignedLegacyTransactionSpec
with ScalaCheckPropertyChecks
with SecureRandomBuilder {
"SignedTransaction" should "correctly set pointSign for chainId with chain specific signing schema" in {
forAll(Generators.transactionGen(), Arbitrary.arbitrary[Unit].map(_ => generateKeyPair(secureRandom))) {
(tx, key) =>
val chainId: Byte = 0x3d
val allowedPointSigns = Set((chainId * 2 + 35).toByte, (chainId * 2 + 36).toByte)
//byte 0 of encoded ECC point indicates that it is uncompressed point, it is part of bouncycastle encoding
val address = Address(
crypto
.kec256(key.getPublic.asInstanceOf[ECPublicKeyParameters].getQ.getEncoded(false).tail)
.drop(FirstByteOfAddress)
)
val signedTransaction = SignedTransaction.sign(tx, key, Some(chainId))
val result = SignedTransactionWithSender(signedTransaction, Address(key))
forAll(Generators.transactionGen, Arbitrary.arbitrary[Unit].map(_ => generateKeyPair(secureRandom))) { (tx, key) =>
val chainId: Byte = 0x3d
val allowedPointSigns = Set((chainId * 2 + 35).toByte, (chainId * 2 + 36).toByte)
//byte 0 of encoded ECC point indicates that it is uncompressed point, it is part of bouncycastle encoding
val address = Address(
crypto
.kec256(key.getPublic.asInstanceOf[ECPublicKeyParameters].getQ.getEncoded(false).tail)
.drop(FirstByteOfAddress)
)
val signedTransaction = SignedTransaction.sign(tx, key, Some(chainId))
val result = SignedTransactionWithSender(signedTransaction, Address(key))

allowedPointSigns should contain(result.tx.signature.v)
address shouldEqual result.senderAddress
allowedPointSigns should contain(result.tx.signature.v)
address shouldEqual result.senderAddress
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ class EthTxServiceSpec

val contractCreatingTransactionSender: Address = SignedTransaction.getSender(contractCreatingTransaction).get

val fakeReceipt: Receipt = Receipt.withHashOutcome(
val fakeReceipt: LegacyReceipt = LegacyReceipt.withHashOutcome(
postTransactionStateHash = ByteString(Hex.decode("01" * 32)),
cumulativeGasUsed = 43,
logsBloomFilter = ByteString(Hex.decode("00" * 256)),
Expand Down
Loading