Skip to content

Commit 6d525a7

Browse files
author
Karl Ranna
committed
fix(swaps): validate Raiden's resolve request
In this commit we validate Raiden's resolve request to make sure the payment we're receiving has the previously agreed upon `deal.makerCltvDelta` as the minimum duration of the lock. In addition we change the error handling to return a status code 400 (Bad Request) to prevent Raiden from crashing. Previously we returned a status code of 200 with the response `{ secret: 'error message' }`.
1 parent 4ba4335 commit 6d525a7

File tree

8 files changed

+41
-33
lines changed

8 files changed

+41
-33
lines changed

lib/http/HttpServer.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,25 @@ class HttpServer {
6363
default:
6464
res.writeHead(404);
6565
res.end();
66+
break;
6667
}
6768
res.writeHead(200, { 'Content-Type': 'application/json' });
6869
res.end(JSON.stringify(resJson));
6970
} catch (err) {
70-
if (err.code === errorCodes.INVALID_ARGUMENT) {
71-
res.writeHead(400);
72-
res.end(err.message);
73-
} else if (err.code === swapErrorCodes.PAYMENT_HASH_NOT_FOUND) {
74-
res.writeHead(404);
75-
res.end(err.message);
76-
} else {
77-
res.writeHead(500);
78-
res.end(JSON.stringify(err));
71+
switch (err.code) {
72+
case swapErrorCodes.INVALID_RESOLVE_REQUEST:
73+
case errorCodes.INVALID_ARGUMENT:
74+
res.writeHead(400);
75+
res.end(err.message);
76+
break;
77+
case swapErrorCodes.PAYMENT_HASH_NOT_FOUND:
78+
res.writeHead(404);
79+
res.end(err.message);
80+
break;
81+
default:
82+
res.writeHead(500);
83+
res.end(JSON.stringify(err));
84+
break;
7985
}
8086
}
8187
}

lib/http/HttpService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,12 @@ class HttpService {
55
constructor(private service: Service) {}
66

77
public resolveHashRaiden = async (resolveRequest: RaidenResolveRequest): Promise<RaidenResolveResponse> => {
8-
// TODO: add settle time out, etc
98
const secret = await this.service.resolveHash({
109
rHash: resolveRequest.secrethash.slice(2),
1110
amount: resolveRequest.amount,
1211
tokenAddress: resolveRequest.token,
1312
expiration: resolveRequest.expiration,
13+
chain_height: resolveRequest.chain_height,
1414
});
1515
return { secret };
1616
}

lib/p2p/Peer.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -696,6 +696,7 @@ class Peer extends EventEmitter {
696696
this.logger.warn(`Peer (${this.label}): ${err.message}`);
697697
this.emit('reputation', ReputationEvent.WireProtocolErr);
698698
await this.close(DisconnectionReason.WireProtocolErr, err.message);
699+
break;
699700
}
700701
});
701702
}

lib/raidenclient/types.ts

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,15 +64,10 @@ export type RaidenResolveRequest = {
6464
secrethash: string;
6565
/** The amount of the incoming payment pending resolution, in the smallest units supported by the token. */
6666
amount: number;
67-
/** The maximum number of blocks allowed between the setting of a hashlock and the revealing of the related secret. */
68-
reveal_timeout: number;
69-
/** The lock expiration for the incoming payment. */
67+
/** The lock expiration for the incoming payment (absolute block number). */
7068
expiration: number;
71-
// 'settle_timeout': raiden.config['settle_timeout'],
72-
// unused fields on the raiden request listed below, taken from raiden codebase
73-
// 'payment_identifier': secret_request_event.payment_identifier,
74-
// 'payment_sender': to_hex(secret_request_event.recipient),
75-
// 'payment_recipient': to_hex(raiden.address),
69+
/** The current height of the chain */
70+
chain_height: number;
7671
};
7772

