Skip to content

Commit 42a1842

Browse files
Implement action sdk
1 parent e76cfbd commit 42a1842

File tree

48 files changed

+3538
-206
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+3538
-206
lines changed

.vscode/launch.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@
66
"type": "go",
77
"request": "launch",
88
"mode": "debug",
9-
"program": "${workspaceFolder}/main.go",
9+
"program": "${workspaceFolder}/supernode/main.go",
1010
"env": {},
11-
"args": [],
11+
"args": ["start"],
1212
"showLog": true
1313
}
1414
]

actionsdk/action/client.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package action
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"action/config"
8+
"action/task"
9+
10+
"github.com/LumeraProtocol/supernode/pkg/lumera"
11+
)
12+
13+
// ActionClient is the main interface that exposes high-level operations
14+
// for interacting with the Lumera Protocol ecosystem
15+
type ActionClient interface {
16+
// StartSense initiates a Sense operation and returns a unique task ID
17+
StartSense(ctx context.Context, fileHash string, actionID string, filePath string) (string, error)
18+
19+
// StartCascade initiates a Cascade operation and returns a unique task ID
20+
StartCascade(ctx context.Context, fileHash string, actionID string, filePath string) (string, error)
21+
}
22+
23+
// ActionClientImpl implements the ActionClient interface
24+
type ActionClientImpl struct {
25+
lumeraClient lumera.Client
26+
config config.Config
27+
taskManager task.Manager
28+
}
29+
30+
// NewActionClient creates a new instance of ActionClientImpl
31+
func NewActionClient(lumeraClient lumera.Client, config config.Config) *ActionClientImpl {
32+
// Create task manager with config
33+
taskManager := task.NewManager(lumeraClient, config)
34+
35+
return &ActionClientImpl{
36+
lumeraClient: lumeraClient,
37+
config: config,
38+
taskManager: taskManager,
39+
}
40+
}
41+
42+
// StartSense initiates a Sense operation
43+
func (ac *ActionClientImpl) StartSense(
44+
ctx context.Context,
45+
fileHash string,
46+
actionID string,
47+
filePath string,
48+
) (string, error) {
49+
// Input validation
50+
if fileHash == "" {
51+
return "", ErrEmptyFileHash
52+
}
53+
if actionID == "" {
54+
return "", ErrEmptyActionID
55+
}
56+
if filePath == "" {
57+
return "", ErrEmptyFilePath
58+
}
59+
60+
// Create and start the task
61+
taskID, err := ac.taskManager.CreateSenseTask(ctx, fileHash, actionID, filePath)
62+
if err != nil {
63+
return "", fmt.Errorf("failed to create sense task: %w", err)
64+
}
65+
66+
return taskID, nil
67+
}
68+
69+
// StartCascade initiates a Cascade operation
70+
func (ac *ActionClientImpl) StartCascade(
71+
ctx context.Context,
72+
fileHash string,
73+
actionID string,
74+
filePath string,
75+
) (string, error) {
76+
// Input validation
77+
if fileHash == "" {
78+
return "", ErrEmptyFileHash
79+
}
80+
if actionID == "" {
81+
return "", ErrEmptyActionID
82+
}
83+
if filePath == "" {
84+
return "", ErrEmptyFilePath
85+
}
86+
87+
// Create and start the task
88+
taskID, err := ac.taskManager.CreateCascadeTask(ctx, fileHash, actionID, filePath)
89+
if err != nil {
90+
return "", fmt.Errorf("failed to create cascade task: %w", err)
91+
}
92+
93+
return taskID, nil
94+
}

actionsdk/action/errors.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package action
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
)
7+
8+
// Common error definitions
9+
var (
10+
ErrEmptyFileHash = errors.New("file hash cannot be empty")
11+
ErrEmptyActionID = errors.New("action ID cannot be empty")
12+
ErrEmptyFilePath = errors.New("file path cannot be empty")
13+
ErrNoValidAction = errors.New("no action found with the specified ID")
14+
ErrInvalidAction = errors.New("action is not in a valid state")
15+
ErrNoSupernodes = errors.New("no valid supernodes available")
16+
ErrTaskCreation = errors.New("failed to create task")
17+
ErrCommunication = errors.New("communication with supernode failed")
18+
)
19+
20+
// SupernodeError represents an error related to supernode operations
21+
type SupernodeError struct {
22+
NodeID string
23+
Message string
24+
Err error
25+
}
26+
27+
// Error returns the error message
28+
func (e *SupernodeError) Error() string {
29+
return fmt.Sprintf("supernode error (ID: %s): %s: %v", e.NodeID, e.Message, e.Err)
30+
}
31+
32+
// Unwrap returns the underlying error
33+
func (e *SupernodeError) Unwrap() error {
34+
return e.Err
35+
}
36+
37+
// ActionError represents an error related to action operations
38+
type ActionError struct {
39+
ActionID string
40+
Message string
41+
Err error
42+
}
43+
44+
// Error returns the error message
45+
func (e *ActionError) Error() string {
46+
return fmt.Sprintf("action error (ID: %s): %s: %v", e.ActionID, e.Message, e.Err)
47+
}
48+
49+
// Unwrap returns the underlying error
50+
func (e *ActionError) Unwrap() error {
51+
return e.Err
52+
}

actionsdk/action/types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package action
2+
3+
type TaskType string
4+
5+
const (
6+
TaskTypeSense TaskType = "SENSE"
7+
TaskTypeCascade TaskType = "CASCADE"
8+
)
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package lumera
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/LumeraProtocol/lumera/x/action/types"
8+
sntypes "github.com/LumeraProtocol/lumera/x/supernode/types"
9+
lumeraclient "github.com/LumeraProtocol/supernode/pkg/lumera"
10+
)
11+
12+
type Client interface {
13+
GetAction(ctx context.Context, actionID string) (Action, error)
14+
GetSupernodes(ctx context.Context, height int64) ([]Supernode, error)
15+
}
16+
17+
// Adapter adapts the lumera.Client to our Client interface
18+
type Adapter struct {
19+
client lumeraclient.Client
20+
}
21+
22+
// NewAdapter creates a new adapter for the lumera.Client
23+
func NewAdapter(client lumeraclient.Client) Client {
24+
return &Adapter{
25+
client: client,
26+
}
27+
}
28+
29+
// GetAction retrieves action information from the blockchain
30+
func (a *Adapter) GetAction(ctx context.Context, actionID string) (Action, error) {
31+
resp, err := a.client.Action().GetAction(ctx, actionID)
32+
if err != nil {
33+
return Action{}, fmt.Errorf("failed to get action: %w", err)
34+
}
35+
36+
// Transform the response to our simplified Action type
37+
return toSdkAction(resp), nil
38+
}
39+
40+
// GetSupernodes retrieves a list of top supernodes at a given height
41+
func (a *Adapter) GetSupernodes(ctx context.Context, height int64) ([]Supernode, error) {
42+
resp, err := a.client.SuperNode().GetTopSuperNodesForBlock(ctx, uint64(height))
43+
if err != nil {
44+
return nil, fmt.Errorf("failed to get supernodes: %w", err)
45+
}
46+
47+
// Transform the response to our simplified Supernode type
48+
return toSdkSupernodes(resp), nil
49+
}
50+
51+
// Helper functions to transform between types
52+
53+
func toSdkAction(resp *types.QueryGetActionResponse) Action {
54+
return Action{
55+
ID: resp.Action.ActionID,
56+
State: ACTION_STATE(resp.Action.State),
57+
Height: int64(resp.Action.BlockHeight),
58+
ExpirationTime: resp.Action.ExpirationTime,
59+
}
60+
}
61+
62+
func toSdkSupernodes(resp *sntypes.QueryGetTopSuperNodesForBlockResponse) []Supernode {
63+
var result []Supernode
64+
for _, sn := range resp.Supernodes {
65+
ipAddress, err := getLatestIP(sn)
66+
if err != nil {
67+
continue
68+
}
69+
70+
if sn.SupernodeAccount == "" {
71+
continue
72+
}
73+
74+
if sn.States[0].State.String() != string(SUPERNODE_STATE_ACTIVE) {
75+
continue
76+
}
77+
78+
result = append(result, Supernode{
79+
CosmosAddress: sn.SupernodeAccount,
80+
GrpcEndpoint: ipAddress,
81+
State: SUPERNODE_STATE_ACTIVE,
82+
})
83+
}
84+
return result
85+
}
86+
87+
// getLatestIP is a simplified version of the GetLatestIP function in supernode module
88+
func getLatestIP(supernode *sntypes.SuperNode) (string, error) {
89+
if len(supernode.PrevIpAddresses) == 0 {
90+
return "", fmt.Errorf("no ip history exists for the supernode")
91+
}
92+
// Just take the first one for simplicity
93+
return supernode.PrevIpAddresses[0].Address, nil
94+
}

actionsdk/adapters/lumera/types.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package lumera
2+
3+
// ACTION_STATE represents the possible states of an action
4+
type ACTION_STATE string
5+
6+
const (
7+
ACTION_STATE_UNSPECIFIED ACTION_STATE = "ACTION_STATE_UNSPECIFIED"
8+
ACTION_STATE_PENDING ACTION_STATE = "ACTION_STATE_PENDING"
9+
ACTION_STATE_DONE ACTION_STATE = "ACTION_STATE_DONE"
10+
ACTION_STATE_APPROVED ACTION_STATE = "ACTION_STATE_APPROVED"
11+
ACTION_STATE_REJECTED ACTION_STATE = "ACTION_STATE_REJECTED"
12+
ACTION_STATE_FAILED ACTION_STATE = "ACTION_STATE_FAILED"
13+
)
14+
15+
// SUPERNODE_STATE represents the possible states of a supernode
16+
type SUPERNODE_STATE string
17+
18+
const (
19+
SUPERNODE_STATE_UNSPECIFIED SUPERNODE_STATE = "SUPERNODE_STATE_UNSPECIFIED"
20+
SUPERNODE_STATE_ACTIVE SUPERNODE_STATE = "SUPERNODE_STATE_ACTIVE"
21+
SUPERNODE_STATE_DISABLED SUPERNODE_STATE = "SUPERNODE_STATE_DISABLED"
22+
SUPERNODE_STATE_STOPPED SUPERNODE_STATE = "SUPERNODE_STATE_STOPPED"
23+
SUPERNODE_STATE_PENALIZED SUPERNODE_STATE = "SUPERNODE_STATE_PENALIZED"
24+
)
25+
26+
// Action represents an action registered on the Lumera blockchain
27+
type Action struct {
28+
ID string
29+
State ACTION_STATE
30+
Height int64
31+
ExpirationTime string
32+
}
33+
34+
// Supernode represents information about a supernode in the network
35+
type Supernode struct {
36+
CosmosAddress string // Blockchain identity of the supernode
37+
GrpcEndpoint string // Network endpoint for gRPC communication
38+
State SUPERNODE_STATE // Current state of the supernode
39+
}

actionsdk/config/config.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package config
2+
3+
import (
4+
"github.com/LumeraProtocol/lumera/x/lumeraid/securekeyx"
5+
"github.com/cosmos/cosmos-sdk/crypto/keyring"
6+
)
7+
8+
// Config holds configuration values for the ActionClient
9+
type Config struct {
10+
// Security configuration
11+
Keyring keyring.Keyring // Keyring containing identity keys
12+
LocalCosmosAddress string // Local cosmos address for authentication
13+
LocalPeerType securekeyx.PeerType // Local peer type (Simplenode for clients)
14+
15+
// Network configuration
16+
DefaultSupernodePort int // Default port for supernode gRPC endpoints
17+
18+
// Task configuration
19+
MaxRetries int // Maximum number of retries for supernode communication
20+
TimeoutSeconds int // Timeout for supernode communication in seconds
21+
SenseSupernodeCount int // Number of supernodes to select for Sense operations
22+
CascadeSupernodeCount int // Number of supernodes to select for Cascade operations
23+
}
24+
25+
// DefaultConfig returns a Config with default values
26+
func DefaultConfig() Config {
27+
return Config{
28+
LocalPeerType: securekeyx.Simplenode,
29+
DefaultSupernodePort: 50051,
30+
MaxRetries: 3,
31+
TimeoutSeconds: 30,
32+
SenseSupernodeCount: 3,
33+
CascadeSupernodeCount: 1,
34+
}
35+
}

0 commit comments

Comments
 (0)