diff --git a/blockchain/abci.go b/blockchain/abci.go index 4ad3558..1c4bb86 100644 --- a/blockchain/abci.go +++ b/blockchain/abci.go @@ -1,67 +1,24 @@ package blockchain import ( - "bytes" + "encoding/json" "fmt" + "strings" "github.com/dgraph-io/badger" abci "github.com/tendermint/tendermint/abci/types" ) -type KVStoreApplication struct { +type PromiseApp struct { db *badger.DB currentBatch *badger.Txn } -func NewKVStoreApplication(db *badger.DB) *KVStoreApplication { - return &KVStoreApplication{db: db} +func NewPromiseApp(db *badger.DB) *PromiseApp { + return &PromiseApp{db: db} } -// Проверка транзакции (CheckTx) -func (app *KVStoreApplication) isValid(tx []byte) uint32 { - parts := bytes.Split(tx, []byte("=")) - if len(parts) != 2 { - return 1 // неверный формат - } - key, value := parts[0], parts[1] - - // Проверяем, существует ли уже такая запись - err := app.db.View(func(txn *badger.Txn) error { - item, err := txn.Get(key) - if err != nil { - if err == badger.ErrKeyNotFound { - return nil // ключ не найден, все ок - } - return err - } - - return item.Value(func(val []byte) error { - if bytes.Equal(val, value) { - return fmt.Errorf("duplicate key-value") - } - return nil - }) - }) - - if err != nil { - if err.Error() == "duplicate key-value" { - return 2 // дубликат найден - } - // любая другая ошибка - return 1 - } - - return 0 // все проверки пройдены -} - -func (app *KVStoreApplication) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { - code := app.isValid(req.Tx) - return abci.ResponseCheckTx{Code: code, GasWanted: 1} -} - -// Начало блока -func (app *KVStoreApplication) BeginBlock(req abci.RequestBeginBlock) abci.ResponseBeginBlock { - // If there's an existing batch for some reason, discard it first +func (app *PromiseApp) BeginBlock(_ abci.RequestBeginBlock) abci.ResponseBeginBlock { if app.currentBatch != nil { app.currentBatch.Discard() } @@ -69,112 +26,124 @@ func (app *KVStoreApplication) BeginBlock(req abci.RequestBeginBlock) abci.Respo return abci.ResponseBeginBlock{} } -// Применение транзакции -func (app *KVStoreApplication) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { - code := app.isValid(req.Tx) - if code != 0 { - return abci.ResponseDeliverTx{Code: code} +func (app *PromiseApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx { + var tx map[string]interface{} + if err := json.Unmarshal(req.Tx, &tx); err != nil { + return abci.ResponseDeliverTx{Code: 1, Log: "invalid JSON"} } - parts := bytes.Split(req.Tx, []byte("=")) + id, ok := tx["id"].(string) + if !ok || id == "" { + return abci.ResponseDeliverTx{Code: 2, Log: "missing id"} + } + + data, _ := json.Marshal(tx) + if app.currentBatch == nil { - // In case BeginBlock wasn't called or batch was discarded app.currentBatch = app.db.NewTransaction(true) } - - err := app.currentBatch.Set(parts[0], parts[1]) + err := app.currentBatch.Set([]byte(id), data) if err != nil { - return abci.ResponseDeliverTx{ - Code: 1, - Log: fmt.Sprintf("Failed to set key: %v", err), - } + return abci.ResponseDeliverTx{Code: 1, Log: fmt.Sprintf("failed to set: %v", err)} } return abci.ResponseDeliverTx{Code: 0} } -// Завершение блока и фиксация -func (app *KVStoreApplication) Commit() abci.ResponseCommit { +func (app *PromiseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx { + var tx map[string]interface{} + if err := json.Unmarshal(req.Tx, &tx); err != nil { + return abci.ResponseCheckTx{Code: 1, Log: "invalid JSON"} + } + id, ok := tx["id"].(string) + if !ok || id == "" { + return abci.ResponseCheckTx{Code: 2, Log: "missing id"} + } + + // проверка уникальности id + err := app.db.View(func(txn *badger.Txn) error { + _, err := txn.Get([]byte(id)) + if err == badger.ErrKeyNotFound { + return nil + } + return fmt.Errorf("id exists") + }) + if err != nil { + return abci.ResponseCheckTx{Code: 3, Log: "duplicate id"} + } + + return abci.ResponseCheckTx{Code: 0} +} + +func (app *PromiseApp) Commit() abci.ResponseCommit { if app.currentBatch != nil { err := app.currentBatch.Commit() if err != nil { - // Log error but continue - in a real application, you might want - // to handle this more gracefully - fmt.Printf("Error committing batch: %v\n", err) + fmt.Printf("Commit error: %v\n", err) } app.currentBatch = nil } return abci.ResponseCommit{Data: []byte{}} } -// Обслуживание запросов Query -func (app *KVStoreApplication) Query(req abci.RequestQuery) abci.ResponseQuery { - resp := abci.ResponseQuery{Code: 0} +// SELECT * FROM +func (app *PromiseApp) Query(req abci.RequestQuery) abci.ResponseQuery { + parts := strings.Split(strings.Trim(req.Path, "/"), "/") + if len(parts) != 2 || parts[0] != "list" { + return abci.ResponseQuery{Code: 1, Log: "unsupported query"} + } + prefix := []byte(parts[1] + ":") + var result []json.RawMessage err := app.db.View(func(txn *badger.Txn) error { - item, err := txn.Get(req.Data) - if err != nil { - return err - } + it := txn.NewIterator(badger.DefaultIteratorOptions) + defer it.Close() - return item.Value(func(val []byte) error { - resp.Value = val - return nil - }) + for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() { + item := it.Item() + err := item.Value(func(v []byte) error { + var raw json.RawMessage = make([]byte, len(v)) + copy(raw, v) + result = append(result, raw) + return nil + }) + if err != nil { + return err + } + } + return nil }) if err != nil { - resp.Code = 1 - resp.Log = err.Error() + return abci.ResponseQuery{Code: 1, Log: err.Error()} } - return resp + all, _ := json.Marshal(result) + return abci.ResponseQuery{Code: 0, Value: all} } -// Добавляем недостающие методы ABCI интерфейса - -// Info возвращает информацию о приложении -func (app *KVStoreApplication) Info(req abci.RequestInfo) abci.ResponseInfo { - return abci.ResponseInfo{ - Data: "kvstore", - Version: "1.0.0", - AppVersion: 1, - LastBlockHeight: 0, - LastBlockAppHash: []byte{}, - } +// Остальные методы ABCI +func (app *PromiseApp) Info(req abci.RequestInfo) abci.ResponseInfo { + return abci.ResponseInfo{Data: "promises", Version: "0.1"} } - -// SetOption устанавливает опцию приложения -func (app *KVStoreApplication) SetOption(req abci.RequestSetOption) abci.ResponseSetOption { - return abci.ResponseSetOption{Code: 0} +func (app *PromiseApp) SetOption(req abci.RequestSetOption) abci.ResponseSetOption { + return abci.ResponseSetOption{} } - -// InitChain инициализирует блокчейн -func (app *KVStoreApplication) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { +func (app *PromiseApp) InitChain(req abci.RequestInitChain) abci.ResponseInitChain { return abci.ResponseInitChain{} } - -// EndBlock сигнализирует о конце блока -func (app *KVStoreApplication) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { +func (app *PromiseApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock { return abci.ResponseEndBlock{} } - -// ListSnapshots возвращает список доступных снапшотов -func (app *KVStoreApplication) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots { +func (app *PromiseApp) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots { return abci.ResponseListSnapshots{} } - -// OfferSnapshot предлагает снапшот приложению -func (app *KVStoreApplication) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot { +func (app *PromiseApp) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot { return abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT} } - -// LoadSnapshotChunk загружает часть снапшота -func (app *KVStoreApplication) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk { +func (app *PromiseApp) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk { return abci.ResponseLoadSnapshotChunk{} } - -// ApplySnapshotChunk применяет часть снапшота -func (app *KVStoreApplication) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk { +func (app *PromiseApp) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk { return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT} } diff --git a/blockchain/main.go b/blockchain/main.go index 092d159..fec3583 100644 --- a/blockchain/main.go +++ b/blockchain/main.go @@ -63,7 +63,7 @@ func GetNodeInfo(config *cfg.Config, dbPath string) (p2p.NodeInfo, error) { } defer db.Close() - app := NewKVStoreApplication(db) + app := NewPromiseApp(db) nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile()) if err != nil { @@ -110,7 +110,7 @@ func Run(ctx context.Context, dbPath string, config *cfg.Config, laddrReturner c } defer db.Close() - app := NewKVStoreApplication(db) + app := NewPromiseApp(db) node, err := newTendermint(app, config, laddrReturner) if err != nil { return fmt.Errorf("build node: %w", err)