7873
export type RaidenResolveResponse = {

lib/swaps/Swaps.ts

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -727,25 +727,22 @@ class Swaps extends EventEmitter {
727727
* @returns `true` if the resolve request is valid, `false` otherwise
728728
*/
729729
private validateResolveRequest = (deal: SwapDeal, resolveRequest: ResolveRequest) => {
730-
const { amount, tokenAddress, expiration } = resolveRequest;
730+
const { amount, tokenAddress, expiration, chain_height } = resolveRequest;
731731
let expectedAmount: number;
732732
let expectedTokenAddress: string | undefined;
733733
let source: string;
734734
let destination: string;
735-
// TODO: check cltv value
736735
switch (deal.role) {
737736
case SwapRole.Maker:
738737
expectedAmount = deal.makerUnits;
739738
expectedTokenAddress = this.swapClientManager.raidenClient.tokenAddresses.get(deal.makerCurrency);
740739
source = 'Taker';
741740
destination = 'Maker';
742-
if (deal.makerCltvDelta! > expiration) {
743-
// temporary code to relax the lock time check for raiden
744-
this.logger.warn(`lock expiration of ${expiration} does not meet ${deal.makerCltvDelta} minimum for ${deal.rHash}`);
745-
// end temp code
746-
// this.logger.error(`cltvDelta of ${expiration} does not meet ${deal.makerCltvDelta!} minimum`);
747-
// this.failDeal(deal, SwapFailureReason.InvalidResolveRequest, 'Insufficient CLTV received on first leg');
748-
// return false;
741+
const lockExpirationDelta = expiration - chain_height;
742+
if (deal.makerCltvDelta! > lockExpirationDelta) {
743+
this.logger.error(`cltvDelta of ${lockExpirationDelta} does not meet ${deal.makerCltvDelta!} minimum`);
744+
this.failDeal(deal, SwapFailureReason.InvalidResolveRequest, 'Insufficient CLTV received on first leg');
745+
return false;
749746
}
750747
break;
751748
case SwapRole.Taker:
@@ -870,10 +867,10 @@ class Swaps extends EventEmitter {
870867

871868
if (deal) {
872869
if (!this.validateResolveRequest(deal, resolveRequest)) {
873-
return deal.errorMessage || '';
870+
throw errors.INVALID_RESOLVE_REQUEST(rHash, deal.errorMessage || '');
874871
}
875872
} else {
876-
return 'swap deal not found';
873+
throw errors.PAYMENT_HASH_NOT_FOUND(rHash);
877874
}
878875

879876
try {
@@ -886,7 +883,7 @@ class Swaps extends EventEmitter {
886883
return preimage;
887884
} catch (err) {
888885
this.logger.error(err.message);
889-
return err.message;
886+
throw err;
890887
}
891888
}
892889

@@ -1001,6 +998,7 @@ class Swaps extends EventEmitter {
1001998
break;
1002999
default:
10031000
assert.fail('unknown deal phase');
1001+
break;
10041002
}
10051003

10061004
deal.phase = newPhase;

lib/swaps/errors.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ const errorCodes = {
66
SWAP_CLIENT_NOT_CONFIGURED: codesPrefix.concat('.2'),
77
PAYMENT_HASH_NOT_FOUND: codesPrefix.concat('.3'),
88
PAYMENT_ERROR: codesPrefix.concat('.4'),
9-
PAYMENT_REJECTED: codesPrefix.concat('.4'),
9+
PAYMENT_REJECTED: codesPrefix.concat('.5'),
10+
INVALID_RESOLVE_REQUEST: codesPrefix.concat('.6'),
1011
};
1112

1213
const errors = {
@@ -30,6 +31,10 @@ const errors = {
3031
message: 'the recipient rejected our payment for the swap',
3132
code: errorCodes.PAYMENT_REJECTED,
3233
},
34+
INVALID_RESOLVE_REQUEST: (rHash: string, errorMessage: string) => ({
35+
message: `invalid resolve request for rHash ${rHash}: ${errorMessage}`,
36+
code: errorCodes.INVALID_RESOLVE_REQUEST,
37+
}),
3338
};
3439

3540
export { errorCodes, errors };

lib/swaps/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,4 +105,5 @@ export type ResolveRequest = {
105105
tokenAddress: string,
106106
/** The number of blocks before the incoming payment expires. */
107107
expiration: number,
108+
chain_height: number,
108109
};

test/jest/HttpService.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ jest.mock('../../lib/service/Service');
66
const mockedService = <jest.Mock<Service>><any>Service;
77

88
const token = '0x4c354c76d5f73a63a90be776897dc81fb6238772';
9-
const expiration = 75;
9+
const expiration = 7360;
10+
const chain_height = 1000;
1011
const secretHash = '2ea852a816e4390f1468b9b1389be14e3a965479beb2c97354a409993eb52e46';
1112
const resolveRequest: RaidenResolveRequest = {
1213
token,
1314
expiration,
15+
chain_height,
1416
secrethash: `0x${secretHash}`,
1517
amount: 1,
16-
reveal_timeout: 50,
1718
};
1819

1920
describe('HttpService', () => {
@@ -35,6 +36,7 @@ describe('HttpService', () => {
3536
expect(service.resolveHash)
3637
.toHaveBeenCalledWith({
3738
expiration,
39+
chain_height,
3840
amount: resolveRequest.amount,
3941
rHash: secretHash,
4042
tokenAddress: token,

0 commit comments

Comments
 (0)