splitting the modules
This commit is contained in:
parent
a53a500fd7
commit
8705f87504
12 changed files with 402 additions and 396 deletions
180
blockchain/abci.go
Normal file
180
blockchain/abci.go
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/dgraph-io/badger"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
)
|
||||
|
||||
type KVStoreApplication struct {
|
||||
db *badger.DB
|
||||
currentBatch *badger.Txn
|
||||
}
|
||||
|
||||
func NewKVStoreApplication(db *badger.DB) *KVStoreApplication {
|
||||
return &KVStoreApplication{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
|
||||
if app.currentBatch != nil {
|
||||
app.currentBatch.Discard()
|
||||
}
|
||||
app.currentBatch = app.db.NewTransaction(true)
|
||||
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}
|
||||
}
|
||||
|
||||
parts := bytes.Split(req.Tx, []byte("="))
|
||||
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])
|
||||
if err != nil {
|
||||
return abci.ResponseDeliverTx{
|
||||
Code: 1,
|
||||
Log: fmt.Sprintf("Failed to set key: %v", err),
|
||||
}
|
||||
}
|
||||
|
||||
return abci.ResponseDeliverTx{Code: 0}
|
||||
}
|
||||
|
||||
// Завершение блока и фиксация
|
||||
func (app *KVStoreApplication) 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)
|
||||
}
|
||||
app.currentBatch = nil
|
||||
}
|
||||
return abci.ResponseCommit{Data: []byte{}}
|
||||
}
|
||||
|
||||
// Обслуживание запросов Query
|
||||
func (app *KVStoreApplication) Query(req abci.RequestQuery) abci.ResponseQuery {
|
||||
resp := abci.ResponseQuery{Code: 0}
|
||||
|
||||
err := app.db.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get(req.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return item.Value(func(val []byte) error {
|
||||
resp.Value = val
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
resp.Code = 1
|
||||
resp.Log = err.Error()
|
||||
}
|
||||
|
||||
return resp
|
||||
}
|
||||
|
||||
// Добавляем недостающие методы 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{},
|
||||
}
|
||||
}
|
||||
|
||||
// SetOption устанавливает опцию приложения
|
||||
func (app *KVStoreApplication) SetOption(req abci.RequestSetOption) abci.ResponseSetOption {
|
||||
return abci.ResponseSetOption{Code: 0}
|
||||
}
|
||||
|
||||
// InitChain инициализирует блокчейн
|
||||
func (app *KVStoreApplication) InitChain(req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
return abci.ResponseInitChain{}
|
||||
}
|
||||
|
||||
// EndBlock сигнализирует о конце блока
|
||||
func (app *KVStoreApplication) EndBlock(req abci.RequestEndBlock) abci.ResponseEndBlock {
|
||||
return abci.ResponseEndBlock{}
|
||||
}
|
||||
|
||||
// ListSnapshots возвращает список доступных снапшотов
|
||||
func (app *KVStoreApplication) ListSnapshots(req abci.RequestListSnapshots) abci.ResponseListSnapshots {
|
||||
return abci.ResponseListSnapshots{}
|
||||
}
|
||||
|
||||
// OfferSnapshot предлагает снапшот приложению
|
||||
func (app *KVStoreApplication) OfferSnapshot(req abci.RequestOfferSnapshot) abci.ResponseOfferSnapshot {
|
||||
return abci.ResponseOfferSnapshot{Result: abci.ResponseOfferSnapshot_REJECT}
|
||||
}
|
||||
|
||||
// LoadSnapshotChunk загружает часть снапшота
|
||||
func (app *KVStoreApplication) LoadSnapshotChunk(req abci.RequestLoadSnapshotChunk) abci.ResponseLoadSnapshotChunk {
|
||||
return abci.ResponseLoadSnapshotChunk{}
|
||||
}
|
||||
|
||||
// ApplySnapshotChunk применяет часть снапшота
|
||||
func (app *KVStoreApplication) ApplySnapshotChunk(req abci.RequestApplySnapshotChunk) abci.ResponseApplySnapshotChunk {
|
||||
return abci.ResponseApplySnapshotChunk{Result: abci.ResponseApplySnapshotChunk_ACCEPT}
|
||||
}
|
||||
141
blockchain/main.go
Normal file
141
blockchain/main.go
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
package blockchain
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"lbc/cfg"
|
||||
"os"
|
||||
|
||||
"github.com/dgraph-io/badger"
|
||||
abci "github.com/tendermint/tendermint/abci/types"
|
||||
"github.com/tendermint/tendermint/libs/log"
|
||||
nm "github.com/tendermint/tendermint/node"
|
||||
"github.com/tendermint/tendermint/p2p"
|
||||
"github.com/tendermint/tendermint/privval"
|
||||
"github.com/tendermint/tendermint/proxy"
|
||||
tmTypes "github.com/tendermint/tendermint/types"
|
||||
)
|
||||
|
||||
func openBadger(path string) (*badger.DB, error) {
|
||||
return badger.Open(badger.DefaultOptions(path).WithTruncate(true))
|
||||
}
|
||||
|
||||
func newTendermint(app abci.Application, config *cfg.Config, laddrReturner chan string) (*nm.Node, error) {
|
||||
config.P2P.ListenAddress = "tcp://" + <-laddrReturner
|
||||
|
||||
//if config.P2P.PersistentPeers == "" {
|
||||
config.P2P.PersistentPeers = <-laddrReturner
|
||||
//} else {
|
||||
// <- laddrReturner
|
||||
//}
|
||||
|
||||
var pv tmTypes.PrivValidator
|
||||
if _, err := os.Stat(config.PrivValidatorKeyFile()); err == nil {
|
||||
pv = privval.LoadFilePV(
|
||||
config.PrivValidatorKeyFile(),
|
||||
config.PrivValidatorStateFile(),
|
||||
)
|
||||
} else {
|
||||
fmt.Println("⚠️ priv_validator_key.json not found. Node will run as non-validator.")
|
||||
pv = tmTypes.NewMockPV()
|
||||
}
|
||||
|
||||
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load node key: %w", err)
|
||||
}
|
||||
|
||||
clientCreator := proxy.NewLocalClientCreator(app)
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
|
||||
return nm.NewNode(
|
||||
config,
|
||||
pv,
|
||||
nodeKey,
|
||||
clientCreator,
|
||||
nm.DefaultGenesisDocProviderFunc(config),
|
||||
nm.DefaultDBProvider,
|
||||
nm.DefaultMetricsProvider(config.Instrumentation),
|
||||
logger,
|
||||
)
|
||||
}
|
||||
|
||||
func GetNodeInfo(config *cfg.Config, dbPath string) (p2p.NodeInfo, error) {
|
||||
db, err := openBadger(dbPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open badger db to get node info", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
app := NewKVStoreApplication(db)
|
||||
|
||||
nodeKey, err := p2p.LoadNodeKey(config.NodeKeyFile())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("load node key: %w", err)
|
||||
}
|
||||
|
||||
clientCreator := proxy.NewLocalClientCreator(app)
|
||||
logger := log.NewTMLogger(log.NewSyncWriter(os.Stdout))
|
||||
var pv tmTypes.PrivValidator
|
||||
if _, err := os.Stat(config.PrivValidatorKeyFile()); err == nil {
|
||||
pv = privval.LoadFilePV(
|
||||
config.PrivValidatorKeyFile(),
|
||||
config.PrivValidatorStateFile(),
|
||||
)
|
||||
} else {
|
||||
fmt.Println("⚠️ priv_validator_key.json not found. Node will run as non-validator.")
|
||||
pv = tmTypes.NewMockPV()
|
||||
}
|
||||
|
||||
config.P2P.PersistentPeers = ""
|
||||
|
||||
node, err := nm.NewNode(
|
||||
config,
|
||||
pv,
|
||||
nodeKey,
|
||||
clientCreator,
|
||||
nm.DefaultGenesisDocProviderFunc(config),
|
||||
nm.DefaultDBProvider,
|
||||
nm.DefaultMetricsProvider(config.Instrumentation),
|
||||
logger,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return node.NodeInfo(), nil
|
||||
|
||||
}
|
||||
|
||||
// В blockchain/run.go
|
||||
func Run(ctx context.Context, dbPath string, config *cfg.Config, laddrReturner chan string) error {
|
||||
db, err := openBadger(dbPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open badger db: %w", err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
app := NewKVStoreApplication(db)
|
||||
node, err := newTendermint(app, config, laddrReturner)
|
||||
if err != nil {
|
||||
return fmt.Errorf("build node: %w", err)
|
||||
}
|
||||
if err := node.Start(); err != nil {
|
||||
return fmt.Errorf("start node: %w", err)
|
||||
}
|
||||
defer func() {
|
||||
|
||||
}()
|
||||
|
||||
// ждём контекст или внутренних ошибок узла
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-node.Quit(): // Tendermint закрылся сам
|
||||
return err
|
||||
// Node quit signal received
|
||||
}
|
||||
node.Stop()
|
||||
node.Wait()
|
||||
return nil
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue