github.com/badrootd/celestia-core@v0.0.0-20240305091328-aa4207a4b25d/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/cometbft/cometbft-db" 10 11 "github.com/badrootd/celestia-core/abci/example/code" 12 "github.com/badrootd/celestia-core/abci/types" 13 "github.com/badrootd/celestia-core/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 // If true, the app will generate block events in BeginBlock. Used to test the event indexer 72 // Should be false by default to avoid generating too much data. 73 genBlockEvents bool 74 } 75 76 func NewApplication() *Application { 77 state := loadState(dbm.NewMemDB()) 78 return &Application{state: state} 79 } 80 81 func (app *Application) SetGenBlockEvents() { 82 app.genBlockEvents = true 83 } 84 85 func (app *Application) Info(req types.RequestInfo) (resInfo types.ResponseInfo) { 86 return types.ResponseInfo{ 87 Data: fmt.Sprintf("{\"size\":%v}", app.state.Size), 88 Version: version.ABCISemVer, 89 AppVersion: ProtocolVersion, 90 LastBlockHeight: app.state.Height, 91 LastBlockAppHash: app.state.AppHash, 92 } 93 } 94 95 // tx is either "key=value" or just arbitrary bytes 96 func (app *Application) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx { 97 var key, value string 98 99 parts := bytes.Split(req.Tx, []byte("=")) 100 if len(parts) == 2 { 101 key, value = string(parts[0]), string(parts[1]) 102 } else { 103 key, value = string(req.Tx), string(req.Tx) 104 } 105 106 err := app.state.db.Set(prefixKey([]byte(key)), []byte(value)) 107 if err != nil { 108 panic(err) 109 } 110 app.state.Size++ 111 112 events := []types.Event{ 113 { 114 Type: "app", 115 Attributes: []types.EventAttribute{ 116 {Key: "creator", Value: "Cosmoshi Netowoko", Index: true}, 117 {Key: "key", Value: key, Index: true}, 118 {Key: "index_key", Value: "index is working", Index: true}, 119 {Key: "noindex_key", Value: "index is working", Index: false}, 120 }, 121 }, 122 } 123 124 return types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events} 125 } 126 127 func (app *Application) CheckTx(req types.RequestCheckTx) types.ResponseCheckTx { 128 return types.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1} 129 } 130 131 func (app *Application) Commit() types.ResponseCommit { 132 // Using a memdb - just return the big endian size of the db 133 appHash := make([]byte, 8) 134 binary.PutVarint(appHash, app.state.Size) 135 app.state.AppHash = appHash 136 app.state.Height++ 137 saveState(app.state) 138 139 resp := types.ResponseCommit{Data: appHash} 140 if app.RetainBlocks > 0 && app.state.Height >= app.RetainBlocks { 141 resp.RetainHeight = app.state.Height - app.RetainBlocks + 1 142 } 143 return resp 144 } 145 146 // Returns an associated value or nil if missing. 147 func (app *Application) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) { 148 if reqQuery.Prove { 149 value, err := app.state.db.Get(prefixKey(reqQuery.Data)) 150 if err != nil { 151 panic(err) 152 } 153 if value == nil { 154 resQuery.Log = "does not exist" 155 } else { 156 resQuery.Log = "exists" 157 } 158 resQuery.Index = -1 // TODO make Proof return index 159 resQuery.Key = reqQuery.Data 160 resQuery.Value = value 161 resQuery.Height = app.state.Height 162 163 return 164 } 165 166 resQuery.Key = reqQuery.Data 167 value, err := app.state.db.Get(prefixKey(reqQuery.Data)) 168 if err != nil { 169 panic(err) 170 } 171 if value == nil { 172 resQuery.Log = "does not exist" 173 } else { 174 resQuery.Log = "exists" 175 } 176 resQuery.Value = value 177 resQuery.Height = app.state.Height 178 179 return resQuery 180 } 181 182 func (app *Application) BeginBlock(req types.RequestBeginBlock) types.ResponseBeginBlock { 183 184 response := types.ResponseBeginBlock{} 185 186 if !app.genBlockEvents { 187 return response 188 } 189 190 if app.state.Height%2 == 0 { 191 response = types.ResponseBeginBlock{ 192 Events: []types.Event{ 193 { 194 Type: "begin_event", 195 Attributes: []types.EventAttribute{ 196 { 197 Key: "foo", 198 Value: "100", 199 Index: true, 200 }, 201 { 202 Key: "bar", 203 Value: "200", 204 Index: true, 205 }, 206 }, 207 }, 208 { 209 Type: "begin_event", 210 Attributes: []types.EventAttribute{ 211 { 212 Key: "foo", 213 Value: "200", 214 Index: true, 215 }, 216 { 217 Key: "bar", 218 Value: "300", 219 Index: true, 220 }, 221 }, 222 }, 223 }, 224 } 225 } else { 226 response = types.ResponseBeginBlock{ 227 Events: []types.Event{ 228 { 229 Type: "begin_event", 230 Attributes: []types.EventAttribute{ 231 { 232 Key: "foo", 233 Value: "400", 234 Index: true, 235 }, 236 { 237 Key: "bar", 238 Value: "300", 239 Index: true, 240 }, 241 }, 242 }, 243 }, 244 } 245 } 246 247 return response 248 }