diff --git a/cmd/evm/internal/t8ntool/transition.go b/cmd/evm/internal/t8ntool/transition.go index f67c9622e53..d1a909633e6 100644 --- a/cmd/evm/internal/t8ntool/transition.go +++ b/cmd/evm/internal/t8ntool/transition.go @@ -553,7 +553,7 @@ func getTransaction(txJson jsonrpc.RPCTransaction) (types.Transaction, error) { return legacyTx, nil } - case types.DynamicFeeTxType, types.BlobTxType: + case types.DynamicFeeTxType, types.BlobTxType, types.SetCodeTxType: var tip *uint256.Int var feeCap *uint256.Int if txJson.Tip != nil { @@ -588,6 +588,10 @@ func getTransaction(txJson jsonrpc.RPCTransaction) (types.Transaction, error) { dynamicFeeTx.S.SetFromBig(txJson.S.ToInt()) dynamicFeeTx.R.SetFromBig(txJson.R.ToInt()) + if txJson.Type == types.DynamicFeeTxType { + return &dynamicFeeTx, nil + } + if txJson.Type == types.BlobTxType { blobFee, overflow := uint256.FromBig((*big.Int)(txJson.MaxFeePerBlobGas)) if overflow { @@ -598,6 +602,21 @@ func getTransaction(txJson jsonrpc.RPCTransaction) (types.Transaction, error) { MaxFeePerBlobGas: blobFee, BlobVersionedHashes: txJson.BlobVersionedHashes, }, nil + } + + if txJson.Type == types.SetCodeTxType { + auths := make([]types.Authorization, 0) + for _, auth := range *txJson.Authorizations { + a, err := auth.ToAuthorization() + if err != nil { + return nil, err + } + auths = append(auths, a) + } + return &types.SetCodeTransaction{ + DynamicFeeTransaction: dynamicFeeTx, + Authorizations: auths, + }, nil } else { return &dynamicFeeTx, nil } diff --git a/cmd/evm/internal/t8ntool/types.go b/cmd/evm/internal/t8ntool/types.go index 7a1122f9c73..79b141640a1 100644 --- a/cmd/evm/internal/t8ntool/types.go +++ b/cmd/evm/internal/t8ntool/types.go @@ -7,9 +7,10 @@ import ( "math/big" "github.com/holiman/uint256" - "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/types" types2 "github.com/ledgerwatch/erigon/core/types" + + libcommon "github.com/ledgerwatch/erigon-lib/common" ) const ( @@ -22,23 +23,23 @@ const ( type BlockReplica struct { Type string NetworkId uint64 - Hash common.Hash + Hash libcommon.Hash TotalDifficulty *BigInt Header *Header Transactions []*Transaction Uncles []*Header `json:"uncles"` Receipts []*Receipt - Senders []common.Address + Senders []libcommon.Address State *StateSpecimen `json:"State"` Withdrawals []*Withdrawal BlobTxSidecars []*BlobTxSidecar } type Withdrawal struct { - Index uint64 `json:"index"` // monotonically increasing identifier issued by consensus layer - Validator uint64 `json:"validatorIndex"` // index of validator associated with withdrawal - Address common.Address `json:"address"` // target address for withdrawn ether - Amount uint64 `json:"amount"` // value of withdrawal in GWei + Index uint64 `json:"index"` // monotonically increasing identifier issued by consensus layer + Validator uint64 `json:"validatorIndex"` // index of validator associated with withdrawal + Address libcommon.Address `json:"address"` // target address for withdrawn ether + Amount uint64 `json:"amount"` // value of withdrawal in GWei } type StateSpecimen struct { @@ -77,91 +78,91 @@ func (b *Bloom) SetBytes(d []byte) { } type Header struct { - ParentHash common.Hash `json:"parentHash"` - UncleHash common.Hash `json:"sha3Uncles"` - Coinbase common.Address `json:"miner"` - Root common.Hash `json:"stateRoot"` - TxHash common.Hash `json:"transactionsRoot"` - ReceiptHash common.Hash `json:"receiptsRoot"` - Bloom Bloom `json:"logsBloom"` - Difficulty *BigInt `json:"difficulty"` - Number *BigInt `json:"number"` - GasLimit uint64 `json:"gasLimit"` - GasUsed uint64 `json:"gasUsed"` - Time uint64 `json:"timestamp"` - Extra []byte `json:"extraData"` - MixDigest common.Hash `json:"mixHash"` - Nonce BlockNonce `json:"nonce"` - BaseFee *BigInt `json:"baseFeePerGas"` - WithdrawalsHash *common.Hash `json:"withdrawalsRoot" rlp:"nil,optional"` - BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` - ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` - ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` + ParentHash libcommon.Hash `json:"parentHash"` + UncleHash libcommon.Hash `json:"sha3Uncles"` + Coinbase libcommon.Address `json:"miner"` + Root libcommon.Hash `json:"stateRoot"` + TxHash libcommon.Hash `json:"transactionsRoot"` + ReceiptHash libcommon.Hash `json:"receiptsRoot"` + Bloom Bloom `json:"logsBloom"` + Difficulty *BigInt `json:"difficulty"` + Number *BigInt `json:"number"` + GasLimit uint64 `json:"gasLimit"` + GasUsed uint64 `json:"gasUsed"` + Time uint64 `json:"timestamp"` + Extra []byte `json:"extraData"` + MixDigest libcommon.Hash `json:"mixHash"` + Nonce BlockNonce `json:"nonce"` + BaseFee *BigInt `json:"baseFeePerGas"` + WithdrawalsHash *libcommon.Hash `json:"withdrawalsRoot" rlp:"nil,optional"` + BlobGasUsed *uint64 `json:"blobGasUsed" rlp:"optional"` + ExcessBlobGas *uint64 `json:"excessBlobGas" rlp:"optional"` + ParentBeaconRoot *libcommon.Hash `json:"parentBeaconBlockRoot" rlp:"optional"` } type Transaction struct { - Type byte `json:"type"` - AccessList types.AccessList `json:"accessList"` - ChainId *BigInt `json:"chainId"` - AccountNonce uint64 `json:"nonce"` - Price *BigInt `json:"gasPrice"` - GasLimit uint64 `json:"gas"` - GasTipCap *BigInt `json:"gasTipCap"` - GasFeeCap *BigInt `json:"gasFeeCap"` - Sender *common.Address `json:"from"` - Recipient *common.Address `json:"to" rlp:"nil"` // nil means contract creation - Amount *BigInt `json:"value"` - Payload []byte `json:"input"` - V *BigInt `json:"v"` - R *BigInt `json:"r"` - S *BigInt `json:"s"` - BlobFeeCap *BigInt `json:"blobFeeCap" rlp:"optional"` - BlobHashes []common.Hash `json:"blobHashes" rlp:"optional"` - BlobGas uint64 `json:"blobGas" rlp:"optional"` + Type byte `json:"type"` + AccessList types.AccessList `json:"accessList"` + ChainId *BigInt `json:"chainId"` + AccountNonce uint64 `json:"nonce"` + Price *BigInt `json:"gasPrice"` + GasLimit uint64 `json:"gas"` + GasTipCap *BigInt `json:"gasTipCap"` + GasFeeCap *BigInt `json:"gasFeeCap"` + Sender *libcommon.Address `json:"from"` + Recipient *libcommon.Address `json:"to" rlp:"nil"` // nil means contract creation + Amount *BigInt `json:"value"` + Payload []byte `json:"input"` + V *BigInt `json:"v"` + R *BigInt `json:"r"` + S *BigInt `json:"s"` + BlobFeeCap *BigInt `json:"blobFeeCap" rlp:"optional"` + BlobHashes []libcommon.Hash `json:"blobHashes" rlp:"optional"` + BlobGas uint64 `json:"blobGas" rlp:"optional"` } type Logs struct { - Address common.Address `json:"address"` - Topics []common.Hash `json:"topics"` - Data []byte `json:"data"` - BlockNumber uint64 `json:"blockNumber"` - TxHash common.Hash `json:"transactionHash"` - TxIndex uint `json:"transactionIndex"` - BlockHash common.Hash `json:"blockHash"` - Index uint `json:"logIndex"` - Removed bool `json:"removed"` + Address libcommon.Address `json:"address"` + Topics []libcommon.Hash `json:"topics"` + Data []byte `json:"data"` + BlockNumber uint64 `json:"blockNumber"` + TxHash libcommon.Hash `json:"transactionHash"` + TxIndex uint `json:"transactionIndex"` + BlockHash libcommon.Hash `json:"blockHash"` + Index uint `json:"logIndex"` + Removed bool `json:"removed"` } type Receipt struct { PostStateOrStatus []byte CumulativeGasUsed uint64 - TxHash common.Hash - ContractAddress common.Address + TxHash libcommon.Hash + ContractAddress libcommon.Address Logs []*Logs GasUsed uint64 } type AccountRead struct { - Address common.Address + Address libcommon.Address Nonce uint64 Balance *BigInt - CodeHash common.Hash + CodeHash libcommon.Hash } type StorageRead struct { - Account common.Address - SlotKey common.Hash - Value common.Hash + Account libcommon.Address + SlotKey libcommon.Hash + Value libcommon.Hash } type CodeRead struct { - Hash common.Hash + Hash libcommon.Hash Code []byte } type BlockhashRead struct { BlockNumber uint64 - BlockHash common.Hash + BlockHash libcommon.Hash } // BlobTxSidecar contains the blobs of a blob transaction. diff --git a/core/types/authorization.go b/core/types/authorization.go new file mode 100644 index 00000000000..e171e095415 --- /dev/null +++ b/core/types/authorization.go @@ -0,0 +1,218 @@ +package types + +import ( + "bytes" + "errors" + "fmt" + "io" + + "github.com/holiman/uint256" + + libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/common/length" + libcrypto "github.com/ledgerwatch/erigon-lib/crypto" + rlp2 "github.com/ledgerwatch/erigon-lib/rlp" + "github.com/ledgerwatch/erigon/crypto" + "github.com/ledgerwatch/erigon/params" + "github.com/ledgerwatch/erigon/rlp" +) + +type Authorization struct { + ChainID uint64 + Address libcommon.Address + Nonce uint64 + YParity uint8 + R uint256.Int + S uint256.Int +} + +func (ath *Authorization) copy() *Authorization { + return &Authorization{ + ChainID: ath.ChainID, + Address: ath.Address, + Nonce: ath.Nonce, + YParity: ath.YParity, + R: *ath.R.Clone(), + S: *ath.S.Clone(), + } +} + +func (ath *Authorization) RecoverSigner(data *bytes.Buffer, b []byte) (*libcommon.Address, error) { + authLen := rlp2.U64Len(ath.ChainID) + authLen += (1 + length.Addr) + authLen += rlp2.U64Len(ath.Nonce) + + if err := EncodeStructSizePrefix(authLen, data, b); err != nil { + return nil, err + } + + // chainId, address, nonce + if err := rlp.EncodeInt(ath.ChainID, data, b); err != nil { + return nil, err + } + + if err := rlp.EncodeOptionalAddress(&ath.Address, data, b); err != nil { + return nil, err + } + + if err := rlp.EncodeInt(ath.Nonce, data, b); err != nil { + return nil, err + } + + hashData := []byte{params.SetCodeMagicPrefix} + hashData = append(hashData, data.Bytes()...) + hash := crypto.Keccak256Hash(hashData) + + var sig [65]byte + r := ath.R.Bytes() + s := ath.S.Bytes() + copy(sig[32-len(r):32], r) + copy(sig[64-len(s):64], s) + + if ath.Nonce == 1<<64-1 { + return nil, errors.New("failed assertion: auth.nonce < 2**64 - 1") + } + if ath.YParity == 0 || ath.YParity == 1 { + sig[64] = ath.YParity + } else { + return nil, fmt.Errorf("invalid y parity value: %d", ath.YParity) + } + + if !libcrypto.TransactionSignatureIsValid(sig[64], &ath.R, &ath.S, false /* allowPreEip2s */) { + return nil, errors.New("invalid signature") + } + + pubkey, err := crypto.Ecrecover(hash.Bytes(), sig[:]) + if err != nil { + return nil, err + } + if len(pubkey) == 0 || pubkey[0] != 4 { + return nil, errors.New("invalid public key") + } + + var authority libcommon.Address + copy(authority[:], crypto.Keccak256(pubkey[1:])[12:]) + return &authority, nil +} + +func authorizationSize(auth Authorization) (authLen int) { + authLen = rlp2.U64Len(auth.ChainID) + authLen += rlp2.U64Len(auth.Nonce) + authLen += (1 + length.Addr) + + authLen += rlp2.U64Len(uint64(auth.YParity)) + (1 + rlp.Uint256LenExcludingHead(&auth.R)) + (1 + rlp.Uint256LenExcludingHead(&auth.S)) + + return +} + +func authorizationsSize(authorizations []Authorization) (totalSize int) { + for _, auth := range authorizations { + authLen := authorizationSize(auth) + totalSize += rlp2.ListPrefixLen(authLen) + authLen + } + + return +} + +func decodeAuthorizations(auths *[]Authorization, s *rlp.Stream) error { + _, err := s.List() + if err != nil { + return fmt.Errorf("open authorizations: %w", err) + } + var b []byte + i := 0 + for _, err = s.List(); err == nil; _, err = s.List() { + auth := Authorization{} + + // chainId + if auth.ChainID, err = s.Uint(); err != nil { + return err + } + + // address + if b, err = s.Bytes(); err != nil { + return err + } + + if len(b) != 20 { + return fmt.Errorf("wrong size for Address: %d", len(b)) + } + auth.Address = libcommon.BytesToAddress(b) + + // nonce + if auth.Nonce, err = s.Uint(); err != nil { + return err + } + + // yParity + var yParity uint64 + if yParity, err = s.Uint(); err != nil { + return err + } + if yParity >= 1<<8 { + return fmt.Errorf("authorizations: y parity it too big: %d", yParity) + } + auth.YParity = uint8(yParity) + + // r + if b, err = s.Uint256Bytes(); err != nil { + return err + } + auth.R.SetBytes(b) + + // s + if b, err = s.Uint256Bytes(); err != nil { + return err + } + auth.S.SetBytes(b) + + *auths = append(*auths, auth) + // end of authorization + if err = s.ListEnd(); err != nil { + return fmt.Errorf("close Authorization: %w", err) + } + i++ + } + if !errors.Is(err, rlp.EOL) { + return fmt.Errorf("open authorizations: %d %w", i, err) + } + if err = s.ListEnd(); err != nil { + return fmt.Errorf("close authorizations: %w", err) + } + return nil +} + +func encodeAuthorizations(authorizations []Authorization, w io.Writer, b []byte) error { + for _, auth := range authorizations { + // 0. encode length of individual Authorization + authLen := authorizationSize(auth) + if err := EncodeStructSizePrefix(authLen, w, b); err != nil { + return err + } + + // 1. encode ChainId + if err := rlp.EncodeInt(auth.ChainID, w, b); err != nil { + return err + } + // 2. encode Address + if err := rlp.EncodeOptionalAddress(&auth.Address, w, b); err != nil { + return err + } + // 3. encode Nonce + if err := rlp.EncodeInt(auth.Nonce, w, b); err != nil { + return err + } + // 4. encode YParity, R, S + if err := rlp.EncodeInt(uint64(auth.YParity), w, b); err != nil { + return err + } + if err := auth.R.EncodeRLP(w); err != nil { + return err + } + if err := auth.S.EncodeRLP(w); err != nil { + return err + } + } + + return nil +} diff --git a/core/types/set_code_tx.go b/core/types/set_code_tx.go new file mode 100644 index 00000000000..7e17bddcd1a --- /dev/null +++ b/core/types/set_code_tx.go @@ -0,0 +1,353 @@ +package types + +import ( + "bytes" + "errors" + "fmt" + "io" + "math/big" + + "github.com/holiman/uint256" + "github.com/ledgerwatch/erigon-lib/chain" + libcommon "github.com/ledgerwatch/erigon-lib/common" + rlp2 "github.com/ledgerwatch/erigon-lib/rlp" + types2 "github.com/ledgerwatch/erigon-lib/types" + "github.com/ledgerwatch/erigon/params" + "github.com/ledgerwatch/erigon/rlp" +) + +const DelegateDesignationCodeSize = 23 + +var zeroAddr = libcommon.Address{} + +type SetCodeTransaction struct { + DynamicFeeTransaction + Authorizations []Authorization +} + +func (tx *SetCodeTransaction) Unwrap() Transaction { + return tx +} + +func (tx *SetCodeTransaction) Type() byte { + return SetCodeTxType +} + +func (tx *SetCodeTransaction) GetBlobHashes() []libcommon.Hash { + return []libcommon.Hash{} +} + +func (tx *SetCodeTransaction) GetAuthorizations() []Authorization { + return tx.Authorizations +} + +func (tx *SetCodeTransaction) copy() *SetCodeTransaction { + cpy := &SetCodeTransaction{} + cpy.DynamicFeeTransaction = *tx.DynamicFeeTransaction.copy() + + cpy.Authorizations = make([]Authorization, len(tx.Authorizations)) + + for i, ath := range tx.Authorizations { + cpy.Authorizations[i] = *ath.copy() + } + + return cpy +} + +func (tx *SetCodeTransaction) EncodingSize() int { + payloadSize, _, _, _, _ := tx.payloadSize() + // Add envelope size and type size + return 1 + rlp2.ListPrefixLen(payloadSize) + payloadSize +} + +func (tx *SetCodeTransaction) payloadSize() (payloadSize, nonceLen, gasLen, accessListLen, authorizationsLen int) { + payloadSize, nonceLen, gasLen, accessListLen = tx.DynamicFeeTransaction.payloadSize() + // size of Authorizations + authorizationsLen = authorizationsSize(tx.Authorizations) + payloadSize += rlp2.ListPrefixLen(authorizationsLen) + authorizationsLen + + return +} + +func (tx *SetCodeTransaction) WithSignature(signer Signer, sig []byte) (Transaction, error) { + cpy := tx.copy() + r, s, v, err := signer.SignatureValues(tx, sig) + if err != nil { + return nil, err + } + + cpy.R.Set(r) + cpy.S.Set(s) + cpy.V.Set(v) + cpy.ChainID = signer.ChainID() + return cpy, nil +} + +func (tx *SetCodeTransaction) MarshalBinary(w io.Writer) error { + payloadSize, nonceLen, gasLen, accessListLen, authorizationsLen := tx.payloadSize() + var b [33]byte + // encode TxType + b[0] = SetCodeTxType + if _, err := w.Write(b[:1]); err != nil { + return err + } + if err := tx.encodePayload(w, b[:], payloadSize, nonceLen, gasLen, accessListLen, authorizationsLen); err != nil { + return err + } + return nil +} + +func (tx *SetCodeTransaction) AsMessage(s Signer, baseFee *big.Int, rules *chain.Rules) (Message, error) { + msg := Message{ + nonce: tx.Nonce, + gasLimit: tx.Gas, + gasPrice: *tx.FeeCap, + tip: *tx.Tip, + feeCap: *tx.FeeCap, + to: tx.To, + amount: *tx.Value, + data: tx.Data, + accessList: tx.AccessList, + checkNonce: true, + } + if !rules.IsPrague { + return msg, errors.New("SetCodeTransaction is only supported in Prague") + } + if baseFee != nil { + overflow := msg.gasPrice.SetFromBig(baseFee) + if overflow { + return msg, errors.New("gasPrice higher than 2^256-1") + } + } + msg.gasPrice.Add(&msg.gasPrice, tx.Tip) + if msg.gasPrice.Gt(tx.FeeCap) { + msg.gasPrice.Set(tx.FeeCap) + } + + if len(tx.Authorizations) == 0 { + return msg, errors.New("SetCodeTransaction without authorizations is invalid") + } + msg.authorizations = tx.Authorizations + + var err error + msg.from, err = tx.Sender(s) + return msg, err +} + +func (tx *SetCodeTransaction) Sender(signer Signer) (libcommon.Address, error) { + if from := tx.from.Load(); from != nil { + return from.(libcommon.Address), nil + } + addr, err := signer.Sender(tx) + if err != nil { + return libcommon.Address{}, err + } + tx.from.Store(&addr) + return addr, nil +} + +func (tx *SetCodeTransaction) Hash() libcommon.Hash { + if hash := tx.hash.Load(); hash != nil { + return *hash.(*libcommon.Hash) + } + hash := prefixedRlpHash(SetCodeTxType, []interface{}{ + tx.ChainID, + tx.Nonce, + tx.Tip, + tx.FeeCap, + tx.Gas, + tx.To, + tx.Value, + tx.Data, + tx.AccessList, + tx.Authorizations, + tx.V, tx.R, tx.S, + }) + tx.hash.Store(&hash) + return hash +} + +func (tx *SetCodeTransaction) SigningHash(chainID *big.Int) libcommon.Hash { + return prefixedRlpHash( + SetCodeTxType, + []interface{}{ + chainID, + tx.Nonce, + tx.Tip, + tx.FeeCap, + tx.Gas, + tx.To, + tx.Value, + tx.Data, + tx.AccessList, + tx.Authorizations, + }) +} + +func (tx *SetCodeTransaction) EncodeRLP(w io.Writer) error { + payloadSize, nonceLen, gasLen, accessListLen, authorizationsLen := tx.payloadSize() + envelopSize := 1 + rlp2.ListPrefixLen(payloadSize) + payloadSize + var b [33]byte + // encode envelope size + if err := rlp.EncodeStringSizePrefix(envelopSize, w, b[:]); err != nil { + return err + } + // encode TxType + b[0] = SetCodeTxType + if _, err := w.Write(b[:1]); err != nil { + return err + } + + return tx.encodePayload(w, b[:], payloadSize, nonceLen, gasLen, accessListLen, authorizationsLen) +} + +func (tx *SetCodeTransaction) DecodeRLP(s *rlp.Stream) error { + _, err := s.List() + if err != nil { + return err + } + var b []byte + if b, err = s.Uint256Bytes(); err != nil { + return err + } + tx.ChainID = new(uint256.Int).SetBytes(b) + if tx.Nonce, err = s.Uint(); err != nil { + return err + } + if b, err = s.Uint256Bytes(); err != nil { + return err + } + tx.Tip = new(uint256.Int).SetBytes(b) + if b, err = s.Uint256Bytes(); err != nil { + return err + } + tx.FeeCap = new(uint256.Int).SetBytes(b) + if tx.Gas, err = s.Uint(); err != nil { + return err + } + if b, err = s.Bytes(); err != nil { + return err + } + if len(b) != 20 { + return fmt.Errorf("wrong size for To: %d", len(b)) + } + tx.To = &libcommon.Address{} + copy((*tx.To)[:], b) + if b, err = s.Uint256Bytes(); err != nil { + return err + } + tx.Value = new(uint256.Int).SetBytes(b) + if tx.Data, err = s.Bytes(); err != nil { + return err + } + // decode AccessList + tx.AccessList = types2.AccessList{} + if err = decodeAccessList(&tx.AccessList, s); err != nil { + return err + } + + // decode authorizations + tx.Authorizations = make([]Authorization, 0) + if err = decodeAuthorizations(&tx.Authorizations, s); err != nil { + return err + } + + // decode V + if b, err = s.Uint256Bytes(); err != nil { + return err + } + tx.V.SetBytes(b) + if b, err = s.Uint256Bytes(); err != nil { + return err + } + tx.R.SetBytes(b) + if b, err = s.Uint256Bytes(); err != nil { + return err + } + tx.S.SetBytes(b) + return s.ListEnd() +} + +func (tx *SetCodeTransaction) encodePayload(w io.Writer, b []byte, payloadSize, _, _, accessListLen, authorizationsLen int) error { + // prefix + if err := EncodeStructSizePrefix(payloadSize, w, b); err != nil { + return err + } + // encode ChainID + if err := tx.ChainID.EncodeRLP(w); err != nil { + return err + } + // encode Nonce + if err := rlp.EncodeInt(tx.Nonce, w, b); err != nil { + return err + } + // encode MaxPriorityFeePerGas + if err := tx.Tip.EncodeRLP(w); err != nil { + return err + } + // encode MaxFeePerGas + if err := tx.FeeCap.EncodeRLP(w); err != nil { + return err + } + // encode Gas + if err := rlp.EncodeInt(tx.Gas, w, b); err != nil { + return err + } + // encode To + if err := rlp.EncodeOptionalAddress(tx.To, w, b); err != nil { + return err + } + // encode Value + if err := tx.Value.EncodeRLP(w); err != nil { + return err + } + // encode Data + if err := rlp.EncodeString(tx.Data, w, b); err != nil { + return err + } + // prefix + if err := EncodeStructSizePrefix(accessListLen, w, b); err != nil { + return err + } + // encode AccessList + if err := encodeAccessList(tx.AccessList, w, b); err != nil { + return err + } + // prefix + if err := EncodeStructSizePrefix(authorizationsLen, w, b); err != nil { + return err + } + // encode Authorizations + if err := encodeAuthorizations(tx.Authorizations, w, b); err != nil { + return err + } + // encode V + if err := tx.V.EncodeRLP(w); err != nil { + return err + } + // encode R + if err := tx.R.EncodeRLP(w); err != nil { + return err + } + // encode S + if err := tx.S.EncodeRLP(w); err != nil { + return err + } + return nil + +} + +// ParseDelegation tries to parse the address from a delegation slice. +func ParseDelegation(code []byte) (libcommon.Address, bool) { + if len(code) != DelegateDesignationCodeSize || !bytes.HasPrefix(code, params.DelegatedDesignationPrefix) { + return libcommon.Address{}, false + } + var addr libcommon.Address + copy(addr[:], code[len(params.DelegatedDesignationPrefix):]) + return addr, true +} + +// AddressToDelegation adds the delegation prefix to the specified address. +func AddressToDelegation(addr libcommon.Address) []byte { + return append(params.DelegatedDesignationPrefix, addr.Bytes()...) +} diff --git a/core/types/transaction.go b/core/types/transaction.go index 98a89493c00..ffb1c748182 100644 --- a/core/types/transaction.go +++ b/core/types/transaction.go @@ -51,6 +51,7 @@ const ( AccessListTxType DynamicFeeTxType BlobTxType + SetCodeTxType ) // Transaction is an Ethereum transaction. @@ -408,6 +409,7 @@ type Message struct { checkNonce bool isFree bool blobHashes []libcommon.Hash + authorizations []Authorization } func NewMessage(from libcommon.Address, to *libcommon.Address, nonce uint64, amount *uint256.Int, gasLimit uint64, @@ -440,17 +442,21 @@ func NewMessage(from libcommon.Address, to *libcommon.Address, nonce uint64, amo return m } -func (m Message) From() libcommon.Address { return m.from } -func (m Message) To() *libcommon.Address { return m.to } -func (m Message) GasPrice() *uint256.Int { return &m.gasPrice } -func (m Message) FeeCap() *uint256.Int { return &m.feeCap } -func (m Message) Tip() *uint256.Int { return &m.tip } -func (m Message) Value() *uint256.Int { return &m.amount } -func (m Message) Gas() uint64 { return m.gasLimit } -func (m Message) Nonce() uint64 { return m.nonce } -func (m Message) Data() []byte { return m.data } -func (m Message) AccessList() types2.AccessList { return m.accessList } -func (m Message) CheckNonce() bool { return m.checkNonce } +func (m Message) From() libcommon.Address { return m.from } +func (m Message) To() *libcommon.Address { return m.to } +func (m Message) GasPrice() *uint256.Int { return &m.gasPrice } +func (m Message) FeeCap() *uint256.Int { return &m.feeCap } +func (m Message) Tip() *uint256.Int { return &m.tip } +func (m Message) Value() *uint256.Int { return &m.amount } +func (m Message) Gas() uint64 { return m.gasLimit } +func (m Message) Nonce() uint64 { return m.nonce } +func (m Message) Data() []byte { return m.data } +func (m Message) AccessList() types2.AccessList { return m.accessList } +func (m Message) Authorizations() []Authorization { return m.authorizations } +func (m *Message) SetAuthorizations(authorizations []Authorization) { + m.authorizations = authorizations +} +func (m Message) CheckNonce() bool { return m.checkNonce } func (m *Message) SetCheckNonce(checkNonce bool) { m.checkNonce = checkNonce } diff --git a/core/types/transaction_marshalling.go b/core/types/transaction_marshalling.go index b529e517988..16065df6d87 100644 --- a/core/types/transaction_marshalling.go +++ b/core/types/transaction_marshalling.go @@ -4,6 +4,8 @@ import ( "encoding/json" "errors" "fmt" + "math/big" + "github.com/ledgerwatch/erigon-lib/common/hexutil" "github.com/holiman/uint256" @@ -32,8 +34,9 @@ type txJSON struct { To *libcommon.Address `json:"to"` // Access list transaction fields: - ChainID *hexutil.Big `json:"chainId,omitempty"` - AccessList *types2.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + AccessList *types2.AccessList `json:"accessList,omitempty"` + Authorizations *[]JsonAuthorization `json:"authorizationList,omitempty"` // Blob transaction fields: MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` @@ -47,7 +50,51 @@ type txJSON struct { Hash libcommon.Hash `json:"hash"` } -func (tx LegacyTx) MarshalJSON() ([]byte, error) { +type JsonAuthorization struct { + ChainID hexutil.Uint64 `json:"chainId"` + Address libcommon.Address `json:"address"` + Nonce hexutil.Uint64 `json:"nonce"` + V hexutil.Uint64 `json:"v"` + R hexutil.Big `json:"r"` + S hexutil.Big `json:"s"` +} + +func (a JsonAuthorization) FromAuthorization(authorization Authorization) JsonAuthorization { + a.ChainID = (hexutil.Uint64)(authorization.ChainID) + a.Address = authorization.Address + a.Nonce = (hexutil.Uint64)(authorization.Nonce) + + a.V = (hexutil.Uint64)(authorization.YParity) + a.R = hexutil.Big(*authorization.R.ToBig()) + a.S = hexutil.Big(*authorization.S.ToBig()) + return a +} + +func (a JsonAuthorization) ToAuthorization() (Authorization, error) { + auth := Authorization{ + ChainID: a.ChainID.Uint64(), + Address: a.Address, + Nonce: a.Nonce.Uint64(), + } + yParity := a.V.Uint64() + if yParity >= 1<<8 { + return auth, errors.New("y parity in authorization does not fit in 8 bits") + } + auth.YParity = uint8(yParity) + r, overflow := uint256.FromBig((*big.Int)(&a.R)) + if overflow { + return auth, errors.New("r in authorization does not fit in 256 bits") + } + auth.R = *r + s, overflow := uint256.FromBig((*big.Int)(&a.S)) + if overflow { + return auth, errors.New("s in authorization does not fit in 256 bits") + } + auth.S = *s + return auth, nil +} + +func (tx *LegacyTx) MarshalJSON() ([]byte, error) { var enc txJSON // These are set for all tx types. enc.Hash = tx.Hash() @@ -67,7 +114,7 @@ func (tx LegacyTx) MarshalJSON() ([]byte, error) { return json.Marshal(&enc) } -func (tx AccessListTx) MarshalJSON() ([]byte, error) { +func (tx *AccessListTx) MarshalJSON() ([]byte, error) { var enc txJSON // These are set for all tx types. enc.Hash = tx.Hash() @@ -86,7 +133,7 @@ func (tx AccessListTx) MarshalJSON() ([]byte, error) { return json.Marshal(&enc) } -func (tx DynamicFeeTransaction) MarshalJSON() ([]byte, error) { +func (tx *DynamicFeeTransaction) MarshalJSON() ([]byte, error) { var enc txJSON // These are set for all tx types. enc.Hash = tx.Hash() @@ -128,11 +175,11 @@ func toBlobTxJSON(tx *BlobTx) *txJSON { return &enc } -func (tx BlobTx) MarshalJSON() ([]byte, error) { - return json.Marshal(toBlobTxJSON(&tx)) +func (tx *BlobTx) MarshalJSON() ([]byte, error) { + return json.Marshal(toBlobTxJSON(tx)) } -func (tx BlobTxWrapper) MarshalJSON() ([]byte, error) { +func (tx *BlobTxWrapper) MarshalJSON() ([]byte, error) { enc := toBlobTxJSON(&tx.Tx) enc.Blobs = tx.Blobs @@ -181,6 +228,12 @@ func UnmarshalTransactionFromJSON(input []byte) (Transaction, error) { return nil, err } return tx, nil + case SetCodeTxType: + tx := &SetCodeTransaction{} + if err = tx.UnmarshalJSON(input); err != nil { + return nil, err + } + return tx, nil default: return nil, fmt.Errorf("unknown transaction type: %v", txType) } @@ -330,11 +383,7 @@ func (tx *AccessListTx) UnmarshalJSON(input []byte) error { return nil } -func (tx *DynamicFeeTransaction) UnmarshalJSON(input []byte) error { - var dec txJSON - if err := json.Unmarshal(input, &dec); err != nil { - return err - } +func (tx *DynamicFeeTransaction) unmarshalJson(dec txJSON) error { // Access list is optional for now. if dec.AccessList != nil { tx.AccessList = *dec.AccessList @@ -354,9 +403,6 @@ func (tx *DynamicFeeTransaction) UnmarshalJSON(input []byte) error { return errors.New("missing required field 'nonce' in transaction") } tx.Nonce = uint64(*dec.Nonce) - if dec.GasPrice == nil { - return errors.New("missing required field 'gasPrice' in transaction") - } tx.Tip, overflow = uint256.FromBig(dec.Tip.ToInt()) if overflow { return errors.New("'tip' in transaction does not fit in 256 bits") @@ -413,6 +459,35 @@ func (tx *DynamicFeeTransaction) UnmarshalJSON(input []byte) error { return nil } +func (tx *DynamicFeeTransaction) UnmarshalJSON(input []byte) error { + var dec txJSON + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + + return tx.unmarshalJson(dec) +} + +func (tx *SetCodeTransaction) UnmarshalJSON(input []byte) error { + var dec txJSON + if err := json.Unmarshal(input, &dec); err != nil { + return err + } + + if err := tx.DynamicFeeTransaction.unmarshalJson(dec); err != nil { + return err + } + tx.Authorizations = make([]Authorization, len(*dec.Authorizations)) + for i, auth := range *dec.Authorizations { + var err error + tx.Authorizations[i], err = auth.ToAuthorization() + if err != nil { + return err + } + } + return nil +} + func UnmarshalBlobTxJSON(input []byte) (Transaction, error) { var dec txJSON if err := json.Unmarshal(input, &dec); err != nil { @@ -439,9 +514,6 @@ func UnmarshalBlobTxJSON(input []byte) (Transaction, error) { return nil, errors.New("missing required field 'nonce' in transaction") } tx.Nonce = uint64(*dec.Nonce) - // if dec.GasPrice == nil { // do we need gasPrice here? - // return nil, errors.New("missing required field 'gasPrice' in transaction") - // } tx.Tip, overflow = uint256.FromBig(dec.Tip.ToInt()) if overflow { return nil, errors.New("'tip' in transaction does not fit in 256 bits") @@ -517,7 +589,8 @@ func UnmarshalBlobTxJSON(input []byte) (Transaction, error) { } btx := BlobTxWrapper{ - Tx: tx, + // it's ok to copy here - because it's constructor of object - no parallel access yet + Tx: tx, //nolint Commitments: dec.Commitments, Blobs: dec.Blobs, Proofs: dec.Proofs, diff --git a/params/protocol_params.go b/params/protocol_params.go index 8b8c37325ae..eac9b03592b 100644 --- a/params/protocol_params.go +++ b/params/protocol_params.go @@ -171,8 +171,12 @@ const ( // PIP-27: secp256r1 elliptic curve signature verifier gas price P256VerifyGas uint64 = 3450 + // EIP-7702 + SetCodeMagicPrefix = byte(0x05) ) +var DelegatedDesignationPrefix = []byte{0xef, 0x01, 0x00} + // EIP-4788: Beacon block root in the EVM var BeaconRootsAddress = common.HexToAddress("0x000F3df6D732807Ef1319fB7B8bB8522d0Beac02") diff --git a/rlp/encode.go b/rlp/encode.go index f43b71670d8..61092887123 100644 --- a/rlp/encode.go +++ b/rlp/encode.go @@ -856,3 +856,22 @@ func EncodeStringSizePrefix(size int, w io.Writer, buffer []byte) error { } return nil } + +func EncodeOptionalAddress(addr *libcommon.Address, w io.Writer, buffer []byte) error { + if addr == nil { + buffer[0] = 128 + } else { + buffer[0] = 128 + 20 + } + + if _, err := w.Write(buffer[:1]); err != nil { + return err + } + if addr != nil { + if _, err := w.Write(addr.Bytes()); err != nil { + return err + } + } + + return nil +} diff --git a/turbo/jsonrpc/eth_api.go b/turbo/jsonrpc/eth_api.go index 4499d345325..85241ae3e88 100644 --- a/turbo/jsonrpc/eth_api.go +++ b/turbo/jsonrpc/eth_api.go @@ -354,28 +354,29 @@ func NewEthAPI(base *BaseAPI, db kv.RoDB, eth rpchelper.ApiBackend, txPool txpoo // RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction type RPCTransaction struct { - BlockHash *common.Hash `json:"blockHash"` - BlockNumber *hexutil.Big `json:"blockNumber"` - From common.Address `json:"from"` - Gas hexutil.Uint64 `json:"gas"` - GasPrice *hexutil.Big `json:"gasPrice,omitempty"` - Tip *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` - FeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` - Hash common.Hash `json:"hash"` - Input hexutility.Bytes `json:"input"` - Nonce hexutil.Uint64 `json:"nonce"` - To *common.Address `json:"to"` - TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` - Value *hexutil.Big `json:"value"` - Type hexutil.Uint64 `json:"type"` - Accesses *types2.AccessList `json:"accessList,omitempty"` - ChainID *hexutil.Big `json:"chainId,omitempty"` - MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` - BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` - V *hexutil.Big `json:"v"` - YParity *hexutil.Big `json:"yParity,omitempty"` - R *hexutil.Big `json:"r"` - S *hexutil.Big `json:"s"` + BlockHash *common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice,omitempty"` + Tip *hexutil.Big `json:"maxPriorityFeePerGas,omitempty"` + FeeCap *hexutil.Big `json:"maxFeePerGas,omitempty"` + Hash common.Hash `json:"hash"` + Input hexutility.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + Type hexutil.Uint64 `json:"type"` + Accesses *types2.AccessList `json:"accessList,omitempty"` + ChainID *hexutil.Big `json:"chainId,omitempty"` + MaxFeePerBlobGas *hexutil.Big `json:"maxFeePerBlobGas,omitempty"` + BlobVersionedHashes []common.Hash `json:"blobVersionedHashes,omitempty"` + Authorizations *[]types.JsonAuthorization `json:"authorizationList,omitempty"` + V *hexutil.Big `json:"v"` + YParity *hexutil.Big `json:"yParity,omitempty"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` } // NewRPCTransaction returns a transaction that will serialize to the RPC