Compare commits
15 commits
codex/-ygg
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f35c528ba6 | |||
| f65d0d1ee1 | |||
| e7a9e1092b | |||
| ce4ea5c09c | |||
| 388db03fc0 | |||
| cdbef17af3 | |||
| fb3e471172 | |||
| bf5371ea8b | |||
| dae1cd484e | |||
| 57c40d9f07 | |||
| a63abd641a | |||
| cf4580a8de | |||
| 0b102f2272 | |||
| accf5ba341 | |||
| ab5c96ecbd |
12 changed files with 552 additions and 141 deletions
|
|
@ -1,67 +1,260 @@
|
||||||
package blockchain
|
package blockchain
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"crypto/ed25519"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
types "github.com/gregorybednov/lbc_sdk"
|
||||||
|
|
||||||
"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 {
|
type compoundTxRaw struct {
|
||||||
return &KVStoreApplication{db: db}
|
Body json.RawMessage `json:"body"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверка транзакции (CheckTx)
|
type compoundBody struct {
|
||||||
func (app *KVStoreApplication) isValid(tx []byte) uint32 {
|
Promise *types.PromiseTxBody `json:"promise"`
|
||||||
parts := bytes.Split(tx, []byte("="))
|
Commitment *types.CommitmentTxBody `json:"commitment"`
|
||||||
if len(parts) != 2 {
|
}
|
||||||
return 1 // неверный формат
|
|
||||||
}
|
|
||||||
key, value := parts[0], parts[1]
|
|
||||||
|
|
||||||
// Проверяем, существует ли уже такая запись
|
func NewPromiseApp(db *badger.DB) *PromiseApp {
|
||||||
err := app.db.View(func(txn *badger.Txn) error {
|
return &PromiseApp{db: db}
|
||||||
item, err := txn.Get(key)
|
}
|
||||||
if err != nil {
|
|
||||||
if err == badger.ErrKeyNotFound {
|
|
||||||
return nil // ключ не найден, все ок
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return item.Value(func(val []byte) error {
|
func hasPrefix(id, pref string) bool { return strings.HasPrefix(id, pref+":") }
|
||||||
if bytes.Equal(val, value) {
|
|
||||||
return fmt.Errorf("duplicate key-value")
|
func requireIDPrefix(id, pref string) error {
|
||||||
|
if strings.TrimSpace(id) == "" {
|
||||||
|
return fmt.Errorf("missing %s id", pref)
|
||||||
|
}
|
||||||
|
if !hasPrefix(id, pref) {
|
||||||
|
return fmt.Errorf("invalid %s id prefix", pref)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
}
|
||||||
})
|
|
||||||
|
|
||||||
|
func verifyAndExtractBody(db *badger.DB, tx []byte) (map[string]interface{}, error) {
|
||||||
|
var outer struct {
|
||||||
|
Body types.CommiterTxBody `json:"body"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.Unmarshal(tx, &outer); err != nil {
|
||||||
|
return nil, errors.New("invalid JSON wrapper")
|
||||||
|
}
|
||||||
|
|
||||||
|
msg, err := json.Marshal(outer.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err.Error() == "duplicate key-value" {
|
return nil, err
|
||||||
return 2 // дубликат найден
|
|
||||||
}
|
|
||||||
// любая другая ошибка
|
|
||||||
return 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return 0 // все проверки пройдены
|
sig, err := base64.StdEncoding.DecodeString(outer.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid signature base64")
|
||||||
|
}
|
||||||
|
if len(sig) != ed25519.SignatureSize {
|
||||||
|
return nil, fmt.Errorf("invalid signature length: got %d, want %d", len(sig), ed25519.SignatureSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubkeyB64 := strings.TrimSpace(outer.Body.CommiterPubKey)
|
||||||
|
if pubkeyB64 == "" {
|
||||||
|
return nil, errors.New("missing commiter pubkey")
|
||||||
|
}
|
||||||
|
pubkey, err := base64.StdEncoding.DecodeString(pubkeyB64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid pubkey base64")
|
||||||
|
}
|
||||||
|
if len(pubkey) != ed25519.PublicKeySize {
|
||||||
|
return nil, fmt.Errorf("invalid pubkey length: got %d, want %d", len(pubkey), ed25519.PublicKeySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ed25519.Verify(pubkey, msg, sig) {
|
||||||
|
return nil, errors.New("signature verification failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var result map[string]interface{}
|
||||||
|
if err := json.Unmarshal(msg, &result); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (app *KVStoreApplication) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
|
func (app *PromiseApp) CheckTx(req abci.RequestCheckTx) abci.ResponseCheckTx {
|
||||||
code := app.isValid(req.Tx)
|
compound, err := verifyCompoundTx(app.db, req.Tx)
|
||||||
return abci.ResponseCheckTx{Code: code, GasWanted: 1}
|
if err == nil {
|
||||||
|
// ---- Валидация содержимого композита по ER ----
|
||||||
|
p := compound.Body.Promise
|
||||||
|
c := compound.Body.Commitment
|
||||||
|
|
||||||
|
// Оба тела должны присутствовать
|
||||||
|
if p == nil || c == nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 1, Log: "compound must include promise and commitment"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ID-предикаты и префиксы
|
||||||
|
if err := requireIDPrefix(p.ID, "promise"); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: err.Error()}
|
||||||
|
}
|
||||||
|
if err := requireIDPrefix(c.ID, "commitment"); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: err.Error()}
|
||||||
|
}
|
||||||
|
if err := requireIDPrefix(c.CommiterID, "commiter"); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: err.Error()}
|
||||||
|
}
|
||||||
|
//if err := requireIDPrefix(p.BeneficiaryID, "beneficiary"); err != nil {
|
||||||
|
// return abci.ResponseCheckTx{Code: 2, Log: err.Error()}
|
||||||
|
//}
|
||||||
|
if p.ParentPromiseID != nil {
|
||||||
|
if err := requireIDPrefix(*p.ParentPromiseID, "promise"); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: err.Error()}
|
||||||
|
}
|
||||||
|
if *p.ParentPromiseID == p.ID {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: "parent_promise_id must not equal promise id"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Базовые обязательные поля Promise
|
||||||
|
if strings.TrimSpace(p.Text) == "" {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: "promise.text is required"}
|
||||||
|
}
|
||||||
|
//if p.Due == 0 {
|
||||||
|
// return abci.ResponseCheckTx{Code: 2, Log: "promise.due is required"}
|
||||||
|
//}
|
||||||
|
// Commitment due
|
||||||
|
//if c.Due == 0 {
|
||||||
|
// return abci.ResponseCheckTx{Code: 2, Log: "commitment.due is required"}
|
||||||
|
//}
|
||||||
|
|
||||||
|
// Связность по ER
|
||||||
|
if c.PromiseID != p.ID {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: "commitment.promise_id must equal promise.id"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уникальность Promise.ID
|
||||||
|
if err := app.db.View(func(txn *badger.Txn) error {
|
||||||
|
_, e := txn.Get([]byte(p.ID))
|
||||||
|
if e == badger.ErrKeyNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("duplicate promise ID")
|
||||||
|
}); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 3, Log: err.Error()}
|
||||||
|
}
|
||||||
|
// Уникальность Commitment.ID
|
||||||
|
if err := app.db.View(func(txn *badger.Txn) error {
|
||||||
|
_, e := txn.Get([]byte(c.ID))
|
||||||
|
if e == badger.ErrKeyNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("duplicate commitment ID")
|
||||||
|
}); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 3, Log: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Существование коммитера
|
||||||
|
if err := app.db.View(func(txn *badger.Txn) error {
|
||||||
|
_, e := txn.Get([]byte(c.CommiterID))
|
||||||
|
if e == badger.ErrKeyNotFound {
|
||||||
|
return errors.New("unknown commiter")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 4, Log: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Существование бенефициара
|
||||||
|
if err := app.db.View(func(txn *badger.Txn) error {
|
||||||
|
_, e := txn.Get([]byte(p.BeneficiaryID))
|
||||||
|
if e == badger.ErrKeyNotFound {
|
||||||
|
return errors.New("unknown beneficiary")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 5, Log: err.Error()}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Существование parent (если задан)
|
||||||
|
if p.ParentPromiseID != nil {
|
||||||
|
if err := app.db.View(func(txn *badger.Txn) error {
|
||||||
|
_, e := txn.Get([]byte(*p.ParentPromiseID))
|
||||||
|
if e == badger.ErrKeyNotFound {
|
||||||
|
return errors.New("unknown parent promise")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 6, Log: err.Error()}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return abci.ResponseCheckTx{Code: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Попытка ОДИНОЧНЫХ транзакций ----
|
||||||
|
|
||||||
|
// 3.1) Совместимость: одиночный commiter (твоя старая логика)
|
||||||
|
if body, oldErr := verifyAndExtractBody(app.db, req.Tx); oldErr == nil {
|
||||||
|
id, ok := body["id"].(string)
|
||||||
|
if !ok || id == "" {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: "missing id"}
|
||||||
|
}
|
||||||
|
if err := requireIDPrefix(id, "commiter"); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: err.Error()}
|
||||||
|
}
|
||||||
|
// Дубликат
|
||||||
|
if err := app.db.View(func(txn *badger.Txn) error {
|
||||||
|
_, e := txn.Get([]byte(id))
|
||||||
|
if e == badger.ErrKeyNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("duplicate id")
|
||||||
|
}); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 3, Log: err.Error()}
|
||||||
|
}
|
||||||
|
return abci.ResponseCheckTx{Code: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
var single struct {
|
||||||
|
Body types.BeneficiaryTxBody `json:"body"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
if err2 := json.Unmarshal(req.Tx, &single); err2 == nil && single.Body.Type == "beneficiary" {
|
||||||
|
if err := requireIDPrefix(single.Body.ID, "beneficiary"); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: err.Error()}
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(single.Body.Name) == "" {
|
||||||
|
return abci.ResponseCheckTx{Code: 2, Log: "beneficiary.name is required"}
|
||||||
|
}
|
||||||
|
// уникальность
|
||||||
|
if err := app.db.View(func(txn *badger.Txn) error {
|
||||||
|
_, e := txn.Get([]byte(single.Body.ID))
|
||||||
|
if e == badger.ErrKeyNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("duplicate beneficiary ID")
|
||||||
|
}); err != nil {
|
||||||
|
return abci.ResponseCheckTx{Code: 3, Log: err.Error()}
|
||||||
|
}
|
||||||
|
return abci.ResponseCheckTx{Code: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если дошли сюда — составной формат не прошёл; вернём его причину.
|
||||||
|
return abci.ResponseCheckTx{Code: 1, Log: err.Error()}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Начало блока
|
func (app *PromiseApp) BeginBlock(_ abci.RequestBeginBlock) abci.ResponseBeginBlock {
|
||||||
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 +262,222 @@ 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 {
|
// Попытка композита
|
||||||
code := app.isValid(req.Tx)
|
if compound, err := verifyCompoundTx(app.db, req.Tx); err == nil {
|
||||||
if code != 0 {
|
|
||||||
return abci.ResponseDeliverTx{Code: code}
|
|
||||||
}
|
|
||||||
|
|
||||||
parts := bytes.Split(req.Tx, []byte("="))
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
if compound.Body.Promise != nil {
|
||||||
err := app.currentBatch.Set(parts[0], parts[1])
|
data, _ := json.Marshal(compound.Body.Promise)
|
||||||
if err != nil {
|
if err := app.currentBatch.Set([]byte(compound.Body.Promise.ID), data); err != nil {
|
||||||
return abci.ResponseDeliverTx{
|
return abci.ResponseDeliverTx{Code: 1, Log: "failed to save promise"}
|
||||||
Code: 1,
|
}
|
||||||
Log: fmt.Sprintf("Failed to set key: %v", err),
|
}
|
||||||
|
if compound.Body.Commitment != nil {
|
||||||
|
data, _ := json.Marshal(compound.Body.Commitment)
|
||||||
|
if err := app.currentBatch.Set([]byte(compound.Body.Commitment.ID), data); err != nil {
|
||||||
|
return abci.ResponseDeliverTx{Code: 1, Log: "failed to save commitment"}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return abci.ResponseDeliverTx{Code: 0}
|
return abci.ResponseDeliverTx{Code: 0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Одиночный commiter (как раньше)
|
||||||
|
{
|
||||||
|
var outer struct {
|
||||||
|
Body types.CommiterTxBody `json:"body"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(req.Tx, &outer); err == nil && outer.Body.Type == "commiter" {
|
||||||
|
// сигнатуру проверяем прежней функцией
|
||||||
|
if _, vErr := verifyAndExtractBody(app.db, req.Tx); vErr != nil {
|
||||||
|
return abci.ResponseDeliverTx{Code: 1, Log: vErr.Error()}
|
||||||
|
}
|
||||||
|
if app.currentBatch == nil {
|
||||||
|
app.currentBatch = app.db.NewTransaction(true)
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(outer.Body)
|
||||||
|
if err := app.currentBatch.Set([]byte(outer.Body.ID), data); err != nil {
|
||||||
|
return abci.ResponseDeliverTx{Code: 1, Log: err.Error()}
|
||||||
|
}
|
||||||
|
return abci.ResponseDeliverTx{Code: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Одиночный beneficiary
|
||||||
|
{
|
||||||
|
var outer struct {
|
||||||
|
Body types.BeneficiaryTxBody `json:"body"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(req.Tx, &outer); err == nil && outer.Body.Type == "beneficiary" {
|
||||||
|
// (пока без проверки подписи — можно добавить политику позже)
|
||||||
|
if app.currentBatch == nil {
|
||||||
|
app.currentBatch = app.db.NewTransaction(true)
|
||||||
|
}
|
||||||
|
data, _ := json.Marshal(outer.Body)
|
||||||
|
if err := app.currentBatch.Set([]byte(outer.Body.ID), data); err != nil {
|
||||||
|
return abci.ResponseDeliverTx{Code: 1, Log: err.Error()}
|
||||||
|
}
|
||||||
|
return abci.ResponseDeliverTx{Code: 0}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return abci.ResponseDeliverTx{Code: 1, Log: "invalid tx format"}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Завершение блока и фиксация
|
func verifyCompoundTx(db *badger.DB, tx []byte) (*types.CompoundTx, error) {
|
||||||
func (app *KVStoreApplication) Commit() abci.ResponseCommit {
|
// 1) Разобрать внешний конверт, body оставить сырым
|
||||||
|
var outerRaw compoundTxRaw
|
||||||
|
if err := json.Unmarshal(tx, &outerRaw); err != nil {
|
||||||
|
return nil, errors.New("invalid compound tx JSON")
|
||||||
|
}
|
||||||
|
if len(outerRaw.Body) == 0 {
|
||||||
|
return nil, errors.New("missing body")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Вынуть commiter_id из body, не парся всё
|
||||||
|
var tiny struct {
|
||||||
|
Commitment *struct {
|
||||||
|
CommiterID string `json:"commiter_id"`
|
||||||
|
} `json:"commitment"`
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal(outerRaw.Body, &tiny); err != nil {
|
||||||
|
return nil, errors.New("invalid body JSON")
|
||||||
|
}
|
||||||
|
if tiny.Commitment == nil || strings.TrimSpace(tiny.Commitment.CommiterID) == "" {
|
||||||
|
return nil, errors.New("missing commitment")
|
||||||
|
}
|
||||||
|
commiterID := tiny.Commitment.CommiterID
|
||||||
|
|
||||||
|
// 3) Достать коммитера из БД и получить публичный ключ
|
||||||
|
var commiterData []byte
|
||||||
|
err := db.View(func(txn *badger.Txn) error {
|
||||||
|
item, err := txn.Get([]byte(commiterID))
|
||||||
|
if err != nil {
|
||||||
|
return errors.New("unknown commiter")
|
||||||
|
}
|
||||||
|
return item.Value(func(v []byte) error {
|
||||||
|
commiterData = append([]byte{}, v...)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var commiter types.CommiterTxBody
|
||||||
|
if err := json.Unmarshal(commiterData, &commiter); err != nil {
|
||||||
|
return nil, errors.New("corrupted commiter record")
|
||||||
|
}
|
||||||
|
pubkeyB64 := strings.TrimSpace(commiter.CommiterPubKey)
|
||||||
|
if pubkeyB64 == "" {
|
||||||
|
return nil, errors.New("missing commiter pubkey")
|
||||||
|
}
|
||||||
|
pubkey, err := base64.StdEncoding.DecodeString(pubkeyB64)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid pubkey base64")
|
||||||
|
}
|
||||||
|
if len(pubkey) != ed25519.PublicKeySize {
|
||||||
|
return nil, fmt.Errorf("invalid pubkey length: got %d, want %d", len(pubkey), ed25519.PublicKeySize)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4) Проверка подписи над СЫРЫМИ БАЙТАМИ body (как клиент подписывал)
|
||||||
|
sig, err := base64.StdEncoding.DecodeString(outerRaw.Signature)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.New("invalid signature base64")
|
||||||
|
}
|
||||||
|
if len(sig) != ed25519.SignatureSize {
|
||||||
|
return nil, fmt.Errorf("invalid signature length: got %d, want %d", len(sig), ed25519.SignatureSize)
|
||||||
|
}
|
||||||
|
if !ed25519.Verify(pubkey, outerRaw.Body, sig) {
|
||||||
|
return nil, errors.New("signature verification failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5) Только после успешной проверки — распарсить body в наши структуры
|
||||||
|
var body compoundBody
|
||||||
|
if err := json.Unmarshal(outerRaw.Body, &body); err != nil {
|
||||||
|
return nil, errors.New("invalid body JSON")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6) Вернуть в привычной форме
|
||||||
|
return &types.CompoundTx{
|
||||||
|
Body: struct {
|
||||||
|
Promise *types.PromiseTxBody `json:"promise"`
|
||||||
|
Commitment *types.CommitmentTxBody `json:"commitment"`
|
||||||
|
}{
|
||||||
|
Promise: body.Promise,
|
||||||
|
Commitment: body.Commitment,
|
||||||
|
},
|
||||||
|
Signature: outerRaw.Signature,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
func (app *PromiseApp) Query(req abci.RequestQuery) abci.ResponseQuery {
|
||||||
func (app *KVStoreApplication) Query(req abci.RequestQuery) abci.ResponseQuery {
|
parts := strings.Split(strings.Trim(req.Path, "/"), "/")
|
||||||
resp := abci.ResponseQuery{Code: 0}
|
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()
|
|
||||||
}
|
}
|
||||||
|
all, _ := json.Marshal(result)
|
||||||
return resp
|
return abci.ResponseQuery{Code: 0, Value: all}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Добавляем недостающие методы ABCI интерфейса
|
func (app *PromiseApp) Info(req abci.RequestInfo) abci.ResponseInfo {
|
||||||
|
return abci.ResponseInfo{Data: "promises", Version: "0.1"}
|
||||||
// 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{},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"lbc/cfg"
|
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/gregorybednov/lbc/cfg"
|
||||||
|
|
||||||
"github.com/dgraph-io/badger"
|
"github.com/dgraph-io/badger"
|
||||||
abci "github.com/tendermint/tendermint/abci/types"
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
"github.com/tendermint/tendermint/libs/log"
|
"github.com/tendermint/tendermint/libs/log"
|
||||||
|
|
@ -23,6 +24,7 @@ func openBadger(path string) (*badger.DB, error) {
|
||||||
|
|
||||||
func newTendermint(app abci.Application, config *cfg.Config, laddrReturner chan string) (*nm.Node, error) {
|
func newTendermint(app abci.Application, config *cfg.Config, laddrReturner chan string) (*nm.Node, error) {
|
||||||
config.P2P.ListenAddress = "tcp://" + <-laddrReturner
|
config.P2P.ListenAddress = "tcp://" + <-laddrReturner
|
||||||
|
config.P2P.ExternalAddress = <-laddrReturner
|
||||||
config.P2P.PersistentPeers = <-laddrReturner
|
config.P2P.PersistentPeers = <-laddrReturner
|
||||||
|
|
||||||
var pv tmTypes.PrivValidator
|
var pv tmTypes.PrivValidator
|
||||||
|
|
@ -63,7 +65,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 +112,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)
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,13 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"lbc/yggdrasil"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gregorybednov/lbc/yggdrasil"
|
||||||
|
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
cfg "github.com/tendermint/tendermint/config"
|
cfg "github.com/tendermint/tendermint/config"
|
||||||
"github.com/tendermint/tendermint/p2p"
|
"github.com/tendermint/tendermint/p2p"
|
||||||
|
|
@ -163,14 +164,15 @@ func WriteConfig(config *cfg.Config, configPath *string, nodeInfo p2p.NodeInfo)
|
||||||
config.P2P.PersistentPeers = a
|
config.P2P.PersistentPeers = a
|
||||||
}
|
}
|
||||||
|
|
||||||
v.Set("p2p", map[string]interface{}{
|
v.Set("p2p", map[string]any{
|
||||||
"use_legacy": false,
|
"use_legacy": false,
|
||||||
"queue_type": "priority",
|
"queue_type": "priority",
|
||||||
"laddr": strconv.Itoa(yggListenPort) + ":127.0.0.1:8000",
|
"laddr": strconv.Itoa(yggListenPort) + ":127.0.0.1:8000",
|
||||||
"external_address": "", // will be set automatically by Tendermint if needed
|
"external_address": "",
|
||||||
"upnp": false,
|
"upnp": false,
|
||||||
"bootstrap_peers": "",
|
"bootstrap_peers": "",
|
||||||
"persistent_peers": config.P2P.PersistentPeers,
|
"persistent_peers": config.P2P.PersistentPeers,
|
||||||
|
"allow_duplicate_ip": true, // needed because of Yggdrasil proxy
|
||||||
"addr_book_file": "config/addrbook.json",
|
"addr_book_file": "config/addrbook.json",
|
||||||
"addr_book_strict": false,
|
"addr_book_strict": false,
|
||||||
})
|
})
|
||||||
|
|
@ -250,18 +252,25 @@ func UpdateGenesisJson(nodeInfo p2p.NodeInfo, v *viper.Viper, defaultConfigDirec
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitGenesis(chainName, defaultConfigPath string) (*cfg.Config, *viper.Viper) {
|
func InitGenesis(chainName, defaultConfigPath string) (*cfg.Config, *viper.Viper, error) {
|
||||||
config := cfg.DefaultConfig()
|
config := cfg.DefaultConfig()
|
||||||
config.RootDir = filepath.Dir(filepath.Dir(defaultConfigPath))
|
config.RootDir = filepath.Dir(filepath.Dir(defaultConfigPath))
|
||||||
|
|
||||||
nodeinfo := p2p.DefaultNodeInfo{}
|
if err := os.MkdirAll(config.RootDir, 0o755); err != nil {
|
||||||
viper := WriteConfig(config, &defaultConfigPath, nodeinfo)
|
return nil, nil, fmt.Errorf("failed to create config directory %s: %w", config.RootDir, err)
|
||||||
if err := InitTendermintFiles(config, true, chainName); err != nil {
|
}
|
||||||
fmt.Fprintf(os.Stderr, "Failed to init files: %v\n", err)
|
if err := os.MkdirAll(filepath.Dir(defaultConfigPath), 0o755); err != nil {
|
||||||
panic(err)
|
return nil, nil, fmt.Errorf("failed to create config directory %s: %w", filepath.Dir(defaultConfigPath), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return config, viper
|
nodeinfo := p2p.DefaultNodeInfo{}
|
||||||
|
viper := WriteConfig(config, &defaultConfigPath, nodeinfo)
|
||||||
|
|
||||||
|
if err := InitTendermintFiles(config, true, chainName); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to init tendermint files: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, viper, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitJoiner(chainName, defaultConfigPath, path string) error {
|
func InitJoiner(chainName, defaultConfigPath, path string) error {
|
||||||
|
|
|
||||||
15
cli/init.go
15
cli/init.go
|
|
@ -2,11 +2,12 @@ package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"lbc/blockchain"
|
|
||||||
"lbc/cfg"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/gregorybednov/lbc/blockchain"
|
||||||
|
"github.com/gregorybednov/lbc/cfg"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -17,7 +18,11 @@ var initCmd = &cobra.Command{
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
switch args[0] {
|
switch args[0] {
|
||||||
case "genesis":
|
case "genesis":
|
||||||
config, viper := cfg.InitGenesis(chainName, defaultConfigPath)
|
config, viper, err := cfg.InitGenesis(chainName, defaultConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error: %v", err)
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
nodeinfo, err := blockchain.GetNodeInfo(config, dbPath)
|
nodeinfo, err := blockchain.GetNodeInfo(config, dbPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "Error: %v", err)
|
fmt.Fprintf(os.Stderr, "Error: %v", err)
|
||||||
|
|
@ -29,14 +34,14 @@ var initCmd = &cobra.Command{
|
||||||
case "join":
|
case "join":
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
fmt.Fprintln(os.Stderr, "Укажите путь к genesis.json")
|
fmt.Fprintln(os.Stderr, "Укажите путь к genesis.json")
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
cfg.InitJoiner(chainName, defaultConfigPath, args[1])
|
cfg.InitJoiner(chainName, defaultConfigPath, args[1])
|
||||||
|
|
||||||
fmt.Println("Joiner node initialized.")
|
fmt.Println("Joiner node initialized.")
|
||||||
default:
|
default:
|
||||||
fmt.Fprintf(os.Stderr, "Неизвестный режим init: %s\n", args[0])
|
fmt.Fprintf(os.Stderr, "Неизвестный режим init: %s\n", args[0])
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
47
cli/root.go
47
cli/root.go
|
|
@ -3,13 +3,16 @@ package cli
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"lbc/blockchain"
|
"io"
|
||||||
"lbc/cfg"
|
|
||||||
"lbc/yggdrasil"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
|
"path/filepath"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/gregorybednov/lbc/blockchain"
|
||||||
|
"github.com/gregorybednov/lbc/cfg"
|
||||||
|
"github.com/gregorybednov/lbc/yggdrasil"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -41,7 +44,7 @@ var rootCmd = &cobra.Command{
|
||||||
|
|
||||||
По умолчанию файл конфигурации ищется по пути: %s
|
По умолчанию файл конфигурации ищется по пути: %s
|
||||||
`, err, defaultConfigPath)
|
`, err, defaultConfigPath)
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := cfg.ReadConfig(defaultConfigPath)
|
config, err := cfg.ReadConfig(defaultConfigPath)
|
||||||
|
|
@ -50,7 +53,7 @@ var rootCmd = &cobra.Command{
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
laddrReturner := make(chan string, 2)
|
laddrReturner := make(chan string, 3)
|
||||||
go yggdrasil.Yggdrasil(v, laddrReturner)
|
go yggdrasil.Yggdrasil(v, laddrReturner)
|
||||||
go blockchain.Run(ctx, dbPath, config, laddrReturner)
|
go blockchain.Run(ctx, dbPath, config, laddrReturner)
|
||||||
|
|
||||||
|
|
@ -67,6 +70,38 @@ var rootCmd = &cobra.Command{
|
||||||
func Execute() {
|
func Execute() {
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "ошибка: %v\n", err)
|
fmt.Fprintf(os.Stderr, "ошибка: %v\n", err)
|
||||||
os.Exit(1)
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func ExecuteWithArgs(args []string, home string, stdout, stderr io.Writer) error {
|
||||||
|
// Перенаправляем вывод Cobra
|
||||||
|
if stdout != nil {
|
||||||
|
rootCmd.SetOut(stdout)
|
||||||
|
}
|
||||||
|
if stderr != nil {
|
||||||
|
rootCmd.SetErr(stderr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если задан home, подставим дефолты, на которые завязаны флаги
|
||||||
|
// (флаги привязаны к переменным через StringVar, поэтому смена переменных до Execute — норм.)
|
||||||
|
origCfg := defaultConfigPath
|
||||||
|
origDB := dbPath
|
||||||
|
|
||||||
|
if home != "" {
|
||||||
|
defaultConfigPath = filepath.Join(home, "config", "config.toml")
|
||||||
|
// Примем convention: BADGER в (home)/data/badger, но если у тебя другое — поменяй строку ниже.
|
||||||
|
dbPath = filepath.Join(home, "data", "badger")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Важное: подаём именно те аргументы, которые хотел вызвать вызывающий код.
|
||||||
|
rootCmd.SetArgs(args)
|
||||||
|
|
||||||
|
// Выполняем и аккуратно восстанавливаем глобальные дефолты.
|
||||||
|
err := rootCmd.Execute()
|
||||||
|
|
||||||
|
defaultConfigPath = origCfg
|
||||||
|
dbPath = origDB
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,6 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"lbc/cfg"
|
|
||||||
"lbc/yggdrasil"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
@ -15,6 +13,9 @@ import (
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gregorybednov/lbc/cfg"
|
||||||
|
"github.com/gregorybednov/lbc/yggdrasil"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ var testYggdrasilCmd = &cobra.Command{
|
||||||
v, err := cfg.LoadViperConfig(defaultConfigPath)
|
v, err := cfg.LoadViperConfig(defaultConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "не удалось прочитать конфигурацию viper: %v", err)
|
fmt.Fprintf(os.Stderr, "не удалось прочитать конфигурацию viper: %v", err)
|
||||||
os.Exit(1)
|
return err
|
||||||
}
|
}
|
||||||
config, err := cfg.ReadConfig(defaultConfigPath)
|
config, err := cfg.ReadConfig(defaultConfigPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
1
docs/ER.svg
Normal file
1
docs/ER.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 11 KiB |
43
docs/database_schema.md
Normal file
43
docs/database_schema.md
Normal file
|
|
@ -0,0 +1,43 @@
|
||||||
|
# Логическая модель данных
|
||||||
|
|
||||||
|
![[ER.svg]]
|
||||||
|
|
||||||
|
<details>
|
||||||
|
@startuml
|
||||||
|
|
||||||
|
entity Promise {
|
||||||
|
* ID: uuid
|
||||||
|
--
|
||||||
|
* text: string
|
||||||
|
due: datetime
|
||||||
|
BeneficiaryID: uuid
|
||||||
|
ParentPromiseID: uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
entity Beneficiary {
|
||||||
|
* ID: uuid
|
||||||
|
--
|
||||||
|
* name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
entity Commitment {
|
||||||
|
* ID: uuid
|
||||||
|
--
|
||||||
|
PromiseID: uuid
|
||||||
|
CommiterID: uuid
|
||||||
|
due: datetime
|
||||||
|
}
|
||||||
|
|
||||||
|
entity Commiter {
|
||||||
|
* ID: uuid
|
||||||
|
--
|
||||||
|
* name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
Commitment }|--|| Promise : belongs to
|
||||||
|
Commitment }|--|| Commiter : made by
|
||||||
|
Promise }o--|| Beneficiary : has
|
||||||
|
Promise }--o Promise : parent of
|
||||||
|
|
||||||
|
@enduml
|
||||||
|
</details>
|
||||||
3
go.mod
3
go.mod
|
|
@ -1,4 +1,4 @@
|
||||||
module lbc
|
module github.com/gregorybednov/lbc
|
||||||
|
|
||||||
go 1.24.3
|
go 1.24.3
|
||||||
|
|
||||||
|
|
@ -68,6 +68,7 @@ require (
|
||||||
github.com/google/orderedcode v0.0.1 // indirect
|
github.com/google/orderedcode v0.0.1 // indirect
|
||||||
github.com/google/pprof v0.0.0-20241017200806-017d972448fc // indirect
|
github.com/google/pprof v0.0.0-20241017200806-017d972448fc // indirect
|
||||||
github.com/gorilla/websocket v1.5.3 // indirect
|
github.com/gorilla/websocket v1.5.3 // indirect
|
||||||
|
github.com/gregorybednov/lbc_sdk v0.0.0-20250810123844-a90b874431fa // indirect
|
||||||
github.com/gtank/merlin v0.1.1 // indirect
|
github.com/gtank/merlin v0.1.1 // indirect
|
||||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
github.com/hjson/hjson-go/v4 v4.4.0 // indirect
|
github.com/hjson/hjson-go/v4 v4.4.0 // indirect
|
||||||
|
|
|
||||||
4
go.sum
4
go.sum
|
|
@ -195,6 +195,10 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||||
|
github.com/gregorybednov/lbc_sdk v0.0.0-20250810102513-432a51e65f76 h1:e3A+1v+Mjt8nuJcVnHuhHZuh4052KRLCUBpH4g74rVs=
|
||||||
|
github.com/gregorybednov/lbc_sdk v0.0.0-20250810102513-432a51e65f76/go.mod h1:DBE00+SaYBtD4qw+nOtSTLuF6h9Ia4TkuBMJB+6krik=
|
||||||
|
github.com/gregorybednov/lbc_sdk v0.0.0-20250810123844-a90b874431fa h1:8EuqAmsS94ju83o4aEIV8e2fdocdAJ9xVerKRSI+nDA=
|
||||||
|
github.com/gregorybednov/lbc_sdk v0.0.0-20250810123844-a90b874431fa/go.mod h1:DBE00+SaYBtD4qw+nOtSTLuF6h9Ia4TkuBMJB+6krik=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
|
github.com/gtank/merlin v0.1.1 h1:eQ90iG7K9pOhtereWsmyRJ6RAwcP4tHTDBHXNg+u5is=
|
||||||
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
|
github.com/gtank/merlin v0.1.1/go.mod h1:T86dnYJhcGOh5BjZFCJWTDeTK7XW8uE+E21Cy/bIQ+s=
|
||||||
|
|
|
||||||
2
main.go
2
main.go
|
|
@ -1,7 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"lbc/cli"
|
"github.com/gregorybednov/lbc/cli"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
|
||||||
|
|
@ -62,7 +62,7 @@ func Yggdrasil(config *viper.Viper, ch chan string) {
|
||||||
parsed, err := ParseEntries(peers)
|
parsed, err := ParseEntries(peers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
parsed = []ParsedEntry{}
|
parsed = []ParsedEntry{}
|
||||||
ch <- ""
|
// ch <- ""
|
||||||
log.Warnln("Warning: persistent peers has an error")
|
log.Warnln("Warning: persistent peers has an error")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -141,11 +141,18 @@ func Yggdrasil(config *viper.Viper, ch chan string) {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
address, subnet := n.core.Address(), n.core.Subnet()
|
address, subnet := n.core.Address(), n.core.Subnet()
|
||||||
publicstr := hex.EncodeToString(n.core.PublicKey())
|
yggPort := 26656
|
||||||
logger.Printf("Your public key is %s", publicstr)
|
if len(remoteTcp) > 0 && remoteTcp[0].Listen.Port != 0 {
|
||||||
|
yggPort = remoteTcp[0].Listen.Port
|
||||||
|
}
|
||||||
|
ipStr := address.String() // ожидается чистый IPv6 без /префикса
|
||||||
|
yggExternal := fmt.Sprintf("[%s]:%d", ipStr, yggPort)
|
||||||
|
ch <- yggExternal
|
||||||
|
|
||||||
|
//logger.Printf("Your public key is %s", publicstr)
|
||||||
logger.Printf("Your IPv6 address is %s", address.String())
|
logger.Printf("Your IPv6 address is %s", address.String())
|
||||||
logger.Printf("Your IPv6 subnet is %s", subnet.String())
|
logger.Printf("Your IPv6 subnet is %s", subnet.String())
|
||||||
logger.Printf("Your Yggstack resolver name is %s%s", publicstr, types.NameMappingSuffix)
|
//logger.Printf("Your Yggstack resolver name is %s%s", publicstr, types.NameMappingSuffix)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setup the admin socket.
|
// Setup the admin socket.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue