package poj import ( "context" "encoding/json" "fmt" "cosmossdk.io/core/appmodule" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/grpc-ecosystem/grpc-gateway/runtime" "google.golang.org/grpc" "mukan/x/poj/keeper" "mukan/x/poj/types" "mukan/x/poj/client/cli" "github.com/spf13/cobra" ) var ( _ module.AppModuleBasic = (*AppModule)(nil) _ module.AppModule = (*AppModule)(nil) _ module.HasGenesis = (*AppModule)(nil) _ appmodule.AppModule = (*AppModule)(nil) _ appmodule.HasBeginBlocker = (*AppModule)(nil) _ appmodule.HasEndBlocker = (*AppModule)(nil) ) // AppModule implements the AppModule interface that defines the inter-dependent methods that modules need to implement type AppModule struct { cdc codec.Codec keeper keeper.Keeper authKeeper types.AuthKeeper bankKeeper types.BankKeeper stakingKeeper types.StakingKeeper } func NewAppModule( cdc codec.Codec, keeper keeper.Keeper, authKeeper types.AuthKeeper, bankKeeper types.BankKeeper, stakingKeeper types.StakingKeeper, ) AppModule { return AppModule{ cdc: cdc, keeper: keeper, authKeeper: authKeeper, bankKeeper: bankKeeper, stakingKeeper: stakingKeeper, } } // IsAppModule implements the appmodule.AppModule interface. func (AppModule) IsAppModule() {} // Name returns the name of the module as a string. func (AppModule) Name() string { return types.ModuleName } // RegisterLegacyAminoCodec registers the amino codec func (AppModule) RegisterLegacyAminoCodec(*codec.LegacyAmino) {} // RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the module. func (AppModule) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *runtime.ServeMux) { if err := types.RegisterQueryHandlerClient(clientCtx.CmdContext, mux, types.NewQueryClient(clientCtx)); err != nil { panic(err) } } // GetTxCmd returns the root tx command for the module. func (AppModule) GetTxCmd() *cobra.Command { return cli.GetTxCmd() } // GetQueryCmd returns the root query command for the module. func (AppModule) GetQueryCmd() *cobra.Command { return nil } // RegisterInterfaces registers a module's interface types and their concrete implementations as proto.Message. func (AppModule) RegisterInterfaces(registrar codectypes.InterfaceRegistry) { types.RegisterInterfaces(registrar) } // RegisterServices registers a gRPC query service to respond to the module-specific gRPC queries func (am AppModule) RegisterServices(registrar grpc.ServiceRegistrar) error { types.RegisterMsgServer(registrar, keeper.NewMsgServerImpl(am.keeper)) types.RegisterQueryServer(registrar, keeper.NewQueryServerImpl(am.keeper)) return nil } // DefaultGenesis returns a default GenesisState for the module, marshalled to json.RawMessage. // The default GenesisState need to be defined by the module developer and is primarily used for testing. func (am AppModule) DefaultGenesis(codec.JSONCodec) json.RawMessage { return am.cdc.MustMarshalJSON(types.DefaultGenesis()) } // ValidateGenesis used to validate the GenesisState, given in its json.RawMessage form. func (am AppModule) ValidateGenesis(_ codec.JSONCodec, _ client.TxEncodingConfig, bz json.RawMessage) error { var genState types.GenesisState if err := am.cdc.UnmarshalJSON(bz, &genState); err != nil { return fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err) } return genState.Validate() } // InitGenesis performs the module's genesis initialization. It returns no validator updates. func (am AppModule) InitGenesis(ctx sdk.Context, _ codec.JSONCodec, gs json.RawMessage) { var genState types.GenesisState // Initialize global index to index in genesis state if err := am.cdc.UnmarshalJSON(gs, &genState); err != nil { panic(fmt.Errorf("failed to unmarshal %s genesis state: %w", types.ModuleName, err)) } if err := am.keeper.InitGenesis(ctx, genState); err != nil { panic(fmt.Errorf("failed to initialize %s genesis state: %w", types.ModuleName, err)) } } // ExportGenesis returns the module's exported genesis state as raw JSON bytes. func (am AppModule) ExportGenesis(ctx sdk.Context, _ codec.JSONCodec) json.RawMessage { genState, err := am.keeper.ExportGenesis(ctx) if err != nil { panic(fmt.Errorf("failed to export %s genesis state: %w", types.ModuleName, err)) } bz, err := am.cdc.MarshalJSON(genState) if err != nil { panic(fmt.Errorf("failed to marshal %s genesis state: %w", types.ModuleName, err)) } return bz } // ConsensusVersion is a sequence number for state-breaking change of the module. // It should be incremented on each consensus-breaking change introduced by the module. // To avoid wrong/empty versions, the initial version should be set to 1. func (AppModule) ConsensusVersion() uint64 { return 1 } // BeginBlock contains the logic that is automatically triggered at the beginning of each block. // The begin block implementation is optional. func (am AppModule) BeginBlock(ctx context.Context) error { sdkCtx := sdk.UnwrapSDKContext(ctx) proposerAddr := sdkCtx.BlockHeader().ProposerAddress if proposerAddr == nil { return nil } // Map ConsAddress to Validator and then to AccountAddress validator, err := am.stakingKeeper.ValidatorByConsAddr(sdkCtx, proposerAddr) if err != nil { // If not found, skip (might happen in genesis or non-validator blocks) return nil } // Operator address is usually what we want to reward operatorAddr := validator.GetOperator() // Convert operator address (valoper) to account address (acc) // In Cosmos SDK, operator and account share the same public key but different prefix. // For now, we use the string directly if it's already a valid address string, // or we can convert it. // The doc says "Madenci cüzdan adresi". // Implementation Detail: GetOperator() returns the valoper address. // We should convert it to a standard account address for the reward. // However, many chains just send to the account associated with the valoper. // Let's use a helper if available, or just convert via bytes. valAddr, err := sdk.ValAddressFromBech32(operatorAddr) if err != nil { return nil } accAddr := sdk.AccAddress(valAddr) minerAddr := accAddr.String() // 1. Apply PoJ logic if am.keeper.ProcessBlockReward(ctx, minerAddr) { reward := am.keeper.GetBlockReward(ctx) // Mint and send reward to miner err := am.bankKeeper.MintCoins(sdkCtx, types.ModuleName, sdk.NewCoins(reward)) if err != nil { return err } err = am.bankKeeper.SendCoinsFromModuleToAccount(sdkCtx, types.ModuleName, accAddr, sdk.NewCoins(reward)) if err != nil { return err } } return nil } // EndBlock contains the logic that is automatically triggered at the end of each block. func (am AppModule) EndBlock(ctx context.Context) error { sdkCtx := sdk.UnwrapSDKContext(ctx) // SAFU Check: If no miner won this block, reward goes to the Törük (Wealth Fund) hasWinner := am.keeper.HasBlockWinner(ctx, sdkCtx.BlockHeight()) if !hasWinner { params := am.keeper.GetParams(ctx) torukAddr, err := sdk.AccAddressFromBech32(params.TorukAddress) if err == nil { reward := am.keeper.GetBlockReward(ctx) // Mint and send reward to Törük err = am.bankKeeper.MintCoins(sdkCtx, types.ModuleName, sdk.NewCoins(reward)) if err == nil { _ = am.bankKeeper.SendCoinsFromModuleToAccount(sdkCtx, types.ModuleName, torukAddr, sdk.NewCoins(reward)) } } } return nil }