Skip to content

Commit 073700b

Browse files
Fix ic, add action debug helpers. Match Index file format with sdk-js (#241)
* Fix ic, add action debug helpers. Match Index file format with sdk-js * Fix unit tests in gh pipeline
1 parent b2a2c8a commit 073700b

File tree

4 files changed

+245
-8
lines changed

4 files changed

+245
-8
lines changed

pkg/cascadekit/index.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,20 @@ const SeparatorByte byte = 46
1414
// IndexFile represents the structure of the index file referenced on-chain.
1515
// The JSON fields must match the existing format.
1616
type IndexFile struct {
17-
Version int `json:"version,omitempty"`
17+
// Note: field order is chosen to match the JS SDK's canonical JSON:
18+
// {"layout_ids":[...],"layout_signature":"...","version":1}
1819
LayoutIDs []string `json:"layout_ids"`
1920
LayoutSignature string `json:"layout_signature"`
21+
Version int `json:"version,omitempty"`
2022
}
2123

2224
// BuildIndex creates an IndexFile from layout IDs and the layout signature.
2325
func BuildIndex(layoutIDs []string, layoutSigB64 string) IndexFile {
24-
return IndexFile{LayoutIDs: layoutIDs, LayoutSignature: layoutSigB64}
26+
return IndexFile{
27+
LayoutIDs: layoutIDs,
28+
LayoutSignature: layoutSigB64,
29+
Version: 1,
30+
}
2531
}
2632

2733
// EncodeIndexB64 marshals an index file and returns its base64-encoded JSON.

sdk/action/client.go

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package action
22

33
import (
44
"context"
5+
crand "crypto/rand"
56
"encoding/base64"
67
"fmt"
8+
"math/big"
79
"os"
810
"path/filepath"
911
"strconv"
@@ -282,8 +284,8 @@ func (c *ClientImpl) BuildCascadeMetadataFromFile(ctx context.Context, filePath
282284
max = 50
283285
}
284286
// Pick a random initial counter in [1,100]
285-
//rnd, _ := crand.Int(crand.Reader, big.NewInt(100))
286-
ic := uint32(6)
287+
rnd, _ := crand.Int(crand.Reader, big.NewInt(100))
288+
ic := uint32(rnd.Int64() + 1) // 1..100
287289

288290
// Create signatures from the layout struct using ADR-36 scheme (JS compatible).
289291
indexSignatureFormat, _, err := cascadekit.CreateSignaturesWithKeyringADR36(
@@ -386,7 +388,3 @@ func (c *ClientImpl) GenerateDownloadSignature(ctx context.Context, actionID, cr
386388
}
387389
return base64.StdEncoding.EncodeToString(sig), nil
388390
}
389-
390-
func (c *ClientImpl) signerAddress() string {
391-
return c.signerAddr
392-
}

supernode/cascade/debug_action.go

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package cascade
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
8+
actiontypes "github.com/LumeraProtocol/lumera/x/action/v1/types"
9+
"github.com/LumeraProtocol/supernode/v2/pkg/cascadekit"
10+
)
11+
12+
// DebugReverseEngineerAction fetches an action by ID, decodes its Cascade
13+
// metadata and embedded index information, and prints a detailed breakdown.
14+
// It performs read-only inspection and is intended for internal debugging.
15+
func (task *CascadeRegistrationTask) DebugReverseEngineerAction(ctx context.Context, actionID string) error {
16+
_ = ctx // kept for parity with other methods; not used here
17+
18+
if actionID == "" {
19+
fmt.Println("DebugReverseEngineerAction: empty actionID, nothing to do")
20+
return nil
21+
}
22+
23+
fmt.Printf("DebugReverseEngineerAction: fetching action %s\n", actionID)
24+
25+
// Step 1: Fetch the action from the chain
26+
action, err := task.fetchAction(context.Background(), actionID, nil)
27+
if err != nil {
28+
fmt.Printf("DebugReverseEngineerAction: error fetching action %s: %v\n", actionID, err)
29+
return err
30+
}
31+
32+
// Step 2: Print basic action summary
33+
fmt.Println("=== Action Summary ===")
34+
fmt.Printf("Action ID : %s\n", action.ActionID)
35+
fmt.Printf("Creator : %s\n", action.Creator)
36+
fmt.Printf("Action Type : %s\n", action.ActionType.String())
37+
fmt.Printf("State : %s\n", action.State.String())
38+
fmt.Printf("Block Height : %d\n", action.BlockHeight)
39+
fmt.Printf("Price : %s\n", action.Price)
40+
fmt.Printf("Expiration (unix): %d\n", action.ExpirationTime)
41+
fmt.Printf("Metadata bytes : %d\n", len(action.Metadata))
42+
fmt.Printf("Supernodes (%d) : %v\n", len(action.SuperNodes), action.SuperNodes)
43+
44+
// Only Cascade actions carry CascadeMetadata
45+
if action.ActionType != actiontypes.ActionTypeCascade {
46+
fmt.Println("Action is not of type CASCADE; skipping cascade-specific decoding.")
47+
return nil
48+
}
49+
50+
if len(action.Metadata) == 0 {
51+
fmt.Println("Cascade action has empty metadata; nothing to decode.")
52+
return nil
53+
}
54+
55+
// Step 3: Decode Cascade metadata
56+
cascadeMeta, err := cascadekit.UnmarshalCascadeMetadata(action.Metadata)
57+
if err != nil {
58+
fmt.Printf("Failed to unmarshal cascade metadata: %v\n", err)
59+
return err
60+
}
61+
62+
fmt.Println("\n=== Cascade Metadata (summary) ===")
63+
fmt.Printf("Data hash (b64) : %s\n", cascadeMeta.DataHash)
64+
fmt.Printf("File name : %s\n", cascadeMeta.FileName)
65+
fmt.Printf("rq_ids_ic : %d\n", cascadeMeta.RqIdsIc)
66+
fmt.Printf("rq_ids_max : %d\n", cascadeMeta.RqIdsMax)
67+
fmt.Printf("rq_ids_ids (#) : %d\n", len(cascadeMeta.RqIdsIds))
68+
fmt.Printf("Public : %v\n", cascadeMeta.Public)
69+
if len(cascadeMeta.RqIdsIds) > 0 {
70+
fmt.Printf("rq_ids_ids : %v\n", cascadeMeta.RqIdsIds)
71+
}
72+
fmt.Printf("Signatures len : %d\n", len(cascadeMeta.Signatures))
73+
74+
if metaJSON, mErr := json.MarshalIndent(cascadeMeta, "", " "); mErr == nil {
75+
fmt.Println("\n=== Cascade Metadata (JSON) ===")
76+
fmt.Println(string(metaJSON))
77+
}
78+
79+
// Step 4: Decode index information from the signatures field
80+
if cascadeMeta.Signatures == "" {
81+
fmt.Println("\nCascade metadata has empty signatures field; cannot decode index.")
82+
return nil
83+
}
84+
85+
indexB64, creatorSigB64, err := cascadekit.ExtractIndexAndCreatorSig(cascadeMeta.Signatures)
86+
if err != nil {
87+
fmt.Printf("Failed to extract index and creator signature: %v\n", err)
88+
return err
89+
}
90+
91+
fmt.Println("\n=== Index Signature Components ===")
92+
fmt.Printf("index_b64 length : %d\n", len(indexB64))
93+
fmt.Printf("creator_sig_b64 length : %d\n", len(creatorSigB64))
94+
if len(indexB64) > 0 {
95+
previewLen := 64
96+
if len(indexB64) < previewLen {
97+
previewLen = len(indexB64)
98+
}
99+
fmt.Printf("index_b64 prefix : %s\n", indexB64[:previewLen])
100+
}
101+
if len(creatorSigB64) > 0 {
102+
previewLen := 64
103+
if len(creatorSigB64) < previewLen {
104+
previewLen = len(creatorSigB64)
105+
}
106+
fmt.Printf("creator_sig_b64 prefix : %s\n", creatorSigB64[:previewLen])
107+
}
108+
109+
// Step 5: Decode the logical index file (base64(JSON(IndexFile)))
110+
indexFile, err := cascadekit.DecodeIndexB64(indexB64)
111+
if err != nil {
112+
fmt.Printf("Failed to decode index file from base64: %v\n", err)
113+
return err
114+
}
115+
116+
fmt.Println("\n=== Index File (summary) ===")
117+
fmt.Printf("Version : %d\n", indexFile.Version)
118+
fmt.Printf("Layout IDs (#) : %d\n", len(indexFile.LayoutIDs))
119+
fmt.Printf("Layout IDs : %v\n", indexFile.LayoutIDs)
120+
fmt.Printf("Layout signature len : %d\n", len(indexFile.LayoutSignature))
121+
if layoutSig := indexFile.LayoutSignature; layoutSig != "" {
122+
previewLen := 64
123+
if len(layoutSig) < previewLen {
124+
previewLen = len(layoutSig)
125+
}
126+
fmt.Printf("Layout signature prefix: %s\n", layoutSig[:previewLen])
127+
}
128+
129+
if indexJSON, iErr := json.MarshalIndent(indexFile, "", " "); iErr == nil {
130+
fmt.Println("\n=== Index File (JSON) ===")
131+
fmt.Println(string(indexJSON))
132+
}
133+
134+
fmt.Println("\nDebugReverseEngineerAction: completed successfully.")
135+
136+
return nil
137+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
package cascade
2+
3+
//NOTE: Commented intentionally as this is not a real test rather a helper for manula debugging
4+
5+
// import (
6+
// "context"
7+
// "os"
8+
// "path/filepath"
9+
// "testing"
10+
11+
// "github.com/LumeraProtocol/supernode/v2/pkg/keyring"
12+
// "github.com/LumeraProtocol/supernode/v2/pkg/logtrace"
13+
// "github.com/LumeraProtocol/supernode/v2/pkg/lumera"
14+
// "github.com/LumeraProtocol/supernode/v2/supernode/config"
15+
// )
16+
17+
// // debugActionID is intentionally a constant so it is easy to change
18+
// // and re-run this helper test from VS Code for different actions.
19+
// // Set it to a real on-chain Cascade action ID before running.
20+
// const debugActionID = "10113"
21+
22+
// // TestDebugReverseEngineerAction is a convenience wrapper around
23+
// // DebugReverseEngineerAction. It is not a real unit test; it simply
24+
// // wires up configuration and logs a detailed breakdown for a single
25+
// // action ID, making it easy to inspect from VS Code.
26+
// func TestDebugReverseEngineerAction(t *testing.T) {
27+
// if debugActionID == "" {
28+
// t.Skip("set debugActionID to a real action ID to run this debug helper")
29+
// }
30+
31+
// // Initialize Cosmos SDK config (Bech32 prefixes, etc.).
32+
// keyring.InitSDKConfig()
33+
34+
// // Use the same logging setup as the supernode binary for consistent output.
35+
// logtrace.Setup("supernode-debug")
36+
37+
// ctx := context.Background()
38+
39+
// // Derive base directory and config path as in the supernode CLI.
40+
// homeDir, err := os.UserHomeDir()
41+
// if err != nil {
42+
// t.Fatalf("failed to get home directory: %v", err)
43+
// }
44+
// baseDir := filepath.Join(homeDir, ".supernode")
45+
// cfgFile := filepath.Join(baseDir, "config.yml")
46+
47+
// cfg, err := config.LoadConfig(cfgFile, baseDir)
48+
// if err != nil {
49+
// t.Fatalf("failed to load supernode config from %s: %v", cfgFile, err)
50+
// }
51+
52+
// // Initialize keyring using the configured directory.
53+
// keyringCfg := cfg.KeyringConfig
54+
// keyringCfg.Dir = cfg.GetKeyringDir()
55+
56+
// kr, err := keyring.InitKeyring(keyringCfg)
57+
// if err != nil {
58+
// t.Fatalf("failed to initialize keyring: %v", err)
59+
// }
60+
61+
// // Initialize Lumera client using the same configuration as the supernode.
62+
// lumeraCfg, err := lumera.NewConfig(
63+
// cfg.LumeraClientConfig.GRPCAddr,
64+
// cfg.LumeraClientConfig.ChainID,
65+
// cfg.SupernodeConfig.KeyName,
66+
// kr,
67+
// )
68+
// if err != nil {
69+
// t.Fatalf("failed to create Lumera config: %v", err)
70+
// }
71+
72+
// lumeraClient, err := lumera.NewClient(ctx, lumeraCfg)
73+
// if err != nil {
74+
// t.Fatalf("failed to create Lumera client: %v", err)
75+
// }
76+
// defer func() {
77+
// _ = lumeraClient.Close()
78+
// }()
79+
80+
// // We only need the Lumera client for this debug helper; P2P and codec
81+
// // are left nil because DebugReverseEngineerAction is read-only and
82+
// // does not depend on them.
83+
// service := NewCascadeService(
84+
// cfg.SupernodeConfig.Identity,
85+
// lumeraClient,
86+
// nil, // p2p.Client
87+
// nil, // codec.Codec
88+
// nil, // rqstore.Store
89+
// )
90+
91+
// task := NewCascadeRegistrationTask(service)
92+
93+
// if err := task.DebugReverseEngineerAction(ctx, debugActionID); err != nil {
94+
// t.Fatalf("DebugReverseEngineerAction returned error: %v", err)
95+
// }
96+
// }

0 commit comments

Comments
 (0)