From 2498dc676f57cf990f974200ffbf51d486407e04 Mon Sep 17 00:00:00 2001 From: NightCrawler Date: Tue, 31 Mar 2026 14:03:27 +0000 Subject: [PATCH 1/2] sdk: replace cascade evidence map payload with structured fields --- go.mod | 2 +- go.sum | 4 ++-- sdk/adapters/lumera/adapter.go | 27 +++++++++++++++++++++------ sdk/task/cascade.go | 12 ++++++------ sdk/task/download.go | 12 ++++++------ sdk/task/evidence.go | 24 ++++++++++-------------- 6 files changed, 46 insertions(+), 35 deletions(-) diff --git a/go.mod b/go.mod index f11b6800..ef4abf9b 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( cosmossdk.io/math v1.5.3 github.com/AlecAivazis/survey/v2 v2.3.7 github.com/DataDog/zstd v1.5.7 - github.com/LumeraProtocol/lumera v1.11.0-rc + github.com/LumeraProtocol/lumera v1.11.2-0.20260331140230-4aeb5d0d7a89 github.com/LumeraProtocol/rq-go v0.2.1 github.com/btcsuite/btcutil v1.0.3-0.20201208143702-a53e38424cce github.com/cenkalti/backoff/v4 v4.3.0 diff --git a/go.sum b/go.sum index a6c9eaa4..1c799cc9 100644 --- a/go.sum +++ b/go.sum @@ -111,8 +111,8 @@ github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.50 github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0 h1:ig/FpDD2JofP/NExKQUbn7uOSZzJAQqogfqluZK4ed4= github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.50.0/go.mod h1:otE2jQekW/PqXk1Awf5lmfokJx4uwuqcj1ab5SpGeW0= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= -github.com/LumeraProtocol/lumera v1.11.0-rc h1:ISJLUhjihuOterLMHpgGWpMZmybR1vmQLNgmSHkc1WA= -github.com/LumeraProtocol/lumera v1.11.0-rc/go.mod h1:p2sZZG3bLzSBdaW883qjuU3DXXY4NJzTTwLywr8uI0w= +github.com/LumeraProtocol/lumera v1.11.2-0.20260331140230-4aeb5d0d7a89 h1:wDZnZ5wi4l0qyMufE3bOQImu1BF/igMAsxr6aMWRmp4= +github.com/LumeraProtocol/lumera v1.11.2-0.20260331140230-4aeb5d0d7a89/go.mod h1:p2sZZG3bLzSBdaW883qjuU3DXXY4NJzTTwLywr8uI0w= github.com/LumeraProtocol/rq-go v0.2.1 h1:8B3UzRChLsGMmvZ+UVbJsJj6JZzL9P9iYxbdUwGsQI4= github.com/LumeraProtocol/rq-go v0.2.1/go.mod h1:APnKCZRh1Es2Vtrd2w4kCLgAyaL5Bqrkz/BURoRJ+O8= github.com/Masterminds/semver/v3 v3.3.1 h1:QtNSWtVZ3nBfk8mAOu/B6v7FMJ+NHTIgUPi7rj+4nv4= diff --git a/sdk/adapters/lumera/adapter.go b/sdk/adapters/lumera/adapter.go index d0a23cb2..0e41c77e 100644 --- a/sdk/adapters/lumera/adapter.go +++ b/sdk/adapters/lumera/adapter.go @@ -64,6 +64,17 @@ type SuperNodeInfo struct { CurrentState string `json:"current_state"` } +// CascadeClientFailureDetails is the structured payload for audit cascade-client-failure evidence. +type CascadeClientFailureDetails struct { + Operation string + Iteration string + SupernodeEndpoint string + SupernodeAccount string + TaskID string + Error string + ActionID string +} + // ConfigParams holds configuration parameters from global config type ConfigParams struct { GRPCAddr string @@ -389,7 +400,7 @@ func (a *Adapter) SubmitCascadeClientFailureEvidence( subjectAddress string, actionID string, targetSupernodeAccounts []string, - details map[string]string, + details CascadeClientFailureDetails, ) error { if a.client == nil { return fmt.Errorf("lumera client is nil") @@ -398,14 +409,18 @@ func (a *Adapter) SubmitCascadeClientFailureEvidence( if subjectAddress == "" { return fmt.Errorf("subject address cannot be empty") } - if details == nil { - details = map[string]string{} - } - meta := audittypes.CascadeClientFailureEvidenceMetadata{ ReporterComponent: audittypes.CascadeClientFailureReporterComponent_CASCADE_CLIENT_FAILURE_REPORTER_COMPONENT_SDK_GO, TargetSupernodeAccounts: append([]string(nil), targetSupernodeAccounts...), - Details: details, + Details: &audittypes.CascadeClientFailureDetails{ + Operation: details.Operation, + Iteration: details.Iteration, + SupernodeEndpoint: details.SupernodeEndpoint, + SupernodeAccount: details.SupernodeAccount, + TaskId: details.TaskID, + Error: details.Error, + ActionId: details.ActionID, + }, } bz, err := json.Marshal(meta) if err != nil { diff --git a/sdk/task/cascade.go b/sdk/task/cascade.go index 5fe7315c..5f4eb928 100644 --- a/sdk/task/cascade.go +++ b/sdk/task/cascade.go @@ -139,12 +139,12 @@ func (t *CascadeTask) registerWithSupernodes(ctx context.Context, supernodes lum event.KeyIteration: iteration, event.KeyError: err.Error(), }) - t.submitCascadeClientFailureEvidence(ctx, sn.CosmosAddress, []string{sn.CosmosAddress}, map[string]string{ - "operation": "register", - "iteration": fmt.Sprintf("%d", iteration), - "supernode_endpoint": sn.GrpcEndpoint, - "supernode_account": sn.CosmosAddress, - "error": err.Error(), + t.submitCascadeClientFailureEvidence(ctx, sn.CosmosAddress, []string{sn.CosmosAddress}, lumera.CascadeClientFailureDetails{ + Operation: "register", + Iteration: fmt.Sprintf("%d", iteration), + SupernodeEndpoint: sn.GrpcEndpoint, + SupernodeAccount: sn.CosmosAddress, + Error: err.Error(), }) lastErr = err continue diff --git a/sdk/task/download.go b/sdk/task/download.go index 20187312..528f8702 100644 --- a/sdk/task/download.go +++ b/sdk/task/download.go @@ -131,12 +131,12 @@ func (t *CascadeDownloadTask) downloadFromSupernodes(ctx context.Context, supern event.KeyIteration: iteration, event.KeyError: err.Error(), }) - t.submitCascadeClientFailureEvidence(ctx, sn.CosmosAddress, []string{sn.CosmosAddress}, map[string]string{ - "operation": "download", - "iteration": fmt.Sprintf("%d", iteration), - "supernode_endpoint": sn.GrpcEndpoint, - "supernode_account": sn.CosmosAddress, - "error": err.Error(), + t.submitCascadeClientFailureEvidence(ctx, sn.CosmosAddress, []string{sn.CosmosAddress}, lumera.CascadeClientFailureDetails{ + Operation: "download", + Iteration: fmt.Sprintf("%d", iteration), + SupernodeEndpoint: sn.GrpcEndpoint, + SupernodeAccount: sn.CosmosAddress, + Error: err.Error(), }) lastErr = err continue diff --git a/sdk/task/evidence.go b/sdk/task/evidence.go index a3600cb8..32bc282f 100644 --- a/sdk/task/evidence.go +++ b/sdk/task/evidence.go @@ -4,6 +4,8 @@ import ( "context" "strings" "time" + + "github.com/LumeraProtocol/supernode/v2/sdk/adapters/lumera" ) // Optional interface so existing test doubles that only implement the base @@ -14,7 +16,7 @@ type cascadeClientFailureEvidenceSubmitter interface { subjectAddress string, actionID string, targetSupernodeAccounts []string, - details map[string]string, + details lumera.CascadeClientFailureDetails, ) error } @@ -24,7 +26,7 @@ func (t *BaseTask) submitCascadeClientFailureEvidence( ctx context.Context, subjectAddress string, targetSupernodeAccounts []string, - details map[string]string, + details lumera.CascadeClientFailureDetails, ) { subjectAddress = strings.TrimSpace(subjectAddress) if subjectAddress == "" { @@ -37,24 +39,18 @@ func (t *BaseTask) submitCascadeClientFailureEvidence( return } - if details == nil { - details = map[string]string{} - } - if _, exists := details["task_id"]; !exists { - details["task_id"] = t.TaskID + if details.TaskID == "" { + details.TaskID = t.TaskID } - if _, exists := details["action_id"]; !exists { - details["action_id"] = t.ActionID + if details.ActionID == "" { + details.ActionID = t.ActionID } targetsCopy := append([]string(nil), targetSupernodeAccounts...) - detailsCopy := make(map[string]string, len(details)) - for k, v := range details { - detailsCopy[k] = v - } + detailsCopy := details // Evidence submission should not block retry loops. - go func(parent context.Context, subject string, actionID string, targets []string, metadata map[string]string) { + go func(parent context.Context, subject string, actionID string, targets []string, metadata lumera.CascadeClientFailureDetails) { submitCtx, cancel := context.WithTimeout(context.WithoutCancel(parent), cascadeEvidenceSubmitTimeout) defer cancel() From c02ec3ff9f17db4f0ed72ca050caf6b84681bb7c Mon Sep 17 00:00:00 2001 From: NightCrawler Date: Wed, 1 Apr 2026 09:59:35 +0000 Subject: [PATCH 2/2] storage-challenge: assert structured failure evidence payload --- supernode/storage_challenge/service.go | 38 +++++++++++++----- .../service_metadata_test.go | 39 +++++++++++++++++++ 2 files changed, 68 insertions(+), 9 deletions(-) create mode 100644 supernode/storage_challenge/service_metadata_test.go diff --git a/supernode/storage_challenge/service.go b/supernode/storage_challenge/service.go index c9e972df..b2227de0 100644 --- a/supernode/storage_challenge/service.go +++ b/supernode/storage_challenge/service.go @@ -595,15 +595,15 @@ func (s *Service) maybeSubmitEvidence(ctx context.Context, params audittypes.Par return nil } - meta := audittypes.StorageChallengeFailureEvidenceMetadata{ - EpochId: epochID, - ChallengerSupernodeAccount: s.identity, - ChallengedSupernodeAccount: recipient, - ChallengeId: challengeID, - FileKey: fileKey, - FailureType: failureType, - TranscriptHash: transcriptHashHex, - } + meta := buildStorageChallengeFailureEvidenceMetadata( + epochID, + s.identity, + recipient, + challengeID, + fileKey, + failureType, + transcriptHashHex, + ) bz, err := json.Marshal(meta) if err != nil { return err @@ -628,6 +628,26 @@ func (s *Service) maybeSubmitEvidence(ctx context.Context, params audittypes.Par return nil } +func buildStorageChallengeFailureEvidenceMetadata( + epochID uint64, + challengerSupernodeAccount string, + challengedSupernodeAccount string, + challengeID string, + fileKey string, + failureType string, + transcriptHashHex string, +) audittypes.StorageChallengeFailureEvidenceMetadata { + return audittypes.StorageChallengeFailureEvidenceMetadata{ + EpochId: epochID, + ChallengerSupernodeAccount: challengerSupernodeAccount, + ChallengedSupernodeAccount: challengedSupernodeAccount, + ChallengeId: challengeID, + FileKey: fileKey, + FailureType: failureType, + TranscriptHash: transcriptHashHex, + } +} + func deriveChallengeID(seed []byte, epochID uint64, fileKey, challenger, recipient string) string { msg := []byte("sc:challenge:" + hex.EncodeToString(seed) + ":" + strconv.FormatUint(epochID, 10) + ":" + fileKey + ":" + challenger + ":" + recipient) sum := blake3.Sum256(msg) diff --git a/supernode/storage_challenge/service_metadata_test.go b/supernode/storage_challenge/service_metadata_test.go new file mode 100644 index 00000000..2a7528d9 --- /dev/null +++ b/supernode/storage_challenge/service_metadata_test.go @@ -0,0 +1,39 @@ +package storage_challenge + +import ( + "encoding/json" + "testing" + + audittypes "github.com/LumeraProtocol/lumera/x/audit/v1/types" + "github.com/stretchr/testify/require" +) + +func TestBuildStorageChallengeFailureEvidenceMetadata_NoMapPayload(t *testing.T) { + meta := buildStorageChallengeFailureEvidenceMetadata( + 42, + "lumera1challengerxxxxxxxxxxxxxxxxxxxx", + "lumera1recipientxxxxxxxxxxxxxxxxxxxxx", + "challenge-id-123", + "file-key-abc", + "INVALID_PROOF", + "deadbeef", + ) + + require.Equal(t, uint64(42), meta.EpochId) + require.Equal(t, "challenge-id-123", meta.ChallengeId) + require.Equal(t, "file-key-abc", meta.FileKey) + require.Equal(t, "INVALID_PROOF", meta.FailureType) + require.Equal(t, "deadbeef", meta.TranscriptHash) + + bz, err := json.Marshal(meta) + require.NoError(t, err) + + var got map[string]any + require.NoError(t, json.Unmarshal(bz, &got)) + require.NotContains(t, got, "details") + require.NotContains(t, got, "metadata") + + var roundtrip audittypes.StorageChallengeFailureEvidenceMetadata + require.NoError(t, json.Unmarshal(bz, &roundtrip)) + require.Equal(t, meta, roundtrip) +}