mirror of
https://github.com/sonr-io/sonr.git
synced 2026-08-02 17:31:39 +00:00
-74
@@ -1,74 +0,0 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante"
|
||||
"github.com/cosmos/ibc-go/v8/modules/core/keeper"
|
||||
|
||||
circuitante "cosmossdk.io/x/circuit/ante"
|
||||
circuitkeeper "cosmossdk.io/x/circuit/keeper"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
||||
|
||||
sdkmath "cosmossdk.io/math"
|
||||
poaante "github.com/strangelove-ventures/poa/ante"
|
||||
|
||||
globalfeeante "github.com/strangelove-ventures/globalfee/x/globalfee/ante"
|
||||
globalfeekeeper "github.com/strangelove-ventures/globalfee/x/globalfee/keeper"
|
||||
)
|
||||
|
||||
// HandlerOptions extend the SDK's AnteHandler options by requiring the IBC
|
||||
// channel keeper.
|
||||
type HandlerOptions struct {
|
||||
ante.HandlerOptions
|
||||
IBCKeeper *keeper.Keeper
|
||||
CircuitKeeper *circuitkeeper.Keeper
|
||||
StakingKeeper *stakingkeeper.Keeper
|
||||
GlobalFeeKeeper globalfeekeeper.Keeper
|
||||
BypassMinFeeMsgTypes []string
|
||||
}
|
||||
|
||||
// NewAnteHandler constructor
|
||||
func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
|
||||
if options.AccountKeeper == nil {
|
||||
return nil, errors.New("account keeper is required for ante builder")
|
||||
}
|
||||
if options.BankKeeper == nil {
|
||||
return nil, errors.New("bank keeper is required for ante builder")
|
||||
}
|
||||
if options.SignModeHandler == nil {
|
||||
return nil, errors.New("sign mode handler is required for ante builder")
|
||||
}
|
||||
if options.CircuitKeeper == nil {
|
||||
return nil, errors.New("circuit keeper is required for ante builder")
|
||||
}
|
||||
|
||||
poaDoGenTxRateValidation := false
|
||||
poaRateFloor := sdkmath.LegacyMustNewDecFromStr("0.05")
|
||||
poaRateCeil := sdkmath.LegacyMustNewDecFromStr("0.25")
|
||||
|
||||
anteDecorators := []sdk.AnteDecorator{
|
||||
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
|
||||
circuitante.NewCircuitBreakerDecorator(options.CircuitKeeper),
|
||||
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
|
||||
ante.NewValidateBasicDecorator(),
|
||||
ante.NewTxTimeoutHeightDecorator(),
|
||||
ante.NewValidateMemoDecorator(options.AccountKeeper),
|
||||
// ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
|
||||
globalfeeante.NewFeeDecorator(options.BypassMinFeeMsgTypes, options.GlobalFeeKeeper, 2_000_000),
|
||||
// ante.NewDeductFeeDecorator(options.AccountKeeper, options.BankKeeper, options.FeegrantKeeper, options.TxFeeChecker),
|
||||
ante.NewSetPubKeyDecorator(options.AccountKeeper), // SetPubKeyDecorator must be called before all signature verification decorators
|
||||
ante.NewValidateSigCountDecorator(options.AccountKeeper),
|
||||
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
|
||||
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
|
||||
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
|
||||
ibcante.NewRedundantRelayDecorator(options.IBCKeeper),
|
||||
poaante.NewPOADisableStakingDecorator(),
|
||||
poaante.NewCommissionLimitDecorator(poaDoGenTxRateValidation, poaRateFloor, poaRateCeil),
|
||||
}
|
||||
|
||||
return sdk.ChainAnteDecorators(anteDecorators...), nil
|
||||
}
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
// Package ante provides ante handler implementations for transaction processing
|
||||
// in the Sonr blockchain. It supports both Cosmos SDK and Ethereum transactions.
|
||||
package ante
|
||||
|
||||
import (
|
||||
errorsmod "cosmossdk.io/errors"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
)
|
||||
|
||||
// NewAnteHandler returns an ante handler responsible for attempting to route an
|
||||
// Ethereum or SDK transaction to an internal ante handler for performing
|
||||
// transaction-level processing (e.g. fee payment, signature verification) before
|
||||
// being passed onto it's respective handler.
|
||||
//
|
||||
// The handler inspects the transaction type and extension options to determine
|
||||
// the appropriate processing path:
|
||||
// - Ethereum transactions with ExtensionOptionsEthereumTx
|
||||
// - Cosmos SDK transactions with ExtensionOptionDynamicFeeTx
|
||||
// - Standard Cosmos SDK transactions
|
||||
func NewAnteHandler(options HandlerOptions) sdk.AnteHandler {
|
||||
return func(
|
||||
ctx sdk.Context, tx sdk.Tx, sim bool,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
var anteHandler sdk.AnteHandler
|
||||
|
||||
txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx)
|
||||
if ok {
|
||||
opts := txWithExtensions.GetExtensionOptions()
|
||||
if len(opts) > 0 {
|
||||
switch typeURL := opts[0].GetTypeUrl(); typeURL {
|
||||
case "/cosmos.evm.vm.v1.ExtensionOptionsEthereumTx":
|
||||
// handle as *evmtypes.MsgEthereumTx
|
||||
anteHandler = newMonoEVMAnteHandler(options)
|
||||
case "/cosmos.evm.vm.v1.ExtensionOptionDynamicFeeTx":
|
||||
// cosmos-sdk tx with dynamic fee extension
|
||||
anteHandler = NewCosmosAnteHandler(options)
|
||||
default:
|
||||
return ctx, errorsmod.Wrapf(
|
||||
errortypes.ErrUnknownExtensionOptions,
|
||||
"rejecting tx with unsupported extension option: %s", typeURL,
|
||||
)
|
||||
}
|
||||
|
||||
return anteHandler(ctx, tx, sim)
|
||||
}
|
||||
}
|
||||
|
||||
// handle as totally normal Cosmos SDK tx
|
||||
switch tx.(type) {
|
||||
case sdk.Tx:
|
||||
anteHandler = NewCosmosAnteHandler(options)
|
||||
default:
|
||||
return ctx, errorsmod.Wrapf(errortypes.ErrUnknownRequest, "invalid transaction type: %T", tx)
|
||||
}
|
||||
|
||||
return anteHandler(ctx, tx, sim)
|
||||
}
|
||||
}
|
||||
Executable
+107
@@ -0,0 +1,107 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
sdkvesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
|
||||
evmoscosmosante "github.com/cosmos/evm/ante/cosmos"
|
||||
evmante "github.com/cosmos/evm/ante/evm"
|
||||
evmtypes "github.com/cosmos/evm/x/vm/types"
|
||||
|
||||
circuitante "cosmossdk.io/x/circuit/ante"
|
||||
ibcante "github.com/cosmos/ibc-go/v8/modules/core/ante"
|
||||
)
|
||||
|
||||
// NewCosmosAnteHandler creates the default ante handler for Cosmos SDK transactions.
|
||||
// It sets up a chain of decorators that perform various checks and operations:
|
||||
// - Rejects Ethereum transactions in Cosmos context
|
||||
// - Enforces authz limitations
|
||||
// - Sets up transaction context
|
||||
// - Validates basic transaction properties
|
||||
// - Handles WebAuthn gasless transactions
|
||||
// - Handles gas consumption and fee deduction
|
||||
// - Performs signature verification
|
||||
// - Manages account sequences
|
||||
// - Handles IBC-specific checks
|
||||
func NewCosmosAnteHandler(options HandlerOptions) sdk.AnteHandler {
|
||||
// Determine if we should use enhanced gasless mode
|
||||
// Enhanced mode allows address generation from credentials for true gasless onboarding
|
||||
enhancedGaslessMode := options.EnableEnhancedGasless
|
||||
|
||||
// Build the decorator chain
|
||||
decorators := []sdk.AnteDecorator{
|
||||
// WebAuthn bypass - must be first to intercept WebAuthn transactions
|
||||
NewWebAuthnBypassDecorator(),
|
||||
evmoscosmosante.NewRejectMessagesDecorator(), // reject MsgEthereumTxs
|
||||
evmoscosmosante.NewAuthzLimiterDecorator( // disable the Msg types that cannot be included on an authz.MsgExec msgs field
|
||||
sdk.MsgTypeURL(&evmtypes.MsgEthereumTx{}),
|
||||
sdk.MsgTypeURL(&sdkvesting.MsgCreateVestingAccount{}),
|
||||
),
|
||||
|
||||
ante.NewSetUpContextDecorator(),
|
||||
circuitante.NewCircuitBreakerDecorator(options.CircuitKeeper),
|
||||
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
|
||||
ante.NewValidateBasicDecorator(),
|
||||
ante.NewTxTimeoutHeightDecorator(),
|
||||
ante.NewValidateMemoDecorator(options.AccountKeeper),
|
||||
|
||||
// UCAN validation - must come before fee deduction for gasless support
|
||||
NewConditionalUCANDecorator(NewUCANDecorator()),
|
||||
evmoscosmosante.NewMinGasPriceDecorator(
|
||||
options.FeeMarketKeeper,
|
||||
options.EvmKeeper,
|
||||
options.ControlPanelKeeper,
|
||||
),
|
||||
ante.NewConsumeGasForTxSizeDecorator(options.AccountKeeper),
|
||||
|
||||
// WebAuthn gasless transaction support - must come before fee deduction
|
||||
// Enhanced mode allows true gasless onboarding without pre-existing accounts
|
||||
NewWebAuthnGaslessDecorator(options.AccountKeeper, options.DidKeeper, enhancedGaslessMode),
|
||||
|
||||
// Conditional fee deduction - skips fees for gasless WebAuthn and UCAN
|
||||
NewUCANGaslessDecorator(
|
||||
NewConditionalFeeDecorator(ante.NewDeductFeeDecorator(
|
||||
options.AccountKeeper,
|
||||
options.BankKeeper,
|
||||
options.FeegrantKeeper,
|
||||
options.TxFeeChecker,
|
||||
)),
|
||||
),
|
||||
}
|
||||
|
||||
// Add signature verification decorators
|
||||
// In enhanced gasless mode, we wrap these to be conditional
|
||||
if enhancedGaslessMode {
|
||||
// Conditional decorators that skip verification for gasless transactions
|
||||
decorators = append(
|
||||
decorators,
|
||||
NewConditionalPubKeyDecorator(ante.NewSetPubKeyDecorator(options.AccountKeeper)),
|
||||
NewConditionalSigCountDecorator(
|
||||
ante.NewValidateSigCountDecorator(options.AccountKeeper),
|
||||
),
|
||||
NewConditionalSigGasDecorator(
|
||||
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
|
||||
),
|
||||
NewConditionalSignatureDecorator(
|
||||
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
// Standard signature verification decorators
|
||||
decorators = append(decorators,
|
||||
ante.NewSetPubKeyDecorator(options.AccountKeeper),
|
||||
ante.NewValidateSigCountDecorator(options.AccountKeeper),
|
||||
ante.NewSigGasConsumeDecorator(options.AccountKeeper, options.SigGasConsumer),
|
||||
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
|
||||
)
|
||||
}
|
||||
|
||||
// Add remaining decorators
|
||||
decorators = append(decorators,
|
||||
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
|
||||
ibcante.NewRedundantRelayDecorator(options.IBCKeeper),
|
||||
evmante.NewGasWantedDecorator(options.EvmKeeper, options.FeeMarketKeeper),
|
||||
)
|
||||
|
||||
return sdk.ChainAnteDecorators(decorators...)
|
||||
}
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
evmante "github.com/cosmos/evm/ante/evm"
|
||||
)
|
||||
|
||||
// newMonoEVMAnteHandler creates the ante handler for Ethereum Virtual Machine transactions.
|
||||
// It uses a single decorator that handles all EVM-specific validation and processing,
|
||||
// including gas calculation, fee market dynamics, and account management.
|
||||
//
|
||||
// The mono decorator performs all EVM ante operations in a single pass for efficiency.
|
||||
func newMonoEVMAnteHandler(options HandlerOptions) sdk.AnteHandler {
|
||||
return sdk.ChainAnteDecorators(
|
||||
evmante.NewEVMMonoDecorator(
|
||||
options.AccountKeeper,
|
||||
options.FeeMarketKeeper,
|
||||
options.EvmKeeper,
|
||||
options.ControlPanelKeeper,
|
||||
options.MaxTxGasWanted,
|
||||
),
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
anteinterfaces "github.com/cosmos/evm/ante/interfaces"
|
||||
)
|
||||
|
||||
// Ensure ControlPanelKeeper implements the interface
|
||||
var _ anteinterfaces.ControlPanelKeeper = (*ControlPanelKeeper)(nil)
|
||||
|
||||
// ControlPanelKeeper provides control panel functionality for sponsored transactions.
|
||||
// This is a simple implementation that can be extended to support sponsored addresses
|
||||
// and custom transaction priorities in the future.
|
||||
type ControlPanelKeeper struct {
|
||||
// sponsoredAddresses could be loaded from state or configuration
|
||||
sponsoredAddresses map[string]bool
|
||||
// priority for sponsored transactions
|
||||
sponsoredTxPriority int64
|
||||
}
|
||||
|
||||
// NewControlPanelKeeper creates a new ControlPanelKeeper instance
|
||||
func NewControlPanelKeeper() *ControlPanelKeeper {
|
||||
return &ControlPanelKeeper{
|
||||
sponsoredAddresses: make(map[string]bool),
|
||||
sponsoredTxPriority: 0, // Default priority
|
||||
}
|
||||
}
|
||||
|
||||
// IsSponsoredAddress checks if an address is sponsored for gasless transactions
|
||||
func (k *ControlPanelKeeper) IsSponsoredAddress(ctx context.Context, addr []byte) bool {
|
||||
// For now, return false for all addresses
|
||||
// This can be extended to check against a whitelist or state
|
||||
return false
|
||||
}
|
||||
|
||||
// GetSponsoredTransactionPriority returns the priority for sponsored transactions
|
||||
func (k *ControlPanelKeeper) GetSponsoredTransactionPriority(ctx context.Context) int64 {
|
||||
// Return default priority
|
||||
// This can be made configurable or dynamic based on chain state
|
||||
return k.sponsoredTxPriority
|
||||
}
|
||||
|
||||
// SetSponsoredAddress adds or removes an address from the sponsored list
|
||||
// This is a helper method for future use
|
||||
func (k *ControlPanelKeeper) SetSponsoredAddress(addr sdk.AccAddress, sponsored bool) {
|
||||
if sponsored {
|
||||
k.sponsoredAddresses[addr.String()] = true
|
||||
} else {
|
||||
delete(k.sponsoredAddresses, addr.String())
|
||||
}
|
||||
}
|
||||
|
||||
// SetSponsoredTransactionPriority updates the priority for sponsored transactions
|
||||
// This is a helper method for future use
|
||||
func (k *ControlPanelKeeper) SetSponsoredTransactionPriority(priority int64) {
|
||||
k.sponsoredTxPriority = priority
|
||||
}
|
||||
Executable
+129
@@ -0,0 +1,129 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
addresscodec "cosmossdk.io/core/address"
|
||||
errorsmod "cosmossdk.io/errors"
|
||||
storetypes "cosmossdk.io/store/types"
|
||||
circuitkeeper "cosmossdk.io/x/circuit/keeper"
|
||||
txsigning "cosmossdk.io/x/tx/signing"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/ante"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
anteinterfaces "github.com/cosmos/evm/ante/interfaces"
|
||||
ibckeeper "github.com/cosmos/ibc-go/v8/modules/core/keeper"
|
||||
)
|
||||
|
||||
// WebAuthnKeeperInterface defines the required methods from the DID keeper for WebAuthn gasless processing
|
||||
type WebAuthnKeeperInterface interface {
|
||||
// HasExistingCredential checks if this credential ID already exists
|
||||
HasExistingCredential(ctx sdk.Context, credentialId string) bool
|
||||
}
|
||||
|
||||
// BankKeeper defines the contract needed for supply related APIs.
|
||||
// It provides methods for checking send permissions and transferring coins
|
||||
// between accounts and modules.
|
||||
type BankKeeper interface {
|
||||
IsSendEnabledCoins(ctx context.Context, coins ...sdk.Coin) error
|
||||
SendCoins(ctx context.Context, from, to sdk.AccAddress, amt sdk.Coins) error
|
||||
SendCoinsFromAccountToModule(
|
||||
ctx context.Context,
|
||||
senderAddr sdk.AccAddress,
|
||||
recipientModule string,
|
||||
amt sdk.Coins,
|
||||
) error
|
||||
}
|
||||
|
||||
// AccountKeeper defines the account management interface required by ante handlers.
|
||||
// It provides methods for account creation, retrieval, modification, and
|
||||
// sequence number management.
|
||||
type AccountKeeper interface {
|
||||
NewAccountWithAddress(ctx context.Context, addr sdk.AccAddress) sdk.AccountI
|
||||
GetModuleAddress(moduleName string) sdk.AccAddress
|
||||
GetAccount(ctx context.Context, addr sdk.AccAddress) sdk.AccountI
|
||||
SetAccount(ctx context.Context, account sdk.AccountI)
|
||||
RemoveAccount(ctx context.Context, account sdk.AccountI)
|
||||
GetParams(ctx context.Context) (params authtypes.Params)
|
||||
GetSequence(ctx context.Context, addr sdk.AccAddress) (uint64, error)
|
||||
AddressCodec() addresscodec.Codec
|
||||
}
|
||||
|
||||
// HandlerOptions defines the list of module keepers and configurations required
|
||||
// to run the ante handler decorators. It includes both standard Cosmos SDK
|
||||
// keepers and EVM-specific components for processing different transaction types.
|
||||
type HandlerOptions struct {
|
||||
Cdc codec.BinaryCodec
|
||||
AccountKeeper AccountKeeper
|
||||
BankKeeper BankKeeper
|
||||
FeegrantKeeper ante.FeegrantKeeper
|
||||
ExtensionOptionChecker ante.ExtensionOptionChecker
|
||||
SignModeHandler *txsigning.HandlerMap
|
||||
SigGasConsumer func(meter storetypes.GasMeter, sig signing.SignatureV2, params authtypes.Params) error
|
||||
TxFeeChecker ante.TxFeeChecker // safe to be nil
|
||||
|
||||
MaxTxGasWanted uint64
|
||||
FeeMarketKeeper anteinterfaces.FeeMarketKeeper
|
||||
EvmKeeper anteinterfaces.EVMKeeper
|
||||
ControlPanelKeeper anteinterfaces.ControlPanelKeeper
|
||||
|
||||
IBCKeeper *ibckeeper.Keeper
|
||||
CircuitKeeper *circuitkeeper.Keeper
|
||||
|
||||
// WebAuthn gasless transaction support
|
||||
DidKeeper WebAuthnKeeperInterface
|
||||
EnableEnhancedGasless bool // Enable enhanced gasless mode for true onboarding without pre-existing accounts
|
||||
|
||||
// UCAN module keepers for permission validation
|
||||
DwnKeeper interface{} // Will be cast to proper type in decorator
|
||||
DexKeeper interface{} // Will be cast to proper type in decorator
|
||||
SvcKeeper interface{} // Will be cast to proper type in decorator
|
||||
}
|
||||
|
||||
// Validate checks if all required keepers and handlers are properly initialized.
|
||||
// It ensures that the HandlerOptions struct has all necessary components to
|
||||
// process transactions without nil pointer errors.
|
||||
func (options HandlerOptions) Validate() error {
|
||||
if options.Cdc == nil {
|
||||
return errorsmod.Wrap(errortypes.ErrLogic, "codec is required for AnteHandler")
|
||||
}
|
||||
if options.AccountKeeper == nil {
|
||||
return errorsmod.Wrap(errortypes.ErrLogic, "account keeper is required for AnteHandler")
|
||||
}
|
||||
if options.BankKeeper == nil {
|
||||
return errorsmod.Wrap(errortypes.ErrLogic, "bank keeper is required for AnteHandler")
|
||||
}
|
||||
if options.SigGasConsumer == nil {
|
||||
return errorsmod.Wrap(
|
||||
errortypes.ErrLogic,
|
||||
"signature gas consumer is required for AnteHandler",
|
||||
)
|
||||
}
|
||||
if options.SignModeHandler == nil {
|
||||
return errorsmod.Wrap(errortypes.ErrLogic, "sign mode handler is required for AnteHandler")
|
||||
}
|
||||
if options.CircuitKeeper == nil {
|
||||
return errorsmod.Wrap(errortypes.ErrLogic, "circuit keeper is required for ante builder")
|
||||
}
|
||||
|
||||
if options.TxFeeChecker == nil {
|
||||
return errorsmod.Wrap(errortypes.ErrLogic, "tx fee checker is required for AnteHandler")
|
||||
}
|
||||
if options.FeeMarketKeeper == nil {
|
||||
return errorsmod.Wrap(errortypes.ErrLogic, "fee market keeper is required for AnteHandler")
|
||||
}
|
||||
if options.EvmKeeper == nil {
|
||||
return errorsmod.Wrap(errortypes.ErrLogic, "evm keeper is required for AnteHandler")
|
||||
}
|
||||
if options.ControlPanelKeeper == nil {
|
||||
return errorsmod.Wrap(
|
||||
errortypes.ErrLogic,
|
||||
"control panel keeper is required for AnteHandler",
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/keys"
|
||||
"github.com/sonr-io/sonr/crypto/ucan"
|
||||
)
|
||||
|
||||
// UCANDecorator validates UCAN tokens in transactions
|
||||
// This is a placeholder implementation that sets up the infrastructure
|
||||
// for UCAN validation. In production, UCAN tokens would be passed
|
||||
// in message fields or transaction extensions.
|
||||
type UCANDecorator struct {
|
||||
verifier *ucan.Verifier
|
||||
}
|
||||
|
||||
// NewUCANDecorator creates a new UCAN decorator
|
||||
func NewUCANDecorator() UCANDecorator {
|
||||
// Create a basic DID resolver
|
||||
didResolver := &BasicDIDResolver{}
|
||||
verifier := ucan.NewVerifier(didResolver)
|
||||
|
||||
return UCANDecorator{
|
||||
verifier: verifier,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle validates UCAN tokens for transactions requiring authorization
|
||||
func (ud UCANDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||
// Skip validation in simulation mode
|
||||
if simulate {
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// Check if transaction has UCAN extension
|
||||
// This is where we would extract and validate UCAN tokens
|
||||
// For now, this is a placeholder that demonstrates the structure
|
||||
|
||||
// Future implementation would:
|
||||
// 1. Extract UCAN token from transaction extensions or memo
|
||||
// 2. Validate the token using the verifier
|
||||
// 3. Check capabilities against message types
|
||||
// 4. Mark transaction as gasless if appropriate
|
||||
|
||||
// Check if transaction qualifies for gasless execution
|
||||
if ud.isGaslessTransaction(ctx, tx) {
|
||||
// Mark context for gasless processing
|
||||
ctx = ctx.WithValue("gasless_ucan", true)
|
||||
}
|
||||
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// isGaslessTransaction checks if transaction qualifies for gasless execution
|
||||
// This is a placeholder implementation
|
||||
func (ud UCANDecorator) isGaslessTransaction(ctx sdk.Context, tx sdk.Tx) bool {
|
||||
// In production, this would check for UCAN tokens with gasless capabilities
|
||||
// For now, return false to maintain normal fee processing
|
||||
return false
|
||||
}
|
||||
|
||||
// CheckTokenExpiration checks if UCAN token has expired
|
||||
func (ud UCANDecorator) CheckTokenExpiration(ctx sdk.Context, token *ucan.Token) error {
|
||||
if token.ExpiresAt > 0 {
|
||||
currentTime := ctx.BlockTime().Unix()
|
||||
if currentTime > token.ExpiresAt {
|
||||
return fmt.Errorf("UCAN token has expired")
|
||||
}
|
||||
}
|
||||
|
||||
// Check NotBefore
|
||||
if token.NotBefore > 0 {
|
||||
currentTime := ctx.BlockTime().Unix()
|
||||
if currentTime < token.NotBefore {
|
||||
return fmt.Errorf("UCAN token is not yet valid")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidateCapabilities validates UCAN capabilities against required permissions
|
||||
func (ud UCANDecorator) ValidateCapabilities(token *ucan.Token, requiredCapabilities []string) error {
|
||||
// Check if token grants required capabilities
|
||||
for _, att := range token.Attenuations {
|
||||
if att.Capability.Grants(requiredCapabilities) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("UCAN token does not grant required capabilities")
|
||||
}
|
||||
|
||||
// BasicDIDResolver implements ucan.DIDResolver for the ante handler
|
||||
type BasicDIDResolver struct{}
|
||||
|
||||
// ResolveDIDKey resolves DID to public key for UCAN verification
|
||||
func (r *BasicDIDResolver) ResolveDIDKey(ctx context.Context, did string) (keys.DID, error) {
|
||||
// This is a basic implementation that accepts all DIDs
|
||||
// In production, this would query the DID module
|
||||
return keys.Parse(did)
|
||||
}
|
||||
|
||||
// ConditionalUCANDecorator wraps UCAN decorator to skip for certain transactions
|
||||
type ConditionalUCANDecorator struct {
|
||||
decorator sdk.AnteDecorator
|
||||
}
|
||||
|
||||
// NewConditionalUCANDecorator creates a conditional UCAN decorator
|
||||
func NewConditionalUCANDecorator(decorator sdk.AnteDecorator) ConditionalUCANDecorator {
|
||||
return ConditionalUCANDecorator{decorator: decorator}
|
||||
}
|
||||
|
||||
// AnteHandle conditionally applies UCAN validation
|
||||
func (cud ConditionalUCANDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||
// Skip UCAN validation if already marked as gasless WebAuthn
|
||||
if ctx.Value("bypass_ucan") != nil {
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// Apply UCAN validation
|
||||
return cud.decorator.AnteHandle(ctx, tx, simulate, next)
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/ucan"
|
||||
)
|
||||
|
||||
// TestUCANDecorator tests the UCAN decorator functionality
|
||||
func TestUCANDecorator(t *testing.T) {
|
||||
// Test decorator creation
|
||||
decorator := NewUCANDecorator()
|
||||
assert.NotNil(t, decorator)
|
||||
}
|
||||
|
||||
// TestUCANGaslessDecorator tests the gasless decorator
|
||||
func TestUCANGaslessDecorator(t *testing.T) {
|
||||
// Create mock fee decorator
|
||||
mockFeeDecorator := &mockAnteDecorator{}
|
||||
|
||||
// Test gasless decorator creation
|
||||
gaslessDecorator := NewUCANGaslessDecorator(mockFeeDecorator)
|
||||
assert.NotNil(t, gaslessDecorator)
|
||||
}
|
||||
|
||||
// TestConditionalUCANDecorator tests conditional UCAN decorator
|
||||
func TestConditionalUCANDecorator(t *testing.T) {
|
||||
// Create mock UCAN decorator
|
||||
mockUCANDecorator := &mockAnteDecorator{}
|
||||
|
||||
// Test conditional decorator creation
|
||||
conditionalDecorator := NewConditionalUCANDecorator(mockUCANDecorator)
|
||||
assert.NotNil(t, conditionalDecorator)
|
||||
}
|
||||
|
||||
// TestTokenExpiration tests UCAN token expiration validation
|
||||
func TestTokenExpiration(t *testing.T) {
|
||||
decorator := NewUCANDecorator()
|
||||
ctx := sdk.Context{}.WithBlockTime(time.Now())
|
||||
|
||||
// Test expired token
|
||||
expiredToken := &ucan.Token{
|
||||
ExpiresAt: time.Now().Unix() - 3600, // 1 hour ago
|
||||
}
|
||||
|
||||
err := decorator.CheckTokenExpiration(ctx, expiredToken)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "UCAN token has expired")
|
||||
|
||||
// Test valid token
|
||||
validToken := &ucan.Token{
|
||||
ExpiresAt: time.Now().Unix() + 3600, // 1 hour from now
|
||||
}
|
||||
|
||||
err = decorator.CheckTokenExpiration(ctx, validToken)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test token not yet valid
|
||||
futureToken := &ucan.Token{
|
||||
NotBefore: time.Now().Unix() + 3600, // 1 hour from now
|
||||
}
|
||||
|
||||
err = decorator.CheckTokenExpiration(ctx, futureToken)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "UCAN token is not yet valid")
|
||||
}
|
||||
|
||||
// TestValidateCapabilities tests capability validation
|
||||
func TestValidateCapabilities(t *testing.T) {
|
||||
decorator := NewUCANDecorator()
|
||||
|
||||
// Create token with single capability
|
||||
token := &ucan.Token{
|
||||
Attenuations: []ucan.Attenuation{
|
||||
{
|
||||
Capability: &ucan.SimpleCapability{
|
||||
Action: "did/update",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Test with matching capability
|
||||
err := decorator.ValidateCapabilities(token, []string{"did/update"})
|
||||
require.NoError(t, err)
|
||||
|
||||
// Test with non-matching capability
|
||||
err = decorator.ValidateCapabilities(token, []string{"did/delete"})
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "UCAN token does not grant required capabilities")
|
||||
|
||||
// Test with multiple capabilities
|
||||
multiToken := &ucan.Token{
|
||||
Attenuations: []ucan.Attenuation{
|
||||
{
|
||||
Capability: &ucan.MultiCapability{
|
||||
Actions: []string{"did/update", "did/create"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err = decorator.ValidateCapabilities(multiToken, []string{"did/update"})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = decorator.ValidateCapabilities(multiToken, []string{"did/create"})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = decorator.ValidateCapabilities(multiToken, []string{"did/delete"})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
// mockAnteDecorator is a helper for testing
|
||||
type mockAnteDecorator struct{}
|
||||
|
||||
func (m *mockAnteDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
// UCANGaslessDecorator allows gasless transactions for UCAN-authorized operations
|
||||
type UCANGaslessDecorator struct {
|
||||
feeDecorator sdk.AnteDecorator
|
||||
}
|
||||
|
||||
// NewUCANGaslessDecorator creates a new UCAN gasless decorator
|
||||
func NewUCANGaslessDecorator(feeDecorator sdk.AnteDecorator) UCANGaslessDecorator {
|
||||
return UCANGaslessDecorator{
|
||||
feeDecorator: feeDecorator,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle conditionally skips fee deduction for UCAN gasless transactions
|
||||
func (ugd UCANGaslessDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
|
||||
// Check if transaction is marked as UCAN gasless
|
||||
if ctx.Value("gasless_ucan") != nil {
|
||||
// Skip fee deduction for gasless UCAN transaction
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// Apply normal fee deduction
|
||||
return ugd.feeDecorator.AnteHandle(ctx, tx, simulate, next)
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package ante
|
||||
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
|
||||
|
||||
didtypes "github.com/sonr-io/sonr/x/did/types"
|
||||
)
|
||||
|
||||
// WebAuthnBypassDecorator completely bypasses signature verification for WebAuthn registration.
|
||||
// This decorator must be placed FIRST in the ante handler chain to intercept WebAuthn
|
||||
// transactions before any signature validation occurs.
|
||||
type WebAuthnBypassDecorator struct{}
|
||||
|
||||
// NewWebAuthnBypassDecorator creates a new WebAuthnBypassDecorator
|
||||
func NewWebAuthnBypassDecorator() WebAuthnBypassDecorator {
|
||||
return WebAuthnBypassDecorator{}
|
||||
}
|
||||
|
||||
// AnteHandle validates WebAuthn transactions and marks them for controlled processing
|
||||
// This decorator performs essential security checks while allowing gasless processing
|
||||
func (wbd WebAuthnBypassDecorator) AnteHandle(
|
||||
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
msgs := tx.GetMsgs()
|
||||
|
||||
// Check if this is a single WebAuthn registration transaction
|
||||
if len(msgs) != 1 {
|
||||
// Not a single message transaction, proceed normally
|
||||
return next(ctx, tx, sim)
|
||||
}
|
||||
|
||||
msg, ok := msgs[0].(*didtypes.MsgRegisterWebAuthnCredential)
|
||||
if !ok {
|
||||
// Not a WebAuthn registration, proceed normally
|
||||
return next(ctx, tx, sim)
|
||||
}
|
||||
|
||||
// This is a WebAuthn registration - perform security validation
|
||||
ctx.Logger().Info("Processing WebAuthn registration with controlled bypass",
|
||||
"username", msg.Username,
|
||||
"credential_id", msg.WebauthnCredential.CredentialId)
|
||||
|
||||
// CRITICAL SECURITY CHECK 1: Validate the WebAuthn credential structure
|
||||
if err := msg.WebauthnCredential.ValidateStructure(); err != nil {
|
||||
ctx.Logger().Error("WebAuthn credential structure validation failed", "error", err)
|
||||
return ctx, err
|
||||
}
|
||||
|
||||
// CRITICAL SECURITY CHECK 2: Handle signatures (dummy signatures are allowed for mempool validation)
|
||||
if sigTx, ok := tx.(authsigning.SigVerifiableTx); ok {
|
||||
sigs, err := sigTx.GetSignaturesV2()
|
||||
if err != nil {
|
||||
ctx.Logger().Error("Failed to get signatures from WebAuthn transaction", "error", err)
|
||||
return ctx, err
|
||||
}
|
||||
if len(sigs) > 0 {
|
||||
ctx.Logger().
|
||||
Debug("WebAuthn transaction has dummy signatures for mempool validation", "sig_count", len(sigs))
|
||||
|
||||
// This is expected - dummy signatures are used to pass mempool validation
|
||||
// The actual signature verification will be bypassed by conditional decorators
|
||||
} else {
|
||||
ctx.Logger().Debug("WebAuthn transaction has no signatures - gasless flow")
|
||||
}
|
||||
}
|
||||
|
||||
// CRITICAL SECURITY CHECK 3: Validate credential uniqueness to prevent replay attacks
|
||||
// Note: This will be enforced in the WebAuthnGaslessDecorator with keeper access
|
||||
|
||||
// Mark context for controlled WebAuthn processing
|
||||
// These flags will be checked by conditional decorators
|
||||
ctx = ctx.WithValue("webauthn_bypass_validated", true)
|
||||
|
||||
ctx.Logger().Info("WebAuthn transaction validated - proceeding with controlled processing",
|
||||
"credential_id", msg.WebauthnCredential.CredentialId,
|
||||
"username", msg.Username)
|
||||
|
||||
// Continue to next decorator (WebAuthnGaslessDecorator) for full processing
|
||||
return next(ctx, tx, sim)
|
||||
}
|
||||
@@ -0,0 +1,359 @@
|
||||
// Package ante provides ante handler implementations for transaction processing
|
||||
// in the Sonr blockchain. It supports both Cosmos SDK and Ethereum transactions.
|
||||
package ante
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
errorsmod "cosmossdk.io/errors"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
errortypes "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
|
||||
didtypes "github.com/sonr-io/sonr/x/did/types"
|
||||
)
|
||||
|
||||
// WebAuthnGaslessDecorator provides gasless transaction processing for WebAuthn registration.
|
||||
// This decorator identifies WebAuthn credential registration messages and bypasses fee deduction,
|
||||
// enabling users to create their first decentralized identity without requiring existing tokens.
|
||||
//
|
||||
// The decorator supports two modes:
|
||||
// 1. Standard mode: Requires a controller address (for users with existing accounts)
|
||||
// 2. Enhanced mode: Generates controller address from credential (for brand new users)
|
||||
//
|
||||
// Security considerations:
|
||||
// - Only applies to MsgRegisterWebAuthnCredential messages
|
||||
// - Validates WebAuthn credential authenticity before fee waiving
|
||||
// - Prevents abuse through cryptographic WebAuthn requirements
|
||||
// - Limited to one gasless transaction per unique credential
|
||||
type WebAuthnGaslessDecorator struct {
|
||||
accountKeeper AccountKeeper
|
||||
didKeeper WebAuthnKeeperInterface
|
||||
enhancedMode bool // If true, allows address generation from credentials
|
||||
}
|
||||
|
||||
// NewWebAuthnGaslessDecorator creates a new WebAuthn gasless transaction decorator.
|
||||
// This decorator must be placed in the ante handler chain BEFORE the fee deduction decorator
|
||||
// to effectively bypass fee requirements for qualifying WebAuthn transactions.
|
||||
//
|
||||
// Set enhancedMode to true to enable automatic address generation from credentials,
|
||||
// allowing truly gasless onboarding without pre-existing accounts.
|
||||
func NewWebAuthnGaslessDecorator(
|
||||
accountKeeper AccountKeeper,
|
||||
didKeeper WebAuthnKeeperInterface,
|
||||
enhancedMode bool,
|
||||
) WebAuthnGaslessDecorator {
|
||||
return WebAuthnGaslessDecorator{
|
||||
accountKeeper: accountKeeper,
|
||||
didKeeper: didKeeper,
|
||||
enhancedMode: enhancedMode,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle processes the transaction and determines if WebAuthn gasless processing applies.
|
||||
// For qualifying WebAuthn registration transactions, it sets transaction fees to zero
|
||||
// and validates the WebAuthn credential to prevent abuse.
|
||||
//
|
||||
// Gasless criteria:
|
||||
// 1. Transaction contains exactly one MsgRegisterWebAuthnCredential
|
||||
// 2. WebAuthn credential passes cryptographic validation
|
||||
// 3. Credential ID has not been used before (prevents replay attacks)
|
||||
// 4. Transaction sender account exists or can be created
|
||||
//
|
||||
// In enhanced mode, if no controller is provided, it generates one from the credential.
|
||||
func (wgd WebAuthnGaslessDecorator) AnteHandle(
|
||||
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
// Process during both CheckTx (simulation) and DeliverTx (execution)
|
||||
// This ensures gasless flags are set during mempool validation
|
||||
|
||||
msgs := tx.GetMsgs()
|
||||
|
||||
// Debug: Log entry into gasless decorator
|
||||
ctx.Logger().Debug("WebAuthnGaslessDecorator: processing transaction",
|
||||
"msg_count", len(msgs), "sim", sim)
|
||||
|
||||
// Check if this is a single WebAuthn registration transaction
|
||||
if len(msgs) != 1 {
|
||||
// Multi-message transactions don't qualify for gasless processing
|
||||
return next(ctx, tx, sim)
|
||||
}
|
||||
|
||||
msg, ok := msgs[0].(*didtypes.MsgRegisterWebAuthnCredential)
|
||||
if !ok {
|
||||
// Not a WebAuthn registration message
|
||||
return next(ctx, tx, sim)
|
||||
}
|
||||
|
||||
// Check if WebAuthn bypass validation already occurred
|
||||
if bypassed, ok := ctx.Value("webauthn_bypass_validated").(bool); !ok || !bypassed {
|
||||
// Validate the WebAuthn credential to prevent abuse (if not already validated)
|
||||
if err := msg.WebauthnCredential.ValidateStructure(); err != nil {
|
||||
return ctx, errorsmod.Wrapf(
|
||||
errortypes.ErrInvalidRequest,
|
||||
"invalid WebAuthn credential for gasless transaction: %v", err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent credential reuse (anti-replay protection)
|
||||
if wgd.didKeeper.HasExistingCredential(ctx, msg.WebauthnCredential.CredentialId) {
|
||||
return ctx, errorsmod.Wrapf(
|
||||
errortypes.ErrInvalidRequest,
|
||||
"WebAuthn credential already registered: %s", msg.WebauthnCredential.CredentialId,
|
||||
)
|
||||
}
|
||||
|
||||
// Handle controller address
|
||||
var controllerAddr sdk.AccAddress
|
||||
|
||||
// Enhanced mode: Generate address from credential if not provided
|
||||
if wgd.enhancedMode && msg.GetController() == "" {
|
||||
// Generate deterministic address from credential ID
|
||||
controllerAddr = GenerateAddressFromCredential(msg.WebauthnCredential.CredentialId)
|
||||
|
||||
// Note: We can't modify the message directly in most cases,
|
||||
// but we can pass the generated address through context
|
||||
ctx = ctx.WithValue("generated_controller", controllerAddr.String())
|
||||
|
||||
ctx.Logger().Info(
|
||||
"Generated controller address for gasless WebAuthn registration",
|
||||
"generated_address", controllerAddr.String(),
|
||||
"credential_id", msg.WebauthnCredential.CredentialId,
|
||||
)
|
||||
} else {
|
||||
// Standard mode or enhanced mode with provided controller
|
||||
controllerStr := msg.GetController()
|
||||
if controllerStr == "" {
|
||||
return ctx, errorsmod.Wrap(
|
||||
errortypes.ErrInvalidAddress,
|
||||
"controller address required for WebAuthn registration",
|
||||
)
|
||||
}
|
||||
|
||||
controllerAddr, err = sdk.AccAddressFromBech32(controllerStr)
|
||||
if err != nil {
|
||||
return ctx, errorsmod.Wrapf(
|
||||
errortypes.ErrInvalidAddress,
|
||||
"invalid controller address: %v", err,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the account exists (simulation-safe)
|
||||
account := wgd.accountKeeper.GetAccount(ctx, controllerAddr)
|
||||
if account == nil {
|
||||
// Create account if it doesn't exist (common for first-time WebAuthn users)
|
||||
// Only actually create during execution, not simulation
|
||||
if !sim {
|
||||
account = wgd.accountKeeper.NewAccountWithAddress(ctx, controllerAddr)
|
||||
wgd.accountKeeper.SetAccount(ctx, account)
|
||||
|
||||
ctx.Logger().Info(
|
||||
"Created new account for gasless WebAuthn registration",
|
||||
"address", controllerAddr.String(),
|
||||
)
|
||||
} else {
|
||||
// During simulation (CheckTx), just create a temporary account for validation
|
||||
// We don't assign it back to account variable since it's only for validation
|
||||
wgd.accountKeeper.NewAccountWithAddress(ctx, controllerAddr)
|
||||
}
|
||||
}
|
||||
|
||||
// Mark this transaction as gasless
|
||||
gaslessCtx := ctx.WithValue("webauthn_gasless", true)
|
||||
|
||||
// In enhanced mode, also mark to skip signature verification
|
||||
if wgd.enhancedMode {
|
||||
gaslessCtx = gaslessCtx.WithValue("skip_sig_verification", true)
|
||||
gaslessCtx = gaslessCtx.WithValue("skip_pubkey_verification", true)
|
||||
}
|
||||
|
||||
// Log the gasless transaction for monitoring and security purposes
|
||||
ctx.Logger().Info(
|
||||
"Processing gasless WebAuthn registration",
|
||||
"controller", controllerAddr.String(),
|
||||
"credential_id", msg.WebauthnCredential.CredentialId,
|
||||
"username", msg.Username,
|
||||
"auto_vault", msg.AutoCreateVault,
|
||||
"enhanced_mode", wgd.enhancedMode,
|
||||
)
|
||||
|
||||
return next(gaslessCtx, tx, sim)
|
||||
}
|
||||
|
||||
// GenerateAddressFromCredential generates a deterministic address from a WebAuthn credential ID.
|
||||
// This ensures the same credential always generates the same address, allowing for
|
||||
// predictable account creation without requiring pre-existing blockchain state.
|
||||
func GenerateAddressFromCredential(credentialID string) sdk.AccAddress {
|
||||
// Create a deterministic hash from the credential ID
|
||||
// Add a domain separator to prevent collisions with other address generation methods
|
||||
domainSeparator := "webauthn_gasless_v1"
|
||||
data := domainSeparator + credentialID
|
||||
|
||||
// Generate SHA256 hash
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
|
||||
// Take the first 20 bytes for the address (Ethereum-compatible)
|
||||
return sdk.AccAddress(hash[:20])
|
||||
}
|
||||
|
||||
// GenerateDIDFromCredential generates a deterministic DID from a WebAuthn credential.
|
||||
// This creates a unique, reproducible DID for each WebAuthn credential.
|
||||
func GenerateDIDFromCredential(credentialID string, username string) string {
|
||||
// Create a deterministic hash from credential ID and username
|
||||
data := credentialID + ":" + username
|
||||
hash := sha256.Sum256([]byte(data))
|
||||
|
||||
// Create a DID with the sonr method
|
||||
// Format: did:sonr:<hex-encoded-hash-prefix>
|
||||
didSuffix := hex.EncodeToString(hash[:16]) // Use first 16 bytes for shorter DIDs
|
||||
return fmt.Sprintf("did:sonr:%s", didSuffix)
|
||||
}
|
||||
|
||||
// ConditionalFeeDecorator wraps the standard fee deduction decorator to conditionally
|
||||
// skip fee deduction for gasless WebAuthn transactions marked by WebAuthnGaslessDecorator.
|
||||
// This is the simplest possible implementation that reuses all existing SDK infrastructure.
|
||||
type ConditionalFeeDecorator struct {
|
||||
standardFeeDecorator sdk.AnteDecorator
|
||||
}
|
||||
|
||||
// NewConditionalFeeDecorator creates a decorator that conditionally skips fee deduction
|
||||
// for gasless transactions while maintaining all standard fee logic for other transactions.
|
||||
func NewConditionalFeeDecorator(standardFeeDecorator sdk.AnteDecorator) ConditionalFeeDecorator {
|
||||
return ConditionalFeeDecorator{
|
||||
standardFeeDecorator: standardFeeDecorator,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle processes transactions and conditionally skips fee deduction for gasless WebAuthn transactions.
|
||||
func (cfd ConditionalFeeDecorator) AnteHandle(
|
||||
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
// Check if this transaction was marked as gasless by WebAuthnGaslessDecorator
|
||||
if gasless, ok := ctx.Value("webauthn_gasless").(bool); ok && gasless {
|
||||
// Skip fee deduction for gasless WebAuthn transactions
|
||||
ctx.Logger().Info("Waiving fees for gasless WebAuthn registration")
|
||||
return next(ctx, tx, sim)
|
||||
}
|
||||
|
||||
// For all other transactions, use the standard fee deduction decorator
|
||||
return cfd.standardFeeDecorator.AnteHandle(ctx, tx, sim, next)
|
||||
}
|
||||
|
||||
// ConditionalSignatureDecorator wraps signature verification to skip it for gasless transactions
|
||||
// This is used in enhanced mode where WebAuthn itself is the authentication mechanism.
|
||||
type ConditionalSignatureDecorator struct {
|
||||
sigVerifyDecorator sdk.AnteDecorator
|
||||
}
|
||||
|
||||
// NewConditionalSignatureDecorator creates a decorator that conditionally skips signature verification
|
||||
func NewConditionalSignatureDecorator(
|
||||
sigVerifyDecorator sdk.AnteDecorator,
|
||||
) ConditionalSignatureDecorator {
|
||||
return ConditionalSignatureDecorator{
|
||||
sigVerifyDecorator: sigVerifyDecorator,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle conditionally skips signature verification for gasless transactions
|
||||
func (csd ConditionalSignatureDecorator) AnteHandle(
|
||||
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
// Check if signature verification should be skipped
|
||||
if skip, ok := ctx.Value("skip_sig_verification").(bool); ok && skip {
|
||||
ctx.Logger().Debug("Skipping signature verification for gasless transaction")
|
||||
return next(ctx, tx, sim)
|
||||
}
|
||||
|
||||
// For all other transactions, use the standard signature verification
|
||||
return csd.sigVerifyDecorator.AnteHandle(ctx, tx, sim, next)
|
||||
}
|
||||
|
||||
// ConditionalPubKeyDecorator wraps pubkey setting to skip it for gasless transactions
|
||||
// This is used in enhanced mode where accounts are created without pre-existing keys.
|
||||
type ConditionalPubKeyDecorator struct {
|
||||
setPubKeyDecorator sdk.AnteDecorator
|
||||
}
|
||||
|
||||
// NewConditionalPubKeyDecorator creates a decorator that conditionally skips pubkey setting
|
||||
func NewConditionalPubKeyDecorator(
|
||||
setPubKeyDecorator sdk.AnteDecorator,
|
||||
) ConditionalPubKeyDecorator {
|
||||
return ConditionalPubKeyDecorator{
|
||||
setPubKeyDecorator: setPubKeyDecorator,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle conditionally skips pubkey setting for gasless transactions
|
||||
func (cpd ConditionalPubKeyDecorator) AnteHandle(
|
||||
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
// Check if pubkey verification should be skipped
|
||||
if skip, ok := ctx.Value("skip_pubkey_verification").(bool); ok && skip {
|
||||
ctx.Logger().Debug("Skipping pubkey setting for gasless transaction")
|
||||
return next(ctx, tx, sim)
|
||||
}
|
||||
|
||||
// For all other transactions, use the standard pubkey decorator
|
||||
return cpd.setPubKeyDecorator.AnteHandle(ctx, tx, sim, next)
|
||||
}
|
||||
|
||||
// ConditionalSigCountDecorator wraps signature count validation to skip it for gasless transactions
|
||||
// This is critical for gasless WebAuthn transactions which have no signatures to validate.
|
||||
type ConditionalSigCountDecorator struct {
|
||||
sigCountDecorator sdk.AnteDecorator
|
||||
}
|
||||
|
||||
// NewConditionalSigCountDecorator creates a decorator that conditionally skips signature count validation
|
||||
func NewConditionalSigCountDecorator(
|
||||
sigCountDecorator sdk.AnteDecorator,
|
||||
) ConditionalSigCountDecorator {
|
||||
return ConditionalSigCountDecorator{
|
||||
sigCountDecorator: sigCountDecorator,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle conditionally skips signature count validation for gasless transactions
|
||||
func (cscd ConditionalSigCountDecorator) AnteHandle(
|
||||
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
// Check if signature verification should be skipped (implies no signatures to count)
|
||||
if skip, ok := ctx.Value("skip_sig_verification").(bool); ok && skip {
|
||||
ctx.Logger().Debug("Skipping signature count validation for gasless transaction")
|
||||
return next(ctx, tx, sim)
|
||||
}
|
||||
|
||||
// For all other transactions, use the standard signature count validator
|
||||
return cscd.sigCountDecorator.AnteHandle(ctx, tx, sim, next)
|
||||
}
|
||||
|
||||
// ConditionalSigGasDecorator wraps signature gas consumption to skip it for gasless transactions
|
||||
// This prevents "no signatures supplied" errors for gasless WebAuthn transactions.
|
||||
type ConditionalSigGasDecorator struct {
|
||||
sigGasDecorator sdk.AnteDecorator
|
||||
}
|
||||
|
||||
// NewConditionalSigGasDecorator creates a decorator that conditionally skips signature gas consumption
|
||||
func NewConditionalSigGasDecorator(
|
||||
sigGasDecorator sdk.AnteDecorator,
|
||||
) ConditionalSigGasDecorator {
|
||||
return ConditionalSigGasDecorator{
|
||||
sigGasDecorator: sigGasDecorator,
|
||||
}
|
||||
}
|
||||
|
||||
// AnteHandle conditionally skips signature gas consumption for gasless transactions
|
||||
func (csgd ConditionalSigGasDecorator) AnteHandle(
|
||||
ctx sdk.Context, tx sdk.Tx, sim bool, next sdk.AnteHandler,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
// Check if signature verification should be skipped (implies no signature gas consumption needed)
|
||||
if skip, ok := ctx.Value("skip_sig_verification").(bool); ok && skip {
|
||||
ctx.Logger().Debug("Skipping signature gas consumption for gasless transaction")
|
||||
return next(ctx, tx, sim)
|
||||
}
|
||||
|
||||
// For all other transactions, use the standard signature gas decorator
|
||||
return csgd.sigGasDecorator.AnteHandle(ctx, tx, sim, next)
|
||||
}
|
||||
@@ -0,0 +1,271 @@
|
||||
package ante_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/sonr-io/sonr/app/ante"
|
||||
)
|
||||
|
||||
// MockWebAuthnKeeper implements the WebAuthnKeeperInterface for testing
|
||||
type MockWebAuthnKeeper struct {
|
||||
existingCredentials map[string]bool
|
||||
}
|
||||
|
||||
func NewMockWebAuthnKeeper() *MockWebAuthnKeeper {
|
||||
return &MockWebAuthnKeeper{
|
||||
existingCredentials: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *MockWebAuthnKeeper) HasExistingCredential(ctx sdk.Context, credentialId string) bool {
|
||||
return m.existingCredentials[credentialId]
|
||||
}
|
||||
|
||||
func (m *MockWebAuthnKeeper) AddCredential(credentialId string) {
|
||||
m.existingCredentials[credentialId] = true
|
||||
}
|
||||
|
||||
// MockAccountKeeper - Simple mock without complex interface implementation
|
||||
// For comprehensive testing, we'd implement the full AccountKeeper interface
|
||||
|
||||
// MockTransaction implements sdk.Tx for testing
|
||||
type MockTransaction struct {
|
||||
messages []sdk.Msg
|
||||
}
|
||||
|
||||
func (m *MockTransaction) GetMsgs() []sdk.Msg {
|
||||
return m.messages
|
||||
}
|
||||
|
||||
func (m *MockTransaction) ValidateBasic() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// WebAuthnGaslessTestSuite tests the WebAuthn gasless decorator
|
||||
type WebAuthnGaslessTestSuite struct {
|
||||
suite.Suite
|
||||
didKeeper *MockWebAuthnKeeper
|
||||
}
|
||||
|
||||
func TestWebAuthnGaslessTestSuite(t *testing.T) {
|
||||
suite.Run(t, new(WebAuthnGaslessTestSuite))
|
||||
}
|
||||
|
||||
func (suite *WebAuthnGaslessTestSuite) SetupTest() {
|
||||
// Setup minimal test context and keepers
|
||||
suite.didKeeper = NewMockWebAuthnKeeper()
|
||||
// We'll focus on testing the logic without complex keeper setup
|
||||
}
|
||||
|
||||
func (suite *WebAuthnGaslessTestSuite) TestGenerateAddressFromCredential() {
|
||||
// Test that the same credential always generates the same address
|
||||
credentialID := "test-credential-id-123"
|
||||
|
||||
addr1 := ante.GenerateAddressFromCredential(credentialID)
|
||||
addr2 := ante.GenerateAddressFromCredential(credentialID)
|
||||
|
||||
suite.Require().Equal(addr1, addr2, "Same credential should generate same address")
|
||||
suite.Require().NotNil(addr1, "Generated address should not be nil")
|
||||
suite.Require().Equal(20, len(addr1), "Address should be 20 bytes")
|
||||
}
|
||||
|
||||
func (suite *WebAuthnGaslessTestSuite) TestGenerateDIDFromCredential() {
|
||||
// Test DID generation
|
||||
credentialID := "test-credential-id-123"
|
||||
username := "testuser"
|
||||
|
||||
did1 := ante.GenerateDIDFromCredential(credentialID, username)
|
||||
did2 := ante.GenerateDIDFromCredential(credentialID, username)
|
||||
|
||||
suite.Require().Equal(did1, did2, "Same inputs should generate same DID")
|
||||
suite.Require().Contains(did1, "did:sonr:", "DID should have correct prefix")
|
||||
|
||||
// Different username should generate different DID
|
||||
did3 := ante.GenerateDIDFromCredential(credentialID, "differentuser")
|
||||
suite.Require().NotEqual(did1, did3, "Different username should generate different DID")
|
||||
}
|
||||
|
||||
func (suite *WebAuthnGaslessTestSuite) TestWebAuthnGaslessDecorator_StandardMode() {
|
||||
// Test standard mode behavior
|
||||
decorator := ante.NewWebAuthnGaslessDecorator(
|
||||
nil, // accountKeeper would be mocked in full test
|
||||
suite.didKeeper,
|
||||
false, // standard mode
|
||||
)
|
||||
|
||||
suite.Require().NotNil(decorator, "Decorator should be created")
|
||||
}
|
||||
|
||||
func (suite *WebAuthnGaslessTestSuite) TestWebAuthnGaslessDecorator_EnhancedMode() {
|
||||
// Test enhanced mode behavior
|
||||
decorator := ante.NewWebAuthnGaslessDecorator(
|
||||
nil, // accountKeeper would be mocked in full test
|
||||
suite.didKeeper,
|
||||
true, // enhanced mode
|
||||
)
|
||||
|
||||
suite.Require().NotNil(decorator, "Decorator should be created in enhanced mode")
|
||||
}
|
||||
|
||||
func (suite *WebAuthnGaslessTestSuite) TestConditionalDecorators() {
|
||||
// Test that conditional decorators are created properly
|
||||
// We'll use a simple mock decorator for testing
|
||||
mockDecorator := &MockAnteDecorator{}
|
||||
|
||||
feeDecorator := ante.NewConditionalFeeDecorator(mockDecorator)
|
||||
suite.Require().NotNil(feeDecorator, "Conditional fee decorator should be created")
|
||||
|
||||
sigDecorator := ante.NewConditionalSignatureDecorator(mockDecorator)
|
||||
suite.Require().NotNil(sigDecorator, "Conditional signature decorator should be created")
|
||||
|
||||
pubKeyDecorator := ante.NewConditionalPubKeyDecorator(mockDecorator)
|
||||
suite.Require().NotNil(pubKeyDecorator, "Conditional pubkey decorator should be created")
|
||||
}
|
||||
|
||||
func (suite *WebAuthnGaslessTestSuite) TestWebAuthnCredentialValidation() {
|
||||
// Test WebAuthn credential structure validation
|
||||
// This tests the core logic without full ante handler setup
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
credentialID string
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "valid credential ID",
|
||||
credentialID: "valid-credential-123",
|
||||
expectError: false,
|
||||
},
|
||||
{
|
||||
name: "empty credential ID should be caught by message validation",
|
||||
credentialID: "",
|
||||
expectError: true, // This would be caught by ValidateStructure
|
||||
},
|
||||
{
|
||||
name: "duplicate credential",
|
||||
credentialID: "duplicate-cred",
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
|
||||
// Add a duplicate credential
|
||||
suite.didKeeper.AddCredential("duplicate-cred")
|
||||
|
||||
for _, tc := range testCases {
|
||||
suite.Run(tc.name, func() {
|
||||
hasExisting := suite.didKeeper.HasExistingCredential(sdk.Context{}, tc.credentialID)
|
||||
|
||||
switch tc.credentialID {
|
||||
case "duplicate-cred":
|
||||
suite.Require().True(hasExisting, "Duplicate credential should exist")
|
||||
case "valid-credential-123":
|
||||
suite.Require().False(hasExisting, "New credential should not exist")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (suite *WebAuthnGaslessTestSuite) TestEnhancedModeAddressGeneration() {
|
||||
// Test enhanced mode functionality for address generation
|
||||
testCredentialID := "test-enhanced-credential"
|
||||
|
||||
// Test that the same credential always generates the same address
|
||||
addr1 := ante.GenerateAddressFromCredential(testCredentialID)
|
||||
addr2 := ante.GenerateAddressFromCredential(testCredentialID)
|
||||
|
||||
suite.Require().Equal(addr1, addr2, "Same credential should generate same address")
|
||||
suite.Require().Equal(20, len(addr1), "Address should be 20 bytes")
|
||||
|
||||
// Test that different credentials generate different addresses
|
||||
addr3 := ante.GenerateAddressFromCredential("different-credential")
|
||||
suite.Require().
|
||||
NotEqual(addr1, addr3, "Different credentials should generate different addresses")
|
||||
}
|
||||
|
||||
// MockAnteDecorator is a simple mock implementation of sdk.AnteDecorator for testing
|
||||
type MockAnteDecorator struct{}
|
||||
|
||||
func (m *MockAnteDecorator) AnteHandle(
|
||||
ctx sdk.Context,
|
||||
tx sdk.Tx,
|
||||
simulate bool,
|
||||
next sdk.AnteHandler,
|
||||
) (sdk.Context, error) {
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// TestAddressGeneration tests the deterministic address generation
|
||||
func TestAddressGeneration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
credentialID string
|
||||
expectSame bool
|
||||
}{
|
||||
{
|
||||
name: "same credential generates same address",
|
||||
credentialID: "credential-1",
|
||||
expectSame: true,
|
||||
},
|
||||
{
|
||||
name: "different credential generates different address",
|
||||
credentialID: "credential-2",
|
||||
expectSame: false,
|
||||
},
|
||||
}
|
||||
|
||||
baseAddr := ante.GenerateAddressFromCredential("base-credential")
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
addr := ante.GenerateAddressFromCredential(tc.credentialID)
|
||||
require.NotNil(t, addr)
|
||||
require.Equal(t, 20, len(addr))
|
||||
|
||||
if tc.expectSame && tc.credentialID == "base-credential" {
|
||||
require.Equal(t, baseAddr, addr)
|
||||
} else if !tc.expectSame {
|
||||
require.NotEqual(t, baseAddr, addr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestDIDGeneration tests the deterministic DID generation
|
||||
func TestDIDGeneration(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
credentialID string
|
||||
username string
|
||||
expectedPrefix string
|
||||
}{
|
||||
{
|
||||
name: "generates valid DID",
|
||||
credentialID: "cred-1",
|
||||
username: "user1",
|
||||
expectedPrefix: "did:sonr:",
|
||||
},
|
||||
{
|
||||
name: "different username generates different DID",
|
||||
credentialID: "cred-1",
|
||||
username: "user2",
|
||||
expectedPrefix: "did:sonr:",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
did := ante.GenerateDIDFromCredential(tc.credentialID, tc.username)
|
||||
require.NotEmpty(t, did)
|
||||
require.Contains(t, did, tc.expectedPrefix)
|
||||
|
||||
// Verify deterministic generation
|
||||
did2 := ante.GenerateDIDFromCredential(tc.credentialID, tc.username)
|
||||
require.Equal(t, did, did2)
|
||||
})
|
||||
}
|
||||
}
|
||||
+607
-326
File diff suppressed because it is too large
Load Diff
Regular → Executable
+29
-2
@@ -5,12 +5,14 @@ import (
|
||||
|
||||
abci "github.com/cometbft/cometbft/abci/types"
|
||||
dbm "github.com/cosmos/cosmos-db"
|
||||
"github.com/cosmos/gogoproto/proto"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
|
||||
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/msgservice"
|
||||
)
|
||||
|
||||
func TestAppExport(t *testing.T) {
|
||||
@@ -34,6 +36,7 @@ func TestAppExport(t *testing.T) {
|
||||
// Making a new app object with the db, so that initchain hasn't been called
|
||||
newGapp := NewChainApp(
|
||||
logger, db, nil, true, simtestutil.NewAppOptionsWithFlagHome(t.TempDir()),
|
||||
EVMAppOptions,
|
||||
)
|
||||
_, err = newGapp.ExportAppStateAndValidators(false, []string{}, nil)
|
||||
require.NoError(t, err, "ExportAppStateAndValidators should not have an error")
|
||||
@@ -51,12 +54,36 @@ func TestBlockedAddrs(t *testing.T) {
|
||||
} else {
|
||||
addr = gapp.AccountKeeper.GetModuleAddress(acc)
|
||||
}
|
||||
require.True(t, gapp.BankKeeper.BlockedAddr(addr), "ensure that blocked addresses are properly set in bank keeper")
|
||||
require.True(
|
||||
t,
|
||||
gapp.BankKeeper.BlockedAddr(addr),
|
||||
"ensure that blocked addresses are properly set in bank keeper",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMaccPerms(t *testing.T) {
|
||||
dup := GetMaccPerms()
|
||||
require.Equal(t, maccPerms, dup, "duplicated module account permissions differed from actual module account permissions")
|
||||
require.Equal(
|
||||
t,
|
||||
maccPerms,
|
||||
dup,
|
||||
"duplicated module account permissions differed from actual module account permissions",
|
||||
)
|
||||
}
|
||||
|
||||
// TestMergedRegistry tests that fetching the gogo/protov2 merged registry
|
||||
// doesn't fail after loading all file descriptors.
|
||||
func TestMergedRegistry(t *testing.T) {
|
||||
r, err := proto.MergedRegistry()
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, r.NumFiles(), 0)
|
||||
}
|
||||
|
||||
func TestProtoAnnotations(t *testing.T) {
|
||||
r, err := proto.MergedRegistry()
|
||||
require.NoError(t, err)
|
||||
err = msgservice.ValidateProtoAnnotations(r)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
// Package commands contains utility functions for the snrd command
|
||||
package commands
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
genutilcli "github.com/cosmos/cosmos-sdk/x/genutil/client/cli"
|
||||
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
|
||||
"github.com/sonr-io/sonr/app"
|
||||
"github.com/sonr-io/sonr/crypto/vrf"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func EnhancedInit(chainApp *app.ChainApp) *cobra.Command {
|
||||
baseCmd := genutilcli.InitCmd(chainApp.BasicModuleManager, app.DefaultNodeHome)
|
||||
baseCmd.PostRunE = handleInitPostE
|
||||
return baseCmd
|
||||
}
|
||||
|
||||
// handleInitPostE generates VRF keypair and stores it securely after chain initialization
|
||||
func handleInitPostE(cmd *cobra.Command, args []string) error {
|
||||
// Extract chain-id from genesis file for network-aware key generation
|
||||
chainID, err := getChainIDFromGenesis()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get chain-id: %w", err)
|
||||
}
|
||||
|
||||
// Create deterministic entropy source from chainID
|
||||
entropySource, err := createChainIDEntropySource(chainID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create entropy source: %w", err)
|
||||
}
|
||||
|
||||
// Generate VRF keypair using chainID-derived entropy
|
||||
privateKey, err := vrf.GenerateKey(entropySource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate VRF keypair: %w", err)
|
||||
}
|
||||
|
||||
// Validate the generated keypair
|
||||
if len(privateKey) != vrf.PrivateKeySize {
|
||||
return fmt.Errorf(
|
||||
"invalid VRF private key size: expected %d, got %d",
|
||||
vrf.PrivateKeySize,
|
||||
len(privateKey),
|
||||
)
|
||||
}
|
||||
|
||||
// Get public key to validate keypair
|
||||
_, ok := privateKey.Public()
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to derive public key from VRF private key")
|
||||
}
|
||||
|
||||
// Create secure storage path
|
||||
vrfKeyPath := filepath.Join(app.DefaultNodeHome, "vrf_secret.key")
|
||||
|
||||
// Ensure directory exists with proper permissions
|
||||
if err := os.MkdirAll(app.DefaultNodeHome, 0o750); err != nil {
|
||||
return fmt.Errorf("failed to create node home directory: %w", err)
|
||||
}
|
||||
|
||||
// Store VRF secret key with restrictive permissions (owner read/write only)
|
||||
if err := os.WriteFile(vrfKeyPath, privateKey, 0o600); err != nil {
|
||||
return fmt.Errorf("failed to save VRF secret key: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✓ VRF keypair generated for network: %s\n", chainID)
|
||||
fmt.Printf("✓ VRF secret key stored securely: %s\n", vrfKeyPath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// getChainIDFromGenesis extracts chain-id from the genesis file
|
||||
func getChainIDFromGenesis() (string, error) {
|
||||
genesisPath := filepath.Join(app.DefaultNodeHome, "config", "genesis.json")
|
||||
|
||||
// #nosec G304 - genesisPath is constructed from trusted app.DefaultNodeHome constant
|
||||
genesisData, err := os.ReadFile(genesisPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read genesis file: %w", err)
|
||||
}
|
||||
|
||||
var genesis genutiltypes.AppGenesis
|
||||
if err := json.Unmarshal(genesisData, &genesis); err != nil {
|
||||
return "", fmt.Errorf("failed to parse genesis file: %w", err)
|
||||
}
|
||||
|
||||
if genesis.ChainID == "" {
|
||||
return "", fmt.Errorf("chain-id not found in genesis file")
|
||||
}
|
||||
|
||||
return genesis.ChainID, nil
|
||||
}
|
||||
|
||||
// entropyReader implements io.Reader to provide deterministic entropy from chainID
|
||||
type entropyReader struct {
|
||||
seed []byte
|
||||
pos int
|
||||
}
|
||||
|
||||
func (e *entropyReader) Read(p []byte) (int, error) {
|
||||
n := 0
|
||||
for n < len(p) && e.pos < len(e.seed) {
|
||||
p[n] = e.seed[e.pos]
|
||||
n++
|
||||
e.pos++
|
||||
}
|
||||
|
||||
// If we need more bytes than available in seed, use crypto/rand for additional randomness
|
||||
if n < len(p) {
|
||||
remaining, err := rand.Read(p[n:])
|
||||
if err != nil {
|
||||
return n, err
|
||||
}
|
||||
n += remaining
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// createChainIDEntropySource creates a deterministic entropy source from chainID
|
||||
// This provides network-specific but still secure randomness for VRF key generation
|
||||
func createChainIDEntropySource(chainID string) (io.Reader, error) {
|
||||
if chainID == "" {
|
||||
return nil, fmt.Errorf("chainID cannot be empty")
|
||||
}
|
||||
|
||||
// Create deterministic seed from chainID using SHA256
|
||||
hash := sha256.Sum256([]byte(chainID))
|
||||
|
||||
return &entropyReader{
|
||||
seed: hash[:],
|
||||
pos: 0,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
package commands
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/flags"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
)
|
||||
|
||||
func GovCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "gov",
|
||||
Short: "Governance utilities",
|
||||
Long: `Utilities for governance operations and compliance.`,
|
||||
}
|
||||
|
||||
cmd.AddCommand(DunaAmendmentCmd())
|
||||
return cmd
|
||||
}
|
||||
|
||||
func DunaAmendmentCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "duna-amendment",
|
||||
Short: "Generate Wyoming DAO compliance filing content",
|
||||
Long: `Extract governance identifiers and generate Wyoming DAO LC amendment filing content
|
||||
for compliance with Wyoming Statute 17-31-106(b).`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
clientCtx, err := client.GetClientQueryContext(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Use the hardcoded ChainID from app.go
|
||||
chainID := "sonrtest_1-1"
|
||||
// Allow override from client context if set
|
||||
if clientCtx.ChainID != "" {
|
||||
chainID = clientCtx.ChainID
|
||||
}
|
||||
|
||||
saveToFile, _ := cmd.Flags().GetBool("output-files")
|
||||
|
||||
fmt.Printf(
|
||||
"🏛️ Wyoming DAO compliance check for Decentralized Identity DAO LC (diDAO)\n\n",
|
||||
)
|
||||
|
||||
// Get governance address
|
||||
govAddress, err := getGovernanceAddress(clientCtx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get governance address: %w", err)
|
||||
}
|
||||
fmt.Printf("✓ Governance module address: %s\n", govAddress)
|
||||
|
||||
// Get genesis hash
|
||||
genesisHash, err := getGenesisHash(clientCtx.HomeDir)
|
||||
if err != nil {
|
||||
fmt.Printf("⚠️ Warning: Could not calculate genesis hash: %v\n", err)
|
||||
genesisHash = fmt.Sprintf(
|
||||
"Genesis file location: %s/config/genesis.json",
|
||||
clientCtx.HomeDir,
|
||||
)
|
||||
} else {
|
||||
fmt.Printf("✓ Genesis hash: %s\n", genesisHash)
|
||||
}
|
||||
|
||||
// Generate and output filing content
|
||||
filingContent, amendmentText := generateWyomingContent(govAddress, chainID, genesisHash)
|
||||
|
||||
if saveToFile {
|
||||
err = saveWyomingFiles(filingContent, amendmentText)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to save Wyoming filing content: %w", err)
|
||||
}
|
||||
fmt.Printf("✓ Wyoming filing content generated: wyoming_amendment_content.txt\n")
|
||||
fmt.Printf("✓ Article VI amendment text: article_vi_amendment.txt\n")
|
||||
} else {
|
||||
fmt.Printf("%s", "\n"+filingContent+"\n")
|
||||
}
|
||||
|
||||
fmt.Printf("\n✅ Wyoming compliance identifiers ready!\n\n")
|
||||
fmt.Printf("Key Information:\n")
|
||||
fmt.Printf("- Governance Address: %s\n", govAddress)
|
||||
fmt.Printf("- Chain ID: %s\n", chainID)
|
||||
fmt.Printf("- Genesis Hash: %s\n", genesisHash)
|
||||
|
||||
if saveToFile {
|
||||
fmt.Printf("\nNext Steps:\n")
|
||||
fmt.Printf("1. Review: wyoming_amendment_content.txt\n")
|
||||
fmt.Printf("2. File online at: https://wyobiz.wy.gov\n")
|
||||
fmt.Printf("3. Pay $60 fee and submit\n")
|
||||
} else {
|
||||
fmt.Printf("\nUse --output-files flag to save content to files.\n")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
flags.AddQueryFlagsToCmd(cmd)
|
||||
cmd.Flags().
|
||||
Bool("output-files", false, "Save filing content to files instead of printing to stdout")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getGovernanceAddress(clientCtx client.Context) (string, error) {
|
||||
queryClient := authtypes.NewQueryClient(clientCtx)
|
||||
|
||||
res, err := queryClient.ModuleAccounts(
|
||||
clientCtx.CmdContext,
|
||||
&authtypes.QueryModuleAccountsRequest{},
|
||||
)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Find the governance module account
|
||||
for _, account := range res.Accounts {
|
||||
var acc sdk.AccountI
|
||||
if err := clientCtx.InterfaceRegistry.UnpackAny(account, &acc); err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if modAcc, ok := acc.(sdk.ModuleAccountI); ok {
|
||||
if modAcc.GetName() == "gov" {
|
||||
return acc.GetAddress().String(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("governance module account not found")
|
||||
}
|
||||
|
||||
func getGenesisHash(homeDir string) (string, error) {
|
||||
genesisPath := filepath.Join(homeDir, "config", "genesis.json")
|
||||
|
||||
file, err := os.Open(genesisPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
hash := sha256.New()
|
||||
if _, err := io.Copy(hash, file); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", hash.Sum(nil)), nil
|
||||
}
|
||||
|
||||
func generateWyomingContent(govAddress, chainID, genesisHash string) (string, string) {
|
||||
now := time.Now()
|
||||
|
||||
// Generate full filing content
|
||||
filingContent := fmt.Sprintf(`WYOMING DAO LC AMENDMENT - ARTICLE VI
|
||||
|
||||
Entity Name: Decentralized Identity DAO LC
|
||||
Amendment Type: Articles of Organization Amendment
|
||||
|
||||
ARTICLE VI - DAO PUBLIC IDENTIFIER:
|
||||
|
||||
The publicly available identifier for smart contracts used to manage, facilitate, and operate the decentralized autonomous organization is: %s (Cosmos SDK governance module address on chain %s). This identifier provides access to all governance functions including proposal submission, voting, parameter changes, and treasury management as required by Wyoming Statute 17-31-106(b).
|
||||
|
||||
VERIFICATION:
|
||||
- Governance Address: %s
|
||||
- Chain ID: %s
|
||||
- Verification Command: snrd query auth module-account gov
|
||||
|
||||
Generated on: %s`, govAddress, chainID, govAddress, chainID, now.Format("2006-01-02 15:04:05"))
|
||||
|
||||
// Generate Article VI amendment text only
|
||||
amendmentText := fmt.Sprintf(
|
||||
`The publicly available identifier for smart contracts used to manage, facilitate, and operate the decentralized autonomous organization is: %s (Cosmos SDK governance module address on chain %s). This identifier provides access to all governance functions including proposal submission, voting, parameter changes, and treasury management as required by Wyoming Statute 17-31-106(b).`,
|
||||
govAddress,
|
||||
chainID,
|
||||
)
|
||||
|
||||
return filingContent, amendmentText
|
||||
}
|
||||
|
||||
func saveWyomingFiles(filingContent, amendmentText string) error {
|
||||
// Write full filing content
|
||||
err := os.WriteFile("wyoming_amendment_content.txt", []byte(filingContent), 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Write Article VI amendment text
|
||||
err = os.WriteFile("article_vi_amendment.txt", []byte(amendmentText), 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
// Package commands contains utility functions for the snrd command
|
||||
package commands
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/sonr-io/sonr/app"
|
||||
"github.com/sonr-io/sonr/crypto/vrf"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// VRFKeysCmd returns the VRF key management command
|
||||
func VRFKeysCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "vrf",
|
||||
Short: "Manage VRF (Verifiable Random Function) keys",
|
||||
Long: `Manage VRF keys used for consensus-based encryption in multi-validator networks.
|
||||
|
||||
VRF keys are required for:
|
||||
- Multi-validator encryption key derivation
|
||||
- Consensus-based key rotation
|
||||
- Deterministic randomness in distributed systems
|
||||
|
||||
VRF keys are automatically generated during 'snrd init', but this command
|
||||
allows you to inspect or regenerate them if needed.`,
|
||||
}
|
||||
|
||||
cmd.AddCommand(
|
||||
vrfGenerateCmd(),
|
||||
vrfShowCmd(),
|
||||
vrfVerifyCmd(),
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// vrfGenerateCmd creates a command to generate new VRF keys
|
||||
func vrfGenerateCmd() *cobra.Command {
|
||||
var chainID string
|
||||
var force bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "generate",
|
||||
Short: "Generate new VRF keypair",
|
||||
Long: `Generate a new VRF keypair for the node.
|
||||
|
||||
The keypair is generated deterministically from the chain-id to ensure
|
||||
reproducibility. Keys are stored at ~/.sonr/vrf_secret.key with 0600 permissions.
|
||||
|
||||
WARNING: Regenerating VRF keys will invalidate any existing consensus-based
|
||||
encryption keys. Only do this if you understand the implications.`,
|
||||
Example: ` # Generate VRF keys for a specific chain
|
||||
snrd keys vrf generate --chain-id sonrtest_1-1
|
||||
|
||||
# Force regenerate (overwrite existing keys)
|
||||
snrd keys vrf generate --chain-id sonrtest_1-1 --force`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// Get chain-id
|
||||
if chainID == "" {
|
||||
// Try to read from genesis file
|
||||
var err error
|
||||
chainID, err = getChainIDFromGenesis()
|
||||
if err != nil {
|
||||
return fmt.Errorf("chain-id not provided and could not be read from genesis: %w\n"+
|
||||
"Please provide --chain-id flag", err)
|
||||
}
|
||||
}
|
||||
|
||||
vrfKeyPath := filepath.Join(app.DefaultNodeHome, "vrf_secret.key")
|
||||
|
||||
// Check if keys already exist
|
||||
if _, err := os.Stat(vrfKeyPath); err == nil && !force {
|
||||
return fmt.Errorf("VRF keys already exist at %s\n"+
|
||||
"Use --force flag to overwrite existing keys\n"+
|
||||
"WARNING: Overwriting keys will invalidate existing consensus encryption", vrfKeyPath)
|
||||
}
|
||||
|
||||
// Backup existing keys if they exist
|
||||
if _, err := os.Stat(vrfKeyPath); err == nil && force {
|
||||
backupPath := vrfKeyPath + ".backup"
|
||||
if err := os.Rename(vrfKeyPath, backupPath); err != nil {
|
||||
return fmt.Errorf("failed to backup existing VRF keys: %w", err)
|
||||
}
|
||||
fmt.Printf("Backed up existing keys to: %s\n", backupPath)
|
||||
}
|
||||
|
||||
// Generate VRF keys
|
||||
fmt.Printf("Generating VRF keypair for chain-id: %s\n", chainID)
|
||||
|
||||
entropySource, err := createChainIDEntropySource(chainID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create entropy source: %w", err)
|
||||
}
|
||||
|
||||
privateKey, err := vrf.GenerateKey(entropySource)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to generate VRF keypair: %w", err)
|
||||
}
|
||||
|
||||
// Validate keypair
|
||||
if len(privateKey) != vrf.PrivateKeySize {
|
||||
return fmt.Errorf("invalid VRF private key size: expected %d, got %d",
|
||||
vrf.PrivateKeySize, len(privateKey))
|
||||
}
|
||||
|
||||
publicKey, ok := privateKey.Public()
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to derive public key from VRF private key")
|
||||
}
|
||||
|
||||
// Ensure directory exists
|
||||
if err := os.MkdirAll(app.DefaultNodeHome, 0o750); err != nil {
|
||||
return fmt.Errorf("failed to create node home directory: %w", err)
|
||||
}
|
||||
|
||||
// Store VRF secret key with restrictive permissions
|
||||
if err := os.WriteFile(vrfKeyPath, privateKey, 0o600); err != nil {
|
||||
return fmt.Errorf("failed to save VRF secret key: %w", err)
|
||||
}
|
||||
|
||||
fmt.Printf("✓ VRF keypair generated successfully\n")
|
||||
fmt.Printf("✓ Private key stored at: %s\n", vrfKeyPath)
|
||||
fmt.Printf("✓ Public key (hex): %s\n", hex.EncodeToString(publicKey))
|
||||
fmt.Printf("✓ Key size: %d bytes\n", len(privateKey))
|
||||
fmt.Printf("✓ Permissions: 0600 (owner read/write only)\n")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&chainID, "chain-id", "", "Chain ID for deterministic key generation")
|
||||
cmd.Flags().BoolVar(&force, "force", false, "Force overwrite existing VRF keys (creates backup)")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// vrfShowCmd creates a command to display VRF key information
|
||||
func vrfShowCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "show",
|
||||
Short: "Show VRF public key information",
|
||||
Long: `Display information about the node's VRF public key.
|
||||
|
||||
This command shows the public key without exposing the private key.
|
||||
Useful for verifying that VRF keys are properly installed.`,
|
||||
Example: ` # Show VRF key information
|
||||
snrd keys vrf show`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
vrfKeyPath := filepath.Join(app.DefaultNodeHome, "vrf_secret.key")
|
||||
|
||||
// Read VRF private key
|
||||
// #nosec G304 - vrfKeyPath is constructed from trusted DefaultNodeHome constant
|
||||
privateKeyData, err := os.ReadFile(vrfKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read VRF keys from %s: %w\n"+
|
||||
"VRF keys may not be initialized. Run 'snrd init' or 'snrd keys vrf generate'",
|
||||
vrfKeyPath, err)
|
||||
}
|
||||
|
||||
// Validate key size
|
||||
if len(privateKeyData) != vrf.PrivateKeySize {
|
||||
return fmt.Errorf("invalid VRF private key size: expected %d, got %d",
|
||||
vrf.PrivateKeySize, len(privateKeyData))
|
||||
}
|
||||
|
||||
privateKey := vrf.PrivateKey(privateKeyData)
|
||||
publicKey, ok := privateKey.Public()
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to derive public key from VRF private key")
|
||||
}
|
||||
|
||||
// Get file info
|
||||
fileInfo, err := os.Stat(vrfKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to stat VRF key file: %w", err)
|
||||
}
|
||||
|
||||
fmt.Println("VRF Key Information:")
|
||||
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
fmt.Printf("Key Path: %s\n", vrfKeyPath)
|
||||
fmt.Printf("Public Key: %s\n", hex.EncodeToString(publicKey))
|
||||
fmt.Printf("Key Size: %d bytes\n", len(privateKeyData))
|
||||
fmt.Printf("Public Key Size: %d bytes\n", len(publicKey))
|
||||
fmt.Printf("Permissions: %s\n", fileInfo.Mode().Perm())
|
||||
fmt.Printf("Modified: %s\n", fileInfo.ModTime().Format("2006-01-02 15:04:05"))
|
||||
|
||||
// Verify permissions
|
||||
if fileInfo.Mode().Perm() != 0o600 {
|
||||
fmt.Println("\n⚠️ WARNING: VRF key file has incorrect permissions!")
|
||||
fmt.Printf(" Current: %s, Expected: 0600\n", fileInfo.Mode().Perm())
|
||||
fmt.Println(" Run: chmod 0600 " + vrfKeyPath)
|
||||
} else {
|
||||
fmt.Println("\n✓ VRF keys are properly configured")
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// vrfVerifyCmd creates a command to verify VRF key functionality
|
||||
func vrfVerifyCmd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "verify",
|
||||
Short: "Verify VRF key functionality",
|
||||
Long: `Verify that VRF keys are properly installed and functional.
|
||||
|
||||
This command performs a test VRF computation to ensure the keys work correctly.`,
|
||||
Example: ` # Verify VRF keys
|
||||
snrd keys vrf verify`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
vrfKeyPath := filepath.Join(app.DefaultNodeHome, "vrf_secret.key")
|
||||
|
||||
// Read VRF private key
|
||||
// #nosec G304 - vrfKeyPath is constructed from trusted DefaultNodeHome constant
|
||||
privateKeyData, err := os.ReadFile(vrfKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read VRF keys: %w", err)
|
||||
}
|
||||
|
||||
if len(privateKeyData) != vrf.PrivateKeySize {
|
||||
return fmt.Errorf("invalid VRF private key size: expected %d, got %d",
|
||||
vrf.PrivateKeySize, len(privateKeyData))
|
||||
}
|
||||
|
||||
privateKey := vrf.PrivateKey(privateKeyData)
|
||||
publicKey, ok := privateKey.Public()
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to derive public key")
|
||||
}
|
||||
|
||||
// Test VRF computation with proof
|
||||
testInput := []byte("test-input-for-verification")
|
||||
vrfOutput, proof := privateKey.Prove(testInput)
|
||||
|
||||
// Verify the output
|
||||
if !publicKey.Verify(testInput, vrfOutput, proof) {
|
||||
return fmt.Errorf("VRF verification failed - keys may be corrupted")
|
||||
}
|
||||
|
||||
// Compute output hash
|
||||
outputHash := sha256.Sum256(vrfOutput)
|
||||
|
||||
fmt.Println("VRF Key Verification:")
|
||||
fmt.Println("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━")
|
||||
fmt.Printf("✓ VRF keys loaded successfully\n")
|
||||
fmt.Printf("✓ Public key derived successfully\n")
|
||||
fmt.Printf("✓ VRF proof generated successfully\n")
|
||||
fmt.Printf("✓ VRF verification successful\n")
|
||||
fmt.Printf("\nTest Output (hex): %s\n", hex.EncodeToString(vrfOutput))
|
||||
fmt.Printf("Proof (hex): %s\n", hex.EncodeToString(proof))
|
||||
fmt.Printf("Output Hash: %s\n", hex.EncodeToString(outputHash[:]))
|
||||
|
||||
fmt.Println("\n✓ VRF keys are fully functional")
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
Executable
+122
@@ -0,0 +1,122 @@
|
||||
// Package app provides configuration utilities for the Sonr blockchain application.
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"cosmossdk.io/math"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
evmtypes "github.com/cosmos/evm/x/vm/types"
|
||||
)
|
||||
|
||||
// EVMOptionsFn defines a function type for setting app options specifically for
|
||||
// the app. The function should receive the chainID and return an error if
|
||||
// any.
|
||||
type EVMOptionsFn func(string) error
|
||||
|
||||
// NoOpEVMOptions is a no-op function that can be used when the app does not
|
||||
// need any specific configuration.
|
||||
func NoOpEVMOptions(_ string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// sealed tracks whether the EVM configuration has been initialized.
|
||||
// Once sealed, the configuration cannot be changed.
|
||||
var sealed = false
|
||||
|
||||
// ChainsCoinInfo is a map of the chain id and its corresponding EvmCoinInfo
|
||||
// that allows initializing the app with different coin info based on the
|
||||
// chain id
|
||||
var ChainsCoinInfo = map[string]evmtypes.EvmCoinInfo{
|
||||
// Default local development chain
|
||||
ChainID: {
|
||||
Denom: BaseDenom,
|
||||
DisplayDenom: DisplayDenom,
|
||||
Decimals: evmtypes.EighteenDecimals,
|
||||
},
|
||||
// Starship testnet configuration
|
||||
"sonrtestnet_1-1": {
|
||||
Denom: BaseDenom,
|
||||
DisplayDenom: DisplayDenom,
|
||||
Decimals: evmtypes.EighteenDecimals,
|
||||
},
|
||||
"sonr-testnet-1": {
|
||||
Denom: BaseDenom,
|
||||
DisplayDenom: DisplayDenom,
|
||||
Decimals: evmtypes.EighteenDecimals,
|
||||
},
|
||||
// Additional testnet configurations
|
||||
"sonr_1-1": {
|
||||
Denom: BaseDenom,
|
||||
DisplayDenom: DisplayDenom,
|
||||
Decimals: evmtypes.EighteenDecimals,
|
||||
},
|
||||
}
|
||||
|
||||
// EVMAppOptions sets up the global EVM configuration for the chain.
|
||||
// It configures the base denomination, chain config, and EVM coin info
|
||||
// based on the provided chain ID. The configuration is sealed after
|
||||
// first initialization to prevent modifications.
|
||||
func EVMAppOptions(chainID string) error {
|
||||
if sealed {
|
||||
return nil
|
||||
}
|
||||
|
||||
if chainID == "" {
|
||||
chainID = ChainID
|
||||
}
|
||||
|
||||
// Try to find config by base chain ID (without suffix)
|
||||
id := strings.Split(chainID, "-")[0]
|
||||
coinInfo, found := ChainsCoinInfo[id]
|
||||
if !found {
|
||||
// Try full chain ID
|
||||
coinInfo, found = ChainsCoinInfo[chainID]
|
||||
if !found {
|
||||
// Use a default configuration for unknown chains with warning
|
||||
fmt.Printf("Warning: Unknown chain ID %s, using default usnr configuration\n", chainID)
|
||||
coinInfo = evmtypes.EvmCoinInfo{
|
||||
Denom: BaseDenom,
|
||||
DisplayDenom: DisplayDenom,
|
||||
Decimals: evmtypes.EighteenDecimals,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// set the denom info for the chain
|
||||
if err := setBaseDenom(coinInfo); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
baseDenom, err := sdk.GetBaseDenom()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ethCfg := evmtypes.DefaultChainConfig(chainID)
|
||||
|
||||
err = evmtypes.NewEVMConfigurator().
|
||||
WithChainConfig(ethCfg).
|
||||
WithEVMCoinInfo(baseDenom, uint8(coinInfo.Decimals)).
|
||||
Configure()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sealed = true
|
||||
return nil
|
||||
}
|
||||
|
||||
// setBaseDenom registers the display denom and base denom and sets the
|
||||
// base denom for the chain. It ensures proper decimal conversion between
|
||||
// the base denomination and display denomination.
|
||||
func setBaseDenom(ci evmtypes.EvmCoinInfo) error {
|
||||
if err := sdk.RegisterDenom(ci.DisplayDenom, math.LegacyOneDec()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// sdk.RegisterDenom will automatically overwrite the base denom when the
|
||||
// new setBaseDenom() are lower than the current base denom's units.
|
||||
return sdk.RegisterDenom(ci.Denom, math.LegacyNewDecWithPrec(1, int64(ci.Decimals)))
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/tx"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
txtypes "github.com/cosmos/cosmos-sdk/types/tx"
|
||||
"github.com/cosmos/cosmos-sdk/types/tx/signing"
|
||||
)
|
||||
|
||||
// BroadcastTx broadcasts a transaction to the blockchain using the stored client context
|
||||
func (sc *SonrContext) BroadcastTx(txBytes []byte) error {
|
||||
clientCtx, err := sc.GetClientContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client context: %w", err)
|
||||
}
|
||||
|
||||
// Create broadcast request
|
||||
txReq := &txtypes.BroadcastTxRequest{
|
||||
TxBytes: txBytes,
|
||||
Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC,
|
||||
}
|
||||
|
||||
// Get the gRPC client connection
|
||||
grpcConn := clientCtx.GRPCClient
|
||||
|
||||
// Create transaction service client
|
||||
txClient := txtypes.NewServiceClient(grpcConn)
|
||||
|
||||
// Broadcast the transaction
|
||||
res, err := txClient.BroadcastTx(context.Background(), txReq)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to broadcast transaction: %w", err)
|
||||
}
|
||||
|
||||
// Check if transaction was accepted
|
||||
if res.TxResponse.Code != 0 {
|
||||
return fmt.Errorf(
|
||||
"transaction failed with code %d: %s",
|
||||
res.TxResponse.Code,
|
||||
res.TxResponse.RawLog,
|
||||
)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BroadcastTxWithResponse broadcasts a transaction and returns the response
|
||||
func (sc *SonrContext) BroadcastTxWithResponse(
|
||||
txBytes []byte,
|
||||
) (*txtypes.BroadcastTxResponse, error) {
|
||||
clientCtx, err := sc.GetClientContext()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get client context: %w", err)
|
||||
}
|
||||
|
||||
// Create broadcast request
|
||||
txReq := &txtypes.BroadcastTxRequest{
|
||||
TxBytes: txBytes,
|
||||
Mode: txtypes.BroadcastMode_BROADCAST_MODE_SYNC,
|
||||
}
|
||||
|
||||
// Get the gRPC client connection
|
||||
grpcConn := clientCtx.GRPCClient
|
||||
|
||||
// Create transaction service client
|
||||
txClient := txtypes.NewServiceClient(grpcConn)
|
||||
|
||||
// Broadcast the transaction
|
||||
res, err := txClient.BroadcastTx(context.Background(), txReq)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to broadcast transaction: %w", err)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// SignAndBroadcastTx signs a transaction and broadcasts it
|
||||
func (sc *SonrContext) SignAndBroadcastTx(txBuilder client.TxBuilder) error {
|
||||
clientCtx, err := sc.GetClientContext()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get client context: %w", err)
|
||||
}
|
||||
|
||||
// Sign the transaction
|
||||
txFactory, err := tx.NewFactoryCLI(clientCtx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create tx factory: %w", err)
|
||||
}
|
||||
err = tx.Sign(clientCtx.CmdContext, txFactory, clientCtx.GetFromName(), txBuilder, true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to sign transaction: %w", err)
|
||||
}
|
||||
|
||||
// Encode the transaction
|
||||
txBytes, err := clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to encode transaction: %w", err)
|
||||
}
|
||||
|
||||
// Broadcast the transaction
|
||||
return sc.BroadcastTx(txBytes)
|
||||
}
|
||||
|
||||
// CreateUnsignedTx creates an unsigned transaction from messages
|
||||
func (sc *SonrContext) CreateUnsignedTx(msgs ...sdk.Msg) (client.TxBuilder, error) {
|
||||
clientCtx, err := sc.GetClientContext()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get client context: %w", err)
|
||||
}
|
||||
|
||||
// Create transaction builder
|
||||
txBuilder := clientCtx.TxConfig.NewTxBuilder()
|
||||
|
||||
// Set messages
|
||||
err = txBuilder.SetMsgs(msgs...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to set messages: %w", err)
|
||||
}
|
||||
|
||||
// Set gas limit and fees (these should be estimated or configured)
|
||||
txBuilder.SetGasLimit(200000) // Default gas limit
|
||||
|
||||
// Set fee amount (you may want to make this configurable)
|
||||
feeAmount := sdk.NewCoins(sdk.NewInt64Coin("usonr", 1000))
|
||||
txBuilder.SetFeeAmount(feeAmount)
|
||||
|
||||
return txBuilder, nil
|
||||
}
|
||||
|
||||
// EstimateGas estimates gas for a transaction
|
||||
func (sc *SonrContext) EstimateGas(txBuilder client.TxBuilder) (uint64, error) {
|
||||
clientCtx, err := sc.GetClientContext()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get client context: %w", err)
|
||||
}
|
||||
|
||||
// Simulate the transaction to estimate gas
|
||||
simReq, err := sc.buildSimTx(clientCtx, txBuilder)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to build simulation request: %w", err)
|
||||
}
|
||||
|
||||
// Get the gRPC client connection
|
||||
grpcConn := clientCtx.GRPCClient
|
||||
|
||||
// Create transaction service client
|
||||
txClient := txtypes.NewServiceClient(grpcConn)
|
||||
|
||||
// Simulate the transaction
|
||||
simRes, err := txClient.Simulate(context.Background(), simReq)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to simulate transaction: %w", err)
|
||||
}
|
||||
|
||||
// Return estimated gas with some buffer
|
||||
return simRes.GasInfo.GasUsed + 10000, nil
|
||||
}
|
||||
|
||||
// buildSimTx builds a simulation request from a transaction builder
|
||||
func (sc *SonrContext) buildSimTx(
|
||||
clientCtx client.Context,
|
||||
txBuilder client.TxBuilder,
|
||||
) (*txtypes.SimulateRequest, error) {
|
||||
// Create a copy of the transaction builder for simulation
|
||||
simBuilder := clientCtx.TxConfig.NewTxBuilder()
|
||||
err := simBuilder.SetMsgs(txBuilder.GetTx().GetMsgs()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get account info for signature
|
||||
fromAddr := clientCtx.GetFromAddress()
|
||||
if fromAddr.Empty() {
|
||||
return nil, fmt.Errorf("from address is empty")
|
||||
}
|
||||
|
||||
// Set dummy signature for simulation - we need a public key from the keyring
|
||||
keyInfo, err := clientCtx.Keyring.Key(clientCtx.GetFromName())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get key info: %w", err)
|
||||
}
|
||||
pubKey, err := keyInfo.GetPubKey()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get public key: %w", err)
|
||||
}
|
||||
|
||||
sigV2 := signing.SignatureV2{
|
||||
PubKey: pubKey,
|
||||
Data: &signing.SingleSignatureData{
|
||||
SignMode: signing.SignMode_SIGN_MODE_DIRECT,
|
||||
Signature: nil,
|
||||
},
|
||||
Sequence: 0,
|
||||
}
|
||||
|
||||
err = simBuilder.SetSignatures(sigV2)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Encode the simulation transaction
|
||||
simTxBytes, err := clientCtx.TxConfig.TxEncoder()(simBuilder.GetTx())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &txtypes.SimulateRequest{
|
||||
TxBytes: simTxBytes,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,196 @@
|
||||
// Package context provides the Sonr context system for managing node-specific state.
|
||||
package context
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/vrf"
|
||||
)
|
||||
|
||||
// SonrContext manages node-specific state and configuration
|
||||
type SonrContext struct {
|
||||
logger log.Logger
|
||||
|
||||
// VRF keypair for the node
|
||||
vrfPrivateKey vrf.PrivateKey
|
||||
vrfPublicKey vrf.PublicKey
|
||||
|
||||
// Client context for transaction operations
|
||||
clientCtx client.Context
|
||||
|
||||
// Synchronization for thread-safe access
|
||||
mu sync.RWMutex
|
||||
|
||||
// Initialization state
|
||||
initialized bool
|
||||
}
|
||||
|
||||
// NewSonrContext creates a new SonrContext instance
|
||||
func NewSonrContext(logger log.Logger) *SonrContext {
|
||||
if logger == nil {
|
||||
logger = log.NewNopLogger()
|
||||
}
|
||||
|
||||
return &SonrContext{
|
||||
logger: logger.With("component", "sonr-context"),
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SetClientContext sets the client context for transaction operations (thread-safe)
|
||||
func (sc *SonrContext) SetClientContext(clientCtx client.Context) {
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
|
||||
sc.clientCtx = clientCtx
|
||||
}
|
||||
|
||||
// GetClientContext returns the client context (thread-safe)
|
||||
func (sc *SonrContext) GetClientContext() (client.Context, error) {
|
||||
sc.mu.RLock()
|
||||
defer sc.mu.RUnlock()
|
||||
|
||||
if sc.clientCtx.Codec == nil {
|
||||
return client.Context{}, fmt.Errorf("client context not initialized")
|
||||
}
|
||||
|
||||
return sc.clientCtx, nil
|
||||
}
|
||||
|
||||
// Initialize loads the VRF keypair from storage
|
||||
func (sc *SonrContext) Initialize() error {
|
||||
sc.mu.Lock()
|
||||
defer sc.mu.Unlock()
|
||||
|
||||
if sc.initialized {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Load VRF private key from storage
|
||||
// Use hardcoded default path to avoid import cycle
|
||||
defaultNodeHome := os.ExpandEnv("$HOME/.sonr")
|
||||
vrfKeyPath := filepath.Join(defaultNodeHome, "vrf_secret.key")
|
||||
|
||||
// #nosec G304 - vrfKeyPath is constructed from trusted DefaultNodeHome constant
|
||||
vrfKeyData, err := os.ReadFile(vrfKeyPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read VRF secret key from %s: %w\n"+
|
||||
"VRF keys are required for multi-validator encryption features.\n"+
|
||||
"To generate VRF keys:\n"+
|
||||
" 1. For new nodes: Run 'snrd init <moniker>' to initialize with VRF keys\n"+
|
||||
" 2. For existing nodes: VRF keys should have been generated during init\n"+
|
||||
" 3. If encryption is not needed, disable it in DWN module params",
|
||||
vrfKeyPath, err)
|
||||
}
|
||||
|
||||
// Validate key size
|
||||
if len(vrfKeyData) != vrf.PrivateKeySize {
|
||||
return fmt.Errorf(
|
||||
"invalid VRF private key size: expected %d, got %d",
|
||||
vrf.PrivateKeySize,
|
||||
len(vrfKeyData),
|
||||
)
|
||||
}
|
||||
|
||||
sc.vrfPrivateKey = vrf.PrivateKey(vrfKeyData)
|
||||
|
||||
// Derive public key
|
||||
publicKey, ok := sc.vrfPrivateKey.Public()
|
||||
if !ok {
|
||||
return fmt.Errorf("failed to derive VRF public key from private key")
|
||||
}
|
||||
|
||||
sc.vrfPublicKey = publicKey
|
||||
sc.initialized = true
|
||||
|
||||
sc.logger.Info("SonrContext initialized successfully",
|
||||
"vrf_key_path", vrfKeyPath,
|
||||
"public_key_size", len(sc.vrfPublicKey),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetVRFPrivateKey returns the VRF private key (thread-safe)
|
||||
func (sc *SonrContext) GetVRFPrivateKey() (vrf.PrivateKey, error) {
|
||||
sc.mu.RLock()
|
||||
defer sc.mu.RUnlock()
|
||||
|
||||
if !sc.initialized {
|
||||
return nil, fmt.Errorf("SonrContext not initialized")
|
||||
}
|
||||
|
||||
return sc.vrfPrivateKey, nil
|
||||
}
|
||||
|
||||
// GetVRFPublicKey returns the VRF public key (thread-safe)
|
||||
func (sc *SonrContext) GetVRFPublicKey() (vrf.PublicKey, error) {
|
||||
sc.mu.RLock()
|
||||
defer sc.mu.RUnlock()
|
||||
|
||||
if !sc.initialized {
|
||||
return nil, fmt.Errorf("SonrContext not initialized")
|
||||
}
|
||||
|
||||
return sc.vrfPublicKey, nil
|
||||
}
|
||||
|
||||
// IsInitialized returns whether the context has been initialized (thread-safe)
|
||||
func (sc *SonrContext) IsInitialized() bool {
|
||||
sc.mu.RLock()
|
||||
defer sc.mu.RUnlock()
|
||||
|
||||
return sc.initialized
|
||||
}
|
||||
|
||||
// ComputeVRF generates VRF output for the given input using the loaded private key
|
||||
func (sc *SonrContext) ComputeVRF(input []byte) ([]byte, error) {
|
||||
sc.mu.RLock()
|
||||
defer sc.mu.RUnlock()
|
||||
|
||||
if !sc.initialized {
|
||||
return nil, fmt.Errorf("SonrContext not initialized")
|
||||
}
|
||||
|
||||
if len(input) == 0 {
|
||||
return nil, fmt.Errorf("VRF input cannot be empty")
|
||||
}
|
||||
|
||||
return sc.vrfPrivateKey.Compute(input), nil
|
||||
}
|
||||
|
||||
// ProveVRF generates VRF output with proof for the given input
|
||||
func (sc *SonrContext) ProveVRF(input []byte) (vrf []byte, proof []byte, err error) {
|
||||
sc.mu.RLock()
|
||||
defer sc.mu.RUnlock()
|
||||
|
||||
if !sc.initialized {
|
||||
return nil, nil, fmt.Errorf("SonrContext not initialized")
|
||||
}
|
||||
|
||||
if len(input) == 0 {
|
||||
return nil, nil, fmt.Errorf("VRF input cannot be empty")
|
||||
}
|
||||
|
||||
vrf, proof = sc.vrfPrivateKey.Prove(input)
|
||||
return vrf, proof, nil
|
||||
}
|
||||
|
||||
// Global context instance (initialized by the node)
|
||||
var globalSonrContext *SonrContext
|
||||
|
||||
// SetGlobalSonrContext sets the global SonrContext instance
|
||||
func SetGlobalSonrContext(ctx *SonrContext) {
|
||||
globalSonrContext = ctx
|
||||
}
|
||||
|
||||
// GetGlobalSonrContext returns the global SonrContext instance
|
||||
func GetGlobalSonrContext() *SonrContext {
|
||||
return globalSonrContext
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
package context
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/sonr-io/sonr/crypto/vrf"
|
||||
)
|
||||
|
||||
// TestSonrContextInitialization tests SonrContext initialization with VRF keys
|
||||
func TestSonrContextInitialization(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Create temporary directory for test
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
// Test initialization without VRF keys
|
||||
t.Setenv("HOME", tmpDir)
|
||||
logger := log.NewNopLogger()
|
||||
ctx := NewSonrContext(logger)
|
||||
|
||||
err := ctx.Initialize()
|
||||
require.Error(err, "Should fail to initialize without VRF keys")
|
||||
require.False(ctx.IsInitialized(), "Context should not be initialized without VRF keys")
|
||||
}
|
||||
|
||||
// TestSonrContextWithValidKeys tests SonrContext with valid VRF keys
|
||||
func TestSonrContextWithValidKeys(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Create temporary directory for test
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("HOME", tmpDir)
|
||||
|
||||
// Create .sonr directory
|
||||
sonrDir := filepath.Join(tmpDir, ".sonr")
|
||||
err := os.MkdirAll(sonrDir, 0o750)
|
||||
require.NoError(err)
|
||||
|
||||
// Generate VRF keys
|
||||
privateKey, err := vrf.GenerateKey(nil)
|
||||
require.NoError(err)
|
||||
|
||||
// Write VRF keys
|
||||
vrfKeyPath := filepath.Join(sonrDir, "vrf_secret.key")
|
||||
err = os.WriteFile(vrfKeyPath, privateKey, 0o600)
|
||||
require.NoError(err)
|
||||
|
||||
// Test initialization with valid VRF keys
|
||||
logger := log.NewNopLogger()
|
||||
ctx := NewSonrContext(logger)
|
||||
|
||||
err = ctx.Initialize()
|
||||
require.NoError(err, "Should initialize successfully with valid VRF keys")
|
||||
require.True(ctx.IsInitialized(), "Context should be initialized")
|
||||
|
||||
// Test VRF key retrieval
|
||||
privKey, err := ctx.GetVRFPrivateKey()
|
||||
require.NoError(err)
|
||||
require.Len(privKey, vrf.PrivateKeySize)
|
||||
|
||||
pubKey, err := ctx.GetVRFPublicKey()
|
||||
require.NoError(err)
|
||||
require.Len(pubKey, vrf.PublicKeySize)
|
||||
}
|
||||
|
||||
// TestSonrContextInvalidKeySize tests handling of invalid key size
|
||||
func TestSonrContextInvalidKeySize(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Create temporary directory for test
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("HOME", tmpDir)
|
||||
|
||||
// Create .sonr directory
|
||||
sonrDir := filepath.Join(tmpDir, ".sonr")
|
||||
err := os.MkdirAll(sonrDir, 0o750)
|
||||
require.NoError(err)
|
||||
|
||||
// Write invalid VRF key (wrong size)
|
||||
vrfKeyPath := filepath.Join(sonrDir, "vrf_secret.key")
|
||||
invalidKey := make([]byte, 32) // Should be 64 bytes
|
||||
err = os.WriteFile(vrfKeyPath, invalidKey, 0o600)
|
||||
require.NoError(err)
|
||||
|
||||
// Test initialization with invalid key size
|
||||
logger := log.NewNopLogger()
|
||||
ctx := NewSonrContext(logger)
|
||||
|
||||
err = ctx.Initialize()
|
||||
require.Error(err, "Should fail to initialize with invalid key size")
|
||||
require.Contains(err.Error(), "invalid VRF private key size")
|
||||
require.False(ctx.IsInitialized(), "Context should not be initialized with invalid keys")
|
||||
}
|
||||
|
||||
// TestSonrContextThreadSafety tests thread-safe access to VRF keys
|
||||
func TestSonrContextThreadSafety(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Create temporary directory for test
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("HOME", tmpDir)
|
||||
|
||||
// Create .sonr directory with valid keys
|
||||
sonrDir := filepath.Join(tmpDir, ".sonr")
|
||||
err := os.MkdirAll(sonrDir, 0o750)
|
||||
require.NoError(err)
|
||||
|
||||
privateKey, err := vrf.GenerateKey(nil)
|
||||
require.NoError(err)
|
||||
|
||||
vrfKeyPath := filepath.Join(sonrDir, "vrf_secret.key")
|
||||
err = os.WriteFile(vrfKeyPath, privateKey, 0o600)
|
||||
require.NoError(err)
|
||||
|
||||
// Initialize context
|
||||
logger := log.NewNopLogger()
|
||||
ctx := NewSonrContext(logger)
|
||||
err = ctx.Initialize()
|
||||
require.NoError(err)
|
||||
|
||||
// Test concurrent access (simple check - not exhaustive)
|
||||
done := make(chan bool, 10)
|
||||
for i := 0; i < 10; i++ {
|
||||
go func() {
|
||||
_, err := ctx.GetVRFPrivateKey()
|
||||
require.NoError(err)
|
||||
_, err = ctx.GetVRFPublicKey()
|
||||
require.NoError(err)
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
|
||||
// Wait for all goroutines
|
||||
for i := 0; i < 10; i++ {
|
||||
<-done
|
||||
}
|
||||
}
|
||||
|
||||
// TestSonrContextErrorMessages tests that error messages are helpful
|
||||
func TestSonrContextErrorMessages(t *testing.T) {
|
||||
require := require.New(t)
|
||||
|
||||
// Create temporary directory for test
|
||||
tmpDir := t.TempDir()
|
||||
t.Setenv("HOME", tmpDir)
|
||||
|
||||
logger := log.NewNopLogger()
|
||||
ctx := NewSonrContext(logger)
|
||||
|
||||
err := ctx.Initialize()
|
||||
require.Error(err)
|
||||
require.Contains(err.Error(), "failed to read VRF secret key")
|
||||
require.Contains(err.Error(), "VRF keys are required")
|
||||
require.Contains(err.Error(), "snrd init")
|
||||
}
|
||||
Regular → Executable
+17
-3
@@ -1,3 +1,5 @@
|
||||
// Package decorators provides custom ante handler decorators for transaction processing
|
||||
// in the Sonr blockchain application.
|
||||
package decorators
|
||||
|
||||
import (
|
||||
@@ -8,7 +10,8 @@ import (
|
||||
"github.com/cosmos/gogoproto/proto"
|
||||
)
|
||||
|
||||
// MsgFilterDecorator is an ante.go decorator template for filtering messages.
|
||||
// MsgFilterDecorator is an ante handler decorator that filters out specific message types
|
||||
// from transactions. It prevents certain message types from being processed by the chain.
|
||||
type MsgFilterDecorator struct {
|
||||
blockedTypes []sdk.Msg
|
||||
}
|
||||
@@ -17,7 +20,8 @@ type MsgFilterDecorator struct {
|
||||
// contains any of the blocked message types.
|
||||
//
|
||||
// Example:
|
||||
// - decorators.FilterDecorator(&banktypes.MsgSend{})
|
||||
// - decorators.FilterDecorator(&banktypes.MsgSend{})
|
||||
//
|
||||
// This would block any MsgSend messages from being included in a transaction if set in ante.go
|
||||
func FilterDecorator(blockedMsgTypes ...sdk.Msg) MsgFilterDecorator {
|
||||
return MsgFilterDecorator{
|
||||
@@ -25,7 +29,14 @@ func FilterDecorator(blockedMsgTypes ...sdk.Msg) MsgFilterDecorator {
|
||||
}
|
||||
}
|
||||
|
||||
func (mfd MsgFilterDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
|
||||
// AnteHandle implements the AnteDecorator interface. It checks if the transaction
|
||||
// contains any disallowed message types and rejects it if found.
|
||||
func (mfd MsgFilterDecorator) AnteHandle(
|
||||
ctx sdk.Context,
|
||||
tx sdk.Tx,
|
||||
simulate bool,
|
||||
next sdk.AnteHandler,
|
||||
) (newCtx sdk.Context, err error) {
|
||||
if mfd.HasDisallowedMessage(ctx, tx.GetMsgs()) {
|
||||
currHeight := ctx.BlockHeight()
|
||||
return ctx, fmt.Errorf("tx contains unsupported message types at height %d", currHeight)
|
||||
@@ -34,6 +45,9 @@ func (mfd MsgFilterDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo
|
||||
return next(ctx, tx, simulate)
|
||||
}
|
||||
|
||||
// HasDisallowedMessage recursively checks if any of the provided messages or their
|
||||
// nested messages (in case of authz.MsgExec) match the blocked message types.
|
||||
// Returns true if a disallowed message is found.
|
||||
func (mfd MsgFilterDecorator) HasDisallowedMessage(ctx sdk.Context, msgs []sdk.Msg) bool {
|
||||
for _, msg := range msgs {
|
||||
// check nested messages in a recursive manner
|
||||
|
||||
Regular → Executable
+4
-10
@@ -10,21 +10,13 @@ import (
|
||||
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||
"github.com/stretchr/testify/suite"
|
||||
|
||||
app "github.com/sonr-io/snrd/app"
|
||||
"github.com/sonr-io/snrd/app/decorators"
|
||||
"github.com/sonr-io/sonr/app/decorators"
|
||||
)
|
||||
|
||||
type AnteTestSuite struct {
|
||||
suite.Suite
|
||||
|
||||
ctx sdk.Context
|
||||
app *app.SonrApp
|
||||
}
|
||||
|
||||
func (s *AnteTestSuite) SetupTest() {
|
||||
isCheckTx := false
|
||||
s.app = app.Setup(s.T())
|
||||
s.ctx = s.app.BaseApp.NewContext(isCheckTx)
|
||||
}
|
||||
|
||||
func TestAnteTestSuite(t *testing.T) {
|
||||
@@ -48,7 +40,9 @@ func (s *AnteTestSuite) TestAnteMsgFilterLogic() {
|
||||
// validate other messages go through still (such as MsgMultiSend)
|
||||
msgMultiSend := banktypes.NewMsgMultiSend(
|
||||
banktypes.NewInput(acc, sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(1)))),
|
||||
[]banktypes.Output{banktypes.NewOutput(acc, sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(1))))},
|
||||
[]banktypes.Output{
|
||||
banktypes.NewOutput(acc, sdk.NewCoins(sdk.NewCoin("stake", sdkmath.NewInt(1)))),
|
||||
},
|
||||
)
|
||||
_, err = ante.AnteHandle(s.ctx, decorators.NewMockTx(msgMultiSend), false, decorators.EmptyAnte)
|
||||
s.Require().NoError(err)
|
||||
|
||||
Regular → Executable
+9
-1
@@ -5,31 +5,39 @@ import (
|
||||
protov2 "google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
// Define an empty ante handle
|
||||
// EmptyAnte is a no-op ante handler used for testing purposes.
|
||||
// It simply returns the context without performing any operations.
|
||||
var (
|
||||
EmptyAnte = func(ctx sdk.Context, tx sdk.Tx, simulate bool) (sdk.Context, error) {
|
||||
return ctx, nil
|
||||
}
|
||||
)
|
||||
|
||||
// MockTx is a mock transaction implementation used for testing decorators.
|
||||
// It implements the sdk.Tx interface with minimal functionality.
|
||||
type MockTx struct {
|
||||
msgs []sdk.Msg
|
||||
}
|
||||
|
||||
// NewMockTx creates a new mock transaction with the provided messages.
|
||||
// This is useful for testing ante handler decorators in isolation.
|
||||
func NewMockTx(msgs ...sdk.Msg) MockTx {
|
||||
return MockTx{
|
||||
msgs: msgs,
|
||||
}
|
||||
}
|
||||
|
||||
// GetMsgs returns the messages contained in the mock transaction.
|
||||
func (tx MockTx) GetMsgs() []sdk.Msg {
|
||||
return tx.msgs
|
||||
}
|
||||
|
||||
// GetMsgsV2 implements the sdk.Tx interface. Returns nil as this is a mock.
|
||||
func (tx MockTx) GetMsgsV2() ([]protov2.Message, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ValidateBasic implements the sdk.Tx interface. Always returns nil for the mock.
|
||||
func (tx MockTx) ValidateBasic() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
Regular → Executable
+36
-3
@@ -1,18 +1,26 @@
|
||||
// Package app provides encoding utilities for the Sonr blockchain application.
|
||||
package app
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
dbm "github.com/cosmos/cosmos-db"
|
||||
"github.com/cosmos/gogoproto/proto"
|
||||
|
||||
"cosmossdk.io/log"
|
||||
"cosmossdk.io/x/tx/signing"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/address"
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
|
||||
"github.com/sonr-io/snrd/app/params"
|
||||
"github.com/sonr-io/sonr/app/params"
|
||||
)
|
||||
|
||||
// MakeEncodingConfig creates a new EncodingConfig with all modules registered. For testing only
|
||||
// MakeEncodingConfig creates a new EncodingConfig with all modules registered.
|
||||
// This function is intended for testing purposes only, as it creates a temporary
|
||||
// application instance to extract the encoding configuration.
|
||||
func MakeEncodingConfig(t testing.TB) params.EncodingConfig {
|
||||
t.Helper()
|
||||
// we "pre"-instantiate the application for getting the injected/configured encoding configuration
|
||||
@@ -23,11 +31,15 @@ func MakeEncodingConfig(t testing.TB) params.EncodingConfig {
|
||||
nil,
|
||||
true,
|
||||
simtestutil.NewAppOptionsWithFlagHome(t.TempDir()),
|
||||
EVMAppOptions,
|
||||
)
|
||||
return makeEncodingConfig(tempApp)
|
||||
}
|
||||
|
||||
func makeEncodingConfig(tempApp *SonrApp) params.EncodingConfig {
|
||||
// makeEncodingConfig extracts the encoding configuration from a ChainApp instance.
|
||||
// It returns an EncodingConfig struct containing the interface registry, codec,
|
||||
// transaction config, and amino codec.
|
||||
func makeEncodingConfig(tempApp *ChainApp) params.EncodingConfig {
|
||||
encodingConfig := params.EncodingConfig{
|
||||
InterfaceRegistry: tempApp.InterfaceRegistry(),
|
||||
Codec: tempApp.AppCodec(),
|
||||
@@ -36,3 +48,24 @@ func makeEncodingConfig(tempApp *SonrApp) params.EncodingConfig {
|
||||
}
|
||||
return encodingConfig
|
||||
}
|
||||
|
||||
// GetInterfaceRegistry creates and returns a new interface registry with proper
|
||||
// address codecs configured. This registry is used for protobuf Any type
|
||||
// registration and message routing.
|
||||
func GetInterfaceRegistry() types.InterfaceRegistry {
|
||||
interfaceRegistry, err := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{
|
||||
ProtoFiles: proto.HybridResolver,
|
||||
SigningOptions: signing.Options{
|
||||
AddressCodec: address.Bech32Codec{
|
||||
Bech32Prefix: sdk.GetConfig().GetBech32AccountAddrPrefix(),
|
||||
},
|
||||
ValidatorAddressCodec: address.Bech32Codec{
|
||||
Bech32Prefix: sdk.GetConfig().GetBech32ValidatorAddrPrefix(),
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return interfaceRegistry
|
||||
}
|
||||
|
||||
Regular → Executable
+118
-82
@@ -1,3 +1,4 @@
|
||||
// Package app provides export functionality for the Sonr blockchain application.
|
||||
package app
|
||||
|
||||
import (
|
||||
@@ -5,8 +6,10 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
storetypes "cosmossdk.io/store/types"
|
||||
cmtproto "github.com/cometbft/cometbft/proto/tendermint/types"
|
||||
|
||||
storetypes "cosmossdk.io/store/types"
|
||||
|
||||
servertypes "github.com/cosmos/cosmos-sdk/server/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
|
||||
@@ -15,8 +18,19 @@ import (
|
||||
)
|
||||
|
||||
// ExportAppStateAndValidators exports the state of the application for a genesis
|
||||
// file.
|
||||
func (app *SonrApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedAddrs, modulesToExport []string) (servertypes.ExportedApp, error) {
|
||||
// file. It can export at the current height or prepare the state for a zero-height
|
||||
// genesis, which is useful for chain upgrades or migrations.
|
||||
//
|
||||
// Parameters:
|
||||
// - forZeroHeight: If true, prepares the state for zero-height genesis
|
||||
// - jailAllowedAddrs: List of validator addresses allowed to remain unjailed
|
||||
// - modulesToExport: Specific modules to export (exports all if empty)
|
||||
//
|
||||
// Returns the exported application state and validator set.
|
||||
func (app *ChainApp) ExportAppStateAndValidators(
|
||||
forZeroHeight bool,
|
||||
jailAllowedAddrs, modulesToExport []string,
|
||||
) (servertypes.ExportedApp, error) {
|
||||
// as if they could withdraw from the start of the next block
|
||||
ctx := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
|
||||
|
||||
@@ -39,22 +53,39 @@ func (app *SonrApp) ExportAppStateAndValidators(forZeroHeight bool, jailAllowedA
|
||||
}
|
||||
|
||||
validators, err := staking.WriteValidators(ctx, app.StakingKeeper)
|
||||
if err != nil {
|
||||
return servertypes.ExportedApp{}, err
|
||||
}
|
||||
|
||||
return servertypes.ExportedApp{
|
||||
AppState: appState,
|
||||
Validators: validators,
|
||||
Height: height,
|
||||
ConsensusParams: app.GetConsensusParams(ctx),
|
||||
}, err
|
||||
}, nil
|
||||
}
|
||||
|
||||
// prepare for fresh start at zero height
|
||||
// NOTE zero height genesis is a temporary feature which will be deprecated
|
||||
// prepForZeroHeightGenesis prepares the application state for a fresh start at zero height.
|
||||
// This includes withdrawing all rewards, resetting validator states, and optionally
|
||||
// jailing validators not in the allowed list. This function modifies the state to ensure
|
||||
// a clean genesis export.
|
||||
//
|
||||
// in favor of export at a block height
|
||||
func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) {
|
||||
// NOTE: Zero height genesis is a temporary feature which will be deprecated
|
||||
// in favor of export at a block height.
|
||||
func (app *ChainApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) {
|
||||
var err error
|
||||
|
||||
// Just to be safe, assert the invariants on current state.
|
||||
app.CrisisKeeper.AssertInvariants(ctx)
|
||||
|
||||
// set context height to zero
|
||||
height := ctx.BlockHeight()
|
||||
ctx = ctx.WithBlockHeight(0)
|
||||
|
||||
applyAllowedAddrs := len(jailAllowedAddrs) > 0
|
||||
|
||||
// check if there is a allowed address list
|
||||
|
||||
allowedAddrsMap := make(map[string]bool)
|
||||
|
||||
for _, addr := range jailAllowedAddrs {
|
||||
@@ -65,20 +96,20 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
|
||||
allowedAddrsMap[addr] = true
|
||||
}
|
||||
|
||||
// Just to be safe, assert the invariants on current state.
|
||||
app.CrisisKeeper.AssertInvariants(ctx)
|
||||
|
||||
// Handle fee distribution state.
|
||||
|
||||
// withdraw all validator commission
|
||||
err := app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
|
||||
valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, valBz)
|
||||
return false
|
||||
})
|
||||
err = app.StakingKeeper.IterateValidators(
|
||||
ctx,
|
||||
func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
|
||||
valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
_, _ = app.DistrKeeper.WithdrawValidatorCommission(ctx, valBz)
|
||||
return false
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -90,9 +121,9 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
|
||||
}
|
||||
|
||||
for _, delegation := range dels {
|
||||
valAddr, errB := sdk.ValAddressFromBech32(delegation.ValidatorAddress)
|
||||
if errB != nil {
|
||||
panic(errB)
|
||||
valAddr, err := sdk.ValAddressFromBech32(delegation.ValidatorAddress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
delAddr := sdk.MustAccAddressFromBech32(delegation.DelegatorAddress)
|
||||
@@ -108,55 +139,54 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
|
||||
// clear validator historical rewards
|
||||
app.DistrKeeper.DeleteAllValidatorHistoricalRewards(ctx)
|
||||
|
||||
// set context height to zero
|
||||
height := ctx.BlockHeight()
|
||||
ctx = ctx.WithBlockHeight(0)
|
||||
|
||||
// reinitialize all validators
|
||||
err = app.StakingKeeper.IterateValidators(ctx, func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
|
||||
valBz, errB := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator())
|
||||
if errB != nil {
|
||||
panic(errB)
|
||||
}
|
||||
// donate any unwithdrawn outstanding reward fraction tokens to the community pool
|
||||
scraps, errC := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz)
|
||||
if errC != nil {
|
||||
panic(errC)
|
||||
}
|
||||
feePool, errD := app.DistrKeeper.FeePool.Get(ctx)
|
||||
if errD != nil {
|
||||
panic(errD)
|
||||
}
|
||||
feePool.CommunityPool = feePool.CommunityPool.Add(scraps...)
|
||||
if errE := app.DistrKeeper.FeePool.Set(ctx, feePool); errE != nil {
|
||||
panic(errE)
|
||||
}
|
||||
err = app.StakingKeeper.IterateValidators(
|
||||
ctx,
|
||||
func(_ int64, val stakingtypes.ValidatorI) (stop bool) {
|
||||
valBz, err := app.StakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// donate any unwithdrawn outstanding reward fraction tokens to the community pool
|
||||
scraps, err := app.DistrKeeper.GetValidatorOutstandingRewardsCoins(ctx, valBz)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
feePool, err := app.DistrKeeper.FeePool.Get(ctx)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
feePool.CommunityPool = feePool.CommunityPool.Add(scraps...)
|
||||
if err := app.DistrKeeper.FeePool.Set(ctx, feePool); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if errF := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, valBz); errF != nil {
|
||||
panic(errF)
|
||||
}
|
||||
return false
|
||||
})
|
||||
if err := app.DistrKeeper.Hooks().AfterValidatorCreated(ctx, valBz); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return false
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// reinitialize all delegations
|
||||
for _, del := range dels {
|
||||
valAddr, errcc := sdk.ValAddressFromBech32(del.ValidatorAddress)
|
||||
if errcc != nil {
|
||||
panic(errcc)
|
||||
valAddr, err := sdk.ValAddressFromBech32(del.ValidatorAddress)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
delAddr := sdk.MustAccAddressFromBech32(del.DelegatorAddress)
|
||||
|
||||
if errcb := app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr); errcb != nil {
|
||||
if err := app.DistrKeeper.Hooks().BeforeDelegationCreated(ctx, delAddr, valAddr); err != nil {
|
||||
// never called as BeforeDelegationCreated always returns nil
|
||||
panic(fmt.Errorf("error while incrementing period: %w", errcb))
|
||||
panic(fmt.Errorf("error while incrementing period: %w", err))
|
||||
}
|
||||
|
||||
if errcd := app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr); errcd != nil {
|
||||
if err := app.DistrKeeper.Hooks().AfterDelegationModified(ctx, delAddr, valAddr); err != nil {
|
||||
// never called as AfterDelegationModified always returns nil
|
||||
panic(fmt.Errorf("error while creating a new delegation period record: %w", errcd))
|
||||
panic(fmt.Errorf("error while creating a new delegation period record: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,31 +196,37 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
|
||||
// Handle staking state.
|
||||
|
||||
// iterate through redelegations, reset creation height
|
||||
err = app.StakingKeeper.IterateRedelegations(ctx, func(_ int64, red stakingtypes.Redelegation) (stop bool) {
|
||||
for i := range red.Entries {
|
||||
red.Entries[i].CreationHeight = 0
|
||||
}
|
||||
err = app.StakingKeeper.SetRedelegation(ctx, red)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return false
|
||||
})
|
||||
err = app.StakingKeeper.IterateRedelegations(
|
||||
ctx,
|
||||
func(_ int64, red stakingtypes.Redelegation) (stop bool) {
|
||||
for i := range red.Entries {
|
||||
red.Entries[i].CreationHeight = 0
|
||||
}
|
||||
err = app.StakingKeeper.SetRedelegation(ctx, red)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return false
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// iterate through unbonding delegations, reset creation height
|
||||
err = app.StakingKeeper.IterateUnbondingDelegations(ctx, func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) {
|
||||
for i := range ubd.Entries {
|
||||
ubd.Entries[i].CreationHeight = 0
|
||||
}
|
||||
err = app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return false
|
||||
})
|
||||
err = app.StakingKeeper.IterateUnbondingDelegations(
|
||||
ctx,
|
||||
func(_ int64, ubd stakingtypes.UnbondingDelegation) (stop bool) {
|
||||
for i := range ubd.Entries {
|
||||
ubd.Entries[i].CreationHeight = 0
|
||||
}
|
||||
err = app.StakingKeeper.SetUnbondingDelegation(ctx, ubd)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return false
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
@@ -202,8 +238,8 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
|
||||
|
||||
for ; iter.Valid(); iter.Next() {
|
||||
addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key()))
|
||||
validator, errr := app.StakingKeeper.GetValidator(ctx, addr)
|
||||
if errr != nil {
|
||||
validator, err := app.StakingKeeper.GetValidator(ctx, addr)
|
||||
if err != nil {
|
||||
panic("expected validator, not found")
|
||||
}
|
||||
|
||||
@@ -218,8 +254,8 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
|
||||
}
|
||||
}
|
||||
|
||||
if erra := iter.Close(); erra != nil {
|
||||
app.Logger().Error("error while closing the key-value store reverse prefix iterator: ", erra)
|
||||
if err := iter.Close(); err != nil {
|
||||
app.Logger().Error("error while closing the key-value store reverse prefix iterator: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -235,8 +271,8 @@ func (app *SonrApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs [
|
||||
ctx,
|
||||
func(addr sdk.ConsAddress, info slashingtypes.ValidatorSigningInfo) (stop bool) {
|
||||
info.StartHeight = 0
|
||||
if errb := app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info); errb != nil {
|
||||
panic(errb)
|
||||
if err := app.SlashingKeeper.SetValidatorSigningInfo(ctx, addr, info); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return false
|
||||
},
|
||||
|
||||
Regular → Executable
+8
-7
@@ -1,14 +1,15 @@
|
||||
// Package app provides genesis state management for the Sonr blockchain application.
|
||||
package app
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// GenesisState of the blockchain is represented here as a map of raw json
|
||||
// messages key'd by a identifier string.
|
||||
// The identifier is used to determine which module genesis information belongs
|
||||
// to so it may be appropriately routed during init chain.
|
||||
// Within this application default genesis information is retrieved from
|
||||
// the ModuleBasicManager which populates json from each BasicModule
|
||||
// object provided to it during init.
|
||||
// GenesisState represents the initial state of the blockchain as a map of raw JSON
|
||||
// messages keyed by module identifier strings. Each module's genesis state is stored
|
||||
// as raw JSON to allow flexible initialization during chain setup.
|
||||
//
|
||||
// The identifier is used to route genesis information to the appropriate module
|
||||
// during the init chain process. Default genesis information is populated by
|
||||
// the ModuleBasicManager from each registered BasicModule.
|
||||
type GenesisState map[string]json.RawMessage
|
||||
|
||||
Regular → Executable
+7
-3
@@ -1,11 +1,12 @@
|
||||
/*
|
||||
Package params defines the simulation parameters in the gaia.
|
||||
Package params defines the simulation parameters and encoding configuration
|
||||
for the Sonr blockchain application.
|
||||
|
||||
It contains the default weights used for each transaction used on the module's
|
||||
simulation. These weights define the chance for a transaction to be simulated at
|
||||
any gived operation.
|
||||
any given operation.
|
||||
|
||||
You can repace the default values for the weights by providing a params.json
|
||||
You can replace the default values for the weights by providing a params.json
|
||||
file with the weights defined for each of the transaction operations:
|
||||
|
||||
{
|
||||
@@ -15,5 +16,8 @@ file with the weights defined for each of the transaction operations:
|
||||
|
||||
In the example above, the `MsgSend` has 60% chance to be simulated, while the
|
||||
`MsgDelegate` will always be simulated.
|
||||
|
||||
The package also provides encoding configuration types used throughout the
|
||||
application for consistent codec usage.
|
||||
*/
|
||||
package params
|
||||
|
||||
Regular → Executable
+2
@@ -8,6 +8,8 @@ import (
|
||||
|
||||
// EncodingConfig specifies the concrete encoding types to use for a given app.
|
||||
// This is provided for compatibility between protobuf and amino implementations.
|
||||
// It encapsulates all the required components for encoding/decoding transactions
|
||||
// and other data structures in the blockchain.
|
||||
type EncodingConfig struct {
|
||||
InterfaceRegistry types.InterfaceRegistry
|
||||
Codec codec.Codec
|
||||
|
||||
Regular → Executable
+3
-1
@@ -12,7 +12,9 @@ import (
|
||||
"github.com/cosmos/cosmos-sdk/x/auth/tx"
|
||||
)
|
||||
|
||||
// MakeEncodingConfig creates an EncodingConfig for an amino based test configuration.
|
||||
// MakeEncodingConfig creates a default EncodingConfig with standard Cosmos SDK
|
||||
// encoding settings. It sets up the interface registry with proper address codecs,
|
||||
// creates the protobuf codec, and configures transaction handling with default sign modes.
|
||||
func MakeEncodingConfig() EncodingConfig {
|
||||
amino := codec.NewLegacyAmino()
|
||||
interfaceRegistry, err := types.NewInterfaceRegistryWithOptions(types.InterfaceRegistryOptions{
|
||||
|
||||
Executable
+127
@@ -0,0 +1,127 @@
|
||||
// Package app provides EVM precompiled contracts configuration for the Sonr blockchain.
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"maps"
|
||||
|
||||
evidencekeeper "cosmossdk.io/x/evidence/keeper"
|
||||
authzkeeper "github.com/cosmos/cosmos-sdk/x/authz/keeper"
|
||||
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
|
||||
distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper"
|
||||
govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"
|
||||
slashingkeeper "github.com/cosmos/cosmos-sdk/x/slashing/keeper"
|
||||
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
||||
bankprecompile "github.com/cosmos/evm/precompiles/bank"
|
||||
"github.com/cosmos/evm/precompiles/bech32"
|
||||
distprecompile "github.com/cosmos/evm/precompiles/distribution"
|
||||
evidenceprecompile "github.com/cosmos/evm/precompiles/evidence"
|
||||
govprecompile "github.com/cosmos/evm/precompiles/gov"
|
||||
ics20precompile "github.com/cosmos/evm/precompiles/ics20"
|
||||
"github.com/cosmos/evm/precompiles/p256"
|
||||
slashingprecompile "github.com/cosmos/evm/precompiles/slashing"
|
||||
stakingprecompile "github.com/cosmos/evm/precompiles/staking"
|
||||
erc20Keeper "github.com/cosmos/evm/x/erc20/keeper"
|
||||
transferkeeper "github.com/cosmos/evm/x/ibc/transfer/keeper"
|
||||
"github.com/cosmos/evm/x/vm/core/vm"
|
||||
evmkeeper "github.com/cosmos/evm/x/vm/keeper"
|
||||
channelkeeper "github.com/cosmos/ibc-go/v8/modules/core/04-channel/keeper"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
)
|
||||
|
||||
// bech32PrecompileBaseGas defines the base gas cost for bech32 address conversion operations.
|
||||
const bech32PrecompileBaseGas = 6_000
|
||||
|
||||
// NewAvailableStaticPrecompiles returns the list of all available static precompiled contracts from EVM.
|
||||
// These precompiles provide native Cosmos SDK functionality accessible from EVM contracts,
|
||||
// including staking, distribution, governance, bank transfers, IBC transfers, and more.
|
||||
//
|
||||
// The function initializes both stateless precompiles (bech32, p256) and stateful precompiles
|
||||
// that interact with Cosmos SDK modules.
|
||||
//
|
||||
// NOTE: this should only be used during initialization of the Keeper.
|
||||
func NewAvailableStaticPrecompiles(
|
||||
stakingKeeper stakingkeeper.Keeper,
|
||||
distributionKeeper distributionkeeper.Keeper,
|
||||
bankKeeper bankkeeper.Keeper,
|
||||
erc20Keeper erc20Keeper.Keeper,
|
||||
authzKeeper authzkeeper.Keeper,
|
||||
transferKeeper transferkeeper.Keeper,
|
||||
channelKeeper channelkeeper.Keeper,
|
||||
evmKeeper *evmkeeper.Keeper,
|
||||
govKeeper govkeeper.Keeper,
|
||||
slashingKeeper slashingkeeper.Keeper,
|
||||
evidenceKeeper evidencekeeper.Keeper,
|
||||
) map[common.Address]vm.PrecompiledContract {
|
||||
// Clone the mapping from the latest EVM fork.
|
||||
precompiles := maps.Clone(vm.PrecompiledContractsBerlin)
|
||||
|
||||
// secp256r1 precompile as per EIP-7212
|
||||
p256Precompile := &p256.Precompile{}
|
||||
|
||||
bech32Precompile, err := bech32.NewPrecompile(bech32PrecompileBaseGas)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to instantiate bech32 precompile: %w", err))
|
||||
}
|
||||
|
||||
stakingPrecompile, err := stakingprecompile.NewPrecompile(stakingKeeper, authzKeeper)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to instantiate staking precompile: %w", err))
|
||||
}
|
||||
|
||||
distributionPrecompile, err := distprecompile.NewPrecompile(
|
||||
distributionKeeper,
|
||||
stakingKeeper,
|
||||
authzKeeper,
|
||||
evmKeeper,
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to instantiate distribution precompile: %w", err))
|
||||
}
|
||||
|
||||
ibcTransferPrecompile, err := ics20precompile.NewPrecompile(
|
||||
stakingKeeper,
|
||||
transferKeeper,
|
||||
channelKeeper,
|
||||
authzKeeper,
|
||||
evmKeeper,
|
||||
)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to instantiate ICS20 precompile: %w", err))
|
||||
}
|
||||
|
||||
bankPrecompile, err := bankprecompile.NewPrecompile(bankKeeper, erc20Keeper)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to instantiate bank precompile: %w", err))
|
||||
}
|
||||
|
||||
govPrecompile, err := govprecompile.NewPrecompile(govKeeper, authzKeeper)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to instantiate gov precompile: %w", err))
|
||||
}
|
||||
|
||||
slashingPrecompile, err := slashingprecompile.NewPrecompile(slashingKeeper, authzKeeper)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to instantiate slashing precompile: %w", err))
|
||||
}
|
||||
|
||||
evidencePrecompile, err := evidenceprecompile.NewPrecompile(evidenceKeeper, authzKeeper)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("failed to instantiate evidence precompile: %w", err))
|
||||
}
|
||||
|
||||
// Stateless precompiles
|
||||
precompiles[bech32Precompile.Address()] = bech32Precompile
|
||||
precompiles[p256Precompile.Address()] = p256Precompile
|
||||
|
||||
// Stateful precompiles
|
||||
precompiles[stakingPrecompile.Address()] = stakingPrecompile
|
||||
precompiles[distributionPrecompile.Address()] = distributionPrecompile
|
||||
precompiles[ibcTransferPrecompile.Address()] = ibcTransferPrecompile
|
||||
precompiles[bankPrecompile.Address()] = bankPrecompile
|
||||
precompiles[govPrecompile.Address()] = govPrecompile
|
||||
precompiles[slashingPrecompile.Address()] = slashingPrecompile
|
||||
precompiles[evidencePrecompile.Address()] = evidencePrecompile
|
||||
|
||||
return precompiles
|
||||
}
|
||||
Regular → Executable
+87
-21
@@ -82,7 +82,10 @@ func TestFullAppSimulation(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAppImportExport(t *testing.T) {
|
||||
config, db, appOptions, app := setupSimulationApp(t, "skipping application import/export simulation")
|
||||
config, db, appOptions, app := setupSimulationApp(
|
||||
t,
|
||||
"skipping application import/export simulation",
|
||||
)
|
||||
|
||||
// Run randomized simulation
|
||||
_, simParams, simErr := simulation.SimulateFromSeed(
|
||||
@@ -113,7 +116,13 @@ func TestAppImportExport(t *testing.T) {
|
||||
|
||||
t.Log("importing genesis...\n")
|
||||
|
||||
newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
|
||||
newDB, newDir, _, _, err := simtestutil.SetupSimulation(
|
||||
config,
|
||||
"leveldb-app-sim-2",
|
||||
"Simulation-2",
|
||||
simcli.FlagVerboseValue,
|
||||
simcli.FlagEnabledValue,
|
||||
)
|
||||
require.NoError(t, err, "simulation setup failed")
|
||||
|
||||
defer func() {
|
||||
@@ -121,7 +130,9 @@ func TestAppImportExport(t *testing.T) {
|
||||
require.NoError(t, os.RemoveAll(newDir))
|
||||
}()
|
||||
|
||||
newApp := NewChainApp(log.NewNopLogger(), newDB, nil, true, appOptions, nil, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
|
||||
newApp := NewChainApp(log.NewNopLogger(), newDB, nil, true, appOptions,
|
||||
EVMAppOptions,
|
||||
fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
|
||||
|
||||
initReq := &abci.RequestInitChain{
|
||||
AppStateBytes: exported.AppState,
|
||||
@@ -130,7 +141,6 @@ func TestAppImportExport(t *testing.T) {
|
||||
ctxA := app.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
|
||||
ctxB := newApp.NewContextLegacy(true, cmtproto.Header{Height: app.LastBlockHeight()})
|
||||
_, err = newApp.InitChainer(ctxB, initReq)
|
||||
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "validator set is empty after InitGenesis") {
|
||||
t.Log("Skipping simulation as all validators have been unbonded")
|
||||
@@ -172,17 +182,38 @@ func TestAppImportExport(t *testing.T) {
|
||||
storeB := ctxB.KVStore(appKeyB)
|
||||
|
||||
failedKVAs, failedKVBs := simtestutil.DiffKVStores(storeA, storeB, skipPrefixes[keyName])
|
||||
if !assert.Equal(t, len(failedKVAs), len(failedKVBs), "unequal sets of key-values to compare in %q", keyName) {
|
||||
if !assert.Equal(
|
||||
t,
|
||||
len(failedKVAs),
|
||||
len(failedKVBs),
|
||||
"unequal sets of key-values to compare in %q",
|
||||
keyName,
|
||||
) {
|
||||
for _, v := range failedKVBs {
|
||||
t.Logf("store missmatch: %q\n", v)
|
||||
t.Logf("store mismatch: %q\n", v)
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
|
||||
t.Logf("compared %d different key/value pairs between %s and %s\n", len(failedKVAs), appKeyA, appKeyB)
|
||||
if !assert.Equal(t, 0, len(failedKVAs), simtestutil.GetSimulationLog(keyName, app.SimulationManager().StoreDecoders, failedKVAs, failedKVBs)) {
|
||||
t.Logf(
|
||||
"compared %d different key/value pairs between %s and %s\n",
|
||||
len(failedKVAs),
|
||||
appKeyA,
|
||||
appKeyB,
|
||||
)
|
||||
if !assert.Equal(
|
||||
t,
|
||||
0,
|
||||
len(failedKVAs),
|
||||
simtestutil.GetSimulationLog(
|
||||
keyName,
|
||||
app.SimulationManager().StoreDecoders,
|
||||
failedKVAs,
|
||||
failedKVBs,
|
||||
),
|
||||
) {
|
||||
for _, v := range failedKVAs {
|
||||
t.Logf("store missmatch: %q\n", v)
|
||||
t.Logf("store mismatch: %q\n", v)
|
||||
}
|
||||
t.FailNow()
|
||||
}
|
||||
@@ -190,7 +221,10 @@ func TestAppImportExport(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestAppSimulationAfterImport(t *testing.T) {
|
||||
config, db, appOptions, app := setupSimulationApp(t, "skipping application simulation after import")
|
||||
config, db, appOptions, app := setupSimulationApp(
|
||||
t,
|
||||
"skipping application simulation after import",
|
||||
)
|
||||
|
||||
// Run randomized simulation
|
||||
stopEarly, simParams, simErr := simulation.SimulateFromSeed(
|
||||
@@ -226,7 +260,13 @@ func TestAppSimulationAfterImport(t *testing.T) {
|
||||
|
||||
fmt.Printf("importing genesis...\n")
|
||||
|
||||
newDB, newDir, _, _, err := simtestutil.SetupSimulation(config, "leveldb-app-sim-2", "Simulation-2", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
|
||||
newDB, newDir, _, _, err := simtestutil.SetupSimulation(
|
||||
config,
|
||||
"leveldb-app-sim-2",
|
||||
"Simulation-2",
|
||||
simcli.FlagVerboseValue,
|
||||
simcli.FlagEnabledValue,
|
||||
)
|
||||
require.NoError(t, err, "simulation setup failed")
|
||||
|
||||
defer func() {
|
||||
@@ -234,7 +274,9 @@ func TestAppSimulationAfterImport(t *testing.T) {
|
||||
require.NoError(t, os.RemoveAll(newDir))
|
||||
}()
|
||||
|
||||
newApp := NewChainApp(log.NewNopLogger(), newDB, nil, true, appOptions, nil, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
|
||||
newApp := NewChainApp(log.NewNopLogger(), newDB, nil, true, appOptions,
|
||||
EVMAppOptions,
|
||||
fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
|
||||
|
||||
_, err = newApp.InitChain(&abci.RequestInitChain{
|
||||
ChainId: SimAppChainID,
|
||||
@@ -256,11 +298,20 @@ func TestAppSimulationAfterImport(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func setupSimulationApp(t *testing.T, msg string) (simtypes.Config, dbm.DB, simtestutil.AppOptionsMap, *SonrApp) {
|
||||
func setupSimulationApp(
|
||||
t *testing.T,
|
||||
msg string,
|
||||
) (simtypes.Config, dbm.DB, simtestutil.AppOptionsMap, *ChainApp) {
|
||||
config := simcli.NewConfigFromFlags()
|
||||
config.ChainID = SimAppChainID
|
||||
|
||||
db, dir, logger, skip, err := simtestutil.SetupSimulation(config, "leveldb-app-sim", "Simulation", simcli.FlagVerboseValue, simcli.FlagEnabledValue)
|
||||
db, dir, logger, skip, err := simtestutil.SetupSimulation(
|
||||
config,
|
||||
"leveldb-app-sim",
|
||||
"Simulation",
|
||||
simcli.FlagVerboseValue,
|
||||
simcli.FlagEnabledValue,
|
||||
)
|
||||
if skip {
|
||||
t.Skip(msg)
|
||||
}
|
||||
@@ -275,7 +326,9 @@ func setupSimulationApp(t *testing.T, msg string) (simtypes.Config, dbm.DB, simt
|
||||
appOptions[flags.FlagHome] = dir // ensure a unique folder
|
||||
appOptions[server.FlagInvCheckPeriod] = simcli.FlagPeriodValue
|
||||
|
||||
app := NewChainApp(logger, db, nil, true, appOptions, nil, fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
|
||||
app := NewChainApp(logger, db, nil, true, appOptions,
|
||||
EVMAppOptions,
|
||||
fauxMerkleModeOpt, baseapp.SetChainID(SimAppChainID))
|
||||
return config, db, appOptions, app
|
||||
}
|
||||
|
||||
@@ -304,7 +357,7 @@ func TestAppStateDeterminism(t *testing.T) {
|
||||
|
||||
appOptions := viper.New()
|
||||
if FlagEnableStreamingValue {
|
||||
m := make(map[string]interface{})
|
||||
m := make(map[string]any)
|
||||
m["streaming.abci.keys"] = []string{"*"}
|
||||
m["streaming.abci.plugin"] = "abci_v1"
|
||||
m["streaming.abci.stop-node-on-err"] = true
|
||||
@@ -317,7 +370,7 @@ func TestAppStateDeterminism(t *testing.T) {
|
||||
|
||||
for i := 0; i < numSeeds; i++ {
|
||||
config.Seed += int64(i)
|
||||
for j := 0; j < numTimesToRunPerSeed; j++ {
|
||||
for j := range numTimesToRunPerSeed {
|
||||
var logger log.Logger
|
||||
if simcli.FlagVerboseValue {
|
||||
logger = log.NewTestLogger(t)
|
||||
@@ -326,7 +379,9 @@ func TestAppStateDeterminism(t *testing.T) {
|
||||
}
|
||||
|
||||
db := dbm.NewMemDB()
|
||||
app := NewChainApp(logger, db, nil, true, appOptions, nil, interBlockCacheOpt(), baseapp.SetChainID(SimAppChainID))
|
||||
app := NewChainApp(logger, db, nil, true, appOptions,
|
||||
EVMAppOptions,
|
||||
interBlockCacheOpt(), baseapp.SetChainID(SimAppChainID))
|
||||
|
||||
fmt.Printf(
|
||||
"running non-determinism simulation; seed %d: %d/%d, attempt: %d/%d\n",
|
||||
@@ -337,7 +392,11 @@ func TestAppStateDeterminism(t *testing.T) {
|
||||
t,
|
||||
os.Stdout,
|
||||
app.BaseApp,
|
||||
simtestutil.AppStateFn(app.AppCodec(), app.SimulationManager(), app.DefaultGenesis()),
|
||||
simtestutil.AppStateFn(
|
||||
app.AppCodec(),
|
||||
app.SimulationManager(),
|
||||
app.DefaultGenesis(),
|
||||
),
|
||||
simtypes.RandomAccounts, // Replace with own random account function if using keys other than secp256k1
|
||||
simtestutil.SimulationOperations(app, app.AppCodec(), config),
|
||||
BlockedAddresses(),
|
||||
@@ -355,8 +414,15 @@ func TestAppStateDeterminism(t *testing.T) {
|
||||
|
||||
if j != 0 {
|
||||
require.Equal(
|
||||
t, string(appHashList[0]), string(appHashList[j]),
|
||||
"non-determinism in seed %d: %d/%d, attempt: %d/%d\n", config.Seed, i+1, numSeeds, j+1, numTimesToRunPerSeed,
|
||||
t,
|
||||
string(appHashList[0]),
|
||||
string(appHashList[j]),
|
||||
"non-determinism in seed %d: %d/%d, attempt: %d/%d\n",
|
||||
config.Seed,
|
||||
i+1,
|
||||
numSeeds,
|
||||
j+1,
|
||||
numTimesToRunPerSeed,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
+128
-35
@@ -44,6 +44,8 @@ import (
|
||||
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
|
||||
)
|
||||
|
||||
const chainID = "sonrtest_1-1"
|
||||
|
||||
// SetupOptions defines arguments that are passed into `ChainApp` constructor.
|
||||
type SetupOptions struct {
|
||||
Logger log.Logger
|
||||
@@ -56,7 +58,7 @@ func setup(
|
||||
chainID string,
|
||||
withGenesis bool,
|
||||
invCheckPeriod uint,
|
||||
) (*SonrApp, GenesisState) {
|
||||
) (*ChainApp, GenesisState) {
|
||||
db := dbm.NewMemDB()
|
||||
nodeHome := t.TempDir()
|
||||
snapshotDir := filepath.Join(nodeHome, "data", "snapshots")
|
||||
@@ -76,6 +78,7 @@ func setup(
|
||||
nil,
|
||||
true,
|
||||
appOptions,
|
||||
EVMAppOptions,
|
||||
bam.SetChainID(chainID),
|
||||
bam.SetSnapshot(snapshotStore, snapshottypes.SnapshotOptions{KeepRecent: 2}),
|
||||
)
|
||||
@@ -86,7 +89,7 @@ func setup(
|
||||
}
|
||||
|
||||
// NewChainAppWithCustomOptions initializes a new ChainApp with custom options.
|
||||
func NewChainAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOptions) *SonrApp {
|
||||
func NewChainAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOptions) *ChainApp {
|
||||
t.Helper()
|
||||
|
||||
privVal := mock.NewPV()
|
||||
@@ -98,7 +101,12 @@ func NewChainAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOpt
|
||||
|
||||
// generate genesis account
|
||||
senderPrivKey := secp256k1.GenPrivKey()
|
||||
acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0)
|
||||
acc := authtypes.NewBaseAccount(
|
||||
senderPrivKey.PubKey().Address().Bytes(),
|
||||
senderPrivKey.PubKey(),
|
||||
0,
|
||||
0,
|
||||
)
|
||||
balance := banktypes.Balance{
|
||||
Address: acc.GetAddress().String(),
|
||||
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))),
|
||||
@@ -109,9 +117,16 @@ func NewChainAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOpt
|
||||
options.DB,
|
||||
nil, true,
|
||||
options.AppOpts,
|
||||
EVMAppOptions,
|
||||
)
|
||||
genesisState := app.DefaultGenesis()
|
||||
genesisState, err = GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, []authtypes.GenesisAccount{acc}, balance)
|
||||
genesisState, err = GenesisStateWithValSet(
|
||||
app.AppCodec(),
|
||||
genesisState,
|
||||
valSet,
|
||||
[]authtypes.GenesisAccount{acc},
|
||||
balance,
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
if !isCheckTx {
|
||||
@@ -135,7 +150,7 @@ func NewChainAppWithCustomOptions(t *testing.T, isCheckTx bool, options SetupOpt
|
||||
// Setup initializes a new ChainApp. A Nop logger is set in ChainApp.
|
||||
func Setup(
|
||||
t *testing.T,
|
||||
) *SonrApp {
|
||||
) *ChainApp {
|
||||
t.Helper()
|
||||
|
||||
privVal := mock.NewPV()
|
||||
@@ -148,12 +163,17 @@ func Setup(
|
||||
|
||||
// generate genesis account
|
||||
senderPrivKey := secp256k1.GenPrivKey()
|
||||
acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0)
|
||||
acc := authtypes.NewBaseAccount(
|
||||
senderPrivKey.PubKey().Address().Bytes(),
|
||||
senderPrivKey.PubKey(),
|
||||
0,
|
||||
0,
|
||||
)
|
||||
balance := banktypes.Balance{
|
||||
Address: acc.GetAddress().String(),
|
||||
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))),
|
||||
}
|
||||
chainID := "testing"
|
||||
|
||||
app := SetupWithGenesisValSet(
|
||||
t,
|
||||
valSet,
|
||||
@@ -175,13 +195,18 @@ func SetupWithGenesisValSet(
|
||||
genAccs []authtypes.GenesisAccount,
|
||||
chainID string,
|
||||
balances ...banktypes.Balance,
|
||||
) *SonrApp {
|
||||
) *ChainApp {
|
||||
t.Helper()
|
||||
|
||||
app, genesisState := setup(
|
||||
t, chainID, true, 5,
|
||||
)
|
||||
genesisState, err := GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, genAccs, balances...)
|
||||
genesisState, err := GenesisStateWithValSet(
|
||||
app.AppCodec(),
|
||||
genesisState,
|
||||
valSet,
|
||||
genAccs,
|
||||
balances...)
|
||||
require.NoError(t, err)
|
||||
|
||||
stateBytes, err := json.MarshalIndent(genesisState, "", " ")
|
||||
@@ -211,14 +236,14 @@ func SetupWithGenesisValSet(
|
||||
}
|
||||
|
||||
// SetupWithEmptyStore set up a chain app instance with empty DB
|
||||
func SetupWithEmptyStore(t testing.TB) *SonrApp {
|
||||
app, _ := setup(t, "testing", false, 0)
|
||||
func SetupWithEmptyStore(t testing.TB) *ChainApp {
|
||||
app, _ := setup(t, chainID, false, 0)
|
||||
return app
|
||||
}
|
||||
|
||||
// GenesisStateWithSingleValidator initializes GenesisState with a single validator and genesis accounts
|
||||
// that also act as delegators.
|
||||
func GenesisStateWithSingleValidator(t *testing.T, app *SonrApp) GenesisState {
|
||||
func GenesisStateWithSingleValidator(t *testing.T, app *ChainApp) GenesisState {
|
||||
t.Helper()
|
||||
|
||||
privVal := mock.NewPV()
|
||||
@@ -231,16 +256,28 @@ func GenesisStateWithSingleValidator(t *testing.T, app *SonrApp) GenesisState {
|
||||
|
||||
// generate genesis account
|
||||
senderPrivKey := secp256k1.GenPrivKey()
|
||||
acc := authtypes.NewBaseAccount(senderPrivKey.PubKey().Address().Bytes(), senderPrivKey.PubKey(), 0, 0)
|
||||
acc := authtypes.NewBaseAccount(
|
||||
senderPrivKey.PubKey().Address().Bytes(),
|
||||
senderPrivKey.PubKey(),
|
||||
0,
|
||||
0,
|
||||
)
|
||||
balances := []banktypes.Balance{
|
||||
{
|
||||
Address: acc.GetAddress().String(),
|
||||
Coins: sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000))),
|
||||
Coins: sdk.NewCoins(
|
||||
sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewInt(100000000000000)),
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
genesisState := app.DefaultGenesis()
|
||||
genesisState, err = GenesisStateWithValSet(app.AppCodec(), genesisState, valSet, []authtypes.GenesisAccount{acc}, balances...)
|
||||
genesisState, err = GenesisStateWithValSet(
|
||||
app.AppCodec(),
|
||||
genesisState,
|
||||
valSet,
|
||||
[]authtypes.GenesisAccount{acc},
|
||||
balances...)
|
||||
require.NoError(t, err)
|
||||
|
||||
return genesisState
|
||||
@@ -248,11 +285,22 @@ func GenesisStateWithSingleValidator(t *testing.T, app *SonrApp) GenesisState {
|
||||
|
||||
// AddTestAddrsIncremental constructs and returns accNum amount of accounts with an
|
||||
// initial balance of accAmt in random order
|
||||
func AddTestAddrsIncremental(app *SonrApp, ctx sdk.Context, accNum int, accAmt sdkmath.Int) []sdk.AccAddress {
|
||||
func AddTestAddrsIncremental(
|
||||
app *ChainApp,
|
||||
ctx sdk.Context,
|
||||
accNum int,
|
||||
accAmt sdkmath.Int,
|
||||
) []sdk.AccAddress {
|
||||
return addTestAddrs(app, ctx, accNum, accAmt, simtestutil.CreateIncrementalAccounts)
|
||||
}
|
||||
|
||||
func addTestAddrs(app *SonrApp, ctx sdk.Context, accNum int, accAmt sdkmath.Int, strategy simtestutil.GenerateAccountStrategy) []sdk.AccAddress {
|
||||
func addTestAddrs(
|
||||
app *ChainApp,
|
||||
ctx sdk.Context,
|
||||
accNum int,
|
||||
accAmt sdkmath.Int,
|
||||
strategy simtestutil.GenerateAccountStrategy,
|
||||
) []sdk.AccAddress {
|
||||
testAddrs := strategy(accNum)
|
||||
bondDenom, err := app.StakingKeeper.BondDenom(ctx)
|
||||
if err != nil {
|
||||
@@ -268,7 +316,7 @@ func addTestAddrs(app *SonrApp, ctx sdk.Context, accNum int, accAmt sdkmath.Int,
|
||||
return testAddrs
|
||||
}
|
||||
|
||||
func initAccountWithCoins(app *SonrApp, ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) {
|
||||
func initAccountWithCoins(app *ChainApp, ctx sdk.Context, addr sdk.AccAddress, coins sdk.Coins) {
|
||||
err := app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, coins)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
@@ -288,11 +336,19 @@ func NewTestNetworkFixture() network.TestFixture {
|
||||
}
|
||||
defer os.RemoveAll(dir)
|
||||
|
||||
app := NewChainApp(log.NewNopLogger(), dbm.NewMemDB(), nil, true, simtestutil.NewAppOptionsWithFlagHome(dir), nil)
|
||||
app := NewChainApp(
|
||||
log.NewNopLogger(),
|
||||
dbm.NewMemDB(),
|
||||
nil,
|
||||
true,
|
||||
simtestutil.NewAppOptionsWithFlagHome(dir),
|
||||
EVMAppOptions,
|
||||
)
|
||||
appCtr := func(val network.ValidatorI) servertypes.Application {
|
||||
return NewChainApp(
|
||||
val.GetCtx().Logger, dbm.NewMemDB(), nil, true,
|
||||
simtestutil.NewAppOptionsWithFlagHome(val.GetCtx().Config.RootDir),
|
||||
EVMAppOptions,
|
||||
bam.SetPruning(pruningtypes.NewPruningOptionsFromString(val.GetAppConfig().Pruning)),
|
||||
bam.SetMinGasPrices(val.GetAppConfig().MinGasPrices),
|
||||
bam.SetChainID(val.GetCtx().Viper.GetString(flags.FlagChainID)),
|
||||
@@ -312,7 +368,17 @@ func NewTestNetworkFixture() network.TestFixture {
|
||||
}
|
||||
|
||||
// SignAndDeliverWithoutCommit signs and delivers a transaction. No commit
|
||||
func SignAndDeliverWithoutCommit(t *testing.T, txCfg client.TxConfig, app *bam.BaseApp, msgs []sdk.Msg, fees sdk.Coins, chainID string, accNums, accSeqs []uint64, blockTime time.Time, priv ...cryptotypes.PrivKey) (*abci.ResponseFinalizeBlock, error) {
|
||||
func SignAndDeliverWithoutCommit(
|
||||
t *testing.T,
|
||||
txCfg client.TxConfig,
|
||||
app *bam.BaseApp,
|
||||
msgs []sdk.Msg,
|
||||
fees sdk.Coins,
|
||||
chainID string,
|
||||
accNums, accSeqs []uint64,
|
||||
blockTime time.Time,
|
||||
priv ...cryptotypes.PrivKey,
|
||||
) (*abci.ResponseFinalizeBlock, error) {
|
||||
tx, err := simtestutil.GenSignedMockTx(
|
||||
rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
txCfg,
|
||||
@@ -366,25 +432,40 @@ func GenesisStateWithValSet(
|
||||
}
|
||||
|
||||
validator := stakingtypes.Validator{
|
||||
OperatorAddress: sdk.ValAddress(val.Address).String(),
|
||||
ConsensusPubkey: pkAny,
|
||||
Jailed: false,
|
||||
Status: stakingtypes.Bonded,
|
||||
Tokens: bondAmt,
|
||||
DelegatorShares: sdkmath.LegacyOneDec(),
|
||||
Description: stakingtypes.Description{},
|
||||
UnbondingHeight: int64(0),
|
||||
UnbondingTime: time.Unix(0, 0).UTC(),
|
||||
Commission: stakingtypes.NewCommission(sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec(), sdkmath.LegacyZeroDec()),
|
||||
OperatorAddress: sdk.ValAddress(val.Address).String(),
|
||||
ConsensusPubkey: pkAny,
|
||||
Jailed: false,
|
||||
Status: stakingtypes.Bonded,
|
||||
Tokens: bondAmt,
|
||||
DelegatorShares: sdkmath.LegacyOneDec(),
|
||||
Description: stakingtypes.Description{},
|
||||
UnbondingHeight: int64(0),
|
||||
UnbondingTime: time.Unix(0, 0).UTC(),
|
||||
Commission: stakingtypes.NewCommission(
|
||||
sdkmath.LegacyZeroDec(),
|
||||
sdkmath.LegacyZeroDec(),
|
||||
sdkmath.LegacyZeroDec(),
|
||||
),
|
||||
MinSelfDelegation: sdkmath.ZeroInt(),
|
||||
}
|
||||
validators = append(validators, validator)
|
||||
delegations = append(delegations, stakingtypes.NewDelegation(genAccs[0].GetAddress().String(), sdk.ValAddress(val.Address).String(), sdkmath.LegacyOneDec()))
|
||||
|
||||
delegations = append(
|
||||
delegations,
|
||||
stakingtypes.NewDelegation(
|
||||
genAccs[0].GetAddress().String(),
|
||||
sdk.ValAddress(val.Address).String(),
|
||||
sdkmath.LegacyOneDec(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
// set validators and delegations
|
||||
stakingGenesis := stakingtypes.NewGenesisState(stakingtypes.DefaultParams(), validators, delegations)
|
||||
stakingGenesis := stakingtypes.NewGenesisState(
|
||||
stakingtypes.DefaultParams(),
|
||||
validators,
|
||||
delegations,
|
||||
)
|
||||
genesisState[stakingtypes.ModuleName] = codec.MustMarshalJSON(stakingGenesis)
|
||||
|
||||
signingInfos := make([]slashingtypes.SigningInfo, len(valSet.Validators))
|
||||
@@ -394,13 +475,19 @@ func GenesisStateWithValSet(
|
||||
ValidatorSigningInfo: slashingtypes.ValidatorSigningInfo{},
|
||||
}
|
||||
}
|
||||
slashingGenesis := slashingtypes.NewGenesisState(slashingtypes.DefaultParams(), signingInfos, nil)
|
||||
slashingGenesis := slashingtypes.NewGenesisState(
|
||||
slashingtypes.DefaultParams(),
|
||||
signingInfos,
|
||||
nil,
|
||||
)
|
||||
genesisState[slashingtypes.ModuleName] = codec.MustMarshalJSON(slashingGenesis)
|
||||
|
||||
// add bonded amount to bonded pool module account
|
||||
balances = append(balances, banktypes.Balance{
|
||||
Address: authtypes.NewModuleAddress(stakingtypes.BondedPoolName).String(),
|
||||
Coins: sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, bondAmt.MulRaw(int64(len(valSet.Validators))))},
|
||||
Coins: sdk.Coins{
|
||||
sdk.NewCoin(sdk.DefaultBondDenom, bondAmt.MulRaw(int64(len(valSet.Validators)))),
|
||||
},
|
||||
})
|
||||
|
||||
totalSupply := sdk.NewCoins()
|
||||
@@ -410,7 +497,13 @@ func GenesisStateWithValSet(
|
||||
}
|
||||
|
||||
// update total supply
|
||||
bankGenesis := banktypes.NewGenesisState(banktypes.DefaultGenesisState().Params, balances, totalSupply, []banktypes.Metadata{}, []banktypes.SendEnabled{})
|
||||
bankGenesis := banktypes.NewGenesisState(
|
||||
banktypes.DefaultGenesisState().Params,
|
||||
balances,
|
||||
totalSupply,
|
||||
[]banktypes.Metadata{},
|
||||
[]banktypes.SendEnabled{},
|
||||
)
|
||||
genesisState[banktypes.ModuleName] = codec.MustMarshalJSON(bankGenesis)
|
||||
|
||||
return genesisState, nil
|
||||
|
||||
Regular → Executable
+6
-6
@@ -10,26 +10,26 @@ import (
|
||||
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
|
||||
)
|
||||
|
||||
func (app *SonrApp) GetIBCKeeper() *ibckeeper.Keeper {
|
||||
func (app *ChainApp) GetIBCKeeper() *ibckeeper.Keeper {
|
||||
return app.IBCKeeper
|
||||
}
|
||||
|
||||
func (app *SonrApp) GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper {
|
||||
func (app *ChainApp) GetScopedIBCKeeper() capabilitykeeper.ScopedKeeper {
|
||||
return app.ScopedIBCKeeper
|
||||
}
|
||||
|
||||
func (app *SonrApp) GetBaseApp() *baseapp.BaseApp {
|
||||
func (app *ChainApp) GetBaseApp() *baseapp.BaseApp {
|
||||
return app.BaseApp
|
||||
}
|
||||
|
||||
func (app *SonrApp) GetBankKeeper() bankkeeper.Keeper {
|
||||
func (app *ChainApp) GetBankKeeper() bankkeeper.Keeper {
|
||||
return app.BankKeeper
|
||||
}
|
||||
|
||||
func (app *SonrApp) GetStakingKeeper() *stakingkeeper.Keeper {
|
||||
func (app *ChainApp) GetStakingKeeper() *stakingkeeper.Keeper {
|
||||
return app.StakingKeeper
|
||||
}
|
||||
|
||||
func (app *SonrApp) GetAccountKeeper() authkeeper.AccountKeeper {
|
||||
func (app *ChainApp) GetAccountKeeper() authkeeper.AccountKeeper {
|
||||
return app.AccountKeeper
|
||||
}
|
||||
|
||||
Executable
+24
@@ -0,0 +1,24 @@
|
||||
// Package app provides ERC20 token pair configuration for the Sonr blockchain.
|
||||
package app
|
||||
|
||||
import erc20types "github.com/cosmos/evm/x/erc20/types"
|
||||
|
||||
// WSonrTokenContractMainnet is the WrappedToken contract address for mainnet.
|
||||
// This address represents the ERC20 wrapper for the native token.
|
||||
const WSonrTokenContractMainnet = "0xD4949664cD82660AaE99bEdc034a0deA8A0bd517"
|
||||
|
||||
// WSonrTokenContractTestnet is the WrappedToken contract address for testnet.
|
||||
// This address represents the ERC20 wrapper for the native token.
|
||||
const WSonrTokenContractTestnet = "0xD4949664cD82660AaE99bEdc034a0deA8A0bd517"
|
||||
|
||||
// SonrETHTokenPairs creates a slice of token pairs that define the mapping between
|
||||
// native Cosmos SDK coins and their ERC20 representations. This allows for seamless
|
||||
// conversion between the two token standards within the EVM module.
|
||||
var SonrETHTokenPairs = []erc20types.TokenPair{
|
||||
{
|
||||
Erc20Address: WSonrTokenContractTestnet,
|
||||
Denom: BaseDenom,
|
||||
Enabled: true,
|
||||
ContractOwner: erc20types.OWNER_MODULE,
|
||||
},
|
||||
}
|
||||
Executable
+8
@@ -0,0 +1,8 @@
|
||||
// Package app contains tool imports to ensure required dependencies are included
|
||||
// in the module graph even if they're not directly referenced in the code.
|
||||
// This prevents "go mod tidy" from removing necessary indirect dependencies.
|
||||
package app
|
||||
|
||||
import (
|
||||
_ "cosmossdk.io/orm"
|
||||
)
|
||||
Regular → Executable
+14
-8
@@ -1,3 +1,4 @@
|
||||
// Package app provides upgrade handling functionality for the Sonr blockchain.
|
||||
package app
|
||||
|
||||
import (
|
||||
@@ -5,15 +6,19 @@ import (
|
||||
|
||||
upgradetypes "cosmossdk.io/x/upgrade/types"
|
||||
|
||||
"github.com/sonr-io/snrd/app/upgrades"
|
||||
"github.com/sonr-io/snrd/app/upgrades/noop"
|
||||
"github.com/sonr-io/sonr/app/upgrades"
|
||||
"github.com/sonr-io/sonr/app/upgrades/noop"
|
||||
)
|
||||
|
||||
// Upgrades list of chain upgrades
|
||||
// Upgrades contains the list of chain upgrades to be applied.
|
||||
// Each upgrade defines the upgrade name, handler, and store migrations.
|
||||
var Upgrades = []upgrades.Upgrade{}
|
||||
|
||||
// RegisterUpgradeHandlers registers the chain upgrade handlers
|
||||
func (app *SonrApp) RegisterUpgradeHandlers() {
|
||||
// RegisterUpgradeHandlers registers the chain upgrade handlers for all defined upgrades.
|
||||
// It sets up the upgrade handlers with the module manager and configurator,
|
||||
// and configures the store loader for the current upgrade if applicable.
|
||||
// If no upgrades are defined, it registers a no-op upgrade for testing purposes.
|
||||
func (app *ChainApp) RegisterUpgradeHandlers() {
|
||||
// setupLegacyKeyTables(&app.ParamsKeeper)
|
||||
if len(Upgrades) == 0 {
|
||||
// always have a unique upgrade registered for the current version to test in system tests
|
||||
@@ -22,7 +27,6 @@ func (app *SonrApp) RegisterUpgradeHandlers() {
|
||||
|
||||
keepers := upgrades.AppKeepers{
|
||||
AccountKeeper: &app.AccountKeeper,
|
||||
DidKeeper: &app.DidKeeper,
|
||||
ParamsKeeper: &app.ParamsKeeper,
|
||||
ConsensusParamsKeeper: &app.ConsensusParamsKeeper,
|
||||
CapabilityKeeper: app.CapabilityKeeper,
|
||||
@@ -30,7 +34,7 @@ func (app *SonrApp) RegisterUpgradeHandlers() {
|
||||
Codec: app.appCodec,
|
||||
GetStoreKey: app.GetKey,
|
||||
}
|
||||
app.GetStoreKeys()
|
||||
|
||||
// register all upgrade handlers
|
||||
for _, upgrade := range Upgrades {
|
||||
app.UpgradeKeeper.SetUpgradeHandler(
|
||||
@@ -55,7 +59,9 @@ func (app *SonrApp) RegisterUpgradeHandlers() {
|
||||
// register store loader for current upgrade
|
||||
for _, upgrade := range Upgrades {
|
||||
if upgradeInfo.Name == upgrade.UpgradeName {
|
||||
app.SetStoreLoader(upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &upgrade.StoreUpgrades)) // nolint:gosec
|
||||
app.SetStoreLoader(
|
||||
upgradetypes.UpgradeStoreLoader(upgradeInfo.Height, &upgrade.StoreUpgrades),
|
||||
) // nolint:gosec
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
Regular → Executable
+9
-2
@@ -1,3 +1,5 @@
|
||||
// Package noop provides a no-operation upgrade handler for testing and development.
|
||||
// This upgrade performs no actual state migrations but runs module migrations.
|
||||
package noop
|
||||
|
||||
import (
|
||||
@@ -8,10 +10,12 @@ import (
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/types/module"
|
||||
|
||||
"github.com/sonr-io/snrd/app/upgrades"
|
||||
"github.com/sonr-io/sonr/app/upgrades"
|
||||
)
|
||||
|
||||
// NewUpgrade constructor
|
||||
// NewUpgrade creates a new no-operation upgrade with the specified semantic version.
|
||||
// This upgrade is typically used for testing upgrade mechanisms without performing
|
||||
// actual state changes beyond standard module migrations.
|
||||
func NewUpgrade(semver string) upgrades.Upgrade {
|
||||
return upgrades.Upgrade{
|
||||
UpgradeName: semver,
|
||||
@@ -23,6 +27,9 @@ func NewUpgrade(semver string) upgrades.Upgrade {
|
||||
}
|
||||
}
|
||||
|
||||
// CreateUpgradeHandler creates an upgrade handler that performs only module migrations.
|
||||
// It does not perform any custom upgrade logic, making it suitable for minor version
|
||||
// upgrades that only require standard module migrations.
|
||||
func CreateUpgradeHandler(
|
||||
mm upgrades.ModuleManager,
|
||||
configurator module.Configurator,
|
||||
|
||||
Regular → Executable
+19
-6
@@ -1,3 +1,4 @@
|
||||
// Package upgrades provides types and interfaces for chain upgrade handling.
|
||||
package upgrades
|
||||
|
||||
import (
|
||||
@@ -14,12 +15,13 @@ import (
|
||||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
||||
consensusparamkeeper "github.com/cosmos/cosmos-sdk/x/consensus/keeper"
|
||||
paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper"
|
||||
didkeeper "github.com/sonr-io/snrd/x/did/keeper"
|
||||
)
|
||||
|
||||
// AppKeepers holds references to all the keepers needed during chain upgrades.
|
||||
// This struct is passed to upgrade handlers to provide access to various
|
||||
// module keepers and core functionality.
|
||||
type AppKeepers struct {
|
||||
AccountKeeper *authkeeper.AccountKeeper
|
||||
DidKeeper *didkeeper.Keeper
|
||||
ParamsKeeper *paramskeeper.Keeper
|
||||
ConsensusParamsKeeper *consensusparamkeeper.Keeper
|
||||
Codec codec.Codec
|
||||
@@ -27,8 +29,15 @@ type AppKeepers struct {
|
||||
CapabilityKeeper *capabilitykeeper.Keeper
|
||||
IBCKeeper *ibckeeper.Keeper
|
||||
}
|
||||
|
||||
// ModuleManager defines the interface for running module migrations during upgrades.
|
||||
// It provides methods to execute migrations and retrieve the current version map.
|
||||
type ModuleManager interface {
|
||||
RunMigrations(ctx context.Context, cfg module.Configurator, fromVM module.VersionMap) (module.VersionMap, error)
|
||||
RunMigrations(
|
||||
ctx context.Context,
|
||||
cfg module.Configurator,
|
||||
fromVM module.VersionMap,
|
||||
) (module.VersionMap, error)
|
||||
GetVersionMap() module.VersionMap
|
||||
}
|
||||
|
||||
@@ -37,10 +46,14 @@ type ModuleManager interface {
|
||||
// An upgrade must implement this struct, and then set it in the app.go.
|
||||
// The app.go will then define the handler.
|
||||
type Upgrade struct {
|
||||
// Upgrade version name, for the upgrade handler, e.g. `v7`
|
||||
// UpgradeName is the version name for the upgrade handler, e.g. `v7`.
|
||||
// This must match the upgrade name in the governance proposal.
|
||||
UpgradeName string
|
||||
|
||||
// CreateUpgradeHandler defines the function that creates an upgrade handler
|
||||
// CreateUpgradeHandler defines the function that creates an upgrade handler.
|
||||
// The handler performs the actual upgrade logic when the chain reaches the upgrade height.
|
||||
CreateUpgradeHandler func(ModuleManager, module.Configurator, *AppKeepers) upgradetypes.UpgradeHandler
|
||||
StoreUpgrades storetypes.StoreUpgrades
|
||||
// StoreUpgrades defines any store migrations needed for this upgrade,
|
||||
// including adding, renaming, or deleting stores.
|
||||
StoreUpgrades storetypes.StoreUpgrades
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user