Skip to content
Open
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
5 changes: 4 additions & 1 deletion app/upgrades.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import (

"github.com/pushchain/push-chain-node/app/upgrades"
"github.com/pushchain/push-chain-node/app/upgrades/noop"
usigverifierprecompilefix "github.com/pushchain/push-chain-node/app/upgrades/usigverifier-precompile-fix"
)

// Upgrades list of chain upgrades
var Upgrades = []upgrades.Upgrade{}
var Upgrades = []upgrades.Upgrade{
usigverifierprecompilefix.NewUpgrade(),
}

// RegisterUpgradeHandlers registers the chain upgrade handlers
func (app *ChainApp) RegisterUpgradeHandlers() {
Expand Down
130 changes: 130 additions & 0 deletions app/upgrades/usigverifier-precompile-fix/upgrade.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package usigverifierprecompilefix

import (
"context"
"fmt"
"slices"
"strings"

"cosmossdk.io/log"
storetypes "cosmossdk.io/store/types"
upgradetypes "cosmossdk.io/x/upgrade/types"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/module"

"github.com/pushchain/push-chain-node/app/upgrades"
usigverifierprecompile "github.com/pushchain/push-chain-node/precompiles/usigverifier"
)

const UpgradeName = "usigverifier-precompile-fix"

// LegacyUSigVerifierAddress is the address the Ed25519 signature verifier precompile
// used to live at. The node no longer instantiates anything at this address — the
// verifier now lives at usigverifierprecompile.USigVerifierPrecompileAddress
// (0xEC..01) — yet the address is still listed in EVM ActiveStaticPrecompiles on
// chains started from an older genesis.
//
// A declared-but-unimplemented address is worse than an unlisted one:
// Keeper.GetStaticPrecompileInstance panics with "precompiled contract not stored
// in memory" for any address that is active in params but absent from the in-memory
// precompile map, so every call to it aborts the transaction.
const LegacyUSigVerifierAddress = "0x00000000000000000000000000000000000000ca"

func NewUpgrade() upgrades.Upgrade {
return upgrades.Upgrade{
UpgradeName: UpgradeName,
CreateUpgradeHandler: CreateUpgradeHandler,
StoreUpgrades: storetypes.StoreUpgrades{
Added: []string{},
Deleted: []string{},
},
}
}

func CreateUpgradeHandler(
mm upgrades.ModuleManager,
configurator module.Configurator,
ak *upgrades.AppKeepers,
) upgradetypes.UpgradeHandler {
return func(ctx context.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
sdkCtx := sdk.UnwrapSDKContext(ctx)
logger := sdkCtx.Logger().With("upgrade", UpgradeName)
logger.Info("Starting upgrade handler")

// 1. Run module migrations
versionMap, err := mm.RunMigrations(ctx, configurator, fromVM)
if err != nil {
return nil, fmt.Errorf("RunMigrations: %w", err)
}

// 2. Point EVM ActiveStaticPrecompiles at the address the verifier is
// actually registered at.
if err := syncUSigVerifierPrecompile(sdkCtx, ak, logger); err != nil {
return nil, fmt.Errorf("syncUSigVerifierPrecompile: %w", err)
}

logger.Info("Upgrade complete")
return versionMap, nil
}
}

// syncUSigVerifierPrecompile drops the legacy Ed25519 verifier address from EVM
// ActiveStaticPrecompiles and makes sure the address the verifier is registered at
// today is present. It is a no-op when params are already in sync.
func syncUSigVerifierPrecompile(sdkCtx sdk.Context, ak *upgrades.AppKeepers, logger log.Logger) error {
evmParams := ak.EVMKeeper.GetParams(sdkCtx)

active, removed, added := syncActiveStaticPrecompiles(evmParams.ActiveStaticPrecompiles)
if !removed && !added {
logger.Info("EVM ActiveStaticPrecompiles already in sync, skipping",
"legacy", LegacyUSigVerifierAddress,
"current", usigverifierprecompile.USigVerifierPrecompileAddress,
)
return nil
}

evmParams.ActiveStaticPrecompiles = active

if err := ak.EVMKeeper.SetParams(sdkCtx, evmParams); err != nil {
return fmt.Errorf("failed to set EVM params after syncing usigverifier precompile: %w", err)
}

logger.Info("Synced usigverifier precompile in EVM params",
"removed_legacy", removed,
"added_current", added,
"legacy", LegacyUSigVerifierAddress,
"current", usigverifierprecompile.USigVerifierPrecompileAddress,
)
return nil
}

// syncActiveStaticPrecompiles returns active with the legacy Ed25519 verifier
// address removed and the current one appended when missing, reporting whether
// either happened. Every other entry is left untouched.
//
// The result is kept sorted because x/vm's ValidatePrecompiles rejects an unsorted
// list; Keeper.SetParams sorts too, but exported genesis is validated as-is.
func syncActiveStaticPrecompiles(active []string) (out []string, removed, added bool) {
out = make([]string, 0, len(active)+1)
hasCurrent := false

for _, addr := range active {
if strings.EqualFold(addr, LegacyUSigVerifierAddress) {
removed = true
continue
}
if strings.EqualFold(addr, usigverifierprecompile.USigVerifierPrecompileAddress) {
hasCurrent = true
}
out = append(out, addr)
}

if !hasCurrent {
out = append(out, usigverifierprecompile.USigVerifierPrecompileAddress)
added = true
}

slices.Sort(out)
return out, removed, added
}
130 changes: 130 additions & 0 deletions app/upgrades/usigverifier-precompile-fix/upgrade_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package usigverifierprecompilefix

import (
"os"
"path/filepath"
"slices"
"strings"
"testing"

"github.com/stretchr/testify/require"

usigverifierprecompile "github.com/pushchain/push-chain-node/precompiles/usigverifier"
)

const currentAddr = usigverifierprecompile.USigVerifierPrecompileAddress

// baseline mirrors the non-verifier entries of a real chain's ActiveStaticPrecompiles.
var baseline = []string{
"0x00000000000000000000000000000000000000CB",
"0x0000000000000000000000000000000000000100",
"0x0000000000000000000000000000000000000400",
"0x0000000000000000000000000000000000000800",
"0x0000000000000000000000000000000000000801",
"0x0000000000000000000000000000000000000802",
"0x0000000000000000000000000000000000000803",
"0x0000000000000000000000000000000000000804",
"0x0000000000000000000000000000000000000805",
}

func withLegacy() []string {
out := append([]string{LegacyUSigVerifierAddress}, baseline...)
slices.Sort(out)
return out
}

func TestSyncActiveStaticPrecompiles_ReplacesLegacyAddress(t *testing.T) {
got, removed, added := syncActiveStaticPrecompiles(withLegacy())

require.True(t, removed, "legacy address should have been removed")
require.True(t, added, "current address should have been added")

require.NotContains(t, got, LegacyUSigVerifierAddress)
require.Contains(t, got, currentAddr)

// Everything else survives untouched, and the list stays sorted so that
// x/vm's ValidatePrecompiles accepts it.
for _, addr := range baseline {
require.Contains(t, got, addr)
}
require.Len(t, got, len(baseline)+1)
require.True(t, slices.IsSorted(got), "precompile list must stay sorted: %v", got)
}

func TestSyncActiveStaticPrecompiles_Idempotent(t *testing.T) {
first, _, _ := syncActiveStaticPrecompiles(withLegacy())

second, removed, added := syncActiveStaticPrecompiles(first)
require.False(t, removed, "second run should find nothing to remove")
require.False(t, added, "second run should find nothing to add")
require.Equal(t, first, second)
}

func TestSyncActiveStaticPrecompiles_AddsCurrentWhenBothMissing(t *testing.T) {
got, removed, added := syncActiveStaticPrecompiles(slices.Clone(baseline))

require.False(t, removed)
require.True(t, added)
require.Contains(t, got, currentAddr)
require.Len(t, got, len(baseline)+1)
}

func TestSyncActiveStaticPrecompiles_RemovesLegacyWhenCurrentPresent(t *testing.T) {
in := append(withLegacy(), currentAddr)
slices.Sort(in)

got, removed, added := syncActiveStaticPrecompiles(in)

require.True(t, removed)
require.False(t, added)
require.NotContains(t, got, LegacyUSigVerifierAddress)
require.Contains(t, got, currentAddr)
require.Len(t, got, len(baseline)+1)
}

func TestSyncActiveStaticPrecompiles_MatchesLegacyCaseInsensitively(t *testing.T) {
in := append([]string{strings.ToUpper(LegacyUSigVerifierAddress[2:])}, baseline...)
in[0] = "0x" + in[0]

got, removed, _ := syncActiveStaticPrecompiles(in)

require.True(t, removed)
for _, addr := range got {
require.False(t, strings.EqualFold(addr, LegacyUSigVerifierAddress))
}
}

// TestGenesisScriptsActivateCurrentVerifier guards the genesis half of the same
// fix: a fresh chain must activate the address the verifier is registered at and
// must not declare the legacy one, which nothing implements.
func TestGenesisScriptsActivateCurrentVerifier(t *testing.T) {
repoRoot := filepath.Join("..", "..", "..")

scripts := []string{
"scripts/test_node.sh",
"local-native/scripts/setup-genesis-auto.sh",
"local-multi-validator/scripts/setup-genesis-auto.sh",
"testnet/core/setup/setup_genesis_validator.sh",
}

for _, script := range scripts {
t.Run(script, func(t *testing.T) {
raw, err := os.ReadFile(filepath.Join(repoRoot, script))
require.NoError(t, err)

var line string
for _, l := range strings.Split(string(raw), "\n") {
if strings.Contains(l, "active_static_precompiles") {
line = l
break
}
}
require.NotEmpty(t, line, "no active_static_precompiles assignment found")

require.NotContains(t, strings.ToLower(line), strings.ToLower(LegacyUSigVerifierAddress),
"genesis must not declare the legacy verifier address, nothing is registered at it")
require.Contains(t, strings.ToLower(line), strings.ToLower(currentAddr),
"genesis must activate the verifier address the node registers")
})
}
}
2 changes: 1 addition & 1 deletion local-multi-validator/scripts/setup-genesis-auto.sh
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ update_genesis '.app_state["gov"]["params"]["expedited_voting_period"]="150s"'

# EVM
update_genesis `printf '.app_state["evm"]["params"]["evm_denom"]="%s"' $DENOM`
update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805"]'
update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]'

