github.com/line/ostracon@v1.0.10-0.20230328032236-7f20145f065d/abci/example/kvstore/kvstore.go (about)

     1  package kvstore
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  	"encoding/json"
     7  	"fmt"
     8  
     9  	"github.com/tendermint/tendermint/abci/types"
    10  	dbm "github.com/tendermint/tm-db"
    11  
    12  	"github.com/line/ostracon/abci/example/code"
    13  	ocabci "github.com/line/ostracon/abci/types"
    14  	"github.com/line/ostracon/version"
    15  )
    16  
    17  var (
    18  	stateKey        = []byte("stateKey")
    19  	kvPairPrefixKey = []byte("kvPairKey:")
    20  
    21  	ProtocolVersion uint64 = 99
    22  )
    23  
    24  type State struct {
    25  	db      dbm.DB
    26  	Size    int64  `json:"size"`
    27  	Height  int64  `json:"height"`
    28  	AppHash []byte `json:"app_hash"`
    29  }
    30  
    31  func loadState(db dbm.DB) State {
    32  	var state State
    33  	state.db = db
    34  	stateBytes, err := db.Get(stateKey)
    35  	if err != nil {
    36  		panic(err)
    37  	}
    38  	if len(stateBytes) == 0 {
    39  		return state
    40  	}
    41  	err = json.Unmarshal(stateBytes, &state)
    42  	if err != nil {
    43  		panic(err)
    44  	}
    45  	return state
    46  }
    47  
    48  func saveState(state State) {
    49  	stateBytes, err := json.Marshal(state)
    50  	if err != nil {
    51  		panic(err)
    52  	}
    53  	err = state.db.Set(stateKey, stateBytes)
    54  	if err != nil {
    55  		panic(err)
    56  	}
    57  }
    58  
    59  func prefixKey(key []byte) []byte {
    60  	return append(kvPairPrefixKey, key...)
    61  }
    62  
    63  //---------------------------------------------------
    64  
    65  var _ ocabci.Application = (*Application)(nil)
    66  
    67  type Application struct {
    68  	ocabci.BaseApplication
    69  
    70  	state        State
    71  	RetainBlocks int64 // blocks to retain after commit (via ResponseCommit.RetainHeight)
    72  }
    73  
    74  func NewApplication() *Application {
    75  	state := loadState(dbm.NewMemDB())
    76  	return &Application{state: state}
    77  }
    78  
    79  func (app *Application) Info(req types.RequestInfo) (resInfo types.ResponseInfo) {
    80  	return types.ResponseInfo{
    81  		Data:             fmt.Sprintf("{\"size\":%v}", app.state.Size),
    82  		Version:          version.ABCIVersion,
    83  		AppVersion:       ProtocolVersion,
    84  		LastBlockHeight:  app.state.Height,
    85  		LastBlockAppHash: app.state.AppHash,
    86  	}
    87  }
    88  
    89  // tx is either "key=value" or just arbitrary bytes
    90  func (app *Application) DeliverTx(req types.RequestDeliverTx) types.ResponseDeliverTx {
    91  	var key, value []byte
    92  	parts := bytes.Split(req.Tx, []byte("="))
    93  	if len(parts) == 2 {
    94  		key, value = parts[0], parts[1]
    95  	} else {
    96  		key, value = req.Tx, req.Tx
    97  	}
    98  
    99  	err := app.state.db.Set(prefixKey(key), value)
   100  	if err != nil {
   101  		panic(err)
   102  	}
   103  	app.state.Size++
   104  
   105  	events := []types.Event{
   106  		{
   107  			Type: "app",
   108  			Attributes: []types.EventAttribute{
   109  				{Key: []byte("creator"), Value: []byte("Cosmoshi Netowoko"), Index: true},
   110  				{Key: []byte("key"), Value: key, Index: true},
   111  				{Key: []byte("index_key"), Value: []byte("index is working"), Index: true},
   112  				{Key: []byte("noindex_key"), Value: []byte("index is working"), Index: false},
   113  			},
   114  		},
   115  	}
   116  
   117  	return types.ResponseDeliverTx{Code: code.CodeTypeOK, Events: events}
   118  }
   119  
   120  func (app *Application) CheckTxSync(req types.RequestCheckTx) ocabci.ResponseCheckTx {
   121  	return app.checkTx(req)
   122  }
   123  
   124  func (app *Application) CheckTxAsync(req types.RequestCheckTx, callback ocabci.CheckTxCallback) {
   125  	callback(app.checkTx(req))
   126  }
   127  
   128  func (app *Application) checkTx(req types.RequestCheckTx) ocabci.ResponseCheckTx {
   129  	return ocabci.ResponseCheckTx{Code: code.CodeTypeOK, GasWanted: 1}
   130  }
   131  
   132  func (app *Application) Commit() types.ResponseCommit {
   133  	// Using a memdb - just return the big endian size of the db
   134  	appHash := make([]byte, 8)
   135  	binary.PutVarint(appHash, app.state.Size)
   136  	app.state.AppHash = appHash
   137  	app.state.Height++
   138  	saveState(app.state)
   139  
   140  	resp := types.ResponseCommit{Data: appHash}
   141  	if app.RetainBlocks > 0 && app.state.Height >= app.RetainBlocks {
   142  		resp.RetainHeight = app.state.Height - app.RetainBlocks + 1
   143  	}
   144  	return resp
   145  }
   146  
   147  // Returns an associated value or nil if missing.
   148  func (app *Application) Query(reqQuery types.RequestQuery) (resQuery types.ResponseQuery) {
   149  	if reqQuery.Prove {
   150  		value, err := app.state.db.Get(prefixKey(reqQuery.Data))
   151  		if err != nil {
   152  			panic(err)
   153  		}
   154  		if value == nil {
   155  			resQuery.Log = "does not exist"
   156  		} else {
   157  			resQuery.Log = "exists"
   158  		}
   159  		resQuery.Index = -1 // TODO make Proof return index
   160  		resQuery.Key = reqQuery.Data
   161  		resQuery.Value = value
   162  		resQuery.Height = app.state.Height
   163  
   164  		return
   165  	}
   166  
   167  	resQuery.Key = reqQuery.Data
   168  	value, err := app.state.db.Get(prefixKey(reqQuery.Data))
   169  	if err != nil {
   170  		panic(err)
   171  	}
   172  	if value == nil {
   173  		resQuery.Log = "does not exist"
   174  	} else {
   175  		resQuery.Log = "exists"
   176  	}
   177  	resQuery.Value = value
   178  	resQuery.Height = app.state.Height
   179  
   180  	return resQuery
   181  }