modified: blockchain/abci.go
modified: blockchain/main.go
This commit is contained in:
parent
7e02054f35
commit
ab5c96ecbd
2 changed files with 85 additions and 116 deletions
|
|
@ -1,67 +1,24 @@
|
||||||
package blockchain
|
package blockchain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/dgraph-io/badger"
|
"github.com/dgraph-io/badger"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
type KVStoreApplication struct {
|
type PromiseApp struct {
|
||||||
db *badger.DB
|
db *badger.DB
|
||||||
currentBatch *badger.Txn
|
currentBatch *badger.Txn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
|
func NewPromiseApp(db *badger.DB) *PromiseApp {
|
||||||
return &KVStoreApplication{db: db}
|
return &PromiseApp{db: db}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка транзакции (CheckTx)
|
func (app *PromiseApp) BeginBlock(_ abci.RequestBeginBlock) abci.ResponseBeginBlock {
|
||||||
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
|
|
||||||
if app.currentBatch != nil {
|
if app.currentBatch != nil {
|
||||||
app.currentBatch.Discard()
|
app.currentBatch.Discard()
|
||||||
}
|
}
|
||||||
|
|
@ -69,112 +26,124 @@ func (app *KVStoreApplication) BeginBlock(req abci.RequestBeginBlock) abci.Respo
|
||||||
return abci.ResponseBeginBlock{}
|
return abci.ResponseBeginBlock{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Применение транзакции
|
func (app *PromiseApp) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
|
||||||
func (app *KVStoreApplication) DeliverTx(req abci.RequestDeliverTx) abci.ResponseDeliverTx {
|
var tx map[string]interface{}
|
||||||
code := app.isValid(req.Tx)
|
if err := json.Unmarshal(req.Tx, &tx); err != nil {
|
||||||
if code != 0 {
|
return abci.ResponseDeliverTx{Code: 1, Log: "invalid JSON"}
|
||||||
return abci.ResponseDeliverTx{Code: code}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
if app.currentBatch == nil {
|
||||||
// In case BeginBlock wasn't called or batch was discarded
|
|
||||||
app.currentBatch = app.db.NewTransaction(true)
|
app.currentBatch = app.db.NewTransaction(true)
|
||||||
}
|
}
|
||||||
|
err := app.currentBatch.Set([]byte(id), data)
|
||||||
err := app.currentBatch.Set(parts[0], parts[1])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return abci.ResponseDeliverTx{
|
return abci.ResponseDeliverTx{Code: 1, Log: fmt.Sprintf("failed to set: %v", err)}
|
||||||
Code: 1,
|
|
||||||
Log: fmt.Sprintf("Failed to set key: %v", err),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return abci.ResponseDeliverTx{Code: 0}
|
return abci.ResponseDeliverTx{Code: 0}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Завершение блока и фиксация
|
func (app *PromiseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
|
||||||
func (app *KVStoreApplication) Commit() abci.ResponseCommit {
|
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 {
|
if app.currentBatch != nil {
|
||||||
err := app.currentBatch.Commit()
|
err := app.currentBatch.Commit()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Log error but continue - in a real application, you might want
|
fmt.Printf("Commit error: %v\n", err)
|
||||||
// to handle this more gracefully
|
|
||||||
fmt.Printf("Error committing batch: %v\n", err)
|
|
||||||
}
|
}
|
||||||
app.currentBatch = nil
|
app.currentBatch = nil
|
||||||
}
|
}
|
||||||
return abci.ResponseCommit{Data: []byte{}}
|
return abci.ResponseCommit{Data: []byte{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Обслуживание запросов Query
|
// SELECT * FROM <table>
|
||||||
func (app *KVStoreApplication) Query(req abci.RequestQuery) abci.ResponseQuery {
|
func (app *PromiseApp) Query(req abci.RequestQuery) abci.ResponseQuery {
|
||||||
resp := abci.ResponseQuery{Code: 0}
|
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 {
|
err := app.db.View(func(txn *badger.Txn) error {
|
||||||
item, err := txn.Get(req.Data)
|
it := txn.NewIterator(badger.DefaultIteratorOptions)
|
||||||
|
defer it.Close()
|
||||||
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return item.Value(func(val []byte) error {
|
|
||||||
resp.Value = val
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
resp.Code = 1
|
return abci.ResponseQuery{Code: 1, Log: err.Error()}
|
||||||
resp.Log = err.Error()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp
|
all, _ := json.Marshal(result)
|
||||||
|
return abci.ResponseQuery{Code: 0, Value: all}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем недостающие методы ABCI интерфейса
|
// Остальные методы ABCI
|
||||||
|
func (app *PromiseApp) Info(req abci.RequestInfo) abci.ResponseInfo {
|
||||||
// Info возвращает информацию о приложении
|
return abci.ResponseInfo{Data: "promises", Version: "0.1"}
|
||||||
func (app *KVStoreApplication) Info(req abci.RequestInfo) abci.ResponseInfo {
|
|
||||||
return abci.ResponseInfo{
|
|
||||||
Data: "kvstore",
|
|
||||||
Version: "1.0.0",
|
|
||||||
AppVersion: 1,
|
|
||||||
LastBlockHeight: 0,
|
|
||||||
LastBlockAppHash: []byte{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
func (app *PromiseApp) SetOption(req abci.RequestSetOption) abci.ResponseSetOption {
|
||||||
// SetOption устанавливает опцию приложения
|
return abci.ResponseSetOption{}
|
||||||
func (app *KVStoreApplication) SetOption(req abci.RequestSetOption) abci.ResponseSetOption {
|
|
||||||
return abci.ResponseSetOption{Code: 0}
|
|
||||||
}
|
}
|
||||||
|
func (app *PromiseApp) InitChain(req abci.RequestInitChain) abci.ResponseInitChain {
|
||||||
// InitChain инициализирует блокчейн
|
|
||||||
func (app *KVStoreApplication) InitChain(req abci.RequestInitChain) abci.ResponseInitChain {
|
|
||||||
return abci.ResponseInitChain{}
|
return abci.ResponseInitChain{}
|
||||||
}
|
}
|
||||||
|
func (app *PromiseApp) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock {
|
||||||
// EndBlock сигнализирует о конце блока
|
|
||||||
func (app *KVStoreApplication) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock {
|
|
||||||
return abci.ResponseEndBlock{}
|
return abci.ResponseEndBlock{}
|
||||||
}
|
}
|
||||||
|
func (app *PromiseApp) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots {
|
||||||
// ListSnapshots возвращает список доступных снапшотов
|
|
||||||
func (app *KVStoreApplication) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots {
|
|
||||||
return abci.ResponseListSnapshots{}
|
return abci.ResponseListSnapshots{}
|
||||||
}
|
}
|
||||||
|
func (app *PromiseApp) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot {
|
||||||
// OfferSnapshot предлагает снапшот приложению
|
|
||||||
func (app *KVStoreApplication) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot {
|
|
||||||
return abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}
|
return abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}
|
||||||
}
|
}
|
||||||
|
func (app *PromiseApp) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk {
|
||||||
// LoadSnapshotChunk загружает часть снапшота
|
|
||||||
func (app *KVStoreApplication) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk {
|
|
||||||
return abci.ResponseLoadSnapshotChunk{}
|
return abci.ResponseLoadSnapshotChunk{}
|
||||||
}
|
}
|
||||||
|
func (app *PromiseApp) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk {
|
||||||
// ApplySnapshotChunk применяет часть снапшота
|
|
||||||
func (app *KVStoreApplication) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk {
|
|
||||||
return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}
|
return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ func GetNodeInfo(config *cfg.Config, dbPath string) (p2p.NodeInfo, error) {
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
app := NewKVStoreApplication(db)
|
app := NewPromiseApp(db)
|
||||||
|
|
||||||
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
|
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -110,7 +110,7 @@ func Run(ctx context.Context, dbPath string, config *cfg.Config, laddrReturner c
|
||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
app := NewKVStoreApplication(db)
|
app := NewPromiseApp(db)
|
||||||
node, err := newTendermint(app, config, laddrReturner)
|
node, err := newTendermint(app, config, laddrReturner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("build node: %w", err)
|
return fmt.Errorf("build node: %w", err)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue