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
15 changes: 10 additions & 5 deletions channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,22 @@ func GetChannelByID(tx *gorm.DB, channelID string) (*Channel, error) {
return &channel, nil
}

// getChannelsForParticipant finds all channels for a participant
func getChannelsForParticipant(tx *gorm.DB, participant string) ([]Channel, error) {
// getChannelsByParticipant finds all channels for a participant
func getChannelsByParticipant(tx *gorm.DB, participant string, status string) ([]Channel, error) {
var channels []Channel
if err := tx.Where("participant = ?",
participant).Order("created_at DESC").Find(&channels).Error; err != nil {
q := tx.Where("participant = ?", participant)
if status != "" {
q = q.Where("status = ?", status)
}

if err := q.Order("created_at DESC").Find(&channels).Error; err != nil {
return nil, fmt.Errorf("error finding channels for participant %s: %w", participant, err)
}

return channels, nil
}

// CheckExistingChannels checks if there is an existing open channel on the same network between participant A and B
// CheckExistingChannels checks if there is an existing open channel on the same network between participant and broker
func CheckExistingChannels(tx *gorm.DB, participantA, token string, chainID uint32) (*Channel, error) {
var channel Channel
err := tx.Where("participant = ? AND token = ? AND chain_id = ? AND status = ?", participantA, token, chainID, ChannelStatusOpen).
Expand Down
51 changes: 45 additions & 6 deletions docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,15 +591,54 @@ Balance updates are sent as unsolicited server messages with the "bu" method:

The balance update provides the latest balances for all assets in the participant's unified ledger, allowing clients to maintain an up-to-date view of available funds without explicitly requesting them.

### Open Channels

The server automatically sends all open channels as a batch update to clients after successful authentication.

```json
{
"res": [1234567890123, "channels", [[
{
"channel_id": "0xfedcba9876543210...",
"participant": "0x1234567890abcdef...",
"status": "open",
"token": "0xeeee567890abcdef...",
"amount": "100000",
"chain_id": 137,
"adjudicator": "0xAdjudicatorContractAddress...",
"challenge": 86400,
"nonce": 1,
"version": 2,
"created_at": "2023-05-01T12:00:00Z",
"updated_at": "2023-05-01T12:30:00Z"
},
{
"channel_id": "0xabcdef1234567890...",
"participant": "0x1234567890abcdef...",
"status": "open",
"token": "0xeeee567890abcdef...",
"amount": "50000",
"chain_id": 42220,
"adjudicator": "0xAdjudicatorContractAddress...",
"challenge": 86400,
"nonce": 1,
"version": 3,
"created_at": "2023-04-15T10:00:00Z",
"updated_at": "2023-04-20T14:30:00Z"
}
]], 1619123456789],
"sig": ["0xabcd1234..."]
}
```

### Channel Updates

The server automatically sends channel updates to clients in these scenarios:
1. After successful authentication (for all existing channels)
2. When a channel is created
3. When a channel's status changes (open, joined, closed)
4. When a channel is resized
For channel updates, the server sends them in these scenarios:
1. When a channel is created
2. When a channel's status changes (open, joined, closed)
3. When a channel is resized

Channel updates are sent as unsolicited server messages with the "cu" method:
Individual channel updates are sent as unsolicited server messages with the "cu" method:

```json
{
Expand Down
4 changes: 3 additions & 1 deletion handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -872,13 +872,15 @@ func HandleCloseChannel(rpc *RPCMessage, db *gorm.DB, signer *Signer) (*RPCMessa
// TODO: add filters, pagination, etc.
func HandleGetChannels(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"]
}
}
}
Expand All @@ -887,7 +889,7 @@ func HandleGetChannels(rpc *RPCMessage, db *gorm.DB) (*RPCMessage, error) {
return nil, errors.New("missing participant parameter")
}

channels, err := getChannelsForParticipant(db, participant)
channels, err := getChannelsByParticipant(db, participant, status)
if err != nil {
return nil, fmt.Errorf("failed to get channels: %w", err)
}
Expand Down
102 changes: 102 additions & 0 deletions handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,108 @@ func TestHandleGetChannels(t *testing.T) {
assert.NotEmpty(t, ch.UpdatedAt, "UpdatedAt should not be empty")
}

// Test with status filter for "open" channels
openStatusParams := map[string]string{
"participant": participantAddr,
"status": string(ChannelStatusOpen),
}
openStatusParamsJSON, err := json.Marshal(openStatusParams)
require.NoError(t, err)

openStatusRequest := &RPCMessage{
Req: &RPCData{
RequestID: 456,
Method: "get_channels",
Params: []any{json.RawMessage(openStatusParamsJSON)},
Timestamp: uint64(time.Now().Unix()),
},
}

reqBytes, err = json.Marshal(openStatusRequest.Req)
require.NoError(t, err)
signed, err = signer.Sign(reqBytes)
require.NoError(t, err)
openStatusRequest.Sig = []string{hexutil.Encode(signed)}

openStatusResponse, err := HandleGetChannels(openStatusRequest, db)
require.NoError(t, err)
require.NotNil(t, openStatusResponse)

// Extract and verify filtered channels
openChannels, ok := openStatusResponse.Res.Params[0].([]ChannelResponse)
require.True(t, ok, "Response parameter should be a slice of ChannelResponse")
assert.Len(t, openChannels, 1, "Should return only 1 open channel")
assert.Equal(t, "0xChannel1", openChannels[0].ChannelID, "Should return the open channel")
assert.Equal(t, ChannelStatusOpen, openChannels[0].Status, "Status should be open")

// Test with status filter for "closed" channels
closedStatusParams := map[string]string{
"participant": participantAddr,
"status": string(ChannelStatusClosed),
}
closedStatusParamsJSON, err := json.Marshal(closedStatusParams)
require.NoError(t, err)

closedStatusRequest := &RPCMessage{
Req: &RPCData{
RequestID: 457,
Method: "get_channels",
Params: []any{json.RawMessage(closedStatusParamsJSON)},
Timestamp: uint64(time.Now().Unix()),
},
}

reqBytes, err = json.Marshal(closedStatusRequest.Req)
require.NoError(t, err)
signed, err = signer.Sign(reqBytes)
require.NoError(t, err)
closedStatusRequest.Sig = []string{hexutil.Encode(signed)}

closedStatusResponse, err := HandleGetChannels(closedStatusRequest, db)
require.NoError(t, err)
require.NotNil(t, closedStatusResponse)

// Extract and verify filtered channels
closedChannels, ok := closedStatusResponse.Res.Params[0].([]ChannelResponse)
require.True(t, ok, "Response parameter should be a slice of ChannelResponse")
assert.Len(t, closedChannels, 1, "Should return only 1 closed channel")
assert.Equal(t, "0xChannel2", closedChannels[0].ChannelID, "Should return the closed channel")
assert.Equal(t, ChannelStatusClosed, closedChannels[0].Status, "Status should be closed")

// Test with status filter for "joining" channels
joiningStatusParams := map[string]string{
"participant": participantAddr,
"status": string(ChannelStatusJoining),
}
joiningStatusParamsJSON, err := json.Marshal(joiningStatusParams)
require.NoError(t, err)

joiningStatusRequest := &RPCMessage{
Req: &RPCData{
RequestID: 458,
Method: "get_channels",
Params: []any{json.RawMessage(joiningStatusParamsJSON)},
Timestamp: uint64(time.Now().Unix()),
},
}

reqBytes, err = json.Marshal(joiningStatusRequest.Req)
require.NoError(t, err)
signed, err = signer.Sign(reqBytes)
require.NoError(t, err)
joiningStatusRequest.Sig = []string{hexutil.Encode(signed)}

joiningStatusResponse, err := HandleGetChannels(joiningStatusRequest, db)
require.NoError(t, err)
require.NotNil(t, joiningStatusResponse)

// Extract and verify filtered channels
joiningChannels, ok := joiningStatusResponse.Res.Params[0].([]ChannelResponse)
require.True(t, ok, "Response parameter should be a slice of ChannelResponse")
assert.Len(t, joiningChannels, 1, "Should return only 1 joining channel")
assert.Equal(t, "0xChannel3", joiningChannels[0].ChannelID, "Should return the joining channel")
assert.Equal(t, ChannelStatusJoining, joiningChannels[0].Status, "Status should be joining")

// Test with missing participant parameter
missingParamReq := &RPCMessage{
Req: &RPCData{
Expand Down
Loading