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
12 changes: 12 additions & 0 deletions cerebro/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -636,6 +636,10 @@ func (o *Operator) getHomeChannel(ctx context.Context, wallet, asset string) {
fmt.Printf("ERROR: Failed to get home channel: %v\n", err)
return
}
if channel == nil {
fmt.Printf("No home channel found for %s (%s)\n", wallet, asset)
return
}

typeStr := "unknown"
switch channel.Type {
Expand Down Expand Up @@ -675,6 +679,10 @@ func (o *Operator) getEscrowChannel(ctx context.Context, escrowChannelID string)
fmt.Printf("ERROR: Failed to get escrow channel: %v\n", err)
return
}
if channel == nil {
fmt.Printf("No escrow channel found with ID %s\n", escrowChannelID)
return
}

typeStr := "unknown"
switch channel.Type {
Expand Down Expand Up @@ -834,6 +842,10 @@ func (o *Operator) getLatestState(ctx context.Context, wallet, asset string) {
fmt.Printf("ERROR: Failed to get state: %v\n", err)
return
}
if state == nil {
fmt.Printf("No state found for %s (%s)\n", wallet, asset)
return
}

fmt.Printf("Latest State for %s (%s)\n", wallet, asset)
fmt.Println("====================================")
Expand Down
36 changes: 16 additions & 20 deletions docs/api.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -525,7 +525,7 @@ api:
- version: v1
methods:
- name: get_home_channel
description: Retrieve current on-chain home channel information
description: Retrieve current on-chain home channel information. Returns a successful response with `channel` omitted when no home channel exists for the wallet/asset pair.
request:
- field_name: wallet
type: string
Expand All @@ -536,13 +536,12 @@ api:
response:
- field_name: channel
type: channel
description: The on-chain channel information
errors:
- message: channel_not_found
description: The specified channel was not found
description: The on-chain channel information; omitted when no home channel exists for the wallet/asset pair
optional: true
errors: []
- name: get_escrow_channel
description: |
Retrieve current on-chain escrow channel information.
Retrieve current on-chain escrow channel information. Returns a successful response with `channel` omitted when no escrow channel exists for the given ID.

Note: when the escrow channel has been closed by the on-chain
purge queue (no signed FINALIZE_ESCROW_DEPOSIT was received
Expand All @@ -556,10 +555,9 @@ api:
response:
- field_name: channel
type: channel
description: The on-chain channel information
errors:
- message: channel_not_found
description: The specified channel was not found
description: The on-chain channel information; omitted when no escrow channel exists for the given ID
optional: true
errors: []
- name: get_channels
description: Retrieve all channels for a user with optional filtering
request:
Expand Down Expand Up @@ -592,7 +590,7 @@ api:
- message: invalid_parameters
description: The request parameters are invalid
- name: get_latest_state
description: Retrieve the current state of the user stored on the Node
description: Retrieve the current state of the user stored on the Node. Returns a successful response with `state` omitted when no state is stored for the wallet/asset pair.
request:
- field_name: wallet
type: string
Expand All @@ -607,10 +605,9 @@ api:
response:
- field_name: state
type: state
description: The current state of the user
errors:
- message: channel_not_found
description: The specified channel was not found
description: The current state of the user; omitted when no state is stored for the wallet/asset pair
optional: true
errors: []
- name: request_creation
description: Request the creation of a channel from Node
request:
Expand Down Expand Up @@ -766,18 +763,17 @@ api:

errors: []
- name: get_app_definition
description: Retrieve the application definition for a specific app session
description: Retrieve the application definition for a specific app session. Returns a successful response with `definition` omitted when no app session exists for the given ID.
request:
- field_name: app_session_id
type: string
description: The application session ID
response:
- field_name: definition
type: app_definition
description: The application definition
errors:
- message: app_session_not_found
description: The specified app session was not found
description: The application definition; omitted when no app session exists for the given ID
optional: true
errors: []
- name: get_app_sessions
description: List all application sessions for a participant with optional filtering
request:
Expand Down
6 changes: 3 additions & 3 deletions nitronode/api/app_session_v1/get_app_definition.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func (h *Handler) GetAppDefinition(c *rpc.Context) {
return
}

var definition rpc.AppDefinitionV1
var definition *rpc.AppDefinitionV1

err := h.useStoreInTx(func(store Store) error {
session, err := store.GetAppSession(req.AppSessionID)
Expand All @@ -23,7 +23,7 @@ func (h *Handler) GetAppDefinition(c *rpc.Context) {
}

if session == nil {
return rpc.Errorf("app_session_not_found")
return nil
}

// Convert participants
Expand All @@ -35,7 +35,7 @@ func (h *Handler) GetAppDefinition(c *rpc.Context) {
}
}

definition = rpc.AppDefinitionV1{
definition = &rpc.AppDefinitionV1{
Application: session.ApplicationID,
Participants: participants,
Quorum: session.Quorum,
Expand Down
12 changes: 9 additions & 3 deletions nitronode/api/app_session_v1/get_app_definition_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func TestGetAppDefinition_Success(t *testing.T) {
var response rpc.AppSessionsV1GetAppDefinitionResponse
err = ctx.Response.Payload.Translate(&response)
require.NoError(t, err)
require.NotNil(t, response.Definition)

assert.Equal(t, "game", response.Definition.Application)
assert.Len(t, response.Definition.Participants, 2)
Expand Down Expand Up @@ -143,9 +144,14 @@ func TestGetAppDefinition_NotFound(t *testing.T) {
// Execute
handler.GetAppDefinition(ctx)

// Assert
assert.NotNil(t, ctx.Response.Error())
assert.Contains(t, ctx.Response.Error().Error(), "app_session_not_found")
// Assert: absence is a successful response with definition == nil
assert.NotNil(t, ctx.Response.Payload)
assert.Nil(t, ctx.Response.Error())

var response rpc.AppSessionsV1GetAppDefinitionResponse
err = ctx.Response.Payload.Translate(&response)
require.NoError(t, err)
assert.Nil(t, response.Definition)

// Verify all mock expectations
mockStore.AssertExpectations(t)
Expand Down
11 changes: 4 additions & 7 deletions nitronode/api/channel_v1/get_escrow_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,6 @@ func (h *Handler) GetEscrowChannel(c *rpc.Context) {
if err != nil {
return rpc.Errorf("failed to get channel: %v", err)
}

if channel == nil {
return rpc.Errorf("channel_not_found")
}

return nil
})

Expand All @@ -38,8 +33,10 @@ func (h *Handler) GetEscrowChannel(c *rpc.Context) {
return
}

response := rpc.ChannelsV1GetEscrowChannelResponse{
Channel: coreChannelToRPC(*channel),
response := rpc.ChannelsV1GetEscrowChannelResponse{}
if channel != nil {
rpcChannel := coreChannelToRPC(*channel)
response.Channel = &rpcChannel
}

payload, err := rpc.NewPayload(response)
Expand Down
48 changes: 48 additions & 0 deletions nitronode/api/channel_v1/get_escrow_channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ func TestGetEscrowChannel_Success(t *testing.T) {
var response rpc.ChannelsV1GetEscrowChannelResponse
err = ctx.Response.Payload.Translate(&response)
require.NoError(t, err)
require.NotNil(t, response.Channel)

assert.Equal(t, escrowChannelID, response.Channel.ChannelID)
assert.Equal(t, userWallet, response.Channel.UserWallet)
Expand All @@ -97,3 +98,50 @@ func TestGetEscrowChannel_Success(t *testing.T) {
// Verify all mock expectations
mockTxStore.AssertExpectations(t)
}

func TestGetEscrowChannel_NotFound(t *testing.T) {
mockTxStore := new(MockStore)
mockAssetStore := new(MockAssetStore)
mockSigner := NewMockSigner()
nodeSigner, _ := core.NewChannelDefaultSigner(mockSigner)
mockStatePacker := new(MockStatePacker)

handler := &Handler{
stateAdvancer: core.NewStateAdvancerV1(mockAssetStore),
statePacker: mockStatePacker,
useStoreInTx: func(h StoreTxHandler) error {
return h(mockTxStore)
},
nodeSigner: nodeSigner,
nodeAddress: mockSigner.PublicKey().Address().String(),
minChallenge: 3600,
metrics: metrics.NewNoopRuntimeMetricExporter(),
maxSessionKeyIDs: 256,
actionGateway: &MockActionGateway{},
}

escrowChannelID := "0xMissingEscrowChannel"
mockTxStore.On("GetChannelByID", escrowChannelID).Return(nil, nil)

reqPayload := rpc.ChannelsV1GetEscrowChannelRequest{EscrowChannelID: escrowChannelID}
payload, err := rpc.NewPayload(reqPayload)
require.NoError(t, err)

ctx := &rpc.Context{
Context: context.Background(),
Request: rpc.Message{Method: "channels.v1.get_escrow_channel", Payload: payload},
}

handler.GetEscrowChannel(ctx)

// Absence is a successful response with channel == nil.
assert.NotNil(t, ctx.Response.Payload)
assert.Nil(t, ctx.Response.Error())

var response rpc.ChannelsV1GetEscrowChannelResponse
err = ctx.Response.Payload.Translate(&response)
require.NoError(t, err)
assert.Nil(t, response.Channel)

mockTxStore.AssertExpectations(t)
}
11 changes: 4 additions & 7 deletions nitronode/api/channel_v1/get_home_channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,6 @@ func (h *Handler) GetHomeChannel(c *rpc.Context) {
if err != nil {
return rpc.Errorf("failed to get home channel: %v", err)
}

if channel == nil {
return rpc.Errorf("channel_not_found")
}

return nil
})

Expand All @@ -40,8 +35,10 @@ func (h *Handler) GetHomeChannel(c *rpc.Context) {
return
}

response := rpc.ChannelsV1GetHomeChannelResponse{
Channel: coreChannelToRPC(*channel),
response := rpc.ChannelsV1GetHomeChannelResponse{}
if channel != nil {
rpcChannel := coreChannelToRPC(*channel)
response.Channel = &rpcChannel
}

payload, err := rpc.NewPayload(response)
Expand Down
12 changes: 9 additions & 3 deletions nitronode/api/channel_v1/get_home_channel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func TestGetHomeChannel_Success(t *testing.T) {
var response rpc.ChannelsV1GetHomeChannelResponse
err = ctx.Response.Payload.Translate(&response)
require.NoError(t, err)
require.NotNil(t, response.Channel)

assert.Equal(t, homeChannelID, response.Channel.ChannelID)
assert.Equal(t, userWallet, response.Channel.UserWallet)
Expand Down Expand Up @@ -156,9 +157,14 @@ func TestGetHomeChannel_NotFound(t *testing.T) {
// Execute
handler.GetHomeChannel(ctx)

// Assert
assert.NotNil(t, ctx.Response.Error())
assert.Contains(t, ctx.Response.Error().Error(), "channel_not_found")
// Assert: absence is a successful response with channel == nil
assert.NotNil(t, ctx.Response.Payload)
assert.Nil(t, ctx.Response.Error())

var response rpc.ChannelsV1GetHomeChannelResponse
err = ctx.Response.Payload.Translate(&response)
require.NoError(t, err)
assert.Nil(t, response.Channel)

// Verify all mock expectations
mockTxStore.AssertExpectations(t)
Expand Down
14 changes: 6 additions & 8 deletions nitronode/api/channel_v1/get_latest_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,14 @@ func (h *Handler) GetLatestState(c *rpc.Context) {
}
req.Wallet = normalizedWallet

var state core.State
var state *core.State
err = h.useStoreInTx(func(tx Store) error {
lastState, err := tx.GetLastUserState(req.Wallet, req.Asset, req.OnlySigned)
if err != nil {
return rpc.Errorf("failed to get last user state: %v", err)
}

if lastState == nil {
return rpc.Errorf("channel not found")
}

state = *lastState
state = lastState
return nil
})

Expand All @@ -40,8 +36,10 @@ func (h *Handler) GetLatestState(c *rpc.Context) {
return
}

response := rpc.ChannelsV1GetLatestStateResponse{
State: coreStateToRPC(state),
response := rpc.ChannelsV1GetLatestStateResponse{}
if state != nil {
rpcState := coreStateToRPC(*state)
response.State = &rpcState
}

payload, err := rpc.NewPayload(response)
Expand Down
38 changes: 38 additions & 0 deletions nitronode/api/channel_v1/get_latest_state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ func TestGetLatestState_Success(t *testing.T) {
var response rpc.ChannelsV1GetLatestStateResponse
err = ctx.Response.Payload.Translate(&response)
require.NoError(t, err)
require.NotNil(t, response.State)

assert.Equal(t, state.ID, response.State.ID)
assert.Equal(t, userWallet, response.State.UserWallet)
Expand Down Expand Up @@ -198,6 +199,7 @@ func TestGetLatestState_OnlySigned(t *testing.T) {
var response rpc.ChannelsV1GetLatestStateResponse
err = ctx.Response.Payload.Translate(&response)
require.NoError(t, err)
require.NotNil(t, response.State)

assert.Equal(t, state.ID, response.State.ID)
assert.Equal(t, "3", response.State.Version)
Expand Down Expand Up @@ -236,3 +238,39 @@ func TestGetLatestState_NormalizesWallet(t *testing.T) {
require.Nil(t, ctx.Response.Error())
mockTxStore.AssertExpectations(t)
}

// TestGetLatestState_NotFound verifies absent state returns a successful response with nil state payload.
func TestGetLatestState_NotFound(t *testing.T) {
mockTxStore := new(MockStore)

handler := &Handler{
useStoreInTx: func(h StoreTxHandler) error { return h(mockTxStore) },
metrics: metrics.NewNoopRuntimeMetricExporter(),
}

userWallet := "0x1234567890123456789012345678901234567890"
asset := "USDC"
mockTxStore.On("GetLastUserState", userWallet, asset, false).Return(nil, nil)

reqPayload := rpc.ChannelsV1GetLatestStateRequest{Wallet: userWallet, Asset: asset}
payload, err := rpc.NewPayload(reqPayload)
require.NoError(t, err)

ctx := &rpc.Context{
Context: context.Background(),
Request: rpc.Message{Method: "channels.v1.get_latest_state", Payload: payload},
}

handler.GetLatestState(ctx)

// Absence is a successful response with state == nil.
require.NotNil(t, ctx.Response.Payload)
require.NoError(t, ctx.Response.Error())

var response rpc.ChannelsV1GetLatestStateResponse
err = ctx.Response.Payload.Translate(&response)
require.NoError(t, err)
assert.Nil(t, response.State)

mockTxStore.AssertExpectations(t)
}
Loading
Loading