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  }