github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/abci/example/kvstore/kvstore.go (about) 1 package kvstore 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "encoding/json" 7 "fmt" 8 9 dbm "github.com/badrootd/nibiru-db" 10 11 "github.com/badrootd/nibiru-cometbft/abci/example/code" 12 "github.com/badrootd/nibiru-cometbft/abci/types" 13 "github.com/badrootd/nibiru-cometbft/version" 14 ) 15 16 var ( 17 stateKey = []byte("stateKey") 18 kvPairPrefixKey = []byte("kvPairKey:") 19 20 ProtocolVersion uint64 = 0x1 21 ) 22 23 type State struct { 24 db dbm.DB 25 Size int64 `json:"size"` 26 Height int64 `json:"height"` 27 AppHash []byte `json:"app_hash"` 28 } 29 30 func loadState(db dbm.DB) State { 31 var state State 32 state.db = db 33 stateBytes, err := db.Get(stateKey) 34 if err != nil { 35 panic(err) 36 } 37 if len(stateBytes) == 0 { 38 return state 39 } 40 err = json.Unmarshal(stateBytes, &state) 41 if err != nil { 42 panic(err) 43 } 44 return state 45 } 46 47 func saveState(state State) { 48 stateBytes, err := json.Marshal(state) 49 if err != nil { 50 panic(err) 51 } 52 err = state.db.Set(stateKey, stateBytes) 53 if err != nil { 54 panic(err) 55 } 56 } 57 58 func prefixKey(key []byte) []byte { 59 return append(kvPairPrefixKey, key...) 60 } 61 62 //--------------------------------------------------- 63 64 var _ types.Application = (*Application)(nil) 65 66 type Application struct { 67 types.BaseApplication 68 69 state State 70 RetainBlocks int64 // blocks to retain after commit (via ResponseCommit.RetainHeight) 71 txToRemove map[string]struct{} 72 // If true, the app will generate block events in BeginBlock. Used to test the event indexer 73 // Should be false by default to avoid generating too much data. 74 genBlockEvents bool 75 } 76 77 func NewApplication() *Application { 78 state := loadState(dbm.NewMemDB()) 79 return &Application{state: state} 80 } 81 82 func (app *Application) SetGenBlockEvents() { 83 app.genBlockEvents = true 84 } 85 86 func (app *Application) Info(req types.RequestInfo) (resInfo types.ResponseInfo) { 87 return types.ResponseInfo{ 88 Data: fmt.Sprintf("{\"size\":%v}", app.state.Size), 89 Version: version.ABCISemVer, 90 AppVersion: ProtocolVersion, 91 LastBlockHeight: app.state.Height, 92 LastBlockAppHash: app.state.AppHash, 93 } 94 } 95 96 // tx is either "key=value" or just arbitrary bytes 97 func (app *Application) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx { 98 if isReplacedTx(req.Tx) { 99 app.txToRemove[string(req.Tx)] = struct{}{} 100 } 101 var key, value string 102 103 parts := bytes.Split(req.Tx, []byte("=")) 104 if len(parts) == 2 { 105 key, value = string(parts[0]), string(parts[1]) 106 } else { 107 key, value = string(req.Tx), string(req.Tx) 108 } 109 110 err := app.state.db.Set(prefixKey([]byte(key)), []byte(value)) 111 if err != nil { 112 panic(err) 113 } 114 app.state.Size++ 115 116 events := []types.Event{ 117 { 118 Type: "app", 119 Attributes: []types.EventAttribute{ 120 {Key: "creator", Value: "Cosmoshi Netowoko", Index: true}, 121 {Key: "key", Value: key, Index: true}, 122 {Key: "index_key", Value: "index is working", Index: true}, 123 {Key: "noindex_key", Value: "index is working", Index: false}, 124 }, 125 }, 126 { 127 Type: "app", 128 Attributes: []types.EventAttribute{ 129 {Key: "creator", Value: "Cosmoshi", Index: true}, 130 {Key: "key", Value: value, Index: true}, 131 {Key: "index_key", Value: "index is working", Index: true}, 132 {Key: "noindex_key", Value: "index is working", Index: false}, 133 }, 134 }, 135 } 136 137 return types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events} 138 } 139 140 func (app *Application) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx { 141 if len(req.Tx) == 0 { 142 return types.ResponseCheckTx{Code: code.CodeTypeRejected} 143 } 144 145 if req.Type == types.CheckTxType_Recheck { 146 if _, ok := app.txToRemove[string(req.Tx)]; ok { 147 return types.ResponseCheckTx{Code: code.CodeTypeExecuted, GasWanted: 1} 148 } 149 } 150 return types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} 151 } 152 153 func (app *Application) Commit() types.ResponseCommit { 154 // Using a memdb - just return the big endian size of the db 155 appHash := make([]byte, 8) 156 binary.PutVarint(appHash, app.state.Size) 157 app.state.AppHash = appHash 158 app.state.Height++ 159 160 // empty out the set of transactions to remove via rechecktx 161 saveState(app.state) 162 163 resp := types.ResponseCommit{Data: appHash} 164 if app.RetainBlocks > 0 && app.state.Height >= app.RetainBlocks { 165 resp.RetainHeight = app.state.Height - app.RetainBlocks + 1 166 } 167 return resp 168 } 169 170 // Returns an associated value or nil if missing. 171 func (app *Application) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { 172 if reqQuery.Prove { 173 value, err := app.state.db.Get(prefixKey(reqQuery.Data)) 174 if err != nil { 175 panic(err) 176 } 177 if value == nil { 178 resQuery.Log = "does not exist" 179 } else { 180 resQuery.Log = "exists" 181 } 182 resQuery.Index = -1 // TODO make Proof return index 183 resQuery.Key = reqQuery.Data 184 resQuery.Value = value 185 resQuery.Height = app.state.Height 186 187 return 188 } 189 190 resQuery.Key = reqQuery.Data 191 value, err := app.state.db.Get(prefixKey(reqQuery.Data)) 192 if err != nil { 193 panic(err) 194 } 195 if value == nil { 196 resQuery.Log = "does not exist" 197 } else { 198 resQuery.Log = "exists" 199 } 200 resQuery.Value = value 201 resQuery.Height = app.state.Height 202 203 return resQuery 204 } 205 206 func (app *Application) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock { 207 app.txToRemove = map[string]struct{}{} 208 response := types.ResponseBeginBlock{} 209 210 if !app.genBlockEvents { 211 return response 212 } 213 214 if app.state.Height%2 == 0 { 215 response = types.ResponseBeginBlock{ 216 Events: []types.Event{ 217 { 218 Type: "begin_event", 219 Attributes: []types.EventAttribute{ 220 { 221 Key: "foo", 222 Value: "100", 223 Index: true, 224 }, 225 { 226 Key: "bar", 227 Value: "200", 228 Index: true, 229 }, 230 }, 231 }, 232 { 233 Type: "begin_event", 234 Attributes: []types.EventAttribute{ 235 { 236 Key: "foo", 237 Value: "200", 238 Index: true, 239 }, 240 { 241 Key: "bar", 242 Value: "300", 243 Index: true, 244 }, 245 }, 246 }, 247 }, 248 } 249 } else { 250 response = types.ResponseBeginBlock{ 251 Events: []types.Event{ 252 { 253 Type: "begin_event", 254 Attributes: []types.EventAttribute{ 255 { 256 Key: "foo", 257 Value: "400", 258 Index: true, 259 }, 260 { 261 Key: "bar", 262 Value: "300", 263 Index: true, 264 }, 265 }, 266 }, 267 }, 268 } 269 } 270 271 return response 272 } 273 274 func (app *Application) ProcessProposal( 275 req types.RequestProcessProposal, 276 ) types.ResponseProcessProposal { 277 for _, tx := range req.Txs { 278 if len(tx) == 0 { 279 return types.ResponseProcessProposal{Status: types.ResponseProcessProposal_REJECT} 280 } 281 } 282 return types.ResponseProcessProposal{Status: types.ResponseProcessProposal_ACCEPT} 283 }