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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
24 changes: 1 addition & 23 deletions .github/workflows/main-pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,28 +34,6 @@ jobs:
project-path: 'integration'
project-name: 'Integration'

test-examples:
name: Test Examples Compilation
needs: test-sdk
uses: ./.github/workflows/build-node-project.yml
strategy:
matrix:
include:
- path: examples/snake/client
name: Snake Client
- path: examples/snake/server
name: Snake Server
- path: examples/tictactoe/client
name: Tic Tac Toe Client
# NOTE: TicTacToe does not require to be compiled,
# still it's present here just to make sure all
# components of example apps are listed.
# - path: examples/tictactoe/server
# name: Tic Tac Toe Server
with:
project-path: ${{ matrix.path }}
project-name: ${{ matrix.name }}

build-and-publish-clearnode:
name: Build and Publish (Clearnode)
needs: test-clearnode
Expand Down Expand Up @@ -88,7 +66,7 @@ jobs:

# TODO: Enable this job if docs preview are needed (do not forget to provide GITHUB_TOKEN and GH access to receive preview URLs).
# https://stackoverflow.com/questions/75514653/firebase-action-hosting-deploy-fails-with-requesterror-resource-not-accessible

# build-and-preview-docs-firebase:
# name: Deploy to Firebase Hosting on PR
# if: ${{ github.event.pull_request.head.repo.full_name == github.repository }}
Expand Down
55 changes: 33 additions & 22 deletions clearnode/app_session_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package main
import (
"context"
"encoding/json"
"errors"
"fmt"

"github.com/ethereum/go-ethereum/common"
Expand All @@ -23,21 +22,21 @@ func NewAppSessionService(db *gorm.DB, wsNotifier *WSNotifier) *AppSessionServic
return &AppSessionService{db: db, wsNotifier: wsNotifier}
}

func (s *AppSessionService) CreateApplication(params *CreateAppSessionParams, rpcSigners map[string]struct{}) (*AppSession, error) {
func (s *AppSessionService) CreateApplication(params *CreateAppSessionParams, rpcSigners map[string]struct{}) (AppSessionResponse, error) {
if len(params.Definition.ParticipantWallets) < 2 {
return nil, RPCErrorf("invalid number of participants")
return AppSessionResponse{}, RPCErrorf("invalid number of participants")
}
if len(params.Definition.Weights) != len(params.Definition.ParticipantWallets) {
return nil, RPCErrorf("number of weights must be equal to participants")
return AppSessionResponse{}, RPCErrorf("number of weights must be equal to participants")
}
if params.Definition.Nonce == 0 {
return nil, RPCErrorf("nonce is zero or not provided")
return AppSessionResponse{}, RPCErrorf("nonce is zero or not provided")
}

// Generate a unique ID for the virtual application
appBytes, err := json.Marshal(params.Definition)
if err != nil {
return nil, RPCErrorf("failed to generate app session ID")
return AppSessionResponse{}, RPCErrorf("failed to generate app session ID")
}
appSessionID := crypto.Keccak256Hash(appBytes).Hex()
sessionAccountID := NewAccountID(appSessionID)
Expand Down Expand Up @@ -82,7 +81,7 @@ func (s *AppSessionService) CreateApplication(params *CreateAppSessionParams, rp
}
_, err = RecordLedgerTransaction(tx, TransactionTypeAppDeposit, userAccountID, sessionAccountID, alloc.AssetSymbol, alloc.Amount)
if err != nil {
return fmt.Errorf("failed to record transaction: %w", err)
return RPCErrorf("failed to record transaction: %w", err)
}
participants[walletAddress] = true
}
Expand All @@ -106,17 +105,21 @@ func (s *AppSessionService) CreateApplication(params *CreateAppSessionParams, rp
})

if err != nil {
return nil, err
return AppSessionResponse{}, err
}

for participant := range participants {
s.wsNotifier.Notify(NewBalanceNotification(participant, s.db))
}

return &AppSession{SessionID: appSessionID, Version: 1, Status: ChannelStatusOpen}, nil
return AppSessionResponse{
AppSessionID: appSessionID,
Version: 1,
Status: string(ChannelStatusOpen),
}, nil
}