# EVM Chain config
update_genesis `printf '.app_state["evm"]["params"]["chain_config"]["chain_id"]=%s' $EVM_CHAIN_ID`
Expand Down
2 changes: 1 addition & 1 deletion local-native/scripts/setup-genesis-auto.sh
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ update_genesis '.app_state["gov"]["params"]["max_deposit_period"]="300s"'
update_genesis '.app_state["gov"]["params"]["voting_period"]="300s"'
update_genesis '.app_state["gov"]["params"]["expedited_voting_period"]="60s"'
update_genesis ".app_state[\"evm\"][\"params\"][\"evm_denom\"]=\"$DENOM\""
update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805"]'
update_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]'
update_genesis ".app_state[\"staking\"][\"params\"][\"bond_denom\"]=\"$DENOM\""
update_genesis ".app_state[\"mint\"][\"params\"][\"mint_denom\"]=\"$DENOM\""
update_genesis '.consensus["params"]["abci"]["vote_extensions_enable_height"]="2"'
Expand Down
2 changes: 1 addition & 1 deletion scripts/test_node.sh
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ from_scratch () {
update_test_genesis '.app_state["gov"]["params"]["expedited_voting_period"]="15s"'

update_test_genesis `printf '.app_state["evm"]["params"]["evm_denom"]="%s"' $DENOM`
update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805"]'
update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]'
update_test_genesis '.app_state["erc20"]["params"]["native_precompiles"]=["0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"]' # https://eips.ethereum.org/EIPS/eip-7528
update_test_genesis `printf '.app_state["erc20"]["token_pairs"]=[{contract_owner:1,erc20_address:"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE",denom:"%s",enabled:true}]' $DENOM`
update_test_genesis '.app_state["feemarket"]["params"]["no_base_fee"]=false'
Expand Down
2 changes: 0 additions & 2 deletions test/utils/bytecode.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion testnet/core/setup/setup_genesis_validator.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ echo "🛠️ Updating genesis parameters..."

