Skip to content

Commit f9fb974

Browse files
committed
feat: add usigverifier-precompile-fix upgrade handler
Drops the legacy ed25519 verifier address from EVM ActiveStaticPrecompiles and adds 0xEC..01 if missing, for chains already past genesis.
1 parent c91a91f commit f9fb974

3 files changed

Lines changed: 264 additions & 1 deletion

File tree

app/upgrades.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import (
77

88
"github.com/pushchain/push-chain-node/app/upgrades"
99
"github.com/pushchain/push-chain-node/app/upgrades/noop"
10+
usigverifierprecompilefix "github.com/pushchain/push-chain-node/app/upgrades/usigverifier-precompile-fix"
1011
)
1112

1213
// Upgrades list of chain upgrades
13-
var Upgrades = []upgrades.Upgrade{}
14+
var Upgrades = []upgrades.Upgrade{
15+
usigverifierprecompilefix.NewUpgrade(),
16+
}
1417

1518
// RegisterUpgradeHandlers registers the chain upgrade handlers
1619
func (app *ChainApp) RegisterUpgradeHandlers() {
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package usigverifierprecompilefix
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"slices"
7+
"strings"
8+
9+
"cosmossdk.io/log"
10+
storetypes "cosmossdk.io/store/types"
11+
upgradetypes "cosmossdk.io/x/upgrade/types"
12+
13+
sdk "github.com/cosmos/cosmos-sdk/types"
14+
"github.com/cosmos/cosmos-sdk/types/module"
15+
16+
"github.com/pushchain/push-chain-node/app/upgrades"
17+
usigverifierprecompile "github.com/pushchain/push-chain-node/precompiles/usigverifier"
18+
)
19+
20+
const UpgradeName = "usigverifier-precompile-fix"
21+
22+
// LegacyUSigVerifierAddress is the address the Ed25519 signature verifier precompile
23+
// used to live at. The node no longer instantiates anything at this address — the
24+
// verifier now lives at usigverifierprecompile.USigVerifierPrecompileAddress
25+
// (0xEC..01) — yet the address is still listed in EVM ActiveStaticPrecompiles on
26+
// chains started from an older genesis.
27+
//
28+
// A declared-but-unimplemented address is worse than an unlisted one:
29+
// Keeper.GetStaticPrecompileInstance panics with "precompiled contract not stored
30+
// in memory" for any address that is active in params but absent from the in-memory
31+
// precompile map, so every call to it aborts the transaction.
32+
const LegacyUSigVerifierAddress = "0x00000000000000000000000000000000000000ca"
33+
34+
func NewUpgrade() upgrades.Upgrade {
35+
return upgrades.Upgrade{
36+
UpgradeName: UpgradeName,
37+
CreateUpgradeHandler: CreateUpgradeHandler,
38+
StoreUpgrades: storetypes.StoreUpgrades{
39+
Added: []string{},
40+
Deleted: []string{},
41+
},
42+
}
43+
}
44+
45+
func CreateUpgradeHandler(
46+
mm upgrades.ModuleManager,
47+
configurator module.Configurator,
48+
ak *upgrades.AppKeepers,
49+
) upgradetypes.UpgradeHandler {
50+
return func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
51+
sdkCtx := sdk.UnwrapSDKContext(ctx)
52+
logger := sdkCtx.Logger().With("upgrade", UpgradeName)
53+
logger.Info("Starting upgrade handler")
54+
55+
// 1. Run module migrations
56+
versionMap, err := mm.RunMigrations(ctx, configurator, fromVM)
57+
if err != nil {
58+
return nil, fmt.Errorf("RunMigrations: %w", err)
59+
}
60+
61+
// 2. Point EVM ActiveStaticPrecompiles at the address the verifier is
62+
// actually registered at.
63+
if err := syncUSigVerifierPrecompile(sdkCtx, ak, logger); err != nil {
64+
return nil, fmt.Errorf("syncUSigVerifierPrecompile: %w", err)
65+
}
66+
67+
logger.Info("Upgrade complete")
68+
return versionMap, nil
69+
}
70+
}
71+
72+
// syncUSigVerifierPrecompile drops the legacy Ed25519 verifier address from EVM
73+
// ActiveStaticPrecompiles and makes sure the address the verifier is registered at
74+
// today is present. It is a no-op when params are already in sync.
75+
func syncUSigVerifierPrecompile(sdkCtx sdk.Context, ak *upgrades.AppKeepers, logger log.Logger) error {
76+
evmParams := ak.EVMKeeper.GetParams(sdkCtx)
77+
78+
active, removed, added := syncActiveStaticPrecompiles(evmParams.ActiveStaticPrecompiles)
79+
if !removed && !added {
80+
logger.Info("EVM ActiveStaticPrecompiles already in sync, skipping",
81+
"legacy", LegacyUSigVerifierAddress,
82+
"current", usigverifierprecompile.USigVerifierPrecompileAddress,
83+
)
84+
return nil
85+
}
86+
87+
evmParams.ActiveStaticPrecompiles = active
88+
89+
if err := ak.EVMKeeper.SetParams(sdkCtx, evmParams); err != nil {
90+
return fmt.Errorf("failed to set EVM params after syncing usigverifier precompile: %w", err)
91+
}
92+
93+
logger.Info("Synced usigverifier precompile in EVM params",
94+
"removed_legacy", removed,
95+
"added_current", added,
96+
"legacy", LegacyUSigVerifierAddress,
97+
"current", usigverifierprecompile.USigVerifierPrecompileAddress,
98+
)
99+
return nil
100+
}
101+
102+
// syncActiveStaticPrecompiles returns active with the legacy Ed25519 verifier
103+
// address removed and the current one appended when missing, reporting whether
104+
// either happened. Every other entry is left untouched.
105+
//
106+
// The result is kept sorted because x/vm's ValidatePrecompiles rejects an unsorted
107+
// list; Keeper.SetParams sorts too, but exported genesis is validated as-is.
108+
func syncActiveStaticPrecompiles(active []string) (out []string, removed, added bool) {
109+
out = make([]string, 0, len(active)+1)
110+
hasCurrent := false
111+
112+
for _, addr := range active {
113+
if strings.EqualFold(addr, LegacyUSigVerifierAddress) {
114+
removed = true
115+
continue
116+
}
117+
if strings.EqualFold(addr, usigverifierprecompile.USigVerifierPrecompileAddress) {
118+
hasCurrent = true
119+
}
120+
out = append(out, addr)
121+
}
122+
123+
if !hasCurrent {
124+
out = append(out, usigverifierprecompile.USigVerifierPrecompileAddress)
125+
added = true
126+
}
127+
128+
slices.Sort(out)
129+
return out, removed, added
130+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package usigverifierprecompilefix
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"slices"
7+
"strings"
8+
"testing"
9+
10+
"github.com/stretchr/testify/require"
11+
12+
usigverifierprecompile "github.com/pushchain/push-chain-node/precompiles/usigverifier"
13+
)
14+
15+
const currentAddr = usigverifierprecompile.USigVerifierPrecompileAddress
16+
17+
// baseline mirrors the non-verifier entries of a real chain's ActiveStaticPrecompiles.
18+
var baseline = []string{
19+
"0x00000000000000000000000000000000000000CB",
20+
"0x0000000000000000000000000000000000000100",
21+
"0x0000000000000000000000000000000000000400",
22+
"0x0000000000000000000000000000000000000800",
23+
"0x0000000000000000000000000000000000000801",
24+
"0x0000000000000000000000000000000000000802",
25+
"0x0000000000000000000000000000000000000803",
26+
"0x0000000000000000000000000000000000000804",
27+
"0x0000000000000000000000000000000000000805",
28+
}
29+
30+
func withLegacy() []string {
31+
out := append([]string{LegacyUSigVerifierAddress}, baseline...)
32+
slices.Sort(out)
33+
return out
34+
}
35+
36+
func TestSyncActiveStaticPrecompiles_ReplacesLegacyAddress(t *testing.T) {
37+
got, removed, added := syncActiveStaticPrecompiles(withLegacy())
38+
39+
require.True(t, removed, "legacy address should have been removed")
40+
require.True(t, added, "current address should have been added")
41+
42+
require.NotContains(t, got, LegacyUSigVerifierAddress)
43+
require.Contains(t, got, currentAddr)
44+
45+
// Everything else survives untouched, and the list stays sorted so that
46+
// x/vm's ValidatePrecompiles accepts it.
47+
for _, addr := range baseline {
48+
require.Contains(t, got, addr)
49+
}
50+
require.Len(t, got, len(baseline)+1)
51+
require.True(t, slices.IsSorted(got), "precompile list must stay sorted: %v", got)
52+
}
53+
54+
func TestSyncActiveStaticPrecompiles_Idempotent(t *testing.T) {
55+
first, _, _ := syncActiveStaticPrecompiles(withLegacy())
56+
57+
second, removed, added := syncActiveStaticPrecompiles(first)
58+
require.False(t, removed, "second run should find nothing to remove")
59+
require.False(t, added, "second run should find nothing to add")
60+
require.Equal(t, first, second)
61+
}
62+
63+
func TestSyncActiveStaticPrecompiles_AddsCurrentWhenBothMissing(t *testing.T) {
64+
got, removed, added := syncActiveStaticPrecompiles(slices.Clone(baseline))
65+
66+
require.False(t, removed)
67+
require.True(t, added)
68+
require.Contains(t, got, currentAddr)
69+
require.Len(t, got, len(baseline)+1)
70+
}
71+
72+
func TestSyncActiveStaticPrecompiles_RemovesLegacyWhenCurrentPresent(t *testing.T) {
73+
in := append(withLegacy(), currentAddr)
74+
slices.Sort(in)
75+
76+
got, removed, added := syncActiveStaticPrecompiles(in)
77+
78+
require.True(t, removed)
79+
require.False(t, added)
80+
require.NotContains(t, got, LegacyUSigVerifierAddress)
81+
require.Contains(t, got, currentAddr)
82+
require.Len(t, got, len(baseline)+1)
83+
}
84+
85+
func TestSyncActiveStaticPrecompiles_MatchesLegacyCaseInsensitively(t *testing.T) {
86+
in := append([]string{strings.ToUpper(LegacyUSigVerifierAddress[2:])}, baseline...)
87+
in[0] = "0x" + in[0]
88+
89+
got, removed, _ := syncActiveStaticPrecompiles(in)
90+
91+
require.True(t, removed)
92+
for _, addr := range got {
93+
require.False(t, strings.EqualFold(addr, LegacyUSigVerifierAddress))
94+
}
95+
}
96+
97+
// TestGenesisScriptsActivateCurrentVerifier guards the genesis half of the same
98+
// fix: a fresh chain must activate the address the verifier is registered at and
99+
// must not declare the legacy one, which nothing implements.
100+
func TestGenesisScriptsActivateCurrentVerifier(t *testing.T) {
101+
repoRoot := filepath.Join("..", "..", "..")
102+
103+
scripts := []string{
104+
"scripts/test_node.sh",
105+
"local-native/scripts/setup-genesis-auto.sh",
106+
"local-multi-validator/scripts/setup-genesis-auto.sh",
107+
"testnet/core/setup/setup_genesis_validator.sh",
108+
}
109+
110+
for _, script := range scripts {
111+
t.Run(script, func(t *testing.T) {
112+
raw, err := os.ReadFile(filepath.Join(repoRoot, script))
113+
require.NoError(t, err)
114+
115+
var line string
116+
for _, l := range strings.Split(string(raw), "\n") {
117+
if strings.Contains(l, "active_static_precompiles") {
118+
line = l
119+
break
120+
}
121+
}
122+
require.NotEmpty(t, line, "no active_static_precompiles assignment found")
123+
124+
require.NotContains(t, strings.ToLower(line), strings.ToLower(LegacyUSigVerifierAddress),
125+
"genesis must not declare the legacy verifier address, nothing is registered at it")
126+
require.Contains(t, strings.ToLower(line), strings.ToLower(currentAddr),
127+
"genesis must activate the verifier address the node registers")
128+
})
129+
}
130+
}

0 commit comments

Comments
 (0)