Skip to content

Commit

Permalink
[ETCM-921] transaction with access list (#1094)
Browse files Browse the repository at this point in the history
* [ETCM-911] Replace LegacyTX with trait Transaction in SignedTransaction

* [ETCM-911] Drop unused 'chainId' parameter from constructor

* [ETCM-911] Add rlp serialization basics

* [ETCM-911] Add rlp tests showing compatibility with geth

* [ETCM-921] rlp access list encoding + unit tests

* [ETCM-921] formatting + code review changes

* [ETCM-921] code review feedback

* [ETCM-921] read chainId from rlp transaction instead of config

* [ETCM-921] rlp encoding and decoding test for Seq[SignedTransaction]

* [ETCM-921] Introduce PrefixedRLPEncodable to handle binary prefixed RLP items

* [ETCM-921] properly decode SignedTransaction in BlockBody + scaladoc

* [ETCM-921] Clean magic number and add unit test for PrefixedRLPEncodable

* [ETCM-921] Code review feedback changes

Co-authored-by: Dominik Zajkowski <dzajkowski@invisibl.es>
  • Loading branch information
strauss-m and dzajkowski committed Aug 23, 2021
1 parent 9178c64 commit 332959a
Show file tree
Hide file tree
Showing 24 changed files with 470 additions and 112 deletions.
2 changes: 2 additions & 0 deletions rlp/src/main/scala/io/iohk/ethereum/rlp/RLP.scala
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ private[rlp] object RLP {
val inputAsBytes = value.bytes
if (inputAsBytes.length == 1 && (inputAsBytes(0) & 0xff) < 0x80) inputAsBytes
else encodeLength(inputAsBytes.length, OffsetShortItem) ++ inputAsBytes
case PrefixedRLPEncodable(prefix, prefixedRLPEncodeable) =>
prefix +: encode(prefixedRLPEncodeable)
}

/** This function transform a byte into byte array
Expand Down
17 changes: 17 additions & 0 deletions rlp/src/main/scala/io/iohk/ethereum/rlp/package.scala
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,23 @@ package object rlp {
override def toString: String = s"RLPValue(${Hex.toHexString(bytes)})"
}

/** Modelise a RLPEncodable that should be binary prefixed by a raw byte.
*
* When converting this RLPEncodable to byte, the resulting value will be:
* prefix || prefixedRLPEncodable.toByte
* where || is the binary concatenation symbol.
*
* To be able to read back the data, use TypedTransaction.TypedTransactionsRLPAggregator
*
* This is for example used for typed transaction and typed receipt.
*
* @param prefix the raw byte
* @param prefixedRLPEncodeable the RLPEncodable to prefix with
*/
case class PrefixedRLPEncodable(prefix: Byte, prefixedRLPEncodeable: RLPEncodeable) extends RLPEncodeable {
require(prefix >= 0, "prefix should be in the range [0; 0x7f]")
}

trait RLPEncoder[T] {
def encode(obj: T): RLPEncodeable
}
Expand Down
3 changes: 1 addition & 2 deletions src/benchmark/scala/io/iohk/ethereum/rlp/RLPSpeedSuite.scala
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,7 @@ class RLPSpeedSuite
),
pointSign = 28,
signatureRandom = ByteString(Hex.decode("cfe3ad31d6612f8d787c45f115cc5b43fb22bcc210b62ae71dc7cbf0a6bea8df")),
signature = ByteString(Hex.decode("57db8998114fae3c337e99dbd8573d4085691880f4576c6c1f6c5bbfe67d6cf0")),
chainId = 0x3d.toByte
signature = ByteString(Hex.decode("57db8998114fae3c337e99dbd8573d4085691880f4576c6c1f6c5bbfe67d6cf0"))
)

