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  }