Skip to content

Commit 0e7fb94

Browse files
Merge pull request #67 from hyperledger/dest-round-trips
Destinations with verification and round trips
2 parents f8f4e5e + d1d4dd7 commit 0e7fb94

File tree

9 files changed

+167
-77
lines changed

9 files changed

+167
-77
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,6 @@ build
33
coverage
44
.nyc_output
55
data/member-a/blobs
6-
data/member-b/blobs
6+
data/member-a/destinations
7+
data/member-b/blobs
8+
data/member-b/destinations

src/handlers/blobs.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,12 +91,12 @@ export const deliverBlob = async ({ blobPath, recipientID, recipientURL, request
9191
let sender = peerID;
9292
if(senderDestination !== undefined) {
9393
formData.append('senderDestination', senderDestination);
94-
sender += utils.constants.ID_SEGMENT_SEPARATOR + senderDestination
94+
sender += '/' + senderDestination
9595
}
9696
let recipient = recipientID;
9797
if(recipientDestination !== undefined) {
9898
formData.append('recipientDestination', recipientDestination);
99-
recipient += utils.constants.ID_SEGMENT_SEPARATOR + recipientDestination;
99+
recipient += '/' + recipientDestination;
100100
}
101101
formData.append('blob', stream);
102102
const httpsAgent = new https.Agent({ cert, key, ca });

src/handlers/messages.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,12 @@ export const deliverMessage = async ({ message, recipientID, recipientURL, reque
4848
let sender = peerID;
4949
if(senderDestination !== undefined) {
5050
formData.append('senderDestination', senderDestination);
51-
sender += utils.constants.ID_SEGMENT_SEPARATOR + senderDestination
51+
sender += '/' + senderDestination
5252
}
5353
let recipient = recipientID;
5454
if(recipientDestination !== undefined) {
5555
formData.append('recipientDestination', recipientDestination);
56-
recipient += utils.constants.ID_SEGMENT_SEPARATOR + recipientDestination;
56+
recipient += '/' + recipientDestination;
5757
}
5858
formData.append('message', message);
5959
log.trace(`Delivering message to ${recipient} at ${recipientURL}`);

src/lib/config.ts

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ const ajv = new Ajv();
2828
const validateConfig = ajv.compile(configSchema);
2929
const configFilePath = path.join(utils.constants.DATA_DIRECTORY, utils.constants.CONFIG_FILE_NAME);
3030
const peersFilePath = path.join(utils.constants.DATA_DIRECTORY, utils.constants.PEERS_FILE_NAME);
31+
const destinationsFilePath = path.join(utils.constants.DATA_DIRECTORY, utils.constants.DESTINATIONS_FILE_NAME);
3132

3233
export let config: IConfig;
3334

@@ -39,6 +40,18 @@ const loadConfig = async () => {
3940
try {
4041
log.debug(`Reading config file ${configFilePath}`);
4142
const data = JSON.parse(await fs.readFile(configFilePath, 'utf8'));
43+
try {
44+
log.debug(`Reading destinations file ${destinationsFilePath}`);
45+
data.destinations = JSON.parse(await fs.readFile(destinationsFilePath, 'utf8'));
46+
} catch (err: any) {
47+
// if file does not exist, just set destinations to an empty array
48+
log.debug(`Error code when reading destinations file ${err.code}`);
49+
if (err.code === 'ENOENT') {
50+
data.destinations = data.destinations;
51+
} else {
52+
throw err;
53+
}
54+
}
4255
try {
4356
log.debug(`Reading peers file ${peersFilePath}`);
4457
data.peers = JSON.parse(await fs.readFile(peersFilePath, 'utf8'));
@@ -68,28 +81,32 @@ const loadConfig = async () => {
6881
};
6982

7083
export const persistPeers = async () => {
71-
await ensurePeersDirectoryExists();
84+
await ensureDirectoryExists(peersFilePath);
7285
await fs.writeFile(peersFilePath, JSON.stringify(config.peers, null, 2));
7386
};
7487

88+
export const persistDestinations = async () => {
89+
await ensureDirectoryExists(destinationsFilePath);
90+
await fs.writeFile(destinationsFilePath, JSON.stringify(config.destinations, null, 2));
91+
};
7592

76-
const ensurePeersDirectoryExists = async () => {
93+
const ensureDirectoryExists = async (directory: string) => {
7794
try {
78-
await fs.access(peersFilePath);
95+
await fs.access(directory);
7996
} catch(err: any) {
8097
if(err.code === 'ENOENT') {
81-
await createPeersDirectory();
98+
await createDirectory(directory);
8299
} else {
83-
log.warn(`Could not check for existence of peers subdirectory ${err.code}`);
100+
log.warn(`Could not check for existence of ${err.code}`);
84101
}
85102
}
86103
};
87104

88-
const createPeersDirectory = async () => {
105+
const createDirectory = async (directory: string) => {
89106
try {
90-
await fs.mkdir(path.parse(peersFilePath).dir, { recursive: true });
91-
log.info('Peers subdirectory created');
107+
await fs.mkdir(path.parse(directory).dir, { recursive: true });
108+
log.info(`Directory ${directory} created`);
92109
} catch(err: any) {
93-
log.error(`Failed to create peers subdirectory ${err.code}`);
110+
log.error(`Failed to create directory ${directory} ${err.code}`);
94111
}
95112
};

src/lib/interfaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ export interface IConfig {
2929
endpoint?: string
3030
}
3131
apiKey?: string
32+
destinations?: string[]
3233
peers: {
3334
id: string
3435
endpoint: string
36+
destinations?: string[]
3537
}[]
3638
jsonParserLimit?: string
3739
}

src/lib/utils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export const constants = {
3636
CERT_FILE: 'cert.pem',
3737
KEY_FILE: 'key.pem',
3838
CA_FILE: 'ca.pem',
39+
DESTINATIONS_FILE_NAME: 'destinations/data.json',
3940
TRANSFER_HASH_ALGORITHM: 'sha256',
4041
REST_API_CALL_MAX_ATTEMPTS: 5,
4142
REST_API_CALL_RETRY_DELAY_MS: 500,
@@ -46,8 +47,7 @@ export const constants = {
4647
SIZE_HEADER_NAME: 'dx-size',
4748
LAST_UPDATE_HEADER_NAME: 'dx-last-update',
4849
DEFAULT_JSON_PARSER_LIMIT: '1mb',
49-
DEFAULT_MAX_INFLIGHT: 100,
50-
ID_SEGMENT_SEPARATOR: '/'
50+
DEFAULT_MAX_INFLIGHT: 100
5151
};
5252
const log = new Logger('utils.ts');
5353
axios.defaults.timeout = constants.REST_API_CALL_REQUEST_TIMEOUT;
@@ -140,7 +140,7 @@ export const axiosWithRetry = async (config: AxiosRequestConfig) => {
140140
const data = err.response?.data;
141141
log.error(`${config.method} ${config.url} attempt ${attempts} [${err.response?.status}]`, (data && !data.on) ? data : err.stack);
142142
if (err.response?.status === 404) {
143-
throw err;
143+
throw data.error ? new Error(data.error) : err;
144144
} else {
145145
currentError = err;
146146
attempts++;

src/routers/api.ts

Lines changed: 87 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import * as blobsHandler from '../handlers/blobs';
2323
import * as eventsHandler from '../handlers/events';
2424
import * as messagesHandler from '../handlers/messages';
2525
import { ca, cert, certBundle, key, peerID } from '../lib/cert';
26-
import { config, persistPeers } from '../lib/config';
26+
import { config, persistDestinations, persistPeers } from '../lib/config';
2727
import { IStatus } from '../lib/interfaces';
2828
import RequestError from '../lib/request-error';
2929
import * as utils from '../lib/utils';
@@ -41,7 +41,8 @@ router.get('/id', async (_req, res, next) => {
4141
res.send({
4242
id: peerID,
4343
endpoint: config.p2p.endpoint ?? `https://${config.p2p.hostname}:${config.p2p.port}`,
44-
cert: certBundle
44+
cert: certBundle,
45+
destinations: config.destinations
4546
});
4647
} catch (err) {
4748
next(err);
@@ -82,24 +83,42 @@ router.get('/peers', (_req, res) => {
8283
res.send(config.peers);
8384
});
8485

85-
router.put('/peers/:id', async (req, res, next) => {
86+
router.put('/peers/:id/:destination?', async (req, res, next) => {
8687
try {
87-
if (req.body.endpoint === undefined) {
88-
throw new RequestError('Missing endpoint', 400);
89-
}
90-
if (req.body.cert !== undefined) {
91-
await fs.writeFile(path.join(utils.constants.DATA_DIRECTORY, utils.constants.PEER_CERTS_SUBDIRECTORY, `${req.params.id}.pem`), req.body.cert);
92-
}
93-
let peer = config.peers.find(peer => peer.id === req.params.id);
94-
if (peer === undefined) {
95-
peer = {
96-
id: req.params.id,
97-
endpoint: req.body.endpoint
98-
};
99-
config.peers.push(peer);
88+
if (req.params.id === peerID) {
89+
if (req.params.destination !== undefined) {
90+
if (config.destinations === undefined) {
91+
config.destinations = [req.params.destination]
92+
} else if (!config.destinations.includes(req.params.destination)) {
93+
config.destinations.push(req.params.destination);
94+
}
95+
await persistDestinations();
96+
}
97+
} else {
98+
let peer = config.peers.find(peer => peer.id === req.params.id);
99+
if (peer === undefined) {
100+
if (req.body.endpoint === undefined) {
101+
throw new RequestError('Missing endpoint', 400);
102+
}
103+
peer = {
104+
id: req.params.id,
105+
endpoint: req.body.endpoint
106+
};
107+
config.peers.push(peer);
108+
}
109+
if (req.params.destination !== undefined) {
110+
if (peer.destinations === undefined) {
111+
peer.destinations = [req.params.destination];
112+
} else if (!peer.destinations.includes(req.params.destination)) {
113+
peer.destinations.push(req.params.destination);
114+
}
115+
}
116+
await persistPeers();
117+
if (req.body.cert !== undefined) {
118+
await fs.writeFile(path.join(utils.constants.DATA_DIRECTORY, utils.constants.PEER_CERTS_SUBDIRECTORY, `${req.params.id}.pem`), req.body.cert);
119+
await refreshCACerts();
120+
}
100121
}
101-
await persistPeers();
102-
await refreshCACerts();
103122
res.send({ status: 'added' });
104123
} catch (err) {
105124
next(err);
@@ -133,40 +152,46 @@ router.post('/messages', async (req, res, next) => {
133152
}
134153
let senderDestination: string | undefined = undefined;
135154
if (typeof req.body.sender === 'string') {
136-
if (!req.body.sender.startsWith(peerID)) {
137-
throw new RequestError('Invalid sender');
138-
} else {
139-
const destination = req.body.sender.substring(peerID.length + 1);
140-
if(destination.length > 0) {
141-
senderDestination = destination;
155+
const segments = req.body.sender.split('/');
156+
if (segments[0] !== peerID) {
157+
throw new RequestError(`Sender ID mismatch expected=${peerID} recieved=${segments[0]}`, 400);
158+
}
159+
if (segments.length > 1) {
160+
if (!config.destinations?.includes(segments[1])) {
161+
throw new RequestError(`Unknown sender destination expected=${config.destinations?.join('|') ?? 'none'} recieved=${segments[1]}`, 400);
142162
}
163+
senderDestination = segments[1];
143164
}
144165
}
145166
let recipientID: string;
146167
let recipientDestination: string | undefined = undefined;
147168
if (typeof req.body.recipient === 'string') {
148-
const index = req.body.recipient.indexOf(utils.constants.ID_SEGMENT_SEPARATOR);
149-
if (index !== -1) {
150-
recipientID = req.body.recipient.substring(0, index);
151-
const destination = req.body.recipient.substring(index + 1);
152-
if(destination.length > 0) {
153-
recipientDestination = destination;
154-
}
155-
} else {
156-
recipientID = req.body.recipient;
169+
const segments = req.body.recipient.split('/');
170+
recipientID = segments[0];
171+
if (segments.length > 1) {
172+
recipientDestination = segments[1];
157173
}
158174
} else {
159175
throw new RequestError('Missing recipient', 400);
160176
}
161-
let recipientURL = config.peers.find(peer => peer.id === recipientID)?.endpoint;
162-
if (recipientURL === undefined) {
163-
throw new RequestError(`Unknown recipient`, 400);
177+
let recipientEndpoint: string;
178+
if (recipientID === peerID) {
179+
recipientEndpoint = config.p2p.endpoint ?? `https://${config.p2p.hostname}:${config.p2p.port}`;
180+
} else {
181+
let recipientPeer = config.peers.find(peer => peer.id === recipientID);
182+
if (recipientPeer === undefined) {
183+
throw new RequestError(`Unknown recipient ${recipientID}`, 400);
184+
}
185+
recipientEndpoint = recipientPeer.endpoint;
186+
if (recipientDestination !== undefined && !recipientPeer.destinations?.includes(recipientDestination)) {
187+
throw new RequestError(`Unknown recipient destination expected=${recipientPeer.destinations?.join('|') ?? 'none'} recieved=${recipientDestination}`, 400);
188+
}
164189
}
165190
let requestId = uuidV4();
166191
if (typeof req.body.requestId === 'string') {
167192
requestId = req.body.requestId;
168193
}
169-
messagesHandler.sendMessage(req.body.message, recipientID, recipientURL, requestId, senderDestination, recipientDestination);
194+
messagesHandler.sendMessage(req.body.message, recipientID, recipientEndpoint, requestId, senderDestination, recipientDestination);
170195
res.send({ requestId });
171196
} catch (err) {
172197
next(err);
@@ -231,40 +256,46 @@ router.post('/transfers', async (req, res, next) => {
231256
await blobsHandler.retrieveMetadata(req.body.path);
232257
let senderDestination: string | undefined = undefined;
233258
if (typeof req.body.sender === 'string') {
234-
if (!req.body.sender.startsWith(peerID)) {
235-
throw new RequestError('Invalid sender');
236-
} else {
237-
const destination = req.body.sender.substring(peerID.length + 1);
238-
if(destination.length > 0) {
239-
senderDestination = destination;
259+
const segments = req.body.sender.split('/');
260+
if (segments[0] !== peerID) {
261+
throw new RequestError(`Sender ID mismatch expected=${peerID} recieved=${segments[0]}`, 400);
262+
}
263+
if (segments.length > 1) {
264+
if (!config.destinations?.includes(segments[1])) {
265+
throw new RequestError(`Unknown sender destination expected=${config.destinations?.join('|')} recieved=${segments[1]}`, 400);
240266
}
267+
senderDestination = segments[1];
241268
}
242269
}
243270
let recipientID: string;
244271
let recipientDestination: string | undefined = undefined;
245272
if (typeof req.body.recipient === 'string') {
246-
const index = req.body.recipient.indexOf(utils.constants.ID_SEGMENT_SEPARATOR);
247-
if (index !== -1) {
248-
recipientID = req.body.recipient.substring(0, index);
249-
const destination = req.body.recipient.substring(index + 1);
250-
if(destination.length > 0) {
251-
recipientDestination = destination;
252-
}
253-
} else {
254-
recipientID = req.body.recipient;
273+
const segments = req.body.recipient.split('/');
274+
recipientID = segments[0];
275+
if (segments.length > 1) {
276+
recipientDestination = segments[1];
255277
}
256278
} else {
257279
throw new RequestError('Missing recipient', 400);
258280
}
259-
let recipientURL = config.peers.find(peer => peer.id === recipientID)?.endpoint;
260-
if (recipientURL === undefined) {
261-
throw new RequestError(`Unknown recipient`, 400);
281+
let recipientEndpoint: string;
282+
if (recipientID === peerID) {
283+
recipientEndpoint = config.p2p.endpoint ?? `https://${config.p2p.hostname}:${config.p2p.port}`;
284+
} else {
285+
let recipientPeer = config.peers.find(peer => peer.id === recipientID);
286+
if (recipientPeer === undefined) {
287+
throw new RequestError(`Unknown recipient`, 400);
288+
}
289+
if (recipientDestination !== undefined && !recipientPeer.destinations?.includes(recipientDestination)) {
290+
throw new RequestError(`Unknown recipient destination expected=${recipientPeer.destinations?.join('|')} recieved=${recipientDestination}`, 400);
291+
}
292+
recipientEndpoint = recipientPeer.endpoint;
262293
}
263294
let requestId = uuidV4();
264295
if (typeof req.body.requestId === 'string') {
265296
requestId = req.body.requestId;
266297
}
267-
blobsHandler.sendBlob(req.body.path, recipientID, recipientURL, requestId, senderDestination, recipientDestination);
298+
blobsHandler.sendBlob(req.body.path, recipientID, recipientEndpoint, requestId, senderDestination, recipientDestination);
268299
res.send({ requestId });
269300
} catch (err) {
270301
next(err);

0 commit comments

Comments
 (0)