func (s *AppSessionService) SubmitAppState(params *SubmitAppStateParams, rpcSigners map[string]struct{}) (uint64, error) {
func (s *AppSessionService) SubmitAppState(params *SubmitAppStateParams, rpcSigners map[string]struct{}) (AppSessionResponse, error) {
var newVersion uint64
err := s.db.Transaction(func(tx *gorm.DB) error {
appSession, participantWeights, err := verifyQuorum(tx, params.AppSessionID, rpcSigners)
Expand All @@ -133,7 +136,7 @@ func (s *AppSessionService) SubmitAppState(params *SubmitAppStateParams, rpcSign
allocationSum := map[string]decimal.Decimal{}
for _, alloc := range params.Allocations {
if alloc.Amount.IsNegative() {
return fmt.Errorf("negative allocation: %s for asset %s", alloc.Amount, alloc.AssetSymbol)
return RPCErrorf("negative allocation: %s for asset %s", alloc.Amount, alloc.AssetSymbol)
}

walletAddress := GetWalletBySigner(alloc.ParticipantWallet)
Expand All @@ -149,7 +152,7 @@ func (s *AppSessionService) SubmitAppState(params *SubmitAppStateParams, rpcSign
ledger := GetWalletLedger(tx, userAddress)
balance, err := ledger.Balance(sessionAccountID, alloc.AssetSymbol)
if err != nil {
return RPCErrorf("failed to get session balance")
return RPCErrorf("failed to get session balance for asset %s", alloc.AssetSymbol)
}

// Reset participant allocation in app session to the new amount
Expand Down Expand Up @@ -181,16 +184,20 @@ func (s *AppSessionService) SubmitAppState(params *SubmitAppStateParams, rpcSign
})

if err != nil {
return 0, err
return AppSessionResponse{}, err
}

return newVersion, nil
return AppSessionResponse{
AppSessionID: params.AppSessionID,
Version: newVersion,
Status: string(ChannelStatusOpen),
}, nil
}

// CloseApplication closes a virtual app session and redistributes funds to participants
func (s *AppSessionService) CloseApplication(params *CloseAppSessionParams, rpcSigners map[string]struct{}) (uint64, error) {
func (s *AppSessionService) CloseApplication(params *CloseAppSessionParams, rpcSigners map[string]struct{}) (AppSessionResponse, error) {
if params.AppSessionID == "" || len(params.Allocations) == 0 {
return 0, errors.New("missing required parameters: app_id or allocations")
return AppSessionResponse{}, RPCErrorf("missing required parameters: app_id or allocations")
}

participants := make(map[string]bool)
Expand All @@ -210,7 +217,7 @@ func (s *AppSessionService) CloseApplication(params *CloseAppSessionParams, rpcS
allocationSum := map[string]decimal.Decimal{}
for _, alloc := range params.Allocations {
if alloc.Amount.IsNegative() {
return fmt.Errorf("negative allocation: %s for asset %s", alloc.Amount, alloc.AssetSymbol)
return RPCErrorf("negative allocation: %s for asset %s", alloc.Amount, alloc.AssetSymbol)
}

walletAddress := GetWalletBySigner(alloc.ParticipantWallet)
Expand All @@ -227,7 +234,7 @@ func (s *AppSessionService) CloseApplication(params *CloseAppSessionParams, rpcS
ledger := GetWalletLedger(tx, userAddress)
balance, err := ledger.Balance(sessionAccountID, alloc.AssetSymbol)
if err != nil {
return RPCErrorf("failed to get session balance")
return RPCErrorf("failed to get session balance for asset %s", alloc.AssetSymbol)
}

// Debit session, credit participant
Expand All @@ -239,7 +246,7 @@ func (s *AppSessionService) CloseApplication(params *CloseAppSessionParams, rpcS
}
_, err = RecordLedgerTransaction(tx, TransactionTypeAppWithdrawal, sessionAccountID, userAccountID, alloc.AssetSymbol, alloc.Amount)
if err != nil {
return fmt.Errorf("failed to record transaction: %w", err)
return RPCErrorf("failed to record transaction: %w", err)
}

if !alloc.Amount.IsZero() {
Expand All @@ -265,17 +272,21 @@ func (s *AppSessionService) CloseApplication(params *CloseAppSessionParams, rpcS
})

if err != nil {
return 0, err
return AppSessionResponse{}, err
}

for participant := range participants {
s.wsNotifier.Notify(NewBalanceNotification(participant, s.db))
}

return newVersion, nil
return AppSessionResponse{
AppSessionID: params.AppSessionID,
Version: newVersion,
Status: string(ChannelStatusClosed),
}, nil
}

// getAppSessions finds all app sessions
// GetAppSessions finds all app sessions
// If participantWallet is specified, it returns only sessions for that participant
// If participantWallet is empty, it returns all sessions
func (s *AppSessionService) GetAppSessions(participantWallet string, status string, options *ListOptions) ([]AppSession, error) {
Expand Down
39 changes: 19 additions & 20 deletions clearnode/app_session_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestAppSessionService_CreateApplication(t *testing.T) {
require.NoError(t, GetWalletLedger(db, userAddressB).Record(userAccountIDB, "usdc", decimal.NewFromInt(200)))

capturedNotifications := make(map[string][]Notification)
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params ...any) {
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params RPCDataParams) {

capturedNotifications[userID] = append(capturedNotifications[userID], Notification{
userID: userID,
Expand Down Expand Up @@ -67,11 +67,11 @@ func TestAppSessionService_CreateApplication(t *testing.T) {
appSession, err := service.CreateApplication(params, rpcSigners)
require.NoError(t, err)
assert.NotNil(t, appSession)
assert.NotEmpty(t, appSession.SessionID)
assert.NotEmpty(t, appSession.AppSessionID)
assert.Equal(t, uint64(1), appSession.Version)
assert.Equal(t, ChannelStatusOpen, appSession.Status)
assert.Equal(t, ChannelStatusOpen, ChannelStatus(appSession.Status))

sessionAccountID := NewAccountID(appSession.SessionID)
sessionAccountID := NewAccountID(appSession.AppSessionID)

assert.Len(t, capturedNotifications, 2)
assertNotifications(t, capturedNotifications, userAddressA.Hex(), 1)
Expand Down Expand Up @@ -103,7 +103,7 @@ func TestAppSessionService_CreateApplication(t *testing.T) {
expectedAmount, exists := expectedTxs[tx.FromAccount]
assert.True(t, exists, "Unexpected destination of a transaction: %s", tx.FromAccount)
assert.Equal(t, TransactionTypeAppDeposit, tx.Type, "Transaction type should be app deposit")
assert.Equal(t, appSession.SessionID, tx.ToAccount, "To account should be app session ID")
assert.Equal(t, appSession.AppSessionID, tx.ToAccount, "To account should be app session ID")
assert.Equal(t, "usdc", tx.AssetSymbol, "Asset symbol should be usdc")
assert.Equal(t, expectedAmount, tx.Amount, "Amount should match allocation")
assert.False(t, tx.CreatedAt.IsZero(), "CreatedAt should be set")
Expand All @@ -117,7 +117,7 @@ func TestAppSessionService_CreateApplication(t *testing.T) {
require.NoError(t, db.Create(&SignerWallet{Signer: userAddressA.Hex(), Wallet: userAddressA.Hex()}).Error)
require.NoError(t, GetWalletLedger(db, userAddressA).Record(userAccountIDA, "usdc", decimal.NewFromInt(50))) // Not enough

service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params ...any) {}, nil))
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params RPCDataParams) {}, nil))
params := &CreateAppSessionParams{
Definition: AppDefinition{
Protocol: "test-proto",
Expand Down Expand Up @@ -148,7 +148,7 @@ func TestAppSessionService_CreateApplication(t *testing.T) {
Status: ChannelStatusChallenged,
}).Error)

service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params ...any) {}, nil))
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params RPCDataParams) {}, nil))
params := &CreateAppSessionParams{
Definition: AppDefinition{
Protocol: "test-proto",
Expand All @@ -175,7 +175,7 @@ func TestAppSessionService_CreateApplication(t *testing.T) {
require.NoError(t, db.Create(&SignerWallet{Signer: userAddressA.Hex(), Wallet: userAddressA.Hex()}).Error)
require.NoError(t, GetWalletLedger(db, userAddressA).Record(userAccountIDA, "usdc", decimal.NewFromInt(100)))

service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params ...any) {}, nil))
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params RPCDataParams) {}, nil))
params := &CreateAppSessionParams{
Definition: AppDefinition{
Protocol: "test-proto",
Expand Down Expand Up @@ -208,7 +208,7 @@ func TestAppSessionService_SubmitAppState(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()

service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params ...any) {}, nil))
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params RPCDataParams) {}, nil))
session := &AppSession{
SessionID: "test-session",
Protocol: "test-proto",
Expand Down Expand Up @@ -240,9 +240,9 @@ func TestAppSessionService_SubmitAppState(t *testing.T) {
userAddressB.Hex(): {},
}

newVersion, err := service.SubmitAppState(params, rpcSigners)
resp, err := service.SubmitAppState(params, rpcSigners)
require.NoError(t, err)
assert.Equal(t, uint64(2), newVersion)
assert.Equal(t, uint64(2), resp.Version)

// Verify balances
appBalA, err := ledgerA.Balance(sessionAccountID, "usdc")
Expand All @@ -258,7 +258,7 @@ func TestAppSessionService_SubmitAppState(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()

service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params ...any) {}, nil))
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params RPCDataParams) {}, nil))
session := &AppSession{
SessionID: "test-session-negative",
Protocol: "test-proto",
Expand Down Expand Up @@ -307,8 +307,7 @@ func TestAppSessionService_CloseApplication(t *testing.T) {
defer cleanup()

capturedNotifications := make(map[string][]Notification)
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params ...any) {

service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params RPCDataParams) {
capturedNotifications[userID] = append(capturedNotifications[userID], Notification{
userID: userID,
eventType: EventType(method),
Expand Down Expand Up @@ -345,9 +344,9 @@ func TestAppSessionService_CloseApplication(t *testing.T) {
userAddressB.Hex(): {},
}

newVersion, err := service.CloseApplication(params, rpcSigners)
resp, err := service.CloseApplication(params, rpcSigners)
require.NoError(t, err)
assert.Equal(t, uint64(2), newVersion)
assert.Equal(t, uint64(2), resp.Version)

assert.Len(t, capturedNotifications, 2)
assertNotifications(t, capturedNotifications, userAddressA.Hex(), 1)
Expand Down Expand Up @@ -395,7 +394,7 @@ func TestAppSessionService_CloseApplication(t *testing.T) {
defer cleanup()

capturedNotifications := make(map[string][]Notification)
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params ...any) {
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params RPCDataParams) {

capturedNotifications[userID] = append(capturedNotifications[userID], Notification{
userID: userID,
Expand Down Expand Up @@ -433,9 +432,9 @@ func TestAppSessionService_CloseApplication(t *testing.T) {
userAddressB.Hex(): {},
}

newVersion, err := service.CloseApplication(params, rpcSigners)
resp, err := service.CloseApplication(params, rpcSigners)
require.NoError(t, err)
assert.Equal(t, uint64(2), newVersion)
assert.Equal(t, uint64(2), resp.Version)

var closedSession AppSession
require.NoError(t, db.First(&closedSession, "session_id = ?", session.SessionID).Error)
Expand All @@ -457,7 +456,7 @@ func TestAppSessionService_CloseApplication(t *testing.T) {
db, cleanup := setupTestDB(t)
defer cleanup()

service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params ...any) {}, nil))
service := NewAppSessionService(db, NewWSNotifier(func(userID string, method string, params RPCDataParams) {}, nil))
session := &AppSession{
SessionID: "test-session-close-negative",
Protocol: "test-proto",
Expand Down
7 changes: 2 additions & 5 deletions clearnode/channel.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
type ChannelStatus string

var (
ChannelStatusJoining ChannelStatus = "joining"
ChannelStatusOpen ChannelStatus = "open"
ChannelStatusClosed ChannelStatus = "closed"
ChannelStatusChallenged ChannelStatus = "challenged"
Expand Down Expand Up @@ -44,15 +43,13 @@ func (Channel) TableName() string {
}

// CreateChannel creates a new channel in the database
// For real channels, participantB is always the broker application
func CreateChannel(tx *gorm.DB, channelID, wallet, participantSigner string, nonce uint64, challenge uint64, adjudicator string, chainID uint32, tokenAddress string, amount decimal.Decimal) (Channel, error) {

channel := Channel{
ChannelID: channelID,
Wallet: wallet,
Participant: participantSigner,
ChainID: chainID, // Set the network ID for channels
Status: ChannelStatusJoining,
ChainID: chainID,
Status: ChannelStatusOpen,
Nonce: nonce,
Adjudicator: adjudicator,
Challenge: challenge,
Expand Down
Loading
Loading