Browse Source

Merge branch 'feature/add-contracts-provider' into 'develop'

Feature/add contracts provider

See merge request ecentrics/concordia!11
develop
Apostolos Fanakis 4 years ago
parent
commit
0a139fdd7e
  1. 7
      packages/concordia-app/src/constants/configuration/defaults.js
  2. 8
      packages/concordia-app/src/constants/contracts/ContractNames.js
  3. 8
      packages/concordia-app/src/options/drizzleOptions.js
  4. 67
      packages/concordia-app/src/utils/drizzleUtils.js
  5. 60
      packages/concordia-contracts-provider/.eslintrc.js
  6. 24
      packages/concordia-contracts-provider/.gitignore
  7. 26
      packages/concordia-contracts-provider/package.json
  8. 11
      packages/concordia-contracts-provider/src/constants.js
  9. 31
      packages/concordia-contracts-provider/src/controllers/download.js
  10. 37
      packages/concordia-contracts-provider/src/controllers/upload.js
  11. 25
      packages/concordia-contracts-provider/src/index.js
  12. 30
      packages/concordia-contracts-provider/src/middleware/upload.js
  13. 14
      packages/concordia-contracts-provider/src/routes/web.js
  14. 17
      packages/concordia-contracts-provider/src/utils/storageUtils.js
  15. 5
      packages/concordia-contracts/constants/config/defaults.js
  16. 10
      packages/concordia-contracts/package.json
  17. 32
      packages/concordia-contracts/utils/contractsProviderUtils.js
  18. 3104
      yarn.lock

7
packages/concordia-app/src/constants/configuration/defaults.js

@ -3,5 +3,12 @@ export const WEB3_PORT_DEFAULT = '8545';
export const WEB3_PORT_SOCKET_TIMEOUT_DEFAULT = 30000;
export const WEB3_PORT_SOCKET_CONNECT_MAX_ATTEMPTS_DEFAULT = 3;
export const REACT_APP_CONCORDIA_HOST_DEFAULT = '127.0.0.1';
export const REACT_APP_CONCORDIA_PORT_DEFAULT = '7000';
export const REACT_APP_RENDEZVOUS_HOST_DEFAULT = '127.0.0.1';
export const REACT_APP_RENDEZVOUS_PORT_DEFAULT = '9090';
export const REACT_APP_CONTRACTS_SUPPLIER_HOST_DEFAULT = '127.0.0.1';
export const REACT_APP_CONTRACTS_SUPPLIER_PORT_DEFAULT = '8400';
export const REACT_APP_CONTRACTS_VERSION_HASH_DEFAULT = 'latest';

8
packages/concordia-app/src/constants/contracts/ContractNames.js

@ -1,3 +1,11 @@
export const FORUM_CONTRACT = 'Forum';
export const POST_VOTING_CONTRACT = 'PostVoting';
export const VOTING_CONTRACT = 'Voting';
const CONTRACTS = [
FORUM_CONTRACT,
POST_VOTING_CONTRACT,
VOTING_CONTRACT,
];
export default CONTRACTS;

8
packages/concordia-app/src/options/drizzleOptions.js

@ -2,13 +2,19 @@
import { contracts } from 'concordia-contracts';
import web3Options from './web3Options';
import appEvents from '../constants/contracts/events';
import downloadContractArtifactsSync from '../utils/drizzleUtils';
const drizzleOptions = {
web3: web3Options,
contracts,
events: { ...appEvents },
reloadWindowOnNetworkChange: true,
reloadWindowOnAccountChange: true, // We need it to reinitialize breeze and create new Orbit databases
};
if (process.env.REACT_APP_USE_EXTERNAL_CONTRACTS_SUPPLIER) {
drizzleOptions.contracts = downloadContractArtifactsSync();
} else {
drizzleOptions.contracts = contracts;
}
export default drizzleOptions;

67
packages/concordia-app/src/utils/drizzleUtils.js

