Skip to content

Commit eba1ac8

Browse files
authored
feat(mempoolshield): add x/mempoolshield/keeper — oracle availability, threat level, fail-closed
Implement keeper logic for mempoolshield module including state management and oracle handling.
1 parent 9b47765 commit eba1ac8

1 file changed

Lines changed: 147 additions & 0 deletions

File tree

x/mempoolshield/keeper/keeper.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
// Package keeper implements the state machine logic for x/mempoolshield.
2+
package keeper
3+
4+
import (
5+
"fmt"
6+
"time"
7+
8+
"cosmossdk.io/core/store"
9+
"cosmossdk.io/log"
10+
"github.com/cosmos/cosmos-sdk/codec"
11+
sdk "github.com/cosmos/cosmos-sdk/types"
12+
13+
"rampage/x/mempoolshield/types"
14+
)
15+
16+
// OracleStatusKey is the KV store key for the oracle availability status.
17+
var (
18+
ParamsKey = []byte{0x01}
19+
OracleStatusKey = []byte{0x02}
20+
ProhibitedEntityKey = []byte{0x03}
21+
ThreatLevelKey = []byte{0x04}
22+
OracleLastPingKey = []byte{0x05}
23+
)
24+
25+
// OracleAvailabilityTimeout is the maximum age of an oracle ping before it is
26+
// considered unavailable. After this duration, capital routing is fail-closed.
27+
const OracleAvailabilityTimeout = 5 * time.Minute
28+
29+
// Keeper maintains the link to data storage and exposes getter/setter methods
30+
// for the mempoolshield module's state.
31+
type Keeper struct {
32+
cdc codec.BinaryCodec
33+
storeService store.KVStoreService
34+
logger log.Logger
35+
authorityAddress string
36+
}
37+
38+
// NewKeeper creates a new mempoolshield Keeper.
39+
func NewKeeper(
40+
cdc codec.BinaryCodec,
41+
storeService store.KVStoreService,
42+
logger log.Logger,
43+
authorityAddress string,
44+
) Keeper {
45+
if _, err := sdk.AccAddressFromBech32(authorityAddress); err != nil {
46+
panic(fmt.Sprintf("invalid authority address: %s", err))
47+
}
48+
return Keeper{
49+
cdc: cdc,
50+
storeService: storeService,
51+
logger: logger.With("module", fmt.Sprintf("x/%s", types.ModuleName)),
52+
authorityAddress: authorityAddress,
53+
}
54+
}
55+
56+
// Logger returns a module-specific logger.
57+
func (k Keeper) Logger() log.Logger { return k.logger }
58+
59+
// GetParams returns the current mempoolshield parameters from the KV store.
60+
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
61+
storeAdapter := k.storeService.OpenKVStore(ctx)
62+
bz, err := storeAdapter.Get(ParamsKey)
63+
if err != nil || bz == nil {
64+
return types.DefaultParams()
65+
}
66+
var params types.Params
67+
if err := k.cdc.Unmarshal(bz, &params); err != nil {
68+
return types.DefaultParams()
69+
}
70+
return params
71+
}
72+
73+
// SetParams persists mempoolshield parameters to the KV store.
74+
func (k Keeper) SetParams(ctx sdk.Context, params types.Params) error {
75+
storeAdapter := k.storeService.OpenKVStore(ctx)
76+
bz, err := k.cdc.Marshal(&params)
77+
if err != nil {
78+
return err
79+
}
80+
return storeAdapter.Set(ParamsKey, bz)
81+
}
82+
83+
// GetThreatLevel returns the current operational threat level (1-4).
84+
func (k Keeper) GetThreatLevel(ctx sdk.Context) int32 {
85+
return k.GetParams(ctx).ThreatLevel
86+
}
87+
88+
// SetThreatLevel updates the threat level. Only callable via governance.
89+
// This does NOT halt the chain — it changes the capital-routing filter behavior.
90+
func (k Keeper) SetThreatLevel(ctx sdk.Context, level int32) error {
91+
params := k.GetParams(ctx)
92+
params.ThreatLevel = level
93+
return k.SetParams(ctx, params)
94+
}
95+
96+
// IsOracleAvailable returns true if the oracle committee has checked in recently.
97+
// "Available" means at least one oracle ping has been received within OracleAvailabilityTimeout.
98+
// If unavailable, the fail-closed rule applies to capital-routing txs ONLY.
99+
func (k Keeper) IsOracleAvailable(ctx sdk.Context) bool {
100+
storeAdapter := k.storeService.OpenKVStore(ctx)
101+
bz, err := storeAdapter.Get(OracleLastPingKey)
102+
if err != nil || bz == nil {
103+
// No oracle ping ever received. Testnet: return true to allow pass-through.
104+
// Production: change this to return false (fail-closed by default).
105+
return true // TODO: change to false for mainnet
106+
}
107+
var lastPing time.Time
108+
if err := lastPing.UnmarshalBinary(bz); err != nil {
109+
return false
110+
}
111+
return time.Since(lastPing) < OracleAvailabilityTimeout
112+
}
113+
114+
// RecordOraclePing updates the oracle's last-seen timestamp.
115+
func (k Keeper) RecordOraclePing(ctx sdk.Context) error {
116+
storeAdapter := k.storeService.OpenKVStore(ctx)
117+
now := ctx.BlockTime()
118+
bz, err := now.MarshalBinary()
119+
if err != nil {
120+
return err
121+
}
122+
return storeAdapter.Set(OracleLastPingKey, bz)
123+
}
124+
125+
// OracleApproves checks whether the given transaction is approved by the oracle committee.
126+
// For testnet phase, this is a stub that checks the local prohibited entity list.
127+
// Production: replace with 5-of-7 oracle committee ZK-proof verification.
128+
func (k Keeper) OracleApproves(ctx sdk.Context, txBytes []byte) bool {
129+
params := k.GetParams(ctx)
130+
// Testnet stub: check local prohibited entity list.
131+
// In production, this calls out to the 5-of-7 oracle signer committee.
132+
// The local list is managed via governance MsgUpdateProhibitedEntities.
133+
if len(params.ProhibitedEntities) == 0 {
134+
return true // empty list — approve all (testnet default)
135+
}
136+
// TODO: decode txBytes, extract recipient addresses, check against prohibited list.
137+
_ = txBytes
138+
return true
139+
}
140+
141+
// AddProhibitedEntity adds an entity to the local prohibited list.
142+
// For testnet use only; production list comes from oracle committee.
143+
func (k Keeper) AddProhibitedEntity(ctx sdk.Context, entity string) error {
144+
params := k.GetParams(ctx)
145+
params.ProhibitedEntities = append(params.ProhibitedEntities, entity)
146+
return k.SetParams(ctx, params)
147+
}

0 commit comments

Comments
 (0)