erigon-pulse/txpool/types.go

382 lines
14 KiB
Go
Raw Normal View History

2021-06-19 22:41:15 +00:00
/*
Copyright 2021 Erigon contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package txpool
import (
"fmt"
"github.com/holiman/uint256"
2021-06-24 16:35:12 +00:00
"golang.org/x/crypto/sha3"
2021-06-19 22:41:15 +00:00
)
// TxSlot contains information extracted from an Ethereum transaction, which is enough to manage it inside the transaction.
// Also, it contains some auxillary information, like ephemeral fields, and indices within priority queues
type TxSlot struct {
txId uint64 // Transaction id (distinct from transaction hash), used as a compact reference to a transaction accross data structures
senderId uint64 // Sender id (distinct from sender address), used as a compact referecne to to a sender accross data structures
nonce uint64 // Nonce of the transaction
tip uint64 // Maximum tip that transaction is giving to miner/block proposer
feeCap uint64 // Maximum fee that transaction burns and gives to the miner/block proposer
gas uint64 // Gas limit of the transaction
value uint256.Int // Value transferred by the transaction
creation bool // Set to true if "To" field of the transation is not set
dataLen int // Length of transaction's data (for calculation of intrinsic gas)
alAddrCount int // Number of addresses in the access list
alStorCount int // Number of storage keys in the access list
bestIdx int // Index of the transaction in the best priority queue (of whatever pool it currently belongs to)
worstIdx int // Index of the transaction in the worst priority queue (of whatever pook it currently belongs to)
local bool // Whether transaction has been injected locally (and hence needs priority when mining or proposing a block)
2021-06-24 16:35:12 +00:00
idHash [32]byte // Transaction hash for the purposes of using it as a transaction Id
2021-06-19 22:41:15 +00:00
}
// beInt parses Big Endian representation of an integer from given payload at given position
func beInt(payload []byte, pos, length int) (int, error) {
var r int
if length > 0 && payload[pos] == 0 {
return 0, fmt.Errorf("integer encoding for RLP must not have leading zeros: %x", payload[pos:pos+length])
}
for _, b := range payload[pos : pos+length] {
r = (r << 8) | int(b)
}
return r, nil
}
// prefix parses RLP prefix from given payload at given position. It returns the offset and length of the RLP element
// as well as the indication of whether it is a list of string
func prefix(payload []byte, pos int) (dataPos int, dataLen int, list bool, err error) {
switch first := payload[pos]; {
case first < 128:
dataPos = pos
dataLen = 1
list = false
case first < 184:
// string of len < 56, and it is non-legacy transaction
dataPos = pos + 1
dataLen = int(first) - 128
list = false
case first < 192:
// string of len >= 56, and it is non-legacy transaction
beLen := int(first) - 183
dataPos = pos + 1 + beLen
dataLen, err = beInt(payload, pos+1, beLen)
list = false
case first < 248:
// list of len < 56, and it is a legacy transaction
dataPos = pos + 1
dataLen = int(first) - 192
list = true
default:
// list of len >= 56, and it is a legacy transaction
beLen := int(first) - 247
dataPos = pos + 1 + beLen
dataLen, err = beInt(payload, pos+1, beLen)
list = true
}
return
}
// parseUint64 parses uint64 number from given payload at given position
func parseUint64(payload []byte, pos int) (int, uint64, error) {
dataPos, dataLen, list, err := prefix(payload, pos)
if err != nil {
return 0, 0, err
}
if list {
return 0, 0, fmt.Errorf("uint64 must be a string, not list")
}
if dataPos+dataLen >= len(payload) {
return 0, 0, fmt.Errorf("unexpected end of payload")
}
if dataLen > 8 {
return 0, 0, fmt.Errorf("uint64 must not be more than 8 bytes long, got %d", dataLen)
}
if dataLen > 0 && payload[dataPos] == 0 {
return 0, 0, fmt.Errorf("integer encoding for RLP must not have leading zeros: %x", payload[dataPos:dataPos+dataLen])
}
var r uint64
for _, b := range payload[dataPos : dataPos+dataLen] {
r = (r << 8) | uint64(b)
}
return dataPos + dataLen, r, nil
}
// parseUint256 parses uint256 number from given payload at given position
func parseUint256(payload []byte, pos int, x *uint256.Int) (int, error) {
dataPos, dataLen, list, err := prefix(payload, pos)
if err != nil {
return 0, err
}
if list {
return 0, fmt.Errorf("uint256 must be a string, not list")
}
2021-06-23 16:45:44 +00:00
if dataPos+dataLen > len(payload) {
2021-06-19 22:41:15 +00:00
return 0, fmt.Errorf("unexpected end of payload")
}
if dataLen > 32 {
return 0, fmt.Errorf("uint256 must not be more than 8 bytes long, got %d", dataLen)
}
if dataLen > 0 && payload[dataPos] == 0 {
return 0, fmt.Errorf("integer encoding for RLP must not have leading zeros: %x", payload[dataPos:dataPos+dataLen])
}
x.SetBytes(payload[dataPos : dataPos+dataLen])
return dataPos + dataLen, nil
}
const (
LegacyTxType int = 0
AccessListTxType int = 1
DynamicFeeTxType int = 2
)
// ParseTransaction extracts all the information from the transactions's payload (RLP) necessary to build TxSlot
// it also performs syntactic validation of the transactions
2021-06-24 16:35:12 +00:00
func ParseTransaction(payload []byte, pos int) (*TxSlot, int, error) {
2021-06-19 22:41:15 +00:00
errorPrefix := "parse transaction payload"
if len(payload) == 0 {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: empty rlp", errorPrefix)
2021-06-19 22:41:15 +00:00
}
2021-06-24 16:35:12 +00:00
// Compute transaction hash
keccak := sha3.NewLegacyKeccak256()
var slot TxSlot
2021-06-19 22:41:15 +00:00
// Legacy transations have list prefix, whereas EIP-2718 transactions have string prefix
// therefore we assign the first returned value of prefix function (list) to legacy variable
2021-06-24 16:35:12 +00:00
dataPos, dataLen, legacy, err := prefix(payload, pos)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: size prefix: %v", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
payloadLen := len(payload)
2021-06-22 12:53:02 +00:00
if dataPos+dataLen != payloadLen {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: transaction must be either 1 list or 1 string", errorPrefix)
2021-06-19 22:41:15 +00:00
}
2021-06-24 16:35:12 +00:00
p := dataPos
2021-06-19 22:41:15 +00:00
var txType int
2021-06-22 12:53:02 +00:00
var list bool
// If it is non-legacy transaction, the transaction type follows, and then the the list
2021-06-19 22:41:15 +00:00
if !legacy {
2021-06-24 16:35:12 +00:00
txType = int(payload[p])
if _, err = keccak.Write(payload[p : p+1]); err != nil {
return nil, 0, fmt.Errorf("%s: computing idHash (hashing type prefix): %w", errorPrefix, err)
}
p++
if p >= payloadLen {
return nil, 0, fmt.Errorf("%s: unexpected end of payload after txType", errorPrefix)
2021-06-19 22:41:15 +00:00
}
2021-06-24 16:35:12 +00:00
dataPos, dataLen, list, err = prefix(payload, p)
2021-06-22 12:53:02 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: envelope prefix: %v", errorPrefix, err)
2021-06-22 12:53:02 +00:00
}
if !list {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: envelope must be a list, not string", errorPrefix)
}
if dataPos+dataLen > payloadLen {
return nil, 0, fmt.Errorf("%s: unexpected end of payload after envelope", errorPrefix)
2021-06-22 12:53:02 +00:00
}
2021-06-24 16:35:12 +00:00
// Hash the envelope, not the full payload
if _, err = keccak.Write(payload[p : dataPos+dataLen]); err != nil {
return nil, 0, fmt.Errorf("%s: computing idHash (hashing the envelope): %w", errorPrefix, err)
2021-06-22 12:53:02 +00:00
}
2021-06-24 16:35:12 +00:00
p = dataPos
2021-06-19 22:41:15 +00:00
}
// If it is non-legacy tx, chainId follows, but we skip it
if !legacy {
2021-06-24 16:35:12 +00:00
dataPos, dataLen, list, err = prefix(payload, p)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: chainId len: %w", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
if list {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: chainId must be a string, not list", errorPrefix)
2021-06-19 22:41:15 +00:00
}
if dataPos+dataLen >= payloadLen {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: unexpected end of payload after chainId", errorPrefix)
2021-06-19 22:41:15 +00:00
}
2021-06-24 16:35:12 +00:00
p = dataPos + dataLen
2021-06-19 22:41:15 +00:00
}
// Next follows the nonce, which we need to parse
2021-06-24 16:35:12 +00:00
p, slot.nonce, err = parseUint64(payload, p)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: nonce: %w", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
// Next follows gas price or tip
// Although consensus rules specify that tip can be up to 256 bit long, we narrow it to 64 bit
2021-06-24 16:35:12 +00:00
p, slot.tip, err = parseUint64(payload, p)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: tip: %w", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
// Next follows feeCap, but only for dynamic fee transactions, for legacy transaction, it is
// equal to tip
if txType < DynamicFeeTxType {
slot.feeCap = slot.tip
} else {
// Although consensus rules specify that feeCap can be up to 256 bit long, we narrow it to 64 bit
2021-06-24 16:35:12 +00:00
p, slot.feeCap, err = parseUint64(payload, p)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: feeCap: %w", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
}
// Next follows gas
2021-06-24 16:35:12 +00:00
p, slot.gas, err = parseUint64(payload, p)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: gas: %w", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
// Next follows the destrination address (if present)
2021-06-24 16:35:12 +00:00
dataPos, dataLen, list, err = prefix(payload, p)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: to len: %w", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
if list {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: to must be a string, not list", errorPrefix)
2021-06-19 22:41:15 +00:00
}
if dataPos+dataLen >= payloadLen {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: unexpected end of payload after to", errorPrefix)
2021-06-19 22:41:15 +00:00
}
if dataLen != 0 && dataLen != 20 {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: unexpected length of to field: %d", errorPrefix, dataLen)
2021-06-19 22:41:15 +00:00
}
// Only note if To field is empty or not
slot.creation = dataLen == 0
2021-06-24 16:35:12 +00:00
p = dataPos + dataLen
2021-06-19 22:41:15 +00:00
// Next follows value
2021-06-24 16:35:12 +00:00
p, err = parseUint256(payload, p, &slot.value)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: value: %w", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
// Next goes data, but we are only interesting in its length
2021-06-24 16:35:12 +00:00
dataPos, dataLen, list, err = prefix(payload, p)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: data len: %w", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
if list {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: data must be a string, not list", errorPrefix)
2021-06-19 22:41:15 +00:00
}
if dataPos+dataLen >= payloadLen {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: unexpected end of payload after data", errorPrefix)
2021-06-19 22:41:15 +00:00
}
slot.dataLen = dataLen
2021-06-24 16:35:12 +00:00
p = dataPos + dataLen
2021-06-19 22:41:15 +00:00
// Next follows access list for non-legacy transactions, we are only interesting in number of addresses and storage keys
2021-06-22 12:53:02 +00:00
if !legacy {
2021-06-24 16:35:12 +00:00
dataPos, dataLen, list, err = prefix(payload, p)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: access list len: %w", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
if !list {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: access list must be a list, not string", errorPrefix)
2021-06-19 22:41:15 +00:00
}
2021-06-22 12:53:02 +00:00
if dataPos+dataLen >= payloadLen {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: unexpected end of payload after access list", errorPrefix)
2021-06-19 22:41:15 +00:00
}
2021-06-22 12:53:02 +00:00
tuplePos := dataPos
var tupleLen int
for tuplePos < dataPos+dataLen {
tuplePos, tupleLen, list, err = prefix(payload, tuplePos)
2021-06-19 22:41:15 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: tuple len: %w", errorPrefix, err)
2021-06-22 12:53:02 +00:00
}
if !list {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: tuple must be a list, not string", errorPrefix)
2021-06-22 12:53:02 +00:00
}
if tuplePos+tupleLen > dataPos+dataLen {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: unexpected end of access list after tuple", errorPrefix)
2021-06-22 12:53:02 +00:00
}
var addrPos, addrLen int
addrPos, addrLen, list, err = prefix(payload, tuplePos)
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: tuple addr len: %w", errorPrefix, err)
2021-06-19 22:41:15 +00:00
}
if list {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: tuple addr must be a string, not list", errorPrefix)
2021-06-22 12:53:02 +00:00
}
if addrPos+addrLen > tuplePos+tupleLen {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: unexpected end of tuple after address ", errorPrefix)
2021-06-19 22:41:15 +00:00
}
2021-06-22 12:53:02 +00:00
if addrLen != 20 {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: unexpected length of tuple address: %d", errorPrefix, addrLen)
2021-06-19 22:41:15 +00:00
}
2021-06-22 12:53:02 +00:00
slot.alAddrCount++
2021-06-24 16:35:12 +00:00
var storagePos, storageLen int
storagePos, storageLen, list, err = prefix(payload, addrPos+addrLen)
if err != nil {
return nil, 0, fmt.Errorf("%s: storage key list len: %w", errorPrefix, err)
}
if !list {
return nil, 0, fmt.Errorf("%s: storage key list must be a list, not string", errorPrefix)
}
if storagePos+storageLen > tuplePos+tupleLen {
return nil, 0, fmt.Errorf("%s: unexpected end of tuple after storage key list", errorPrefix)
}
skeyPos := storagePos
2021-06-22 12:53:02 +00:00
var skeyLen int
2021-06-24 16:35:12 +00:00
for skeyPos < storagePos+storageLen {
skeyPos, skeyLen, list, err = prefix(payload, skeyPos)
2021-06-22 12:53:02 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: tuple storage key len: %w", errorPrefix, err)
2021-06-22 12:53:02 +00:00
}
if list {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: tuple storage key must be a string, not list", errorPrefix)
2021-06-22 12:53:02 +00:00
}
2021-06-24 16:35:12 +00:00
if skeyPos+skeyLen > storagePos+storageLen {
return nil, 0, fmt.Errorf("%s: unexpected end of tuple after storage key", errorPrefix)
2021-06-22 12:53:02 +00:00
}
if skeyLen != 32 {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: unexpected length of tuple storage key: %d", errorPrefix, skeyLen)
2021-06-22 12:53:02 +00:00
}
slot.alStorCount++
skeyPos = skeyPos + skeyLen
2021-06-19 22:41:15 +00:00
}
2021-06-24 16:35:12 +00:00
if skeyPos != storagePos+storageLen {
return nil, 0, fmt.Errorf("%s: extraneous space in the tuple after storage key list", errorPrefix)
}
2021-06-22 12:53:02 +00:00
tuplePos = tuplePos + tupleLen
2021-06-19 22:41:15 +00:00
}
2021-06-24 16:35:12 +00:00
if tuplePos != dataPos+dataLen {
return nil, 0, fmt.Errorf("%s: extraneous space in the access list after all tuples", errorPrefix)
}
p = dataPos + dataLen
2021-06-23 16:45:44 +00:00
}
// Next follows V of the signature
var v uint64
2021-06-24 16:35:12 +00:00
p, v, err = parseUint64(payload, p)
2021-06-23 16:45:44 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: V: %w", errorPrefix, err)
2021-06-23 16:45:44 +00:00
}
if v >= 256 {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: V is loo large: %d", errorPrefix, v)
2021-06-23 16:45:44 +00:00
}
// Next follows R of the signature
var r uint256.Int
2021-06-24 16:35:12 +00:00
p, err = parseUint256(payload, p, &r)
2021-06-23 16:45:44 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: R: %w", errorPrefix, err)
2021-06-23 16:45:44 +00:00
}
// New follows S of the signature
var s uint256.Int
2021-06-24 16:35:12 +00:00
p, err = parseUint256(payload, p, &s)
2021-06-23 16:45:44 +00:00
if err != nil {
2021-06-24 16:35:12 +00:00
return nil, 0, fmt.Errorf("%s: S: %w", errorPrefix, err)
2021-06-23 16:45:44 +00:00
}
2021-06-24 16:35:12 +00:00
// For legacy transactions, hash the full payload
if legacy {
// Hash the envelope, not the full payload
if _, err = keccak.Write(payload[pos:p]); err != nil {
return nil, 0, fmt.Errorf("%s: computing idHash: %w", errorPrefix, err)
}
2021-06-19 22:41:15 +00:00
}
2021-06-24 16:35:12 +00:00
keccak.Sum(slot.idHash[:0])
return &slot, p, nil
2021-06-19 22:41:15 +00:00
}