Skip to content

[ETCM-921] transaction with access list #1094

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 13 commits into from
Aug 23, 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
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]")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isn't this up to 0xFF?

Copy link
Contributor Author

@strauss-m strauss-m Aug 19, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0x7f is the limit for two reasons:

  • EIP-2718: TransactionType is a positive unsigned 8-bit number between 0 and 0x7f that represents the type of the transcation
  • rlp encoding spec: for a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding

That's what allows us to consider a single RLPValue [0; 0x7f] as a single raw byte transaction type (and vice-versa)

Since Byte is a signed type, the range [0; 0x7f] check become a simple positive condition.

}

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