@ -0,0 +1,67 @@
import {
REACT_APP_CONCORDIA_HOST_DEFAULT,
REACT_APP_CONCORDIA_PORT_DEFAULT,
REACT_APP_CONTRACTS_SUPPLIER_HOST_DEFAULT,
REACT_APP_CONTRACTS_SUPPLIER_PORT_DEFAULT,
REACT_APP_CONTRACTS_VERSION_HASH_DEFAULT,
} from '../constants/configuration/defaults';
import CONTRACTS from '../constants/contracts/ContractNames';
function getContractsDownloadRequest() {
const CONTRACTS_SUPPLIER_HOST = process.env.REACT_APP_CONTRACTS_SUPPLIER_HOST
|| REACT_APP_CONTRACTS_SUPPLIER_HOST_DEFAULT;
const CONTRACTS_SUPPLIER_PORT = process.env.REACT_APP_CONTRACTS_SUPPLIER_PORT
|| REACT_APP_CONTRACTS_SUPPLIER_PORT_DEFAULT;
const CONTRACTS_VERSION_HASH = process.env.REACT_APP_CONTRACTS_VERSION_HASH
|| REACT_APP_CONTRACTS_VERSION_HASH_DEFAULT;
const HOST = process.env.REACT_APP_CONCORDIA_HOST || REACT_APP_CONCORDIA_HOST_DEFAULT;
const PORT = process.env.REACT_APP_CONCORDIA_PORT || REACT_APP_CONCORDIA_PORT_DEFAULT;
const xhrRequest = new XMLHttpRequest();
xhrRequest.open('GET',
`http://${CONTRACTS_SUPPLIER_HOST}:${CONTRACTS_SUPPLIER_PORT}/contracts/${CONTRACTS_VERSION_HASH}`,
false);
xhrRequest.setRequestHeader('Access-Control-Allow-Origin', `${HOST}:${PORT}`);
xhrRequest.setRequestHeader('Access-Control-Allow-Credentials', 'true');
return xhrRequest;
}
function validateRemoteContracts(remoteContracts) {
if (remoteContracts.length !== CONTRACTS.length) {
throw new Error(`Version mismatch detected. Artifacts brought ${remoteContracts.length} contracts but app
expected ${CONTRACTS.length}`);
}
const contractsPresentStatus = CONTRACTS.map((contract) => ({
contract,
present: remoteContracts.includes((remoteContract) => remoteContract.contractName === contract),
}));
if (contractsPresentStatus.reduce((accumulator, contract) => accumulator && contract.present, true)) {
throw new Error(`Contracts missing from artifacts. Supplier didn't bring ${contractsPresentStatus
.filter((contractPresentStatus) => contractPresentStatus.present === false)
.map((contractPresentStatus) => contractPresentStatus.contract)
.join(', ')}.`);
}
}
const downloadContractArtifactsSync = () => {
const xhrRequest = getContractsDownloadRequest();
xhrRequest.send(null);
if (xhrRequest.status === 200) {
const contractsRawData = xhrRequest.responseText;
const remoteContracts = JSON.parse(contractsRawData);
validateRemoteContracts(remoteContracts);
return remoteContracts;
}
throw new Error(`Remote contract artifacts download request failed!\n${xhrRequest.responseText}`);
};
export default downloadContractArtifactsSync;

60
packages/concordia-contracts-provider/.eslintrc.js

@ -0,0 +1,60 @@
module.exports = {
env: {
browser: true,
es6: true,
jest: true,
},
extends: [
'plugin:react/recommended',
'airbnb',
],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly',
},
parser: 'babel-eslint',
parserOptions: {
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 2018,
sourceType: 'module',
},
plugins: [
'react',
'react-hooks',
],
rules: {
'react/jsx-props-no-spreading': 'off',
'import/extensions': 'off',
'react/jsx-indent': [
'error',
4,
{
checkAttributes: true,
indentLogicalExpressions: true,
},
],
'react/require-default-props': 'off',
'react/prop-types': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'error',
'max-len': ['warn', { code: 120, tabWidth: 4 }],
'no-unused-vars': 'warn',
'no-console': 'warn',
'no-shadow': 'warn',
'no-multi-str': 'warn',
'jsx-a11y/label-has-associated-control': [2, {
labelAttributes: ['label'],
controlComponents: ['Input'],
depth: 3,
}],
},
settings: {
'import/resolver': {
node: {
extensions: ['.js', '.jsx'],
},
},
},
};

24
packages/concordia-contracts-provider/.gitignore

@ -0,0 +1,24 @@
# Node
/node_modules
# IDE
.DS_Store
.idea
# Build Directories
/build
/src/build
# Logs
/log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Misc
.env.local
.env.development.local
.env.test.local
.env.production.local
contracts-uploads

26
packages/concordia-contracts-provider/package.json

@ -0,0 +1,26 @@
{
"name": "concordia-contracts-provider",
"description": "A server that provides built contracts for Concordia.",
"version": "0.1.0",
"private": true,
"main": "src/index.js",
"scripts": {
"start": "node -r esm src/index.js"
},
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",
"esm": "~3.2.25",
"express": "^4.17.1",
"lodash": "^4.17.20",
"multer": "^1.4.2",
"multiparty": "^4.2.2"
},
"devDependencies": {
"eslint": "^7.19.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.22.0",
"eslint-plugin-react-hooks": "^4.2.0"
}
}

