major update
This commit is contained in:
parent
9c2d1aa992
commit
a8f460388a
3 changed files with 425 additions and 126 deletions
2
go.mod
2
go.mod
|
|
@ -4,6 +4,6 @@ go 1.24.3
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/gregorybednov/lbc_sdk v0.0.0-20250810102513-432a51e65f76
|
github.com/gregorybednov/lbc_sdk v0.0.0-20250810123844-a90b874431fa
|
||||||
github.com/spf13/pflag v1.0.7
|
github.com/spf13/pflag v1.0.7
|
||||||
)
|
)
|
||||||
|
|
|
||||||
2
go.sum
2
go.sum
|
|
@ -2,5 +2,7 @@ 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/gregorybednov/lbc_sdk v0.0.0-20250810102513-432a51e65f76 h1:e3A+1v+Mjt8nuJcVnHuhHZuh4052KRLCUBpH4g74rVs=
|
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-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/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
||||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
|
|
||||||
507
main.go
507
main.go
|
|
@ -6,18 +6,21 @@ import (
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
||||||
types "github.com/gregorybednov/lbc_sdk"
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ===== RPC plumbing =====
|
||||||
|
|
||||||
type rpcResp struct {
|
type rpcResp struct {
|
||||||
JSONRPC string `json:"jsonrpc"`
|
JSONRPC string `json:"jsonrpc"`
|
||||||
ID any `json:"id"`
|
ID any `json:"id"`
|
||||||
|
|
@ -38,10 +41,6 @@ type rpcResp struct {
|
||||||
} `json:"result"`
|
} `json:"result"`
|
||||||
}
|
}
|
||||||
|
|
||||||
const configDir = "./config"
|
|
||||||
const privKeyPath = configDir + "/ed25519.key"
|
|
||||||
const pubKeyPath = configDir + "/ed25519.pub"
|
|
||||||
|
|
||||||
func parseRPCResult(data []byte) error {
|
func parseRPCResult(data []byte) error {
|
||||||
var r rpcResp
|
var r rpcResp
|
||||||
_ = json.Unmarshal(data, &r)
|
_ = json.Unmarshal(data, &r)
|
||||||
|
|
@ -60,6 +59,31 @@ func parseRPCResult(data []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func postRPC(txB64, rpcURL string) error {
|
||||||
|
final := map[string]any{
|
||||||
|
"jsonrpc": "2.0",
|
||||||
|
"id": uuid.NewString(),
|
||||||
|
"method": "broadcast_tx_commit",
|
||||||
|
"params": map[string]string{
|
||||||
|
"tx": txB64,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
finalBytes, _ := json.Marshal(final)
|
||||||
|
resp, err := http.Post(rpcURL, "application/json", bytes.NewReader(finalBytes))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
data, _ := io.ReadAll(resp.Body)
|
||||||
|
return parseRPCResult(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Keys =====
|
||||||
|
|
||||||
|
const configDir = "./config"
|
||||||
|
const privKeyPath = configDir + "/ed25519.key"
|
||||||
|
const pubKeyPath = configDir + "/ed25519.pub"
|
||||||
|
|
||||||
func ensureKeypair() (ed25519.PublicKey, ed25519.PrivateKey, error) {
|
func ensureKeypair() (ed25519.PublicKey, ed25519.PrivateKey, error) {
|
||||||
if _, err := os.Stat(privKeyPath); os.IsNotExist(err) {
|
if _, err := os.Stat(privKeyPath); os.IsNotExist(err) {
|
||||||
fmt.Println("🔐 Generating new ed25519 keypair...")
|
fmt.Println("🔐 Generating new ed25519 keypair...")
|
||||||
|
|
@ -67,163 +91,436 @@ func ensureKeypair() (ed25519.PublicKey, ed25519.PrivateKey, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
os.MkdirAll(configDir, 0700)
|
_ = os.MkdirAll(configDir, 0700)
|
||||||
os.WriteFile(privKeyPath, priv, 0600)
|
_ = os.WriteFile(privKeyPath, priv, 0600)
|
||||||
os.WriteFile(pubKeyPath, pub, 0644)
|
_ = os.WriteFile(pubKeyPath, pub, 0644)
|
||||||
return pub, priv, nil
|
return pub, priv, nil
|
||||||
}
|
}
|
||||||
priv, _ := os.ReadFile(privKeyPath)
|
priv, err := os.ReadFile(privKeyPath)
|
||||||
pub, _ := os.ReadFile(pubKeyPath)
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
pub, err := os.ReadFile(pubKeyPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
return ed25519.PublicKey(pub), ed25519.PrivateKey(priv), nil
|
return ed25519.PublicKey(pub), ed25519.PrivateKey(priv), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendTx(tx types.SignedTx, rpcURL string) error {
|
// ===== Tx bodies per ER =====
|
||||||
txBytes, _ := json.Marshal(tx)
|
|
||||||
txB64 := base64.StdEncoding.EncodeToString(txBytes)
|
type CommiterTxBody struct {
|
||||||
final := map[string]any{
|
Type string `json:"type"` // "commiter"
|
||||||
"jsonrpc": "2.0",
|
ID string `json:"id"`
|
||||||
"id": uuid.NewString(),
|
Name string `json:"name"`
|
||||||
"method": "broadcast_tx_commit",
|
CommiterPubKey string `json:"commiter_pubkey"`
|
||||||
"params": map[string]string{
|
|
||||||
"tx": txB64,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
finalBytes, _ := json.Marshal(final)
|
|
||||||
fmt.Println("🚧 Raw tx JSON:", string(txBytes))
|
type BeneficiaryTxBody struct {
|
||||||
fmt.Printf("🚧 Final RPC body:\n%s\n", string(finalBytes))
|
Type string `json:"type"` // "beneficiary"
|
||||||
resp, err := http.Post(rpcURL, "application/json", bytes.NewReader(finalBytes))
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PromiseTxBody struct {
|
||||||
|
Type string `json:"type"` // "promise"
|
||||||
|
ID string `json:"id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
Due int64 `json:"due"`
|
||||||
|
BeneficiaryID string `json:"beneficiary_id"`
|
||||||
|
ParentPromiseID *string `json:"parent_promise_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommitmentTxBody struct {
|
||||||
|
Type string `json:"type"` // "commitment"
|
||||||
|
ID string `json:"id"`
|
||||||
|
PromiseID string `json:"promise_id"`
|
||||||
|
CommiterID string `json:"commiter_id"`
|
||||||
|
Due int64 `json:"due"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SignedTx struct {
|
||||||
|
Body any `json:"body"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompositeSignedTx struct {
|
||||||
|
Body struct {
|
||||||
|
Promise *PromiseTxBody `json:"promise"`
|
||||||
|
Commitment *CommitmentTxBody `json:"commitment"`
|
||||||
|
} `json:"body"`
|
||||||
|
Signature string `json:"signature"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Helpers =====
|
||||||
|
|
||||||
|
func mustUUID(prefix string) string { return prefix + ":" + uuid.NewString() }
|
||||||
|
|
||||||
|
func parseWhen(s string) (int64, error) {
|
||||||
|
if s == "" {
|
||||||
|
return 0, errors.New("missing datetime")
|
||||||
|
}
|
||||||
|
if t, err := time.Parse(time.RFC3339, s); err == nil {
|
||||||
|
return t.Unix(), nil
|
||||||
|
}
|
||||||
|
if t, err := time.Parse("2006-01-02", s); err == nil {
|
||||||
|
return time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.UTC).Unix(), nil
|
||||||
|
}
|
||||||
|
return 0, fmt.Errorf("cannot parse time: %q (use 2006-01-02 or RFC3339)", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func sign(priv ed25519.PrivateKey, body any) (sigB64 string, raw []byte, err error) {
|
||||||
|
raw, err = json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
sig := ed25519.Sign(priv, raw)
|
||||||
|
return base64.StdEncoding.EncodeToString(sig), raw, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== High-level ops: send =====
|
||||||
|
|
||||||
|
func registerCommiter(name, rpcURL string) error {
|
||||||
|
pub, priv, err := ensureKeypair()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
|
||||||
data, _ := io.ReadAll(resp.Body)
|
|
||||||
fmt.Println("📡 Response:")
|
|
||||||
fmt.Println(string(data))
|
|
||||||
if err := parseRPCResult(data); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func registerCommiter(name, rpcURL string) error {
|
|
||||||
pub, priv, _ := ensureKeypair()
|
|
||||||
pubB64 := base64.StdEncoding.EncodeToString(pub)
|
pubB64 := base64.StdEncoding.EncodeToString(pub)
|
||||||
id := "commiter:" + pubB64
|
id := "commiter:" + pubB64
|
||||||
body := types.CommiterTxBody{
|
body := CommiterTxBody{
|
||||||
Type: "commiter",
|
Type: "commiter",
|
||||||
ID: id,
|
ID: id,
|
||||||
Name: name,
|
Name: name,
|
||||||
CommiterPubKey: pubB64,
|
CommiterPubKey: pubB64,
|
||||||
}
|
}
|
||||||
bodyBytes, _ := json.Marshal(body)
|
sigB64, _, err := sign(priv, body)
|
||||||
sig := ed25519.Sign(priv, bodyBytes)
|
if err != nil {
|
||||||
tx := types.SignedTx{Body: body, Signature: base64.StdEncoding.EncodeToString(sig)}
|
return err
|
||||||
return sendTx(tx, rpcURL)
|
}
|
||||||
|
tx := SignedTx{Body: body, Signature: sigB64}
|
||||||
|
txBytes, _ := json.Marshal(tx)
|
||||||
|
return postRPC(base64.StdEncoding.EncodeToString(txBytes), rpcURL)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createPromise(desc, rpcURL string) error {
|
func createBeneficiary(name, rpcURL string) (string, error) {
|
||||||
pub, priv, _ := ensureKeypair()
|
_, priv, err := ensureKeypair()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
id := mustUUID("beneficiary")
|
||||||
|
body := BeneficiaryTxBody{
|
||||||
|
Type: "beneficiary",
|
||||||
|
ID: id,
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
|
sigB64, _, err := sign(priv, body)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
tx := SignedTx{Body: body, Signature: sigB64}
|
||||||
|
txBytes, _ := json.Marshal(tx)
|
||||||
|
if err := postRPC(base64.StdEncoding.EncodeToString(txBytes), rpcURL); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return id, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type CreatePromiseArgs struct {
|
||||||
|
Text string
|
||||||
|
DueISO string
|
||||||
|
BeneficiaryID string
|
||||||
|
ParentPromiseID string
|
||||||
|
CommitmentDueISO string
|
||||||
|
}
|
||||||
|
|
||||||
|
func createPromiseAndCommit(args CreatePromiseArgs, rpcURL string) error {
|
||||||
|
if args.Text == "" {
|
||||||
|
return errors.New("--text is required")
|
||||||
|
}
|
||||||
|
if args.BeneficiaryID == "" {
|
||||||
|
return errors.New("--beneficiary-id is required")
|
||||||
|
}
|
||||||
|
promiseDue, err := parseWhen(args.DueISO)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("promise --due: %w", err)
|
||||||
|
}
|
||||||
|
commitDue, err := parseWhen(args.CommitmentDueISO)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("commitment --commitment-due: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, priv, err := ensureKeypair()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
pubB64 := base64.StdEncoding.EncodeToString(pub)
|
pubB64 := base64.StdEncoding.EncodeToString(pub)
|
||||||
|
|
||||||
commiterID := "commiter:" + pubB64
|
commiterID := "commiter:" + pubB64
|
||||||
promiseID := "promise:" + uuid.NewString()
|
|
||||||
commitmentID := "commitment:" + uuid.NewString()
|
|
||||||
|
|
||||||
// Объекты тела
|
promiseID := mustUUID("promise")
|
||||||
promise := &types.PromiseTxBody{
|
commitmentID := mustUUID("commitment")
|
||||||
|
|
||||||
|
var parentPtr *string
|
||||||
|
if args.ParentPromiseID != "" {
|
||||||
|
p := args.ParentPromiseID
|
||||||
|
parentPtr = &p
|
||||||
|
}
|
||||||
|
|
||||||
|
promise := &PromiseTxBody{
|
||||||
Type: "promise",
|
Type: "promise",
|
||||||
ID: promiseID,
|
ID: promiseID,
|
||||||
Description: desc,
|
Text: args.Text,
|
||||||
Timestamp: time.Now().Unix(),
|
Due: promiseDue,
|
||||||
|
BeneficiaryID: args.BeneficiaryID,
|
||||||
|
ParentPromiseID: parentPtr,
|
||||||
}
|
}
|
||||||
commitment := &types.CommitmentTxBody{
|
commitment := &CommitmentTxBody{
|
||||||
Type: "commitment",
|
Type: "commitment",
|
||||||
ID: commitmentID,
|
ID: commitmentID,
|
||||||
PromiseID: promiseID,
|
PromiseID: promiseID,
|
||||||
CommiterID: commiterID,
|
CommiterID: commiterID,
|
||||||
|
Due: commitDue,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Сборка тела для подписи
|
var compound CompositeSignedTx
|
||||||
bodyStruct := struct {
|
compound.Body.Promise = promise
|
||||||
Promise *types.PromiseTxBody `json:"promise"`
|
compound.Body.Commitment = commitment
|
||||||
Commitment *types.CommitmentTxBody `json:"commitment"`
|
|
||||||
}{
|
|
||||||
Promise: promise,
|
|
||||||
Commitment: commitment,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сериализация и подпись
|
sigB64, _, err := sign(priv, compound.Body)
|
||||||
bodyBytes, err := json.Marshal(bodyStruct)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal compound body: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
sig := ed25519.Sign(priv, bodyBytes)
|
compound.Signature = sigB64
|
||||||
sigB64 := base64.StdEncoding.EncodeToString(sig)
|
|
||||||
|
|
||||||
// Формирование транзакции
|
|
||||||
compound := types.CompoundTx{
|
|
||||||
Body: bodyStruct,
|
|
||||||
Signature: sigB64,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Отправка
|
|
||||||
txBytes, err := json.Marshal(compound)
|
txBytes, err := json.Marshal(compound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal compound tx: %w", err)
|
return err
|
||||||
}
|
}
|
||||||
txB64 := base64.StdEncoding.EncodeToString(txBytes)
|
return postRPC(base64.StdEncoding.EncodeToString(txBytes), rpcURL)
|
||||||
final := map[string]any{
|
|
||||||
"jsonrpc": "2.0",
|
|
||||||
"id": uuid.NewString(),
|
|
||||||
"method": "broadcast_tx_commit",
|
|
||||||
"params": map[string]string{
|
|
||||||
"tx": txB64,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
finalBytes, _ := json.Marshal(final)
|
// ===== High-level ops: get (abci_query) =====
|
||||||
fmt.Printf("🚧 Final RPC body:\n%s\n", string(finalBytes))
|
|
||||||
resp, err := http.Post(rpcURL, "application/json", bytes.NewReader(finalBytes))
|
type abciQueryResp struct {
|
||||||
|
JSONRPC string `json:"jsonrpc"`
|
||||||
|
ID any `json:"id"`
|
||||||
|
Result struct {
|
||||||
|
Response struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Log string `json:"log"`
|
||||||
|
Info string `json:"info"`
|
||||||
|
Index string `json:"index"`
|
||||||
|
Key string `json:"key"`
|
||||||
|
Value string `json:"value"` // base64
|
||||||
|
ProofOps any `json:"proofOps"`
|
||||||
|
Height string `json:"height"`
|
||||||
|
Codespace string `json:"codespace"`
|
||||||
|
} `json:"response"`
|
||||||
|
} `json:"result"`
|
||||||
|
Error *struct {
|
||||||
|
Code int `json:"code"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
Data string `json:"data"`
|
||||||
|
} `json:"error"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func abciQuery(rpcURL, path, dataB64 string, height string) (*abciQueryResp, error) {
|
||||||
|
v := url.Values{}
|
||||||
|
// Tendermint любит path в кавычках, как в твоём примере
|
||||||
|
v.Set("path", fmt.Sprintf("%q", path))
|
||||||
|
if dataB64 != "" {
|
||||||
|
v.Set("data", dataB64)
|
||||||
|
}
|
||||||
|
if height != "" {
|
||||||
|
v.Set("height", height)
|
||||||
|
}
|
||||||
|
u := strings.TrimRight(rpcURL, "/") + "/abci_query?" + v.Encode()
|
||||||
|
resp, err := http.Get(u)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
data, _ := io.ReadAll(resp.Body)
|
b, _ := io.ReadAll(resp.Body)
|
||||||
fmt.Println("📡 Response:")
|
var q abciQueryResp
|
||||||
fmt.Println(string(data))
|
if err := json.Unmarshal(b, &q); err != nil {
|
||||||
if err := parseRPCResult(data); err != nil {
|
return nil, fmt.Errorf("decode json: %w", err)
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
return nil
|
if q.Error != nil {
|
||||||
|
return nil, fmt.Errorf("RPC error: %d %s (%s)", q.Error.Code, q.Error.Message, q.Error.Data)
|
||||||
}
|
}
|
||||||
|
return &q, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func printRawJSON(obj any) {
|
||||||
|
out, _ := json.MarshalIndent(obj, "", " ")
|
||||||
|
fmt.Println(string(out))
|
||||||
|
}
|
||||||
|
|
||||||
|
func tryPrintValueAsJSONOrText(b []byte) {
|
||||||
|
// попытка как JSON
|
||||||
|
var anyJSON any
|
||||||
|
if json.Unmarshal(b, &anyJSON) == nil {
|
||||||
|
printRawJSON(anyJSON)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// если выглядит как текст — печатаем строкой
|
||||||
|
s := string(b)
|
||||||
|
// грубая эвристика: если есть нулевые байты — покажем как base64
|
||||||
|
if strings.IndexByte(s, 0x00) >= 0 {
|
||||||
|
fmt.Println(base64.StdEncoding.EncodeToString(b))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Println(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== CLI =====
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
var name, desc, rpc string
|
if len(os.Args) < 2 {
|
||||||
pflag.StringVar(&name, "name", "", "register commiter name")
|
usage()
|
||||||
pflag.StringVar(&desc, "desc", "", "create promise description")
|
os.Exit(1)
|
||||||
pflag.StringVar(&rpc, "rpc", "http://localhost:26657", "Tendermint RPC URL")
|
}
|
||||||
pflag.Parse()
|
cmd := os.Args[1]
|
||||||
|
switch cmd {
|
||||||
|
case "send":
|
||||||
|
sendMain(os.Args[2:])
|
||||||
|
case "get":
|
||||||
|
getMain(os.Args[2:])
|
||||||
|
default:
|
||||||
|
usage()
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if name != "" {
|
func usage() {
|
||||||
err := registerCommiter(name, rpc)
|
fmt.Println("Usage:")
|
||||||
|
fmt.Println(" lbc_client send [--rpc URL] (--name NAME | --beneficiary-name NAME | --text TXT --due DATE --beneficiary-id ID [--parent-id ID] --commitment-due DATE)")
|
||||||
|
fmt.Println(" lbc_client get [--rpc URL] --path PATH [--data BYTES] [--height H] [--raw-json | --value]")
|
||||||
|
}
|
||||||
|
|
||||||
|
func sendMain(args []string) {
|
||||||
|
fs := pflag.NewFlagSet("send", pflag.ExitOnError)
|
||||||
|
var rpc string
|
||||||
|
var name string
|
||||||
|
var beneficiaryName string
|
||||||
|
var text, due, beneficiaryID, parentID, commitmentDue string
|
||||||
|
|
||||||
|
fs.StringVar(&rpc, "rpc", "http://localhost:26657", "Tendermint RPC URL")
|
||||||
|
fs.StringVar(&name, "name", "", "register commiter name")
|
||||||
|
fs.StringVar(&beneficiaryName, "beneficiary-name", "", "create beneficiary with a given name")
|
||||||
|
fs.StringVar(&text, "text", "", "promise text (required for promise)")
|
||||||
|
fs.StringVar(&due, "due", "", "promise due (YYYY-MM-DD or RFC3339)")
|
||||||
|
fs.StringVar(&beneficiaryID, "beneficiary-id", "", "beneficiary ID (required for promise)")
|
||||||
|
fs.StringVar(&parentID, "parent-id", "", "optional parent promise ID")
|
||||||
|
fs.StringVar(&commitmentDue, "commitment-due", "", "commitment due (YYYY-MM-DD or RFC3339)")
|
||||||
|
|
||||||
|
_ = fs.Parse(args)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case name != "":
|
||||||
|
if err := registerCommiter(name, rpc); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("✅ Commiter registered")
|
||||||
|
case beneficiaryName != "":
|
||||||
|
id, err := createBeneficiary(beneficiaryName, rpc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("✅ Commiter registered successfully")
|
fmt.Printf("✅ Beneficiary created: %s\n", id)
|
||||||
return
|
default:
|
||||||
|
// promise+commitment
|
||||||
|
if text == "" || due == "" || beneficiaryID == "" || commitmentDue == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "⛔ For promise+commitment you must pass --text, --due, --beneficiary-id, --commitment-due")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
args := CreatePromiseArgs{
|
||||||
|
Text: text,
|
||||||
|
DueISO: due,
|
||||||
|
BeneficiaryID: beneficiaryID,
|
||||||
|
ParentPromiseID: parentID,
|
||||||
|
CommitmentDueISO: commitmentDue,
|
||||||
|
}
|
||||||
|
if err := createPromiseAndCommit(args, rpc); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
fmt.Println("✅ Promise+Commitment created atomically")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if desc != "" {
|
func getMain(args []string) {
|
||||||
err := createPromise(desc, rpc)
|
fs := pflag.NewFlagSet("get", pflag.ExitOnError)
|
||||||
|
var rpc string
|
||||||
|
var path string
|
||||||
|
var listAlias string
|
||||||
|
var dataArg string
|
||||||
|
var height string
|
||||||
|
var rawJSON bool
|
||||||
|
var value bool
|
||||||
|
|
||||||
|
fs.StringVar(&rpc, "rpc", "http://localhost:26657", "Tendermint RPC URL")
|
||||||
|
fs.StringVar(&path, "path", "", "ABCI path (e.g. /list/promise)")
|
||||||
|
fs.StringVar(&listAlias, "list", "", "entity alias: promise | commitment | commiter | beneficiary")
|
||||||
|
fs.StringVar(&dataArg, "data", "", "optional key/arg (sent as base64)")
|
||||||
|
fs.StringVar(&height, "height", "", "block height")
|
||||||
|
fs.BoolVar(&rawJSON, "raw-json", false, "print raw abci_query JSON")
|
||||||
|
fs.BoolVar(&value, "value", false, "decode response.value (base64) and try to parse JSON")
|
||||||
|
|
||||||
|
_ = fs.Parse(args)
|
||||||
|
|
||||||
|
// алиасы -> path
|
||||||
|
if path == "" && listAlias != "" {
|
||||||
|
switch listAlias {
|
||||||
|
case "promise", "commitment", "commiter", "beneficiary":
|
||||||
|
path = "/list/" + listAlias
|
||||||
|
default:
|
||||||
|
fmt.Fprintf(os.Stderr, "⛔ unknown alias for --list: %q\n", listAlias)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if path == "" {
|
||||||
|
fmt.Fprintln(os.Stderr, "⛔ Either --path or --list is required")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// поведение по умолчанию:
|
||||||
|
// - при --list, если ни --raw-json, ни --value не заданы → включаем --value
|
||||||
|
// - при --path — остаётся прежняя логика (тоже включает --value по умолчанию)
|
||||||
|
if !rawJSON && !value {
|
||||||
|
value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// data → base64
|
||||||
|
var dataB64 string
|
||||||
|
if dataArg != "" {
|
||||||
|
dataB64 = base64.StdEncoding.EncodeToString([]byte(dataArg))
|
||||||
|
}
|
||||||
|
|
||||||
|
q, err := abciQuery(rpc, path, dataB64, height)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
|
fmt.Fprintf(os.Stderr, "❌ Error: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println("✅ Promise and Commitment created successfully")
|
|
||||||
|
if rawJSON {
|
||||||
|
printRawJSON(q)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("⛔ Please provide either --name or --desc")
|
// value mode
|
||||||
os.Exit(1)
|
valB64 := q.Result.Response.Value
|
||||||
|
if valB64 == "" {
|
||||||
|
fmt.Println("")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
raw, err := base64.StdEncoding.DecodeString(valB64)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "⚠️ cannot base64-decode value: %v\n", err)
|
||||||
|
fmt.Println(valB64)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
tryPrintValueAsJSONOrText(raw)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue