Skip to content
This repository was archived by the owner on May 26, 2025. It is now read-only.
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
25 changes: 25 additions & 0 deletions app_session.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package main

import (
"fmt"

"github.com/lib/pq"
"gorm.io/gorm"
)

// AppSession represents a virtual payment application session between participants
Expand All @@ -21,3 +24,25 @@ type AppSession struct {
func (AppSession) TableName() string {
return "app_sessions"
}

// getAppSessionsForParticipant finds all channels for a participant
func getAppSessionsForParticipant(tx *gorm.DB, participant string, status string) ([]AppSession, error) {
var sessions []AppSession
switch tx.Dialector.Name() {
case "postgres":
tx = tx.Where("? = ANY(participants)", participant)
case "sqlite":
tx = tx.Where("instr(participants, ?) > 0", participant)
default:
return nil, fmt.Errorf("unsupported database driver: %s", tx.Dialector.Name())
}
if status != "" {
tx = tx.Where("status = ?", status)
}

if err := tx.Find(&sessions).Error; err != nil {
return nil, err
}

return sessions, nil
}
55 changes: 55 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
| `get_config` | Retrieves broker configuration and supported networks |
| `get_assets` | Retrieves all supported assets (optionally filtered by chain_id) |
| `get_app_definition` | Retrieves application definition for a ledger account |
| `get_app_sessions` | Lists virtual applications for a participant with optional status filter |
| `get_ledger_balances` | Lists participants and their balances for a ledger account |
| `get_ledger_entries` | Retrieves detailed ledger entries for a participant |
| `get_channels` | Lists all channels for a participant with their status across all chains |
Expand Down Expand Up @@ -116,6 +117,60 @@ Retrieves the application definition for a specific ledger account.
}
```

### Get App Sessions

Lists all virtual applications for a participant. Optionally, you can filter the results by status (open, closed).

**Request:**

```json
{
"req": [1, "get_app_sessions", [{
"participant": "0x1234567890abcdef...",
"status": "open" // Optional: filter by status
}], 1619123456789],
"sig": ["0x9876fedcba..."]
}
```

**Response:**

```json
{
"res": [1, "get_app_sessions", [[
{
"app_session_id": "0x3456789012abcdef...",
"status": "open",
"participants": [
"0x1234567890abcdef...",
"0x00112233445566778899AaBbCcDdEeFf00112233"
],
"protocol": "NitroAura",
"challenge": 86400,
"weights": [50, 50],
"quorum": 100,
"version": 1,
"nonce": 123456789
},
{
"app_session_id": "0x7890123456abcdef...",
"status": "open",
"participants": [
"0x1234567890abcdef...",
"0xAaBbCcDdEeFf0011223344556677889900aAbBcC"
],
"protocol": "NitroSnake",
"challenge": 86400,
"weights": [70, 30],
"quorum": 100,
"version": 1,
"nonce": 123456790
}
]], 1619123456789],
"sig": ["0xabcd1234..."]
}
```

### Get Ledger Balances

Retrieves the balances of all participants in a specific ledger account.
Expand Down
53 changes: 51 additions & 2 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,15 @@ func (r CloseAppSignData) MarshalJSON() ([]byte, error) {

// AppSessionResponse represents response data for application operations
type AppSessionResponse struct {
AppSessionID string `json:"app_session_id"`
Status string `json:"status"`
AppSessionID string `json:"app_session_id"`
Status string `json:"status"`
Participants []string `json:"participants,omitempty"`
Protocol string `json:"protocol,omitempty"`
Challenge uint64 `json:"challenge,omitempty"`
Weights []int64 `json:"weights,omitempty"`
Quorum uint64 `json:"quorum,omitempty"`
Version uint64 `json:"version,omitempty"`
Nonce uint64 `json:"nonce,omitempty"`
}

// ResizeChannelParams represents parameters needed for resizing a channel
Expand Down Expand Up @@ -613,6 +620,48 @@ func HandleGetAppDefinition(rpc *RPCMessage, db *gorm.DB) (*RPCMessage, error) {
return rpcResponse, nil
}

func HandleGetAppSessions(rpc *RPCMessage, db *gorm.DB) (*RPCMessage, error) {
var participant string
var status string

if len(rpc.Req.Params) > 0 {
paramsJSON, err := json.Marshal(rpc.Req.Params[0])
if err == nil {
var params map[string]string
if err := json.Unmarshal(paramsJSON, &params); err == nil {
participant = params["participant"]
status = params["status"]
}
}
}

if participant == "" {
return nil, errors.New("missing participant")
}

sessions, err := getAppSessionsForParticipant(db, participant, status)
if err != nil {
return nil, fmt.Errorf("failed to find application sessions: %w", err)
}
response := make([]AppSessionResponse, len(sessions))
for i, session := range sessions {
response[i] = AppSessionResponse{
AppSessionID: session.SessionID,
Status: string(session.Status),
Participants: session.Participants,
Protocol: session.Protocol,
Challenge: session.Challenge,
Weights: session.Weights,
Quorum: session.Quorum,
Version: session.Version,
Nonce: session.Nonce,
}
}

rpcResponse := CreateResponse(rpc.Req.RequestID, rpc.Req.Method, []any{response}, time.Now())
return rpcResponse, nil
}

// HandleResizeChannel processes a request to resize a payment channel
func HandleResizeChannel(rpc *RPCMessage, db *gorm.DB, signer *Signer) (*RPCMessage, error) {
if len(rpc.Req.Params) < 1 {
Expand Down
152 changes: 152 additions & 0 deletions handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -917,6 +917,158 @@ func TestHandleGetAssets(t *testing.T) {
assert.Len(t, assets4, 0, "Should return 0 assets for non-existent chain_id")
}

// TestHandleGetAppSessions tests the get app sessions handler functionality
func TestHandleGetAppSessions(t *testing.T) {
rawKey, err := crypto.GenerateKey()
require.NoError(t, err)
signer := Signer{privateKey: rawKey}
participantAddr := signer.GetAddress().Hex()

db, cleanup := setupTestDB(t)
defer cleanup()

// Create some test app sessions
sessions := []AppSession{
{
SessionID: "0xSession1",
Participants: []string{participantAddr, "0xParticipant2"},
Status: ChannelStatusOpen,
Protocol: "test-app-1",
Challenge: 60,
Weights: []int64{50, 50},
Quorum: 75,
Nonce: 1,
Version: 1,
},
{
SessionID: "0xSession2",
Participants: []string{participantAddr, "0xParticipant3"},
Status: ChannelStatusClosed,
Protocol: "test-app-2",
Challenge: 120,
Weights: []int64{30, 70},
Quorum: 80,
Nonce: 2,
Version: 2,
},
{
SessionID: "0xSession3",
Participants: []string{"0xParticipant4", "0xParticipant5"},
Status: ChannelStatusOpen,
Protocol: "test-app-3",
Challenge: 90,
Weights: []int64{40, 60},
Quorum: 60,
Nonce: 3,
Version: 3,
},
}

for _, session := range sessions {
require.NoError(t, db.Create(&session).Error)
}

// Test Case 1: Get all app sessions for the participant
params1 := map[string]string{
"participant": participantAddr,
}
paramsJSON1, err := json.Marshal(params1)
require.NoError(t, err)

rpcRequest1 := &RPCMessage{
Req: &RPCData{
RequestID: 1,
Method: "get_app_sessions",
Params: []any{json.RawMessage(paramsJSON1)},
Timestamp: uint64(time.Now().Unix()),
},
Sig: []string{"dummy-signature"},
}

// Call the handler
resp1, err := HandleGetAppSessions(rpcRequest1, db)
require.NoError(t, err)
assert.NotNil(t, resp1)

// Verify response format
assert.Equal(t, "get_app_sessions", resp1.Res.Method)
assert.Equal(t, uint64(1), resp1.Res.RequestID)
require.Len(t, resp1.Res.Params, 1, "Response should contain an array of AppSessionResponse objects")

// Extract and verify app sessions
sessionResponses, ok := resp1.Res.Params[0].([]AppSessionResponse)
require.True(t, ok, "Response parameter should be a slice of AppSessionResponse")
assert.Len(t, sessionResponses, 2, "Should return 2 app sessions for the participant")

// Verify the response contains the expected app sessions
foundSessions := make(map[string]bool)
for _, session := range sessionResponses {
foundSessions[session.AppSessionID] = true

// Find the original session to compare with
var originalSession AppSession
for _, s := range sessions {
if s.SessionID == session.AppSessionID {
originalSession = s
break
}
}

assert.Equal(t, string(originalSession.Status), session.Status, "Status should match")
}

assert.True(t, foundSessions["0xSession1"], "Should include Session1")
assert.True(t, foundSessions["0xSession2"], "Should include Session2")
assert.False(t, foundSessions["0xSession3"], "Should not include Session3")

// Test Case 2: Get open app sessions for the participant
params2 := map[string]string{
"participant": participantAddr,
"status": string(ChannelStatusOpen),
}
paramsJSON2, err := json.Marshal(params2)
require.NoError(t, err)

rpcRequest2 := &RPCMessage{
Req: &RPCData{
RequestID: 2,
Method: "get_app_sessions",
Params: []any{json.RawMessage(paramsJSON2)},
Timestamp: uint64(time.Now().Unix()),
},
Sig: []string{"dummy-signature"},
}

// Call the handler
resp2, err := HandleGetAppSessions(rpcRequest2, db)
require.NoError(t, err)
assert.NotNil(t, resp2)

// Extract and verify filtered app sessions
sessionResponses2, ok := resp2.Res.Params[0].([]AppSessionResponse)
require.True(t, ok, "Response parameter should be a slice of AppSessionResponse")
assert.Len(t, sessionResponses2, 1, "Should return 1 open app session for the participant")
assert.Equal(t, "0xSession1", sessionResponses2[0].AppSessionID, "Should be Session1")
assert.Equal(t, string(ChannelStatusOpen), sessionResponses2[0].Status, "Status should be open")

// Test Case 3: Error case - missing participant
rpcRequest3 := &RPCMessage{
Req: &RPCData{
RequestID: 3,
Method: "get_app_sessions",
Params: []any{json.RawMessage(`{}`)},
Timestamp: uint64(time.Now().Unix()),
},
Sig: []string{"dummy-signature"},
}

// Call with missing participant
resp3, err := HandleGetAppSessions(rpcRequest3, db)
assert.Error(t, err, "Should return error with missing participant")
assert.Nil(t, resp3)
assert.Contains(t, err.Error(), "missing participant", "Error should mention missing participant")
}

func TestHandleGetRPCHistory(t *testing.T) {
rawKey, err := crypto.GenerateKey()
require.NoError(t, err)
Expand Down
8 changes: 8 additions & 0 deletions ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,14 @@ func (h *UnifiedWSHandler) HandleConnection(w http.ResponseWriter, r *http.Reque
}
h.sendBalanceUpdate(address)

case "get_app_sessions":
rpcResponse, handlerErr = HandleGetAppSessions(&msg, h.db)
if handlerErr != nil {
log.Printf("Error handling get_app_sessions: %v", handlerErr)
h.sendErrorResponse(address, &msg, conn, "Failed to get app sessions: "+handlerErr.Error())
continue
}

case "resize_channel":
rpcResponse, handlerErr = HandleResizeChannel(&msg, h.db, h.signer)
if handlerErr != nil {
Expand Down