11
packages/concordia-contracts-provider/src/constants.js

@ -0,0 +1,11 @@
import path from 'path';
const PROVIDER_PORT = '8400';
const UPLOAD_CONTRACTS_DIRECTORY = path.join(__dirname, '..', 'contracts-uploads');
const CORS_ALLOWED_ORIGINS = ['http://127.0.0.1:7000', 'http://localhost:7000'];
export default {
port: PROVIDER_PORT,
uploadsDirectory: UPLOAD_CONTRACTS_DIRECTORY,
corsAllowedOrigins: CORS_ALLOWED_ORIGINS,
};

31
packages/concordia-contracts-provider/src/controllers/download.js

@ -0,0 +1,31 @@
import * as fs from 'fs';
import path from 'path';
import { getStorageLocation, getTagsDirectory } from '../utils/storageUtils';
const downloadContracts = async (req, res) => {
const { params: { hash: hashOrTag } } = req;
let directoryPath = getStorageLocation(hashOrTag);
if (!fs.existsSync(directoryPath)) {
const tagsDirectory = getTagsDirectory();
if (fs.existsSync(tagsDirectory)) {
const tagFilePath = path.join(tagsDirectory, hashOrTag);
const tagReference = fs.readFileSync(tagFilePath, 'utf-8');
directoryPath = getStorageLocation(tagReference);
}
}
const contracts = [];
fs.readdirSync(directoryPath).forEach((contractFilename) => {
const rawContractData = fs.readFileSync(path.join(`${directoryPath}/${contractFilename}`), 'utf-8');
const contractJson = JSON.parse(rawContractData);
contracts.push(contractJson);
});
res.send(contracts);
};
export default downloadContracts;

37
packages/concordia-contracts-provider/src/controllers/upload.js

@ -0,0 +1,37 @@
import path from 'path';
import fs from 'fs';
import upload from '../middleware/upload';
import { getTagsDirectory } from '../utils/storageUtils';
const addOrTransferTag = (tag, hash) => {
const tagsDirectory = getTagsDirectory();
const tagFilePath = path.join(tagsDirectory, tag);
fs.mkdirSync(tagsDirectory, { recursive: true });
fs.writeFileSync(tagFilePath, hash);
};
const uploadContracts = async (req, res) => {
try {
await upload(req, res);
const { body: { tag } } = req;
const { params: { hash } } = req;
if (tag) {
addOrTransferTag(tag, hash);
}
if (req.files.length <= 0) {
return res.send('You must select at least 1 file.');
}
return res.send('Files have been uploaded.');
} catch (error) {
console.log(error);
return res.send(`Error when trying upload many files: ${error}`);
}
};
export default uploadContracts;

25
packages/concordia-contracts-provider/src/index.js

@ -0,0 +1,25 @@
import express from 'express';
import cors from 'cors';
import initRoutes from './routes/web';
import constants from './constants';
const PROVIDER_PORT = process.env.CONTRACTS_PROVIDER_PORT || constants.port;
const ALLOWED_ORIGINS = process.env.CORS_ALLOWED_ORIGINS
? process.env.CORS_ALLOWED_ORIGINS.split(';')
: constants.corsAllowedOrigins;
const app = express();
const corsOptions = {
origin: ALLOWED_ORIGINS,
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
};
app.use(express.urlencoded({ extended: true }));
app.use(cors(corsOptions));
initRoutes(app);
app.listen(PROVIDER_PORT, () => {
console.log(`Contracts provider listening at http://127.0.0.1:${PROVIDER_PORT}`);
});

30
packages/concordia-contracts-provider/src/middleware/upload.js

@ -0,0 +1,30 @@
import * as util from 'util';
import * as fs from 'fs';
import multer from 'multer';
import { getStorageLocation } from '../utils/storageUtils';
const storage = multer.diskStorage({
destination: (req, file, callback) => {
const { params: { hash } } = req;
const contractsPath = getStorageLocation(hash);
fs.mkdirSync(contractsPath, { recursive: true });
callback(null, contractsPath);
},
filename: (req, file, callback) => {
const match = ['application/json'];
if (match.indexOf(file.mimetype) === -1) {
const message = `<strong>${file.originalname}</strong> is invalid. Only JSON files are accepted.`;
return callback(message, null);
}
const filename = `${file.originalname}`;
callback(null, filename);
},
});
const uploadFiles = multer({ storage }).array('contracts');
const uploadFilesMiddleware = util.promisify(uploadFiles);
export default uploadFilesMiddleware;

