Add Discover service implementation + some validators

This commit is contained in:
Lyubomir Kiprov 2019-04-24 13:56:18 +03:00
parent bd524aff54
commit 27fb4f339e
14 changed files with 375 additions and 47 deletions

View File

@ -1,8 +1,16 @@
{
"extends": ["airbnb", "plugin:prettier/recommended"],
"plugins": ["prettier"],
"extends": [
"airbnb",
"plugin:prettier/recommended"
],
"plugins": [
"prettier"
],
"rules": {
"prettier/prettier": "error"
"prettier/prettier": "error",
"func-names": "off",
"eqeqeq": "off",
"class-methods-use-this": "off"
},
"env": {
"browser": true,
@ -12,4 +20,4 @@
"parserOptions": {
"ecmaVersion": 9
}
}
}

View File

@ -2,23 +2,23 @@ module.exports = {
// applies to all environments
default: {
enabled: true,
rpcHost: "localhost", // HTTP-RPC server listening interface (default: "localhost")
rpcHost: 'localhost', // HTTP-RPC server listening interface (default: "localhost")
rpcPort: 8545, // HTTP-RPC server listening port (default: 8545)
rpcCorsDomain: { // Domains from which to accept cross origin requests (browser enforced). This can also be a comma separated list
auto: true, // When "auto" is true, Embark will automatically set the cors to the address of the webserver
additionalCors: [] // Additional CORS domains to add to the list. If "auto" is false, only those will be added
additionalCors: [], // Additional CORS domains to add to the list. If "auto" is false, only those will be added
},
wsRPC: true, // Enable the WS-RPC server
wsOrigins: { // Same thing as "rpcCorsDomain", but for WS origins
auto: true,
additionalCors: []
additionalCors: [],
},
wsHost: "localhost", // WS-RPC server listening interface (default: "localhost")
wsPort: 8546 // WS-RPC server listening port (default: 8546)
wsHost: 'localhost', // WS-RPC server listening interface (default: "localhost")
wsPort: 8546, // WS-RPC server listening port (default: 8546)
// Accounts to use as node accounts
// The order here corresponds to the order of `web3.eth.getAccounts`, so the first one is the `defaultAccount`
/*,accounts: [
/* ,accounts: [
{
nodeAccounts: true, // Accounts use for the node
numAddresses: "1", // Number of addresses/accounts (defaults to 1)
@ -39,68 +39,68 @@ module.exports = {
numAddresses: "1", // Optional. The number of addresses to get
hdpath: "m/44'/60'/0'/0/" // Optional. HD derivation path
}
]*/
] */
},
// default environment, merges with the settings in default
// assumed to be the intended environment by `embark run` and `embark blockchain`
development: {
ethereumClientName: "geth", // Can be geth or parity (default:geth)
//ethereumClientBin: "geth", // path to the client binary. Useful if it is not in the global PATH
networkType: "custom", // Can be: testnet, rinkeby, livenet or custom, in which case, it will use the specified networkId
ethereumClientName: 'geth', // Can be geth or parity (default:geth)
// ethereumClientBin: "geth", // path to the client binary. Useful if it is not in the global PATH
networkType: 'custom', // Can be: testnet, rinkeby, livenet or custom, in which case, it will use the specified networkId
networkId: 1337, // Network id used when networkType is custom
isDev: true, // Uses and ephemeral proof-of-authority network with a pre-funded developer account, mining enabled
datadir: ".embark/development/datadir", // Data directory for the databases and keystore (Geth 1.8.15 and Parity 2.0.4 can use the same base folder, till now they does not conflict with each other)
datadir: '.embark/development/datadir', // Data directory for the databases and keystore (Geth 1.8.15 and Parity 2.0.4 can use the same base folder, till now they does not conflict with each other)
mineWhenNeeded: true, // Uses our custom script (if isDev is false) to mine only when needed
nodiscover: true, // Disables the peer discovery mechanism (manual peer addition)
maxpeers: 0, // Maximum number of network peers (network disabled if set to 0) (default: 25)
proxy: true, // Proxy is used to present meaningful information about transactions
targetGasLimit: 9000000, // Target gas limit sets the artificial target gas floor for the blocks to mine
simulatorBlocktime: 0 // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining.
simulatorBlocktime: 0, // Specify blockTime in seconds for automatic mining. Default is 0 and no auto-mining.
},
// merges with the settings in default
// used with "embark run privatenet" and/or "embark blockchain privatenet"
privatenet: {
networkType: "custom",
networkType: 'custom',
networkId: 1337,
isDev: false,
datadir: ".embark/privatenet/datadir",
datadir: '.embark/privatenet/datadir',
// -- mineWhenNeeded --
// This options is only valid when isDev is false.
// This options is only valid when isDev is false.
// Enabling this option uses our custom script to mine only when needed.
// Embark creates a development account for you (using `geth account new`) and funds the account. This account can be used for
// development (and even imported in to MetaMask). To enable correct usage, a password for this account must be specified
// in the `account > password` setting below.
// NOTE: once `mineWhenNeeded` is enabled, you must run an `embark reset` on your dApp before running
// `embark blockchain` or `embark run` for the first time.
mineWhenNeeded: true,
mineWhenNeeded: true,
// -- genesisBlock --
// This option is only valid when mineWhenNeeded is true (which is only valid if isDev is false).
// When enabled, geth uses POW to mine transactions as it would normally, instead of using POA as it does in --dev mode.
// On the first `embark blockchain or embark run` after this option is enabled, geth will create a new chain with a
// On the first `embark blockchain or embark run` after this option is enabled, geth will create a new chain with a
// genesis block, which can be configured using the `genesisBlock` configuration option below.
genesisBlock: "config/privatenet/genesis.json", // Genesis block to initiate on first creation of a development node
genesisBlock: 'config/privatenet/genesis.json', // Genesis block to initiate on first creation of a development node
nodiscover: true,
maxpeers: 0,
proxy: true,
accounts: [
{
nodeAccounts: true,
password: "config/privatenet/password" // Password to unlock the account
}
password: 'config/privatenet/password', // Password to unlock the account
},
],
targetGasLimit: 8000000,
simulatorBlocktime: 0
simulatorBlocktime: 0,
},
privateparitynet: {
ethereumClientName: "parity",
networkType: "custom",
ethereumClientName: 'parity',
networkType: 'custom',
networkId: 1337,
isDev: false,
genesisBlock: "config/privatenet/genesis-parity.json", // Genesis block to initiate on first creation of a development node
datadir: ".embark/privatenet/datadir",
genesisBlock: 'config/privatenet/genesis-parity.json', // Genesis block to initiate on first creation of a development node
datadir: '.embark/privatenet/datadir',
mineWhenNeeded: false,
nodiscover: true,
maxpeers: 0,
@ -108,43 +108,43 @@ module.exports = {
accounts: [
{
nodeAccounts: true,
password: "config/privatenet/password"
}
password: 'config/privatenet/password',
},
],
targetGasLimit: 8000000,
simulatorBlocktime: 0
simulatorBlocktime: 0,
},
// merges with the settings in default
// used with "embark run testnet" and/or "embark blockchain testnet"
testnet: {
networkType: "testnet",
syncMode: "light",
networkType: 'testnet',
syncMode: 'light',
accounts: [
{
nodeAccounts: true,
password: "config/testnet/password"
}
]
password: 'config/testnet/password',
},
],
},
// merges with the settings in default
// used with "embark run livenet" and/or "embark blockchain livenet"
livenet: {
networkType: "livenet",
syncMode: "light",
rpcCorsDomain: "http://localhost:8000",
wsOrigins: "http://localhost:8000",
networkType: 'livenet',
syncMode: 'light',
rpcCorsDomain: 'http://localhost:8000',
wsOrigins: 'http://localhost:8000',
accounts: [
{
nodeAccounts: true,
password: "config/livenet/password"
}
]
}
password: 'config/livenet/password',
},
],
},
// you can name an environment with specific settings and then specify with
// "embark run custom_name" or "embark blockchain custom_name"
//custom_name: {
//}
// custom_name: {
// }
};