lazy val blockGen: Gen[Block] = for {
Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/io/iohk/ethereum/domain/Block.scala
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ object Block {

implicit class BlockDec(val bytes: Array[Byte]) extends AnyVal {
import io.iohk.ethereum.network.p2p.messages.BaseETH6XMessages.SignedTransactions._
import io.iohk.ethereum.network.p2p.messages.BaseETH6XMessages.TypedTransaction._
def toBlock: Block = rawDecode(bytes) match {
case RLPList(header: RLPList, stx: RLPList, uncles: RLPList) =>
Block(
header.toBlockHeader,
BlockBody(
stx.items.map(_.toSignedTransaction),
stx.items.toTypedRLPEncodables.map(_.toSignedTransaction),
uncles.items.map(_.toBlockHeader)
)
)
Expand Down
4 changes: 3 additions & 1 deletion src/main/scala/io/iohk/ethereum/domain/BlockBody.scala
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ object BlockBody {

val empty: BlockBody = BlockBody(Seq.empty, Seq.empty)

import io.iohk.ethereum.network.p2p.messages.BaseETH6XMessages.TypedTransaction._

def blockBodyToRlpEncodable(
blockBody: BlockBody,
signedTxToRlpEncodable: SignedTransaction => RLPEncodeable,
Expand Down Expand Up @@ -57,7 +59,7 @@ object BlockBody {
rlpEncodeable match {
case RLPList((transactions: RLPList), (uncles: RLPList)) =>
BlockBody(
transactions.items.map(rlpEncodableToSignedTransaction),
transactions.items.toTypedRLPEncodables.map(rlpEncodableToSignedTransaction),
uncles.items.map(rlpEncodableToBlockHeader)
)
case _ => throw new RuntimeException("Cannot decode BlockBody")
Expand Down
44 changes: 21 additions & 23 deletions src/main/scala/io/iohk/ethereum/domain/SignedTransaction.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import io.iohk.ethereum.network.p2p.messages.BaseETH6XMessages.SignedTransaction
import io.iohk.ethereum.rlp.RLPImplicitConversions._
import io.iohk.ethereum.rlp.RLPImplicits._
import io.iohk.ethereum.rlp.{encode => rlpEncode, _}
import io.iohk.ethereum.utils.Config

object SignedTransaction {

Expand Down Expand Up @@ -50,22 +51,7 @@ object SignedTransaction {
val valueForEmptyS = 0

def apply(
tx: LegacyTransaction,
pointSign: Byte,
signatureRandom: ByteString,
signature: ByteString,
chainId: Byte
): SignedTransaction = {
val txSignature = ECDSASignature(
r = new BigInteger(1, signatureRandom.toArray),
s = new BigInteger(1, signature.toArray),
v = pointSign
)
SignedTransaction(tx, txSignature)
}

def apply(
tx: LegacyTransaction,
tx: Transaction,
pointSign: Byte,
signatureRandom: ByteString,
signature: ByteString
Expand All @@ -79,7 +65,7 @@ object SignedTransaction {
}

def sign(
tx: LegacyTransaction,
tx: Transaction,
keyPair: AsymmetricCipherKeyPair,
chainId: Option[Byte]
): SignedTransaction = {
Expand All @@ -101,13 +87,14 @@ object SignedTransaction {

private def calculateSender(tx: SignedTransaction): Option[Address] = Try {
val ECDSASignature(_, _, v) = tx.signature
val bytesToSign: Array[Byte] = if (v == ECDSASignature.negativePointSign || v == ECDSASignature.positivePointSign) {
generalTransactionBytes(tx.tx)
} else {
chainSpecificTransactionBytes(tx.tx, chainId)
// chainId specific code that will be refactored with the Signer feature (ETCM-1096)
val chainIdOpt = extractChainId(tx)
val bytesToSign: Array[Byte] = chainIdOpt match {
case None => generalTransactionBytes(tx.tx)
case Some(chainId) => chainSpecificTransactionBytes(tx.tx, chainId)
}

val recoveredPublicKey: Option[Array[Byte]] = tx.signature.publicKey(bytesToSign, Some(chainId))
val recoveredPublicKey: Option[Array[Byte]] = tx.signature.publicKey(bytesToSign, chainIdOpt)

for {
key <- recoveredPublicKey
Expand Down Expand Up @@ -157,6 +144,17 @@ object SignedTransaction {
)
}

private def extractChainId(stx: SignedTransaction): Option[Byte] = {
val chainIdOpt: Option[BigInt] = stx.tx match {
case _: LegacyTransaction
if stx.signature.v == ECDSASignature.negativePointSign || stx.signature.v == ECDSASignature.positivePointSign =>
None
case _: LegacyTransaction => Some(Config.blockchains.blockchainConfig.chainId)
case twal: TransactionWithAccessList => Some(twal.chainId)
}
chainIdOpt.map(_.toByte)
}

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

override def fromBytes(bytes: Array[Byte]): SignedTransaction = bytes.toSignedTransaction
Expand All @@ -165,7 +163,7 @@ object SignedTransaction {
}
}

case class SignedTransaction(tx: LegacyTransaction, signature: ECDSASignature) {
case class SignedTransaction(tx: Transaction, signature: ECDSASignature) {

def safeSenderIsEqualTo(address: Address): Boolean =
SignedTransaction.getSender(this).contains(address)
Expand Down
38 changes: 34 additions & 4 deletions src/main/scala/io/iohk/ethereum/domain/Transaction.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import akka.util.ByteString

import org.bouncycastle.util.encoders.Hex

sealed trait Transaction {
sealed trait Transaction extends Product with Serializable {
def nonce: BigInt
def gasPrice: BigInt
def gasLimit: BigInt
Expand All @@ -22,15 +22,31 @@ sealed trait Transaction {
}

object Transaction {
val Type01: Int = 1
val Type01: Byte = 1.toByte

val MinAllowedType: Byte = 0
val MaxAllowedType: Byte = 0x7f

val LegacyThresholdLowerBound: Int = 0xc0
val LegacyThresholdUpperBound: Int = 0xfe

def withGasLimit(gl: BigInt): Transaction => Transaction = {
case tx: LegacyTransaction => tx.copy(gasLimit = gl)
case tx: TransactionWithAccessList => tx.copy(gasLimit = gl)
}

implicit class TransactionTypeValidator(val transactionType: Byte) extends AnyVal {
def isValidTransactionType: Boolean = transactionType >= MinAllowedType && transactionType <= MaxAllowedType
}

implicit class ByteArrayTransactionTypeValidator(val binaryData: Array[Byte]) extends AnyVal {
def isValidTransactionType: Boolean = binaryData.length == 1 && binaryData.head.isValidTransactionType
}
}

sealed trait TypedTransaction extends Transaction

object LegacyTransaction {

val NonceLength = 32
val GasLength = 32
val ValueLength = 32
Expand All @@ -44,7 +60,6 @@ object LegacyTransaction {
payload: ByteString
): LegacyTransaction =
LegacyTransaction(nonce, gasPrice, gasLimit, Some(receivingAddress), value, payload)

}

case class LegacyTransaction(
Expand All @@ -66,7 +81,22 @@ case class LegacyTransaction(
s"}"
}

object TransactionWithAccessList {
def apply(
chainId: BigInt,
nonce: BigInt,
gasPrice: BigInt,
gasLimit: BigInt,
receivingAddress: Address,
value: BigInt,
payload: ByteString,
accessList: List[AccessListItem]
): TransactionWithAccessList =
TransactionWithAccessList(chainId, nonce, gasPrice, gasLimit, Some(receivingAddress), value, payload, accessList)
}

case class TransactionWithAccessList(
chainId: BigInt,
nonce: BigInt,
gasPrice: BigInt,
gasLimit: BigInt,
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/io/iohk/ethereum/ledger/BlockPreparator.scala
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,14 @@ class BlockPreparator(
* @param tx Target transaction
* @return Upfront cost
*/
private[ledger] def calculateUpfrontGas(tx: LegacyTransaction): UInt256 = UInt256(tx.gasLimit * tx.gasPrice)
private[ledger] def calculateUpfrontGas(tx: Transaction): UInt256 = UInt256(tx.gasLimit * tx.gasPrice)

/** v0 ≡ Tg (Tx gas limit) * Tp (Tx gas price) + Tv (Tx value). See YP equation number (65)
*
* @param tx Target transaction
* @return Upfront cost
*/
private[ledger] def calculateUpfrontCost(tx: LegacyTransaction): UInt256 =
private[ledger] def calculateUpfrontCost(tx: Transaction): UInt256 =
UInt256(calculateUpfrontGas(tx) + tx.value)

/** Increments account nonce by 1 stated in YP equation (69) and
Expand Down
7 changes: 6 additions & 1 deletion src/main/scala/io/iohk/ethereum/ledger/StxLedger.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import io.iohk.ethereum.domain.BlockHeader
import io.iohk.ethereum.domain.BlockchainImpl
import io.iohk.ethereum.domain.BlockchainReader
import io.iohk.ethereum.domain.SignedTransactionWithSender
import io.iohk.ethereum.domain.Transaction
import io.iohk.ethereum.ledger.TxResult
import io.iohk.ethereum.nodebuilder.BlockchainConfigBuilder
import io.iohk.ethereum.vm.EvmConfig
Expand Down Expand Up @@ -68,7 +69,11 @@ class StxLedger(
highLimit
} else {
StxLedger.binaryChop(lowLimit, highLimit) { gasLimit =>
simulateTransaction(stx.copy(tx = tx.copy(tx = tx.tx.copy(gasLimit = gasLimit))), blockHeader, world).vmError
simulateTransaction(
stx.copy(tx = tx.copy(tx = Transaction.withGasLimit(gasLimit)(tx.tx))),
blockHeader,
world
).vmError
}
}
}
Expand Down
Loading

0 comments on commit 332959a

Please sign in to comment.