14
packages/concordia-contracts-provider/src/routes/web.js

@ -0,0 +1,14 @@
import express from 'express';
import downloadContracts from '../controllers/download';
import uploadContracts from '../controllers/upload';
const router = express.Router();
const routes = (app) => {
router.get('/contracts/:hash', downloadContracts);
router.post('/contracts/:hash', uploadContracts);
return app.use('/', router);
};
export default routes;

17
packages/concordia-contracts-provider/src/utils/storageUtils.js

@ -0,0 +1,17 @@
import path from 'path';
import constants from '../constants';
export const getStorageLocation = (hash) => {
const UPLOADS_DIRECTORY = process.env.UPLOAD_CONTRACTS_DIRECTORY || constants.uploadsDirectory;
if (hash) {
return path.join(UPLOADS_DIRECTORY, hash);
}
return UPLOADS_DIRECTORY;
};
export const getTagsDirectory = () => {
const uploadsPath = getStorageLocation();
return path.join(uploadsPath, '/tags');
};

5
packages/concordia-contracts/constants/config/defaults.js

@ -4,6 +4,9 @@ const DEVELOP_CHAIN_PORT_DEFAULT = '8545';
const TEST_CHAIN_HOST_DEFAULT = '127.0.0.1';
const TEST_CHAIN_PORT_DEFAULT = '8546';
const CONTRACTS_PROVIDER_HOST_DEFAULT = '127.0.0.1';
const CONTRACTS_PROVIDER_PORT_DEFAULT = '8400';
module.exports = {
develop: {
chainHost: DEVELOP_CHAIN_HOST_DEFAULT,
@ -13,4 +16,6 @@ module.exports = {
chainHost: TEST_CHAIN_HOST_DEFAULT,
chainPort: TEST_CHAIN_PORT_DEFAULT,
},
contractsProviderHost: CONTRACTS_PROVIDER_HOST_DEFAULT,
contractsProviderPort: CONTRACTS_PROVIDER_PORT_DEFAULT,
};

10
packages/concordia-contracts/package.json

@ -10,13 +10,15 @@
"_eslint": "yarn eslint . --format table",
"_solhint": "yarn solhint --formatter table contracts/*.sol test/*.sol",
"test": "yarn truffle test",
"migrate": "yarn _migrate --network develop",
"migrate-reset": "yarn _migrate --network develop --reset",
"_migrate": "yarn truffle migrate"
"migrate": "yarn _migrate --network develop && yarn upload",
"migrate-reset": "yarn _migrate --network develop --reset && yarn upload",
"_migrate": "yarn truffle migrate",
"upload": "node ./utils/contractsProviderUtils.js ${npm_package_version}-dev latest"
},
"dependencies": {
"@openzeppelin/contracts": "~3.2.0",
"truffle": "~5.1.55"
"truffle": "~5.1.55",
"unirest": "^0.6.0"
},
"devDependencies": {
"eslint": "^6.8.0",

32
packages/concordia-contracts/utils/contractsProviderUtils.js

@ -0,0 +1,32 @@
const path = require('path');
const unirest = require('unirest');
const { contracts } = require('../index');
const defaults = require('../constants/config/defaults');
const uploadContractsToProviderUnirest = (versionHash, tag) => {
const CONTRACTS_PROVIDER_HOST = process.env.CONTRACTS_PROVIDER_HOST || defaults.contractsProviderHost;
const CONTRACTS_PROVIDER_PORT = process.env.CONTRACTS_PROVIDER_PORT || defaults.contractsProviderPort;
const uploadPath = `http://${CONTRACTS_PROVIDER_HOST}:${CONTRACTS_PROVIDER_PORT}/contracts/${versionHash}`;
const request = unirest('POST', uploadPath)
.field('tag', tag);
contracts
.forEach((contract) => request
.attach('contracts', path.join(__dirname, '../', 'build/', `${contract.contractName}.json`)));
console.log(`Uploading to ${uploadPath}`);
request.end((res) => {
if (res.error) {
throw new Error(`Failed to upload contracts to provider: ${res.error}`);
}
console.log('Contracts uploaded to provider.');
});
};
const main = () => {
uploadContractsToProviderUnirest(process.argv[2], process.argv[3]);
};
main();

3104
yarn.lock

File diff suppressed because it is too large
Loading…
Cancel
Save