Skip to content
This repository has been archived by the owner on Apr 16, 2021. It is now read-only.

Commit

Permalink
Merge pull request #99 from wibsonorg/WIB-894-exposes-addresses-by-ba…
Browse files Browse the repository at this point in the history
…tpaId

Endpoint for exposes addresses in a payment associated with a batPayId
  • Loading branch information
nicoayala authored Aug 2, 2019
2 parents b8d748d + 95ba2d4 commit b394971
Show file tree
Hide file tree
Showing 19 changed files with 212 additions and 35 deletions.
10 changes: 9 additions & 1 deletion notary-api/src/jobs/payments.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { jobify } from '../utils/jobify';
import { fetchTxData, fetchTxLogs } from '../blockchain/contracts';
import { packPayData } from '../blockchain/batPay';
import { getDataOrder } from '../operations/dataExchange';
import { notarizationResults } from '../utils/stores';
import { notarizationResults, sellersByPayIndex } from '../utils/stores';
import { fromWib } from '../utils/wibson-lib/coin';

const { brokerUrl, batPayId } = config;
Expand Down Expand Up @@ -40,6 +40,14 @@ export async function sendUnlock(payIndex, registerPaymentHash) {
validateOrThrow(registerPayment.amount === fromWib(price), 'amount did not match price');
const fee = (price * (sellers.length * (notarizationPercentage / 100))) + notarizationFee;
validateOrThrow(Number(registerPayment.fee) === fee, 'fee did not match requested fee');

const addressesByBatPayId = sellers.reduce((acc, seller) => (
{
...acc, [seller.id]: acc[seller.id] ? [...acc[seller.id], seller.address] : [seller.address],
}), {});

await sellersByPayIndex.store(payIndex, addressesByBatPayId);

await axios.post(
`${brokerUrl}/unlock`,
{
Expand Down
2 changes: 1 addition & 1 deletion notary-api/src/operations/completeNotarization.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const completeNotarization = async (lockingKeyHash, validatorResult = [])
const filteredSellers = await Promise.all(sellers
.map(seller => ({
...seller,
result: getResultFromValidation(validatorResult.find(({ id }) => id === seller.id)),
result: getResultFromValidation(validatorResult.find(({ id }) => id === seller.address)),
}))
.filter(v => v.result !== 'rejected')
.map(async (seller) => {
Expand Down
51 changes: 51 additions & 0 deletions notary-api/src/operations/getAddressesByBatPayId.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { sellersByPayIndex } from '../utils/stores';
import { BatPay } from '../blockchain/contracts';
import { packMessage } from '../utils/wibson-lib/cryptography';
import { decryptData } from '../services/signingService';

const ZERO_ACCOUNT = '0x0000000000000000000000000000000000000000';

const ERROR_INCOMPLETE_REGISTRATION = {
message: 'The registration for this id is not completed still',
code: 'registrationImcompleted',
};

const ERROR_INVALID_BATPAY_ID = {
message: 'Try with a valid id',
code: 'invalidId',
};

const ERROR_INVALID_SIGNATURE = {
message: 'The signature is invalid or don\'t correspond to this batPayId',
code: 'invalidSignature',
};

/**
* @async
* @function getAddressesByBatPayId
* @param {Object} [params] data payload needed in the operation.
* @param {number} [params.batPayId] ID in BatPay.
* @param {number} [params.payIndex] Payment index in BatPay.
* @param {string} [params.signature] The signed of the owner of the BatPay ID.
* @returns {Array} An array of the addresses.
*/
export const getAddressesByBatPayId = async ({ payIndex, batPayId, signature }) => {
try {
const { owner } = await BatPay.methods.accounts(batPayId).call();
if (owner === ZERO_ACCOUNT) {
return { error: ERROR_INCOMPLETE_REGISTRATION };
}
try {
const decrypted = await decryptData({ senderAddress: owner, encryptedData: signature });
if (decrypted !== packMessage(batPayId, payIndex)) {
return { error: ERROR_INVALID_SIGNATURE };
}
} catch (_e) {
return { error: ERROR_INVALID_SIGNATURE };
}
} catch (e) {
return { error: ERROR_INVALID_BATPAY_ID };
}
const sellers = await sellersByPayIndex.safeFetch(payIndex, {});
return { addresses: sellers[batPayId] || [] };
};
1 change: 1 addition & 0 deletions notary-api/src/operations/notaryInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export const getSignedNotaryInfo = async () => {
notarizationUrl: `${notaryPublicBaseUrl}/buyers/notarization-request`,
dataResponsesUrl: `${notaryPublicBaseUrl}/data-responses`,
headsUpUrl: `${notaryPublicBaseUrl}/sellers/heads-up`,
paymentUrl: `${notaryPublicBaseUrl}/sellers/payment`,
};

const response = await signingService.signNotaryInfo(info);
Expand Down
15 changes: 4 additions & 11 deletions notary-api/src/queues/notarizationsQueue.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import { notarizationResults, dataResponses } from '../utils/stores';
import { validateDataBatch } from '../services/validatorService';
import { completeNotarizationJob } from '../operations/completeNotarization';
import { decryptWithPrivateKey } from '../utils/wibson-lib/cryptography';
import { getDataOrder } from '../operations/dataExchange';

const queueName = 'NotarizationQueue';
const defaultJobOptions = {
Expand Down Expand Up @@ -33,9 +32,9 @@ const fetchData = async (orderId, seller) => {
* @param {number} orderId
* @param {object[]} sellers
*/
const prepareDataBatchForValidation = async (orderId, sellers) =>
const prepareDataBatchForValidation = (orderId, sellers) =>
Promise.all(sellers.map(async seller => ({
id: seller.id,
id: seller.address,
data: await fetchData(orderId, seller),
})));

Expand All @@ -45,17 +44,11 @@ const inAgreement = () => randomInt(1, 100) <= config.responsesPercentage;
export const notarize = async (lockingKeyHash) => {
const notarization = await notarizationResults.safeFetch(lockingKeyHash);
if (!notarization) return false;

const {
request: { orderId },
result: { sellers },
} = notarization;

const { request: { orderId }, result: { sellers } } = notarization;
const sellersToValidate = sellers.filter(inAgreement);
if (sellersToValidate.length > 0) {
const { id } = await getDataOrder(orderId);
const dataBatch = await prepareDataBatchForValidation(orderId, sellersToValidate);
await validateDataBatch(id, lockingKeyHash, dataBatch);
await validateDataBatch(orderId, lockingKeyHash, dataBatch);
await notarizationResults.update(lockingKeyHash, { status: 'validating' });
} else {
completeNotarizationJob(lockingKeyHash);
Expand Down
2 changes: 1 addition & 1 deletion notary-api/src/routes/data.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const router = express.Router();
* - result
* properties:
* dataGroupId:
* type: string
* type: number
* description: The unique identifier for the validation batch.
* dataBatchId:
* type: string
Expand Down
42 changes: 42 additions & 0 deletions notary-api/src/routes/sellers.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from 'express';
import { asyncError } from '../utils';
import { saveSeller } from '../operations/saveSeller';
import { getAddressesByBatPayId } from '../operations/getAddressesByBatPayId';

const router = express.Router();

Expand Down Expand Up @@ -42,4 +43,45 @@ router.post('/heads-up', asyncError(async (req, res) => {
}
}));

/**
* @swagger
* /sellers/payment:
* get:
* description: |
* Exposes the addresses that receive a specific payment in a specific BatPay ID
* parameters:
* - in: query
* name: payIndex
* type: number
* description: Payment index in BatPay
* required: true
* - in: query
* name: batPayId
* type: number
* description: The register id in BatPay.
* required: true
* - in: query
* name: signature
* type: string
* description: The signed of the owner of the BatPay ID.
* required: true
* produces:
* - application/json
* responses:
* 200:
* description: When the app is OK
* 422:
* description: Problem on our side
* 500:
* description: Problem on our side
*/
router.get('/payment', asyncError(async (req, res) => {
const { addresses, error } = await getAddressesByBatPayId(req.query);
if (!error) {
res.json(addresses);
} else {
res.boom.badData(error.message);
}
}));

export default router;
2 changes: 1 addition & 1 deletion notary-api/src/services/validatorService.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ const httpsAgent = new https.Agent({
});

/**
* @param {string} dataGroupId The unique identifier for the validation data group
* @param {number} dataGroupId The unique identifier for the validation data group
* @param {string} dataBatchId The unique identifier for the validation batch
* @param {object[]} payload Information to validate
*/
Expand Down
4 changes: 2 additions & 2 deletions notary-api/src/utils/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ export function errorHandler(err, req, res, next) {
if (err.failedValidation) {
res.boom.badData('Validation error', err.results);
logger.error(`${err.message
}\nerrors\n${err.results.errors.map(e => ` ${e.message}`)
}\nwarnings\n${err.results.warnings.map(e => ` ${e.message}`)
}\nerrors\n${err.results.errors || err.results.errors.map(e => ` ${e.message}`)
}\nwarnings\n${err.results.warnings || err.results.warnings.map(e => ` ${e.message}`)
}`);
} else {
res.boom.badImplementation();
Expand Down
1 change: 1 addition & 0 deletions notary-api/src/utils/stores.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ export const dataValidationResults = createLevelStore('data_validation_results')
/** @type {LevelStore<string, number>} */
export const sellers = createLevelStore('sellers');
export const dataResponses = createLevelStore('data_responses');
export const sellersByPayIndex = createLevelStore('sellers_by_pay_index:');
2 changes: 1 addition & 1 deletion notary-api/src/utils/wibson-lib/cryptography/encription.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export async function encryptSignedMessage(senderPrivateKey, targetPublicKey, me
/**
* It decrypts a signed message, checking it is from the expected sender.
* @param {string} senderAddress Sender's ethereum address.
* @param {string} targetPublicKey Ethereum public key of the target account.
* @param {string} targetPrivateKey Ethereum public key of the target account.
* @param {string} encrypted the signed message to decrypt.
* @public
*/
Expand Down
3 changes: 2 additions & 1 deletion notary-api/test/jobs/payments.mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,9 @@ export const { fetchTxData, fetchTxLogs } = td.replace('../../src/blockchain/con
export const { getDataOrder } = td.replace('../../src/operations/dataExchange', {
getDataOrder: sinon.stub(),
});
export const { notarizationResults } = td.replace('../../src/utils/stores', {
export const { notarizationResults, sellersByPayIndex } = td.replace('../../src/utils/stores', {
notarizationResults: { safeFetch: sinon.stub() },
sellersByPayIndex: { store: sinon.stub() },
});

export const registerPayment = {
Expand Down
44 changes: 44 additions & 0 deletions notary-api/test/operations/getAddressesByBatPayId.mock.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import td from 'testdouble';
import sinon from 'sinon';
import test from 'ava';

const fakeCall = {
call: sinon.stub(),
};

export const { BatPay } = td.replace('../../src/blockchain/contracts', {
BatPay: {
methods: {
accounts: sinon.stub().returns(fakeCall),
},
},
});

export const { packMessage } = td.replace(
'../../src/utils/wibson-lib/cryptography',
{
packMessage: sinon.stub().returns('someMessage'),
},
);

export const { decryptData } = td.replace('../../src/services/signingService', {
decryptData: sinon.stub().resolves('someMessage'),
});

export const { sellersByPayIndex } = td.replace('../../src/utils/stores', {
sellersByPayIndex: {
safeFetch: sinon
.stub()
.resolves({
8: ['0x075a22bc34b55322cabb0aa87d9e590e01b942c4'],
9: ['0x075a22bc34b55322cabb0aa87d9e590e01b942c5'],
10: ['0x075a22bc34b55322cabb0aa87d9e590e01b942c6'],
}),
},
});

test.beforeEach(() => {
BatPay.methods.accounts().call.resolves({ owner: '0x051027180c70923729e8f090bba0d378bc949ce5' });
});

test.afterEach(sinon.resetHistory);
34 changes: 34 additions & 0 deletions notary-api/test/operations/getAddressesByBatPayId.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { serial as it } from 'ava';
import { decryptData, packMessage, BatPay } from './getAddressesByBatPayId.mock';
import { getAddressesByBatPayId } from '../../src/operations/getAddressesByBatPayId';

const params = {
payIndex: 5,
batPayId: 8,
signature: '0x612a0e967efa6414fc1c2071ff3b9675a1554dd76cb6c17dfa892308fab97f41',
publicKey: '12653256c4a94316da675b758537a7391b07ccbb31d85744081c769a353a50aa0a52e74e4326d3acfa5b8ffa55b5aee4b61ed99cce3c17cb5f3bd078060d9361',
};

it('Exposes an array of addresses that receive a specific payment in a specific BatPay ID', async (assert) => {
const result = await getAddressesByBatPayId(params);
assert.is(await decryptData(), packMessage());
assert.deepEqual(result.addresses, ['0x075a22bc34b55322cabb0aa87d9e590e01b942c4']);
});

it('Invalid Id', async (assert) => {
BatPay.methods.accounts().call.rejects('error');
const { error } = await getAddressesByBatPayId(params);
assert.is(error.code, 'invalidId');
});

it('When the resgistration id is not completed', async (assert) => {
BatPay.methods.accounts().call.resolves({ owner: '0x0000000000000000000000000000000000000000' });
const { error } = await getAddressesByBatPayId(params);
assert.is(error.code, 'registrationImcompleted');
});

it('When the signature is invalid or don\'t correspond to this batPayId', async (assert) => {
decryptData.resolves('otherMessage');
const { error } = await getAddressesByBatPayId(params);
assert.is(error.code, 'invalidSignature');
});
4 changes: 0 additions & 4 deletions notary-api/test/queues/notarizationsQueue.mock.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,6 @@ export const { completeNotarizationJob } = td.replace('../../src/operations/comp
completeNotarizationJob: sinon.spy(),
});

export const { getDataOrder } = td.replace('../../src/operations/dataExchange', {
getDataOrder: sinon.spy(dxid => ({ id: `some-uuid-for-order-${dxid}` })),
});

test.beforeEach(() => {
decryptWithPrivateKey.returns(JSON.stringify({
geolocalization: 'Geo Localization Data',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,22 @@ Generated by [AVA](https://ava.li).
## validateDataBatch.args

[
'some-uuid-for-order-42',
42,
'0x7b9a465acbec4bbce6782e7b2f00d2c6e95c49eff319f1748c1119dc9393a2f6',
[
{
data: {
device: 'Device Data',
geolocalization: 'Geo Localization Data',
},
id: 10,
id: '0xSellerA',
},
{
data: {
device: 'Device Data',
geolocalization: 'Geo Localization Data',
},
id: 20,
id: '0xSellerB',
},
],
]
Binary file modified notary-api/test/queues/snapshots/notarizationsQueue.test.js.snap
Binary file not shown.
4 changes: 3 additions & 1 deletion notary-signing-service/src/routes/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ router.post('/info', async (req, res) => {
const address = account.getAddress().toLowerCase();
const publicKey = account.getPublicKey().toLowerCase();
const {
name, notarizationUrl, dataResponsesUrl, headsUpUrl,
name, notarizationUrl, dataResponsesUrl, headsUpUrl, paymentUrl,
} = req.body;

const privateKey = account.getPrivateKey();
Expand All @@ -50,6 +50,7 @@ router.post('/info', async (req, res) => {
notarizationUrl,
dataResponsesUrl,
headsUpUrl,
paymentUrl,
publicKey,
);

Expand All @@ -60,6 +61,7 @@ router.post('/info', async (req, res) => {
dataResponsesUrl,
headsUpUrl,
publicKey,
paymentUrl,
signature,
});
});
Expand Down
Loading

0 comments on commit b394971

Please sign in to comment.