Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions tests/systemtests/audit_peer_observation_completeness_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package system
import (
"strconv"
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/tidwall/sjson"
Expand Down Expand Up @@ -37,9 +38,11 @@ func TestAuditSubmitReport_ProberRequiresAllPeerObservations(t *testing.T) {
registerSupernode(t, cli, n0, "192.168.1.1")
registerSupernode(t, cli, n1, "192.168.1.2")

currentHeight := sut.AwaitNextBlock(t)
currentHeight := sut.AwaitNextBlock(t, 12*time.Second)
epochID, epochStartHeight := nextEpochAfterHeight(originHeight, epochLengthBlocks, currentHeight)
awaitAtLeastHeight(t, epochStartHeight)
if sut.currentHeight < epochStartHeight {
sut.AwaitBlockHeight(t, epochStartHeight, 20*time.Second)
}

host := auditHostReportJSON([]string{"PORT_STATE_OPEN"})
txResp := submitEpochReport(t, cli, n0.nodeName, epochID, host, nil)
Expand Down
2 changes: 1 addition & 1 deletion tests/systemtests/audit_peer_ports_enforcement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestAuditPeerPortsUnanimousClosedPostponesAfterConsecutiveWindows(t *testin
registerSupernode(t, cli, n0, "192.168.1.1")
registerSupernode(t, cli, n1, "192.168.1.2")

currentHeight := sut.AwaitNextBlock(t)
currentHeight := sut.AwaitNextBlock(t, 12*time.Second)
epochID1, epoch1Start := nextEpochAfterHeight(originHeight, epochLengthBlocks, currentHeight)
epochID2 := epochID1 + 1
epoch2Start := epoch1Start + int64(epochLengthBlocks)
Expand Down
25 changes: 18 additions & 7 deletions tests/systemtests/audit_recovery_enforcement_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package system

import (
"testing"
"time"

"github.com/stretchr/testify/require"
"github.com/tidwall/sjson"
Expand Down Expand Up @@ -34,7 +35,7 @@ func TestAuditRecovery_PostponedBecomesActiveWithSelfAndPeerOpen_NoHostThreshold
registerSupernode(t, cli, n0, "192.168.1.1")
registerSupernode(t, cli, n1, "192.168.1.2")

currentHeight := sut.AwaitNextBlock(t)
currentHeight := sut.AwaitNextBlock(t, 12*time.Second)
epochID1, epoch1Start := nextEpochAfterHeight(originHeight, epochLengthBlocks, currentHeight)
epochID2 := epochID1 + 1
epochID3 := epochID2 + 1
Expand All @@ -57,15 +58,19 @@ func TestAuditRecovery_PostponedBecomesActiveWithSelfAndPeerOpen_NoHostThreshold

// Epoch 1: whichever reporter is assigned node1 reports CLOSED for node1.
// Not enough streak yet (consecutive=2), so node1 should remain ACTIVE after epoch1.
awaitAtLeastHeight(t, epoch1Start)
if sut.currentHeight < epoch1Start {
sut.AwaitBlockHeight(t, epoch1Start, 20*time.Second)
}
assigned0e1 := auditQueryAssignedTargets(t, epochID1, true, n0.accAddr)
assigned1e1 := auditQueryAssignedTargets(t, epochID1, true, n1.accAddr)
tx0e1 := submitEpochReport(t, cli, n0.nodeName, epochID1, hostOK, buildObs(assigned0e1.TargetSupernodeAccounts, n1.accAddr))
RequireTxSuccess(t, tx0e1)
tx1e1 := submitEpochReport(t, cli, n1.nodeName, epochID1, hostOK, buildObs(assigned1e1.TargetSupernodeAccounts, ""))
RequireTxSuccess(t, tx1e1)

awaitAtLeastHeight(t, epoch2Start)
if sut.currentHeight < epoch2Start {
sut.AwaitBlockHeight(t, epoch2Start, 20*time.Second)
}

// Epoch 2: repeat CLOSED-for-node1 observations on assigned targets.
assigned0e2 := auditQueryAssignedTargets(t, epochID2, true, n0.accAddr)
Expand All @@ -75,26 +80,32 @@ func TestAuditRecovery_PostponedBecomesActiveWithSelfAndPeerOpen_NoHostThreshold
tx1e2 := submitEpochReport(t, cli, n1.nodeName, epochID2, hostOK, buildObs(assigned1e2.TargetSupernodeAccounts, ""))
RequireTxSuccess(t, tx1e2)

awaitAtLeastHeight(t, epoch3Start)
if sut.currentHeight < epoch3Start {
sut.AwaitBlockHeight(t, epoch3Start, 20*time.Second)
}
require.Equal(t, "SUPERNODE_STATE_POSTPONED", querySupernodeLatestState(t, cli, n1.valAddr))

// Recovery can only happen on epochs where an eligible reporter submits OPEN
// observations for node1. Assignment can vary by epoch, so retry a few epochs.
recovered := false
for i := int64(0); i < 4; i++ {
for i := int64(0); i < 10; i++ {
epochID := epochID3 + uint64(i)
epochStart := epoch3Start + i*int64(epochLengthBlocks)
nextEpochStart := epochStart + int64(epochLengthBlocks)

awaitAtLeastHeight(t, epochStart)
if sut.currentHeight < epochStart {
sut.AwaitBlockHeight(t, epochStart, 20*time.Second)
}
assigned0 := auditQueryAssignedTargets(t, epochID, true, n0.accAddr)
tx0 := submitEpochReport(t, cli, n0.nodeName, epochID, hostOK, buildObs(assigned0.TargetSupernodeAccounts, ""))
RequireTxSuccess(t, tx0)

tx1 := submitEpochReport(t, cli, n1.nodeName, epochID, hostOK, nil)
RequireTxSuccess(t, tx1)

awaitAtLeastHeight(t, nextEpochStart)
if sut.currentHeight < nextEpochStart {
sut.AwaitBlockHeight(t, nextEpochStart, 20*time.Second)
}
if querySupernodeLatestState(t, cli, n1.valAddr) == "SUPERNODE_STATE_ACTIVE" {
recovered = true
break
Expand Down
3 changes: 1 addition & 2 deletions tests/systemtests/metrics_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ func reportSupernodeMetrics(t *testing.T, cli *LumeradCli, fromKey, valAddr, acc
require.NoError(t, err)

out := cli.CustomCommand(
"tx", "supernode", "report-supernode-metrics",
"--validator-address", valAddr,
"tx", "supernode", "report-supernode-metrics", valAddr,
"--metrics", string(metricsJSON),
"--from", fromKey,
)
Expand Down
2 changes: 1 addition & 1 deletion tests/systemtests/system.go
Original file line number Diff line number Diff line change
Expand Up @@ -451,7 +451,7 @@ func (s *SystemUnderTest) AwaitBlockHeight(t *testing.T, targetHeight int64, tim
t.Fatalf("Timeout - block %d not reached within %s", targetHeight, maxWaitTime)
return
default:
if current := s.AwaitNextBlock(t); current >= targetHeight {
if current := s.AwaitNextBlock(t, 12*time.Second); current >= targetHeight {
return
}
}
Expand Down
26 changes: 26 additions & 0 deletions x/audit/v1/client/cli/query.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package cli

import (
"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"

"github.com/LumeraProtocol/lumera/x/audit/v1/types"
)

// GetCustomQueryCmd returns custom audit query commands.
func GetCustomQueryCmd() *cobra.Command {
auditQueryCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Audit query commands",
RunE: client.ValidateCmd,
}

auditQueryCmd.AddCommand(
CmdEpochReport(),
CmdEpochReportsByReporter(),
CmdHostReports(),
)

return auditQueryCmd
}
142 changes: 142 additions & 0 deletions x/audit/v1/client/cli/query_reports.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package cli

import (
"fmt"
"strconv"

"github.com/spf13/cobra"

"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"

"github.com/LumeraProtocol/lumera/x/audit/v1/types"
)

func CmdEpochReport() *cobra.Command {
cmd := &cobra.Command{
Use: "epoch-report [epoch-id] [supernode-account]",
Short: "Query an epoch report by epoch and reporter",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
epochID, err := strconv.ParseUint(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid epoch-id %q: %w", args[0], err)
}
supernodeAccount := args[1]

clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

qc := types.NewQueryClient(clientCtx)
resp, err := qc.EpochReport(cmd.Context(), &types.QueryEpochReportRequest{
EpochId: epochID,
SupernodeAccount: supernodeAccount,
})
if err != nil {
return err
}
return clientCtx.PrintProto(resp)
},
}

flags.AddQueryFlagsToCmd(cmd)
return cmd
}

func CmdEpochReportsByReporter() *cobra.Command {
cmd := &cobra.Command{
Use: "epoch-reports-by-reporter [supernode-account]",
Short: "List epoch reports submitted by a reporter",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
supernodeAccount := args[0]

epochID, err := cmd.Flags().GetUint64("epoch-id")
if err != nil {
return err
}
filterByEpochID, err := cmd.Flags().GetBool("filter-by-epoch-id")
if err != nil {
return err
}

clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

qc := types.NewQueryClient(clientCtx)
resp, err := qc.EpochReportsByReporter(cmd.Context(), &types.QueryEpochReportsByReporterRequest{
SupernodeAccount: supernodeAccount,
EpochId: epochID,
FilterByEpochId: filterByEpochID,
Pagination: pageReq,
})
if err != nil {
return err
}
return clientCtx.PrintProto(resp)
},
}

cmd.Flags().Uint64("epoch-id", 0, "epoch id")
cmd.Flags().Bool("filter-by-epoch-id", false, "filter by epoch id")
flags.AddPaginationFlagsToCmd(cmd, "epoch-reports-by-reporter")
flags.AddQueryFlagsToCmd(cmd)
return cmd
}

func CmdHostReports() *cobra.Command {
cmd := &cobra.Command{
Use: "host-reports [supernode-account]",
Short: "List host reports submitted by a supernode across epochs",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
supernodeAccount := args[0]

epochID, err := cmd.Flags().GetUint64("epoch-id")
if err != nil {
return err
}
filterByEpochID, err := cmd.Flags().GetBool("filter-by-epoch-id")
if err != nil {
return err
}

clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}

pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

qc := types.NewQueryClient(clientCtx)
resp, err := qc.HostReports(cmd.Context(), &types.QueryHostReportsRequest{
SupernodeAccount: supernodeAccount,
EpochId: epochID,
FilterByEpochId: filterByEpochID,
Pagination: pageReq,
})
if err != nil {
return err
}
return clientCtx.PrintProto(resp)
},
}

cmd.Flags().Uint64("epoch-id", 0, "epoch id")
cmd.Flags().Bool("filter-by-epoch-id", false, "filter by epoch id")
flags.AddPaginationFlagsToCmd(cmd, "host-reports")
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
28 changes: 28 additions & 0 deletions x/audit/v1/client/cli/query_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package cli

import "testing"

func TestGetCustomQueryCmd_ContainsFloatReportCommands(t *testing.T) {
cmd := GetCustomQueryCmd()
if cmd == nil {
t.Fatalf("expected non-nil command")
}

wanted := map[string]bool{
"epoch-report": false,
"epoch-reports-by-reporter": false,
"host-reports": false,
}

for _, c := range cmd.Commands() {
if _, ok := wanted[c.Name()]; ok {
wanted[c.Name()] = true
}
}

for name, found := range wanted {
if !found {
t.Fatalf("expected custom query command %q", name)
}
}
}
21 changes: 7 additions & 14 deletions x/audit/v1/module/autocli.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import (
func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions {
return &autocliv1.ModuleOptions{
Query: &autocliv1.ServiceCommandDescriptor{
Service: types.Query_serviceDesc.ServiceName,
Service: types.Query_serviceDesc.ServiceName,
EnhanceCustomCommand: true,
RpcCommandOptions: []*autocliv1.RpcCommandOptions{
{
RpcMethod: "Params",
Expand Down Expand Up @@ -57,16 +58,12 @@ func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions {
PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "supernode_account"}},
},
{
RpcMethod: "EpochReport",
Use: "epoch-report [epoch-id] [supernode-account]",
Short: "Query an epoch report by epoch and reporter",
PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "epoch_id"}, {ProtoField: "supernode_account"}},
RpcMethod: "EpochReport",
Skip: true, // custom command to avoid AutoCLI aminojson float64 marshal bug
},
{
RpcMethod: "EpochReportsByReporter",
Use: "epoch-reports-by-reporter [supernode-account]",
Short: "List epoch reports submitted by a reporter",
PositionalArgs: []*autocliv1.PositionalArgDescriptor{{ProtoField: "supernode_account"}},
RpcMethod: "EpochReportsByReporter",
Skip: true, // custom command to avoid AutoCLI aminojson float64 marshal bug
},
{
RpcMethod: "StorageChallengeReports",
Expand All @@ -76,11 +73,7 @@ func (am AppModule) AutoCLIOptions() *autocliv1.ModuleOptions {
},
{
RpcMethod: "HostReports",
Use: "host-reports [supernode-account]",
Short: "List host reports submitted by a supernode across epochs",
PositionalArgs: []*autocliv1.PositionalArgDescriptor{
{ProtoField: "supernode_account"},
},
Skip: true, // custom command to avoid AutoCLI aminojson float64 marshal bug
},
// this line is used by ignite scaffolding # autocli/query
},
Expand Down
Loading
Loading