# EVM
update_test_genesis `printf '.app_state["evm"]["params"]["evm_denom"]="%s"' $DENOM` # This seems duplicated since chain config already has this
update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x00000000000000000000000000000000000000ca","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805"]'
update_test_genesis '.app_state["evm"]["params"]["active_static_precompiles"]=["0x00000000000000000000000000000000000000CB","0x0000000000000000000000000000000000000100","0x0000000000000000000000000000000000000400","0x0000000000000000000000000000000000000800","0x0000000000000000000000000000000000000801","0x0000000000000000000000000000000000000802","0x0000000000000000000000000000000000000803","0x0000000000000000000000000000000000000804","0x0000000000000000000000000000000000000805","0xEC00000000000000000000000000000000000001"]'
update_test_genesis '.app_state["evm"]["params"]["chain_config"]["homestead_block"]="0"'
update_test_genesis '.app_state["evm"]["params"]["chain_config"]["dao_fork_block"]="0"'
update_test_genesis '.app_state["evm"]["params"]["chain_config"]["dao_fork_support"]=true'
Expand Down
2 changes: 1 addition & 1 deletion x/uexecutor/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ Vote messages check `IsBondedUniversalValidator` and `IsTombstonedUniversalValid
The cryptographic binding is enforced inside the UEA contract's `executeUniversalTx` (see [`UEA_EVM.sol`](https://github.com/pushchain/push-chain-core-contracts/blob/86e20e2d26819e7cc885549f08c66895221dfab0/src/uea/UEA_EVM.sol#L145) and [`UEA_SVM.sol`](https://github.com/pushchain/push-chain-core-contracts/blob/86e20e2d26819e7cc885549f08c66895221dfab0/src/uea/UEA_SVM.sol)):

1. The contract holds the owner's public key as **immutable bytes** set at UEA deployment via `initialize(_id, _factory)`. There is no code path that mutates this after init.
2. `executeUniversalTx(payload, signature)` verifies the `signature` (passed in as `MsgExecutePayload.VerificationData`) against this stored owner — ECDSA recovery for EVM-origin owners, the Ed25519 precompile (`0x00…00ca`) for SVM-origin owners.
2. `executeUniversalTx(payload, signature)` verifies the `signature` (passed in as `MsgExecutePayload.VerificationData`) against this stored owner — ECDSA recovery for EVM-origin owners, the Ed25519 precompile (`0xEC…01`) for SVM-origin owners.
3. The signed payload hash includes a contract-tracked `nonce` (monotonic per UEA) and optional `deadline`, providing replay and freshness protection.
4. If signature verification fails, the contract reverts. The revert propagates as `execErr` from `CallUEAExecutePayload`; the keeper returns the error from `ExecutePayload`; the entire Cosmos transaction (including any partial gas-fee deduction) rolls back atomically. **No state changes survive a failed signature check.**

Expand Down
Loading