Implement PIP-33: Napoli Hardfork (#8975)

Initial support of the upcoming Napoli hard fork on Polygon – see
[PIP-33](https://forum.polygon.technology/t/pip-33-napoli-upgrade). Per
[PIP-31](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/main/PIPs/PIP-31.md),
it parallels the
[Cancun](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md)
upgrade of Ethereum, but does not include
[EIP-4788](https://eips.ethereum.org/EIPS/eip-4788),
[EIP-4844](https://eips.ethereum.org/EIPS/eip-4844),
[EIP-7516](https://eips.ethereum.org/EIPS/eip-7516). In other words,
Napoli includes [EIP-1153](https://eips.ethereum.org/EIPS/eip-1153),
[EIP-5656](https://eips.ethereum.org/EIPS/eip-5656),
[EIP-6780](https://eips.ethereum.org/EIPS/eip-6780) from Cancun.

This PR implements
[PIP-31](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/main/PIPs/PIP-31.md),
[PIP-16: Transaction Dependency
Data](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/main/PIPs/PIP-16.md)
(by merging `ParallelUniverseBlock` into `NapoliBlock`; the bulk of
PIP-16 was implemented in PR #8037), and [PIP-27: Precompiled for
secp256r1 Curve
Support](https://github.com/maticnetwork/Polygon-Improvement-Proposals/blob/main/PIPs/PIP-27.md)
([EIP-7212](https://eips.ethereum.org/EIPS/eip-7212); see also
https://github.com/maticnetwork/bor/pull/1069 &
https://github.com/ethereum/go-ethereum/pull/27540).

---------

Co-authored-by: Anshal Shukla <shukla.anshal85@gmail.com>
This commit is contained in:
Andrew Ashikhmin 2024-01-17 14:13:16 +01:00 committed by GitHub
parent 8f6fe88b29
commit b38e17e393
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 231 additions and 51 deletions

View File

@ -244,9 +244,14 @@ func GatherForks(config *chain.Config, genesisTime uint64) (heightForks []uint64
heightForks = append(heightForks, *config.Aura.PosdaoTransition)
}
if config.Bor != nil && config.Bor.GetAgraBlock() != nil {
if config.Bor != nil {
if config.Bor.GetAgraBlock() != nil {
heightForks = append(heightForks, config.Bor.GetAgraBlock().Uint64())
}
if config.Bor.GetNapoliBlock() != nil {
heightForks = append(heightForks, config.Bor.GetNapoliBlock().Uint64())
}
}
// Sort the fork block numbers & times to permit chronological XOR
slices.Sort(heightForks)

View File

@ -42,7 +42,7 @@ import (
// the following 2 functions are replica for the test
// This is a replica of `bor.GetValidatorBytes` function
// This was needed because currently, `IsParallelUniverse` will always return false.
// This was needed because currently, `IsNapoli` will always return false.
func GetValidatorBytesTest(h *Header) []byte {
if len(h.Extra) < ExtraVanityLength+ExtraSealLength {
log.Error("length of extra is less than vanity and seal")

View File

@ -20,13 +20,13 @@ import (
"crypto/sha256"
"encoding/binary"
"errors"
"github.com/ledgerwatch/erigon-lib/crypto/blake2b"
"math/big"
"github.com/holiman/uint256"
"github.com/ledgerwatch/erigon-lib/chain"
libcommon "github.com/ledgerwatch/erigon-lib/common"
"github.com/ledgerwatch/erigon-lib/crypto/blake2b"
libkzg "github.com/ledgerwatch/erigon-lib/crypto/kzg"
"github.com/ledgerwatch/erigon/common"
@ -34,7 +34,7 @@ import (
"github.com/ledgerwatch/erigon/crypto"
"github.com/ledgerwatch/erigon/crypto/bls12381"
"github.com/ledgerwatch/erigon/crypto/bn256"
"github.com/ledgerwatch/erigon/crypto/secp256r1"
"github.com/ledgerwatch/erigon/params"
//lint:ignore SA1019 Needed for precompile
@ -112,6 +112,19 @@ var PrecompiledContractsCancun = map[libcommon.Address]PrecompiledContract{
libcommon.BytesToAddress([]byte{0x0a}): &pointEvaluation{},
}
var PrecompiledContractsNapoli = map[libcommon.Address]PrecompiledContract{
libcommon.BytesToAddress([]byte{0x01}): &ecrecover{},
libcommon.BytesToAddress([]byte{0x02}): &sha256hash{},
libcommon.BytesToAddress([]byte{0x03}): &ripemd160hash{},
libcommon.BytesToAddress([]byte{0x04}): &dataCopy{},
libcommon.BytesToAddress([]byte{0x05}): &bigModExp{eip2565: true},
libcommon.BytesToAddress([]byte{0x06}): &bn256AddIstanbul{},
libcommon.BytesToAddress([]byte{0x07}): &bn256ScalarMulIstanbul{},
libcommon.BytesToAddress([]byte{0x08}): &bn256PairingIstanbul{},
libcommon.BytesToAddress([]byte{0x09}): &blake2F{},
libcommon.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},
}
// PrecompiledContractsBLS contains the set of pre-compiled Ethereum
// contracts specified in EIP-2537. These are exported for testing purposes.
var PrecompiledContractsBLS = map[libcommon.Address]PrecompiledContract{
@ -127,6 +140,7 @@ var PrecompiledContractsBLS = map[libcommon.Address]PrecompiledContract{
}
var (
PrecompiledAddressesNapoli []libcommon.Address
PrecompiledAddressesCancun []libcommon.Address
PrecompiledAddressesBerlin []libcommon.Address
PrecompiledAddressesIstanbul []libcommon.Address
@ -150,11 +164,16 @@ func init() {
for k := range PrecompiledContractsCancun {
PrecompiledAddressesCancun = append(PrecompiledAddressesCancun, k)
}
for k := range PrecompiledContractsNapoli {
PrecompiledAddressesNapoli = append(PrecompiledAddressesNapoli, k)
}
}
// ActivePrecompiles returns the precompiles enabled with the current configuration.
func ActivePrecompiles(rules *chain.Rules) []libcommon.Address {
switch {
case rules.IsNapoli:
return PrecompiledAddressesNapoli
case rules.IsCancun:
return PrecompiledAddressesCancun
case rules.IsBerlin:
@ -1098,3 +1117,37 @@ func (c *pointEvaluation) RequiredGas(input []byte) uint64 {
func (c *pointEvaluation) Run(input []byte) ([]byte, error) {
return libkzg.PointEvaluationPrecompile(input)
}
// P256VERIFY (secp256r1 signature verification)
// implemented as a native contract
type p256Verify struct{}
// RequiredGas returns the gas required to execute the precompiled contract
func (c *p256Verify) RequiredGas(input []byte) uint64 {
return params.P256VerifyGas
}
// Run executes the precompiled contract with given 160 bytes of param, returning the output and the used gas
func (c *p256Verify) Run(input []byte) ([]byte, error) {
// Required input length is 160 bytes
const p256VerifyInputLength = 160
// Check the input length
if len(input) != p256VerifyInputLength {
// Input length is invalid
return nil, nil
}
// Extract the hash, r, s, x, y from the input
hash := input[0:32]
r, s := new(big.Int).SetBytes(input[32:64]), new(big.Int).SetBytes(input[64:96])
x, y := new(big.Int).SetBytes(input[96:128]), new(big.Int).SetBytes(input[128:160])
// Verify the secp256r1 signature
if secp256r1.Verify(hash, r, s, x, y) {
// Signature is valid
return common.LeftPadBytes(big1.Bytes(), 32), nil
} else {
// Signature is invalid
return nil, nil
}
}

View File

@ -68,6 +68,7 @@ var allPrecompiles = map[libcommon.Address]PrecompiledContract{
libcommon.BytesToAddress([]byte{17}): &bls12381MapG1{},
libcommon.BytesToAddress([]byte{18}): &bls12381MapG2{},
libcommon.BytesToAddress([]byte{20}): &pointEvaluation{},
libcommon.BytesToAddress([]byte{0x01, 0x00}): &p256Verify{},
}
// EIP-152 test vectors
@ -400,3 +401,19 @@ func BenchmarkPrecompiledBLS12381G2MultiExpWorstCase(b *testing.B) {
}
benchmarkPrecompiled(b, "0f", testcase)
}
// Benchmarks the sample inputs from the P256VERIFY precompile.
func BenchmarkPrecompiledP256Verify(b *testing.B) {
testcase := precompiledTest{
Input: "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e",
Expected: "0000000000000000000000000000000000000000000000000000000000000001",
Name: "p256Verify",
}
benchmarkPrecompiled(b, "100", testcase)
}
func TestPrecompiledP256Verify(t *testing.T) {
t.Parallel()
testJson("p256Verify", "100", t)
}

View File

@ -37,6 +37,8 @@ var emptyCodeHash = crypto.Keccak256Hash(nil)
func (evm *EVM) precompile(addr libcommon.Address) (PrecompiledContract, bool) {
var precompiles map[libcommon.Address]PrecompiledContract
switch {
case evm.chainRules.IsNapoli:
precompiles = PrecompiledContractsNapoli
case evm.chainRules.IsCancun:
precompiles = PrecompiledContractsCancun
case evm.chainRules.IsBerlin:

View File

@ -128,6 +128,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
jt = &pragueInstructionSet
case evm.ChainRules().IsCancun:
jt = &cancunInstructionSet
case evm.ChainRules().IsNapoli:
jt = &napoliInstructionSet
case evm.ChainRules().IsShanghai:
jt = &shanghaiInstructionSet
case evm.ChainRules().IsLondon:

View File

@ -61,6 +61,7 @@ var (
berlinInstructionSet = newBerlinInstructionSet()
londonInstructionSet = newLondonInstructionSet()
shanghaiInstructionSet = newShanghaiInstructionSet()
napoliInstructionSet = newNapoliInstructionSet()
cancunInstructionSet = newCancunInstructionSet()
pragueInstructionSet = newPragueInstructionSet()
)
@ -99,12 +100,18 @@ func newPragueInstructionSet() JumpTable {
// constantinople, istanbul, petersburg, berlin, london, paris, shanghai,
// and cancun instructions.
func newCancunInstructionSet() JumpTable {
instructionSet := newNapoliInstructionSet()
enable4844(&instructionSet) // BLOBHASH opcode
enable7516(&instructionSet) // BLOBBASEFEE opcode
validateAndFillMaxStack(&instructionSet)
return instructionSet
}
func newNapoliInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet()
enable1153(&instructionSet) // Transient storage opcodes
enable4844(&instructionSet) // BLOBHASH opcode
enable5656(&instructionSet) // MCOPY opcode
enable6780(&instructionSet) // SELFDESTRUCT only in same transaction
enable7516(&instructionSet) // BLOBBASEFEE opcode
validateAndFillMaxStack(&instructionSet)
return instructionSet
}

View File

@ -0,0 +1,37 @@
[
{
"Input": "4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e",
"Expected": "0000000000000000000000000000000000000000000000000000000000000001",
"Gas": 3450,
"Name": "CallP256Verify",
"NoBenchmark": false
},
{
"Input": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9414de3726ee4d237b410c1d85ebcb05553dc578561d9f7942b7250795beb9b9027b657067322fc00ab35263fde0acabf998cd9fcf1282df9555f85dba7bdbbe2dc90f74c9e210bc3e0c60aeaa03729c9e6acde4a048ee58fd2e466c1e7b0374e606b8c22ad2985df7d792ff344f03ce94a079da801006b13640bc5af7932a7b9",
"Expected": "0000000000000000000000000000000000000000000000000000000000000001",
"Gas": 3450,
"Name": "CallP256Verify",
"NoBenchmark": false
},
{
"Input": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9b35d6a4f7f6fc5620c97d4287696f5174b3d37fa537b74b5fc26997ba79c725d62fe5e5fe6da76eec924e822c5ef853ede6c17069a9e9133a38f87d61599f68e7d5f3c812a255436846ee84a262b79ec4d0783afccf2433deabdca9ecf62bef5ff24e90988c7f139d378549c3a8bc6c94e6a1c911c1e02e6f48ed65aaf3d296e",
"Expected": "0000000000000000000000000000000000000000000000000000000000000001",
"Gas": 3450,
"Name": "CallP256Verify",
"NoBenchmark": false
},
{
"Input": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9c29c3df6ce3431b6f030b1b68b1589508ad9d1a149830489c638653aa4b08af93f6e86a9a7643403b6f5c593410d9f7234a8cd27309bce90447073ce17476850615ff147863bc8652be1e369444f90bbc5f9df05a26362e609f73ab1f1839fe3cd34fd2ae672c110671d49115825fc56b5148321aabe5ba39f2b46f71149cff9",
"Expected": "",
"Gas": 3450,
"Name": "CallP256Verify",
"NoBenchmark": false
},
{
"Input": "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9",
"Expected": "",
"Gas": 3450,
"Name": "CallP256Verify",
"NoBenchmark": false
}
]

View File

@ -0,0 +1,26 @@
package secp256r1
import (
"crypto/ecdsa"
"crypto/elliptic"
"math/big"
)
// Generates approptiate public key format from given coordinates
func newPublicKey(x, y *big.Int) *ecdsa.PublicKey {
// Check if the given coordinates are valid
if x == nil || y == nil || !elliptic.P256().IsOnCurve(x, y) {
return nil
}
// Check if the given coordinates are the reference point (infinity)
if x.Sign() == 0 && y.Sign() == 0 {
return nil
}
return &ecdsa.PublicKey{
Curve: elliptic.P256(),
X: x,
Y: y,
}
}

View File

@ -0,0 +1,21 @@
package secp256r1
import (
"crypto/ecdsa"
"math/big"
)
// Verifies the given signature (r, s) for the given hash and public key (x, y).
func Verify(hash []byte, r, s, x, y *big.Int) bool {
// Create the public key format
publicKey := newPublicKey(x, y)
// Check if they are invalid public key coordinates
if publicKey == nil {
return false
}
// Verify the signature with the public key,
// then return true if it's valid, false otherwise
return ecdsa.Verify(publicKey, hash, r, s)
}

View File

@ -90,6 +90,8 @@ type BorConfig interface {
fmt.Stringer
IsAgra(num uint64) bool
GetAgraBlock() *big.Int
IsNapoli(num uint64) bool
GetNapoliBlock() *big.Int
}
func (c *Config) String() string {
@ -214,6 +216,11 @@ func (c *Config) IsAgra(num uint64) bool {
return (c != nil) && (c.Bor != nil) && c.Bor.IsAgra(num)
}
// Refer to https://forum.polygon.technology/t/pip-33-napoli-upgrade
func (c *Config) IsNapoli(num uint64) bool {
return (c != nil) && (c.Bor != nil) && c.Bor.IsNapoli(num)
}
// IsCancun returns whether time is either equal to the Cancun fork time or greater.
func (c *Config) IsCancun(time uint64) bool {
return isForked(c.CancunTime, time)
@ -486,8 +493,10 @@ func borKeyValueConfigHelper[T uint64 | common.Address](field map[string]T, numb
type Rules struct {
ChainID *big.Int
IsHomestead, IsTangerineWhistle, IsSpuriousDragon bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin, IsLondon, IsShanghai, IsCancun, IsPrague bool
IsByzantium, IsConstantinople, IsPetersburg bool
IsIstanbul, IsBerlin, IsLondon, IsShanghai bool
IsCancun, IsNapoli bool
IsPrague bool
IsAura bool
}
@ -511,6 +520,7 @@ func (c *Config) Rules(num uint64, time uint64) *Rules {
IsLondon: c.IsLondon(num),
IsShanghai: c.IsShanghai(time) || c.IsAgra(num),
IsCancun: c.IsCancun(time),
IsNapoli: c.IsNapoli(num),
IsPrague: c.IsPrague(time),
IsAura: c.Aura != nil,
}

View File

@ -945,7 +945,7 @@ func (p *TxPool) isShanghai() bool {
}
func (p *TxPool) isAgra() bool {
// once this flag has been set for the first time we no longer need to check the timestamp
// once this flag has been set for the first time we no longer need to check the block
set := p.isPostAgra.Load()
if set {
return true

View File

@ -168,6 +168,9 @@ const (
// EIP-4844: Shard Blob Transactions
PointEvaluationGas uint64 = 50000
// PIP-27: secp256r1 elliptic curve signature verifier gas price
P256VerifyGas uint64 = 3450
)
// EIP-4788: Beacon block root in the EVM

View File

@ -550,7 +550,7 @@ func ValidateHeaderUnusedFields(header *types.Header) error {
return consensus.ErrUnexpectedWithdrawals
}
return nil
return misc.VerifyAbsenceOfCancunHeaderFields(header)
}
// verifyCascadingFields verifies all the header fields that are not standalone,
@ -897,7 +897,7 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, s
// sort validator by address
sort.Sort(valset.ValidatorsByAddress(newValidators))
if c.config.IsParallelUniverse(header.Number.Uint64()) {
if c.config.IsNapoli(header.Number.Uint64()) { // PIP-16: Transaction Dependency Data
var tempValidatorBytes []byte
for _, validator := range newValidators {
@ -921,7 +921,7 @@ func (c *Bor) Prepare(chain consensus.ChainHeaderReader, header *types.Header, s
header.Extra = append(header.Extra, validator.HeaderBytes()...)
}
}
} else if c.config.IsParallelUniverse(header.Number.Uint64()) {
} else if c.config.IsNapoli(header.Number.Uint64()) { // PIP-16: Transaction Dependency Data
blockExtraData := &BlockExtraData{
ValidatorBytes: nil,
TxDependency: nil,
@ -1584,7 +1584,7 @@ func GetTxDependency(b *types.Block) [][]uint64 {
func GetValidatorBytes(h *types.Header, config *borcfg.BorConfig) []byte {
tempExtra := h.Extra
if !config.IsParallelUniverse(h.Number.Uint64()) {
if !config.IsNapoli(h.Number.Uint64()) {
return tempExtra[types.ExtraVanityLength : len(tempExtra)-types.ExtraSealLength]
}

View File

@ -20,14 +20,13 @@ type BorConfig struct {
OverrideStateSyncRecords map[string]int `json:"overrideStateSyncRecords"` // override state records count
BlockAlloc map[string]interface{} `json:"blockAlloc"`
JaipurBlock *big.Int `json:"jaipurBlock"` // Jaipur switch block (nil = no fork, 0 = already on jaipur)
DelhiBlock *big.Int `json:"delhiBlock"` // Delhi switch block (nil = no fork, 0 = already on delhi)
IndoreBlock *big.Int `json:"indoreBlock"` // Indore switch block (nil = no fork, 0 = already on indore)
AgraBlock *big.Int `json:"agraBlock"` // Agra switch block (nil = no fork, 0 = already in agra)
JaipurBlock *big.Int `json:"jaipurBlock"` // Jaipur switch block (nil = no fork, 0 = already on Jaipur)
DelhiBlock *big.Int `json:"delhiBlock"` // Delhi switch block (nil = no fork, 0 = already on Delhi)
IndoreBlock *big.Int `json:"indoreBlock"` // Indore switch block (nil = no fork, 0 = already on Indore)
AgraBlock *big.Int `json:"agraBlock"` // Agra switch block (nil = no fork, 0 = already on Agra)
NapoliBlock *big.Int `json:"napoliBlock"` // Napoli switch block (nil = no fork, 0 = already on Napoli)
StateSyncConfirmationDelay map[string]uint64 `json:"stateSyncConfirmationDelay"` // StateSync Confirmation Delay, in seconds, to calculate `to`
ParallelUniverseBlock *big.Int `json:"parallelUniverseBlock"` // TODO: update all occurrence, change name and finalize number (hardfork for block-stm related changes)
sprints sprints
}
@ -126,15 +125,13 @@ func (c *BorConfig) GetAgraBlock() *big.Int {
return c.AgraBlock
}
// TODO: modify this function once the block number is finalized
func (c *BorConfig) IsParallelUniverse(number uint64) bool {
if c.ParallelUniverseBlock != nil {
if c.ParallelUniverseBlock.Cmp(big.NewInt(0)) == 0 {
return false
}
// Refer to https://forum.polygon.technology/t/pip-33-napoli-upgrade
func (c *BorConfig) IsNapoli(num uint64) bool {
return isForked(c.NapoliBlock, num)
}
return isForked(c.ParallelUniverseBlock, number)
func (c *BorConfig) GetNapoliBlock() *big.Int {
return c.NapoliBlock
}
func (c *BorConfig) CalculateStateSyncDelay(number uint64) uint64 {