diff --git a/config/config.json b/config/config.json index f7997c8051..7b3b4121e0 100644 --- a/config/config.json +++ b/config/config.json @@ -40,8 +40,11 @@ "package": "./network/implementation/libp2p-service.js", "config": { "dht": { - "kBucketSize": 20, - "type": "dual" + "kBucketSize": 20 + }, + "nat": { + "enabled": false, + "externalIp": null }, "connectionManager": { "autoDial": true, @@ -161,7 +164,8 @@ "tokenBasedAuthEnabled": false, "loggingEnabled": true, "ipWhitelist": ["::1", "127.0.0.1"], - "publicOperations": [] + "publicOperations": [], + "bothIpAndTokenAuthRequired": false } }, "test": { @@ -206,6 +210,10 @@ "dht": { "kBucketSize": 20 }, + "nat": { + "enabled": false, + "externalIp": null + }, "connectionManager": { "autoDial": true, "autoDialInterval": 10e3, @@ -314,7 +322,8 @@ "tokenBasedAuthEnabled": false, "loggingEnabled": true, "ipWhitelist": ["::1", "127.0.0.1"], - "publicOperations": [] + "publicOperations": [], + "bothIpAndTokenAuthRequired": false } }, "testnet": { @@ -341,6 +350,10 @@ "dht": { "kBucketSize": 20 }, + "nat": { + "enabled": true, + "externalIp": null + }, "connectionManager": { "autoDial": true, "autoDialInterval": 10e3, @@ -480,7 +493,8 @@ "tokenBasedAuthEnabled": false, "loggingEnabled": true, "ipWhitelist": ["::1", "127.0.0.1"], - "publicOperations": [] + "publicOperations": [], + "bothIpAndTokenAuthRequired": false } }, "devnet": { @@ -507,6 +521,10 @@ "dht": { "kBucketSize": 20 }, + "nat": { + "enabled": true, + "externalIp": null + }, "connectionManager": { "autoDial": true, "autoDialInterval": 10e3, @@ -646,7 +664,8 @@ "tokenBasedAuthEnabled": false, "loggingEnabled": true, "ipWhitelist": ["::1", "127.0.0.1"], - "publicOperations": [] + "publicOperations": [], + "bothIpAndTokenAuthRequired": false } }, "mainnet": { @@ -673,6 +692,10 @@ "dht": { "kBucketSize": 20 }, + "nat": { + "enabled": true, + "externalIp": null + }, "connectionManager": { "autoDial": true, "autoDialInterval": 10e3, @@ -812,7 +835,8 @@ "tokenBasedAuthEnabled": false, "loggingEnabled": true, "ipWhitelist": ["::1", "127.0.0.1"], - "publicOperations": [] + "publicOperations": [], + "bothIpAndTokenAuthRequired": false } } } diff --git a/installer/data/template/.origintrail_noderc_one_click_mainnet.json b/installer/data/template/.origintrail_noderc_one_click_mainnet.json new file mode 100644 index 0000000000..8cc582b033 --- /dev/null +++ b/installer/data/template/.origintrail_noderc_one_click_mainnet.json @@ -0,0 +1,82 @@ +{ + "modules":{ + "blockchain":{ + "implementation":{ + "otp:2043":{ + "enabled":true, + "config":{ + "sharesTokenSymbol":"", + "sharesTokenName":"", + "evmManagementWalletPublicKey":"", + "operationalWallets":[ + { + "evmAddress":"", + "privateKey":"" + } + ] + } + }, + "gnosis:100":{ + "enabled":true, + "config":{ + "operatorFee": , + "sharesTokenSymbol":"", + "sharesTokenName":"", + "rpcEndpoints":[ + "" + ], + "evmManagementWalletPublicKey":"", + "operationalWallets":[ + { + "evmAddress":"", + "privateKey":"" + } + ] + } + } + } + }, + "tripleStore":{ + "implementation":{ + "ot-blazegraph":{ + "enabled":true, + "package":"./triple-store/implementation/ot-blazegraph/ot-blazegraph.js", + "config":{ + "repositories":{ + "privateCurrent":{ + "url":"http://localhost:9999", + "name":"private-current", + "username":"admin", + "password":"" + }, + "privateHistory":{ + "url":"http://localhost:9999", + "name":"private-history", + "username":"admin", + "password":"" + }, + "publicCurrent":{ + "url":"http://localhost:9999", + "name":"public-current", + "username":"admin", + "password":"" + }, + "publicHistory":{ + "url":"http://localhost:9999", + "name":"public-history", + "username":"admin", + "password":"" + } + } + } + } + } + } + }, + "auth":{ + "ipWhitelist":[ + "::1", + "127.0.0.1" + ] + } +} diff --git a/installer/data/template/.origintrail_noderc_one_click_testnet.json b/installer/data/template/.origintrail_noderc_one_click_testnet.json index b91fc1356c..b2f1bc855e 100644 --- a/installer/data/template/.origintrail_noderc_one_click_testnet.json +++ b/installer/data/template/.origintrail_noderc_one_click_testnet.json @@ -1,55 +1,82 @@ { - "modules": { - "blockchain": { - "defaultImplementation": "otp", - "implementation": { - "otp": { - "config": { - "sharesTokenSymbol": "", - "sharesTokenName": "", - "evmOperationalWalletPublicKey": "", - "evmOperationalWalletPrivateKey": "", - "evmManagementWalletPrivateKey": "", - "evmManagementWalletPublicKey": "" + "modules":{ + "blockchain":{ + "implementation":{ + "otp:20430":{ + "enabled":true, + "config":{ + "sharesTokenSymbol":"", + "sharesTokenName":"", + "evmManagementWalletPublicKey":"", + "operationalWallets":[ + { + "evmAddress":"", + "privateKey":"" + } + ] + } + }, + "gnosis:10200":{ + "enabled":true, + "config":{ + "operatorFee": , + "sharesTokenSymbol":"", + "sharesTokenName":"", + "rpcEndpoints":[ + "" + ], + "evmManagementWalletPublicKey":"", + "operationalWallets":[ + { + "evmAddress":"", + "privateKey":"" + } + ] } } } }, - "tripleStore": { - "implementation": { - "ot-blazegraph": { - "enabled": true, - "package": "./triple-store/implementation/ot-blazegraph/ot-blazegraph.js", - "config": { - "repositories": { - "privateCurrent": { - "url": "http://localhost:9999", - "name": "private-current", - "username": "admin", - "password": "" + "tripleStore":{ + "implementation":{ + "ot-blazegraph":{ + "enabled":true, + "package":"./triple-store/implementation/ot-blazegraph/ot-blazegraph.js", + "config":{ + "repositories":{ + "privateCurrent":{ + "url":"http://localhost:9999", + "name":"private-current", + "username":"admin", + "password":"" }, - "privateHistory": { - "url": "http://localhost:9999", - "name": "private-history", - "username": "admin", - "password": "" + "privateHistory":{ + "url":"http://localhost:9999", + "name":"private-history", + "username":"admin", + "password":"" }, - "publicCurrent": { - "url": "http://localhost:9999", - "name": "public-current", - "username": "admin", - "password": "" + "publicCurrent":{ + "url":"http://localhost:9999", + "name":"public-current", + "username":"admin", + "password":"" }, - "publicHistory": { - "url": "http://localhost:9999", - "name": "public-history", - "username": "admin", - "password": "" + "publicHistory":{ + "url":"http://localhost:9999", + "name":"public-history", + "username":"admin", + "password":"" } } } } } } + }, + "auth":{ + "ipWhitelist":[ + "::1", + "127.0.0.1" + ] } } diff --git a/package-lock.json b/package-lock.json index 9af8e44640..673c2e145f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "origintrail_node", - "version": "6.2.3", + "version": "6.2.4", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "origintrail_node", - "version": "6.2.3", + "version": "6.2.4", "license": "ISC", "dependencies": { "@comunica/query-sparql": "^2.4.3", diff --git a/package.json b/package.json index 73b34eff3b..1eae3e6d32 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "origintrail_node", - "version": "6.2.3", + "version": "6.2.4", "description": "OTNode V6", "main": "index.js", "type": "module", diff --git a/src/commands/common/log-public-addresses-command.js b/src/commands/common/log-public-addresses-command.js new file mode 100644 index 0000000000..cd32bdb835 --- /dev/null +++ b/src/commands/common/log-public-addresses-command.js @@ -0,0 +1,85 @@ +import ip from 'ip'; +import Command from '../command.js'; +import { NODE_ENVIRONMENTS } from '../../constants/constants.js'; + +class LogPublicAddressesCommand extends Command { + constructor(ctx) { + super(ctx); + this.blockchainModuleManager = ctx.blockchainModuleManager; + this.repositoryModuleManager = ctx.repositoryModuleManager; + this.networkModuleManager = ctx.networkModuleManager; + } + + /** + * Executes command and produces one or more events + * @param command + */ + async execute() { + if ( + process.env.NODE_ENV !== NODE_ENVIRONMENTS.DEVELOPMENT && + process.env.NODE_ENV !== NODE_ENVIRONMENTS.DEVNET + ) + return Command.empty(); + + const publicAddressesMap = {}; + + await Promise.all( + this.blockchainModuleManager.getImplementationNames().map(async (blockchain) => { + const peers = await this.repositoryModuleManager.getAllPeerRecords(blockchain); + await Promise.all( + peers.map(async (p) => { + let peerInfo = await this.networkModuleManager + .getPeerInfo(p.peerId) + .catch(() => ({ addresses: [] })); + if (!peerInfo?.addresses.length) { + peerInfo = { addresses: [] }; + } + + publicAddressesMap[p.peerId] = peerInfo.addresses + .map((addr) => addr.multiaddr) + .filter((addr) => addr.isThinWaistAddress()) + .filter((addr) => !ip.isPrivate(addr.toString().split('/')[2])); + }), + ); + }), + ); + + this.logger.debug( + `Found public addresses for sharding table peers: ${JSON.stringify( + publicAddressesMap, + null, + 2, + )}`, + ); + + return Command.repeat(); + } + + /** + * Recover system from failure + * @param command + * @param error + */ + async recover(command) { + this.logger.warn(`Failed to log public addresses: error: ${command.message}`); + return Command.repeat(); + } + + /** + * Builds default command + * @param map + * @returns {{add, data: *, delay: *, deadline: *}} + */ + default(map) { + const command = { + name: 'logPublicAddressesCommand', + data: {}, + period: 60 * 1000, + transactional: false, + }; + Object.assign(command, map); + return command; + } +} + +export default LogPublicAddressesCommand; diff --git a/src/constants/constants.js b/src/constants/constants.js index 327757a3b8..46d710dbdc 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -346,6 +346,9 @@ export const ERROR_TYPE = { SUBMIT_UPDATE_COMMIT_ERROR: 'SubmitUpdateCommitError', SUBMIT_UPDATE_COMMIT_SEND_TX_ERROR: 'SubmitUpdateCommitSendTxError', }, + GET_BID_SUGGESTION: { + UNSUPPORTED_BID_SUGGESTION_RANGE_ERROR: 'UnsupportedBidSuggestionRangeError', + }, }; export const OPERATION_ID_STATUS = { PENDING: 'PENDING', @@ -757,3 +760,17 @@ export const CACHED_FUNCTIONS = { getParameters: CACHE_DATA_TYPES.ANY, }, }; + +export const LOW_BID_SUGGESTION = 'low'; +export const MED_BID_SUGGESTION = 'med'; +export const HIGH_BID_SUGGESTION = 'high'; +export const ALL_BID_SUGGESTION = 'all'; +export const BID_SUGGESTION_RANGE_ENUM = [ + LOW_BID_SUGGESTION, + MED_BID_SUGGESTION, + HIGH_BID_SUGGESTION, + ALL_BID_SUGGESTION, +]; +export const LOW_BID_SUGGESTION_OFFSET = 9; +export const MED_BID_SUGGESTION_OFFSET = 11; +export const HIGH_BID_SUGGESTION_OFFSET = 14; diff --git a/src/controllers/http-api/v0/bid-suggestion-http-api-controller-v0.js b/src/controllers/http-api/v0/bid-suggestion-http-api-controller-v0.js index ce397f483d..e549db4bcf 100644 --- a/src/controllers/http-api/v0/bid-suggestion-http-api-controller-v0.js +++ b/src/controllers/http-api/v0/bid-suggestion-http-api-controller-v0.js @@ -1,4 +1,5 @@ import BaseController from '../base-http-api-controller.js'; +import { LOW_BID_SUGGESTION } from '../../../constants/constants.js'; class BidSuggestionController extends BaseController { constructor(ctx) { @@ -50,7 +51,7 @@ class BidSuggestionController extends BaseController { firstAssertionId, hashFunctionId, } = req.query; - let { proximityScoreFunctionsPairId } = req.query; + let { proximityScoreFunctionsPairId, bidSuggestionRange } = req.query; try { // TODO: ADD-DOCS if (!proximityScoreFunctionsPairId) { @@ -59,6 +60,9 @@ class BidSuggestionController extends BaseController { else if (blockchain.startsWith('gnosis') || blockchain.startsWith('hardhat2')) proximityScoreFunctionsPairId = 2; } + if (!bidSuggestionRange) { + bidSuggestionRange = LOW_BID_SUGGESTION; + } const bidSuggestion = await this.shardingTableService.getBidSuggestion( blockchain, @@ -68,6 +72,7 @@ class BidSuggestionController extends BaseController { firstAssertionId, hashFunctionId, proximityScoreFunctionsPairId, + bidSuggestionRange, ); this.returnResponse(res, 200, { bidSuggestion }); diff --git a/src/controllers/http-api/v0/request-schema/bid-suggestion-schema-v0.js b/src/controllers/http-api/v0/request-schema/bid-suggestion-schema-v0.js index 715b37ce60..80e5baaa4d 100644 --- a/src/controllers/http-api/v0/request-schema/bid-suggestion-schema-v0.js +++ b/src/controllers/http-api/v0/request-schema/bid-suggestion-schema-v0.js @@ -1,3 +1,5 @@ +import { BID_SUGGESTION_RANGE_ENUM } from '../../../../constants/constants.js'; + export default (argumentsObject) => ({ type: 'object', required: [ @@ -40,5 +42,9 @@ export default (argumentsObject) => ({ minimum: 1, maximum: 2, }, + bidSuggestionRange: { + type: 'string', + enum: BID_SUGGESTION_RANGE_ENUM, + }, }, }); diff --git a/src/modules/network/implementation/libp2p-service.js b/src/modules/network/implementation/libp2p-service.js index 385d9be40e..6ea1343538 100644 --- a/src/modules/network/implementation/libp2p-service.js +++ b/src/modules/network/implementation/libp2p-service.js @@ -48,11 +48,27 @@ class Libp2pService { this.logger = logger; initializationObject.peerRouting = this.config.peerRouting; + + const externalIp = + ip.isV4Format(this.config.nat.externalIp) && ip.isPublic(this.config.nat.externalIp) + ? this.config.nat.externalIp + : undefined; + + if (this.config.nat.externalIp != null && externalIp == null) { + this.logger.warn( + `Invalid external ip defined in configuration: ${this.config.nat.externalIp}. External ip must be in V4 format, and public.`, + ); + } + initializationObject.config = { dht: { enabled: true, ...this.config.dht, }, + nat: { + ...this.config.nat, + externalIp, + }, }; initializationObject.dialer = this.config.connectionManager; @@ -67,8 +83,8 @@ class Libp2pService { }; } initializationObject.addresses = { - listen: [`/ip4/0.0.0.0/tcp/${this.config.port}`], // for production - // announce: ['/dns4/auto-relay.libp2p.io/tcp/443/wss/p2p/QmWDn2LY8nannvSWJzruUYoLZ4vV83vfCBwd8DipvdgQc3'] + listen: [`/ip4/0.0.0.0/tcp/${this.config.port}`], + announce: externalIp ? [`/ip4/${externalIp}/tcp/${this.config.port}`] : [], }; let id; if (!this.config.peerId) { diff --git a/src/service/auth-service.js b/src/service/auth-service.js index 6665462433..caa4b54af5 100644 --- a/src/service/auth-service.js +++ b/src/service/auth-service.js @@ -18,7 +18,18 @@ class AuthService { const isWhitelisted = this._isIpWhitelisted(ip); const isTokenValid = await this._isTokenValid(token); - const isAuthenticated = isWhitelisted && isTokenValid; + const tokenAuthEnabled = this._authConfig.tokenBasedAuthEnabled; + const ipAuthEnabled = this._authConfig.ipBasedAuthEnabled; + const requiresBoth = this._authConfig.bothIpAndTokenAuthRequired; + + let isAuthenticated = false; + if (tokenAuthEnabled && ipAuthEnabled) { + isAuthenticated = requiresBoth + ? isWhitelisted && isTokenValid + : isWhitelisted || isTokenValid; + } else { + isAuthenticated = isWhitelisted && isTokenValid; + } if (!isAuthenticated) { this._logMessage('Received unauthenticated request.'); @@ -38,6 +49,20 @@ class AuthService { return true; } + /* + If IP is whitelisted and both IP and Token Auth is NOT required pass authorization check. + Authentication middleware checks if IP is white listed before authorization middleware. + */ + if (!(await this._isTokenValid(token))) { + if ( + !this._authConfig.bothIpAndTokenAuthRequired && + this._authConfig.ipBasedAuthEnabled + ) { + return true; + } + return false; + } + const tokenId = jwtUtil.getPayload(token).jti; const abilities = await this._repository.getTokenAbilities(tokenId); @@ -80,6 +105,10 @@ class AuthService { return true; } + if (!token) { + return false; + } + if (!jwtUtil.validateJWT(token)) { return false; } diff --git a/src/service/sharding-table-service.js b/src/service/sharding-table-service.js index 77e3c62cec..aabec9b799 100644 --- a/src/service/sharding-table-service.js +++ b/src/service/sharding-table-service.js @@ -3,6 +3,15 @@ import { CONTRACTS, DEFAULT_BLOCKCHAIN_EVENT_SYNC_PERIOD_IN_MILLS, PEER_RECORD_UPDATE_DELAY, + LOW_BID_SUGGESTION, + MED_BID_SUGGESTION, + HIGH_BID_SUGGESTION, + ALL_BID_SUGGESTION, + LOW_BID_SUGGESTION_OFFSET, + MED_BID_SUGGESTION_OFFSET, + HIGH_BID_SUGGESTION_OFFSET, + ERROR_TYPE, + BID_SUGGESTION_RANGE_ENUM, } from '../constants/constants.js'; class ShardingTableService { @@ -170,6 +179,7 @@ class ShardingTableService { firstAssertionId, hashFunctionId, proximityScoreFunctionsPairId, + bidSuggestionRange = LOW_BID_SUGGESTION, ) { const kbSize = assertionSize < BYTES_IN_KILOBYTE ? BYTES_IN_KILOBYTE : assertionSize; const peerRecords = await this.findNeighbourhood( @@ -183,20 +193,72 @@ class ShardingTableService { hashFunctionId, proximityScoreFunctionsPairId, ); - const r1 = await this.blockchainModuleManager.getR1(blockchainId); + const r0 = await this.blockchainModuleManager.getR0(blockchainId); // todo remove this line once we implement logic for storing assertion in publish node if it's in neighbourhood const myPeerId = this.networkModuleManager.getPeerId().toB58String(); const filteredPeerRecords = peerRecords.filter((peer) => peer.peerId !== myPeerId); const sorted = filteredPeerRecords.sort((a, b) => a.ask - b.ask); - let ask; - if (sorted.length > r1) { - ask = sorted[r1 - 1].ask; - } else { - ask = sorted[sorted.length - 1].ask; + if (bidSuggestionRange === ALL_BID_SUGGESTION) { + const allBidSuggestions = {}; + allBidSuggestions[LOW_BID_SUGGESTION] = this.calculateBidSuggestion( + LOW_BID_SUGGESTION_OFFSET, + sorted, + blockchainId, + kbSize, + epochsNumber, + r0, + ); + allBidSuggestions[MED_BID_SUGGESTION] = this.calculateBidSuggestion( + MED_BID_SUGGESTION_OFFSET, + sorted, + blockchainId, + kbSize, + epochsNumber, + r0, + ); + allBidSuggestions[HIGH_BID_SUGGESTION] = this.calculateBidSuggestion( + HIGH_BID_SUGGESTION_OFFSET, + sorted, + blockchainId, + kbSize, + epochsNumber, + r0, + ); + + return allBidSuggestions; + } + let askOffset; + switch (bidSuggestionRange) { + case LOW_BID_SUGGESTION: + askOffset = LOW_BID_SUGGESTION_OFFSET; + break; + case MED_BID_SUGGESTION: + askOffset = MED_BID_SUGGESTION_OFFSET; + break; + case HIGH_BID_SUGGESTION: + askOffset = HIGH_BID_SUGGESTION_OFFSET; + break; + default: + this.logger.error( + `${ERROR_TYPE.UNSUPPORTED_BID_SUGGESTION_RANGE_ERROR}: Supported values: ${BID_SUGGESTION_RANGE_ENUM}.`, + ); + throw Error(ERROR_TYPE.UNSUPPORTED_BID_SUGGESTION_RANGE_ERROR); } + const bidSuggestion = this.calculateBidSuggestion( + askOffset, + sorted, + blockchainId, + kbSize, + epochsNumber, + r0, + ); + return bidSuggestion; + } - const r0 = await this.blockchainModuleManager.getR0(blockchainId); + calculateBidSuggestion(askOffset, sorted, blockchainId, kbSize, epochsNumber, r0) { + const effectiveAskOffset = Math.min(askOffset, sorted.length - 1); + const { ask } = sorted[effectiveAskOffset]; const bidSuggestion = this.blockchainModuleManager .convertToWei(blockchainId, ask)