View File

@ -4,12 +4,14 @@
"version": "0.1.0",
"private": true,
"dependencies": {
"@babel/runtime-corejs2": "^7.4.3",
"@trailofbits/embark-contract-info": "^1.0.0",
"bignumber.js": "^8.1.1",
"bs58": "^4.0.1",
"connected-react-router": "^6.3.2",
"debounce": "^1.2.0",
"decimal.js": "^10.0.2",
"embark": "^4.0.2",
"embark-solium": "0.0.1",
"history": "^4.7.2",
"moment": "^2.24.0",

View File

@ -0,0 +1,14 @@
const discoverValidatorUtils = {
async checkDappCorrectness(dapp, id) {
if (dapp.id != id) {
throw new Error('Error fetching correct data')
}
},
async checkUpVotingAmount(amount, limit) {
if (amount > limit) {
throw new Error('You cannot upvote by this much, try with a lower amount')
}
},
}
export default discoverValidatorUtils

View File

@ -0,0 +1,43 @@
import DiscoverContract from '../../../../embarkArtifacts/contracts/Discover'
import DiscoverRServiceValidator from './validator'
class DiscoverReadService {
constructor() {
this.validator = new DiscoverRServiceValidator(this)
}
// TODO: Amount -> string/bigInt/number ?
// TODO: Maybe we can get id from a DApp name ?
// TODO: formatBigNumberToNumber
// TODO: validators - YUP
async upVoteEffect(id, amount) {
const dapp = await this.getDAppById(id)
await this.validator.validateUpVoteEffect(dapp, id, amount)
return DiscoverContract.methods.upvoteEffect(id, amount).call()
}
async downVoteCost(id) {
const dapp = await this.getDAppById(id)
await this.validator.validateDownVoteCost(dapp, id)
return DiscoverContract.methods.upvoteEffect(id).call()
}
async getDAppById(id) {
const dappId = await DiscoverContract.methods.id2index(id).call()
return DiscoverContract.methods.dapps(dappId).call()
}
async safeMax() {
console.log(DiscoverContract)
debugger
return DiscoverContract.safeMax()
}
async isDAppExists(id) {
return DiscoverContract.methods.existingIDs(id).call()
}
}
export default DiscoverReadService

View File

@ -0,0 +1,26 @@
import DiscoverValidatorUtils from '../discover-validator-utils'
class DiscoverReadServiceValidator {
constructor(service) {
this.service = service
}
async validateUpVoteEffect(id, amount) {
const dapp = await this.service.getDAppById()
await DiscoverValidatorUtils.checkDappCorrectness(dapp, id)
// TODO: should check if dapp.balance is a big number
const safeMax = await this.service.safeMax()
await DiscoverValidatorUtils.checkUpVotingAmount(
dapp.balance + amount,
safeMax,
)
}
async validateDownVoteCost(id) {
const dapp = await this.service.getDAppById()
await DiscoverValidatorUtils.checkDappCorrectness(dapp, id)
}
}
export default DiscoverReadServiceValidator

View File

@ -0,0 +1,84 @@
import DiscoverContract from '../../../../embarkArtifacts/contracts/Discover'
import DiscoverRService from '../read-service/discover-r-service'
import DiscoverWServiceValidator from './validator'
// TODO: Validators ?
// TODO: check for unlocked account: If it is not -> request unlocking
// TODO: preOperation -> inherited method ?
const unlockAccount = async function(account) {
return account
}
class DiscoverWriteService extends DiscoverRService {
constructor(unlockedAccount) {
this.account = unlockedAccount
this.validator = new DiscoverWServiceValidator(this)
}
async createDApp(id, amount, metadata) {
await unlockAccount(this.account)
await this.validator.validateDAppCreation(id, amount)
try {
await DiscoverContract.methods.createDApp(id, amount, metadata, {
from: this.account,
})
} catch (error) {
throw new Error('Transfer failed')
}
}
async upVote(id, amount) {
await unlockAccount(this.account)
await this.validator.validateUpVoting(id, amount)
try {
await DiscoverContract.methods.upVote(id, amount, { from: this.account })
} catch (error) {
throw new Error('Transfer failed')
}
}
async downVote(id, amount) {
await unlockAccount(this.account)
await this.validator.validateDownVoting(id, amount)
try {
await DiscoverContract.methods.downVote(id, amount, {
from: this.account,
})
} catch (error) {
throw new Error('Transfer failed')
}
}
async withdraw(id, amount) {
await unlockAccount(this.account)
await this.validator.validateWithdrawing(id, amount)
try {
await DiscoverContract.methods.withdraw(id, amount, this.account)
} catch (error) {
throw new Error('Transfer failed')
}
}
async setMetadata(id, metadata) {
await unlockAccount(this.account)
await this.validator.validateMetadataSet(id)
await DiscoverContract.methods.setMetadata(id, metadata, {
from: this.account,
})
}
// async receiveApproval(from, amount, token, data) {
// await unlockAccount(this.account);
// await this.validator.validateReceiveApproval();
// await DiscoverContract.methods.receiveApproval(from, amount, token, data, { from: this.account });
// }
}
export default DiscoverWriteService

View File

@ -0,0 +1,71 @@
import DiscoverValidatorUtils from '../discover-validator-utils'
import DiscoverRServiceValidator from '../read-service/validator'
class DiscoverWriteServiceValidator extends DiscoverRServiceValidator {
// TODO: Add SNT allowance checks
async validateDAppCreation(id, amount) {
const dappExists = await this.service.isDAppExists(id)
if (dappExists) {
throw new Error('You must submit a unique ID')
}
if (amount <= 0) {
throw new Error(
'You must spend some SNT to submit a ranking in order to avoid spam',
)
}
const safeMax = await this.service.safeMax()
if (amount > safeMax) {
throw new Error('You cannot stake more SNT than the ceiling dictates')
}
}
// TODO: Add SNT allowance checks
async validateUpVoting(id, amount) {
await super.validateUpVoteEffect(id, amount)
if (amount <= 0) {
throw new Error('You must send some SNT in order to upvote')
}
}
// TODO: Add SNT allowance checks
async validateDownVoting(id, amount) {
await super.validateDownVoteCost(id)
const downVoteCost = await this.service.downVoteCost(id)
if (downVoteCost != amount) {
throw new Error('Incorrect amount: valid iff effect on ranking is 1%')
}
}
async validateWithdrawing(id, amount) {
const dapp = await this.service.getDAppById(id)
await DiscoverValidatorUtils.checkDappCorrectness(dapp, id)
if (dapp.developer != this.service.account) {
throw new Error('Only the developer can withdraw SNT staked on this data')
}
if (amount > dapp.available) {
throw new Error(
'You can only withdraw a percentage of the SNT staked, less what you have already received',
)
}
}
async validateMetadataSet(id) {
const dapp = await this.service.getDAppById(id)
if (dapp.developer != this.service.account) {
throw new Error('Only the developer can update the metadata')
}
}
// async validateReceiveApproval() {
// }
}
export default DiscoverWriteServiceValidator

View File

@ -0,0 +1,25 @@
// import DiscoverContract from '../../../embarkArtifacts/contracts/Discover';
import EmbarkJS from '../../embarkArtifacts/embarkjs'
import DiscoverReadService from './discover-services/read-service/discover-r-service'
import DiscoverWriteService from './discover-services/write-service/discover-w-service'
const ReadOnlyServices = {
DiscoverService: new DiscoverReadService(),
}
// TODO: ask Andy what kind of wallets is going to be used
const init = async function() {
try {
const account = await EmbarkJS.enableEthereum()
const discoverService = new DiscoverWriteService(account)
return { DiscoverService: discoverService, ...ReadOnlyServices }
} catch (error) {
// TODO: Should handle it in an elegant way
throw new Error(error.message)
}
}
export default { init, ...ReadOnlyServices }

View File

@ -0,0 +1,29 @@
const ONE = '1000000000000000000'
const formatBigNumberToNumber = function(bigNumber) {
let stringifyedNumber = bigNumber.toString(10)
if (stringifyedNumber == '0') {
return stringifyedNumber
}
let numberWholePartLength = 0
if (bigNumber.lt(ONE)) {
stringifyedNumber = stringifyedNumber.padStart(19, 0)
numberWholePartLength = 1
} else {
numberWholePartLength = bigNumber.div('1000000000000000000').toString(10)
.length
}
return `${stringifyedNumber.substr(
0,
numberWholePartLength,
)}.${stringifyedNumber.substr(
numberWholePartLength,
stringifyedNumber.length,
)}`
}
export default formatBigNumberToNumber

View File

@ -5,6 +5,7 @@ import Filtered from '../Filtered'
import RecentlyAdded from '../RecentlyAdded'
import Vote from '../Vote'
import Dapps from '../Dapps'
import Example from '../BlockchainExample'
export default () => (
<Switch>
@ -13,5 +14,6 @@ export default () => (
<Route path="/all" component={Dapps} />
<Route path="/recently-added" component={RecentlyAdded} />
<Route path="/vote" component={Vote} />
<Route path="/example" component={Example} />
</Switch>
)

View File

@ -0,0 +1,4 @@
import { connect } from 'react-redux'
import BlockchainExample from './BlockchainExample'
export default connect()(BlockchainExample)

View File

@ -0,0 +1,17 @@
import React from 'react'
import BlockchainSDK from '../../common/blockchain'
// import EmbarkJS from '../../embarkArtifacts/embarkjs';
class Example extends React.Component {
async logDiscoverMethod() {
console.log('here')
debugger
console.log(await BlockchainSDK.DiscoverService.safeMax())
}
render() {
return <h1 onLoad={this.logDiscoverMethod()} />
}
}
export default Example

View File

@ -0,0 +1,3 @@
import BlockchainExample from './BlockchainExample.container'
export default BlockchainExample