From 34cc2eca89e3895e3ff9e3c8b7f439df8e6e1459 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 24 Aug 2018 13:40:21 -0400 Subject: [PATCH 1/9] Service now responds to availability requests --- gas-relayer/src/service.js | 82 +++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 42 deletions(-) diff --git a/gas-relayer/src/service.js b/gas-relayer/src/service.js index 4c7d981..daf1147 100644 --- a/gas-relayer/src/service.js +++ b/gas-relayer/src/service.js @@ -59,50 +59,30 @@ events.on('setup:complete', async (settings) => { // Verifying relayer balance await verifyBalance(); - shhOptions.symKeyId = await web3.shh.addSymKey(config.node.whisper.symKey); shhOptions.kId = await web3.shh.newKeyPair(); + const symKeyID = await web3.shh.addSymKey(config.node.whisper.symKey); + const pubKey = await web3.shh.getPublicKey(shhOptions.kId); + // Listening to whisper + // Individual subscriptions due to https://github.com/ethereum/web3.js/issues/1361 + // once this is fixed, we'll be able to use an array of topics and a single subs for symkey and a single subs for privKey console.info(`Sym Key: ${config.node.whisper.symKey}`); + console.info(`Relayer Public Key: ${pubKey}`); console.info("Topics Available:"); for(let contract in settings.contracts) { console.info("- %s: %s [%s]", settings.getContractByTopic(contract).name, contract, Object.keys(settings.getContractByTopic(contract).allowedFunctions).join(', ')); shhOptions.topics = [contract]; - events.emit('server:listen', shhOptions, settings); + + // Listen to public channel - Used for reporting availability + events.emit('server:listen', Object.assign({symKeyID}, shhOptions), settings); + + // Listen to private channel - Individual transactions + events.emit('server:listen', Object.assign({privateKeyID: shhOptions.kId}, shhOptions), settings); } - - /* - if(config.heartbeat.enabled){ - - web3.shh.addSymKey(config.heartbeat.symKey) - .then(heartbeatSymKeyId => { - - for(let tokenAddress in settings.getTokens()){ - - let heartbeatPayload = settings.getToken(tokenAddress); - heartbeatPayload.address = tokenAddress; - - setInterval(() => { - web3.shh.post({ - symKeyID: heartbeatSymKeyId, - sig: keyId, - ttl: config.node.whisper.ttl, - powTarget:config.node.whisper.minPow, - powTime: config.node.whisper.powTime, - topic: web3.utils.toHex("relay-heartbeat-" + heartbeatPayload.symbol).slice(0, 10), - payload: web3.utils.toHex(JSON.stringify(heartbeatPayload)) - }).catch((err) => { - console.error(err); - process.exit(-1); - }); - }, 60000); - - } - }); - }*/ }); -const reply = (message) => (text, receipt) => { +const replyFunction = (message) => (text, receipt) => { if(message.sig !== undefined){ console.log(text); web3.shh.post({ @@ -121,9 +101,7 @@ const extractInput = (message) => { let obj = { contract: null, address: null, - functionName: null, - functionParameters: null, - payload: null + action: null }; try { @@ -131,9 +109,15 @@ const extractInput = (message) => { let parsedObj = JSON.parse(msg); obj.contract = parsedObj.contract; obj.address = parsedObj.address; - obj.functionName = parsedObj.encodedFunctionCall.slice(0, 10); - obj.functionParameters = "0x" + parsedObj.encodedFunctionCall.slice(10); - obj.payload = parsedObj.encodedFunctionCall; + obj.action = parsedObj.action; + if(obj.action == 'transaction'){ + obj.functionName = parsedObj.encodedFunctionCall.slice(0, 10); + obj.functionParameters = "0x" + parsedObj.encodedFunctionCall.slice(10); + obj.payload = parsedObj.encodedFunctionCall; + } else if(obj.action == 'availability') { + obj.token = parsedObj.token; + obj.gasPrice = parsedObj.gasPrice; + } } catch(err){ console.error("Couldn't parse " + message); } @@ -152,9 +136,23 @@ events.on('server:listen', (shhOptions, settings) => { verifyBalance(true); - processor.process(settings.getContractByTopic(message.topic), - extractInput(message), - reply(message)); + const input = extractInput(message); + const reply = replyFunction(message); + + switch(input.action){ + case 'transaction': + processor.process(settings.getContractByTopic(message.topic), + input, + reply); + break; + case 'availability': + reply("available"); + break; + default: + reply("unknown-action"); + } + + }); }); From dbec23fbeb48a06d500c7e66fc15cba37b04ae51 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 24 Aug 2018 13:50:34 -0400 Subject: [PATCH 2/9] Capturing relayers who report themselves --- test-dapp/app/components/body-identity.js | 28 ++++++++++++++----- .../app/components/body-sntcontroller.js | 25 +++++++++++++---- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/test-dapp/app/components/body-identity.js b/test-dapp/app/components/body-identity.js index 077fe7b..d454179 100644 --- a/test-dapp/app/components/body-identity.js +++ b/test-dapp/app/components/body-identity.js @@ -27,7 +27,8 @@ class Body extends Component { nonce: '0', kid: null, skid: null, - message: '' + message: '', + relayers: [] }; } @@ -45,18 +46,31 @@ class Body extends Component { web3js.shh.addSymKey(config.relaySymKey) .then((skid) => { this.setState({kid, skid}); - web3js.shh.subscribe('messages', { "privateKeyID": kid, "ttl": 1000, "minPow": 0.1, "powTime": 1000 }, (error, message) => { - console.log(message); + console.log(message); + + const msg = web3js.utils.toAscii(message.payload); + const msgObj = JSON.parse(msg); + + if(msgObj.message == 'available'){ + // found a relayer + console.log("Relayer available: " + message.sig); + + let relayers = this.state.relayers; + relayers.push(message.sig); + relayers = relayers.filter((value, index, self) => self.indexOf(value) === index); + this.setState({relayers}); + } + if(error){ console.error(error); } else { - this.setState({message: web3js.utils.toAscii(message.payload)}); + this.setState({message: msg}); } }); @@ -104,7 +118,7 @@ class Body extends Component { } render(){ - const {tab, identityAddress, nonce, web3js, message, kid, skid} = this.state; + const {tab, identityAddress, nonce, web3js, message, kid, skid, relayers} = this.state; return @@ -112,8 +126,8 @@ class Body extends Component { - {tab === 0 && } - {tab === 1 && } + {tab === 0 && } + {tab === 1 && } {tab === 2 && Item Three} diff --git a/test-dapp/app/components/body-sntcontroller.js b/test-dapp/app/components/body-sntcontroller.js index 6bd0903..5206d64 100644 --- a/test-dapp/app/components/body-sntcontroller.js +++ b/test-dapp/app/components/body-sntcontroller.js @@ -28,7 +28,8 @@ class Body extends Component { nonce: '0', kid: null, skid: null, - message: '' + message: '', + relayers: [] }; } @@ -53,7 +54,21 @@ class Body extends Component { "minPow": 0.1, "powTime": 1000 }, (error, message) => { - console.log(message); + console.log(message); + + const msg = web3js.utils.toAscii(message.payload); + const msgObj = JSON.parse(msg); + + if(msgObj.message == 'available'){ + // found a relayer + console.log("Relayer available: " + message.sig); + + let relayers = this.state.relayers; + relayers.push(message.sig); + relayers = relayers.filter((value, index, self) => self.indexOf(value) === index); + this.setState({relayers}); + } + if(error){ console.error(error); } else { @@ -90,15 +105,15 @@ class Body extends Component { } render(){ - const {tab, walletAddress, nonce, web3js, message, kid, skid} = this.state; + const {tab, walletAddress, nonce, web3js, message, kid, skid, relayers} = this.state; return - {tab === 0 && } - {tab === 1 && } + {tab === 0 && } + {tab === 1 && } From 1d34ac0601d54555844c486e5b52fb57bbc4c791 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 24 Aug 2018 15:21:26 -0400 Subject: [PATCH 3/9] Updated the UI to request relayers --- .../components/approveandcallgasrelayed.js | 114 ++++++++++++++--- .../app/components/body-sntcontroller.js | 2 +- test-dapp/app/components/callgasrelayed.js | 117 +++++++++++++++--- test-dapp/app/components/execute.js | 114 ++++++++++++++--- test-dapp/app/components/transfersnt.js | 113 ++++++++++++++--- 5 files changed, 390 insertions(+), 70 deletions(-) diff --git a/test-dapp/app/components/approveandcallgasrelayed.js b/test-dapp/app/components/approveandcallgasrelayed.js index 0ef01f2..45adee8 100644 --- a/test-dapp/app/components/approveandcallgasrelayed.js +++ b/test-dapp/app/components/approveandcallgasrelayed.js @@ -41,6 +41,7 @@ class ApproveAndCallGasRelayed extends Component { gasLimit: 0, gasToken: "0x0000000000000000000000000000000000000000", signature: '', + relayer: '', transactionError: '', messagingError: '', submitting: false @@ -93,7 +94,7 @@ class ApproveAndCallGasRelayed extends Component { } } - sendMessage = event => { + obtainRelayers = event => { event.preventDefault(); const {web3, kid, skid} = this.props; @@ -103,6 +104,50 @@ class ApproveAndCallGasRelayed extends Component { submitting: true }); this.props.clearMessages(); + + try { + const sendOptions = { + ttl: 1000, + sig: kid, + powTarget: 1, + powTime: 20, + topic: this.state.topic, + symKeyID: skid, + payload: web3.utils.toHex({ + 'contract': this.props.identityAddress, + 'address': web3.eth.defaultAccount, + 'action': 'availability', + 'token': this.state.gasToken, + 'gasPrice': this.state.gasPrice + }) + }; + + web3.shh.post(sendOptions) + .then(() => { + this.setState({submitting: false}); + console.log("Message sent"); + return true; + }); + } catch(error){ + this.setState({messagingError: error.message, submitting: false}); + } + } + + sendTransaction = event => { + event.preventDefault(); + + const {web3, kid} = this.props; + + let relayer = this.state.relayer; + if(relayer == '' && this.props.relayers.length == 1){ + relayer = this.props.relayers[0]; + } + + this.setState({ + messagingError: '', + submitting: true + }); + this.props.clearMessages(); try { @@ -124,11 +169,12 @@ class ApproveAndCallGasRelayed extends Component { powTarget: 1, powTime: 20, topic: this.state.topic, - symKeyID: skid, + pubKey: relayer, payload: web3.utils.toHex({ 'contract': this.props.identityAddress, - 'encodedFunctionCall': funCall, - 'address': web3.eth.defaultAccount + 'address': web3.eth.defaultAccount, + 'action': 'transaction', + 'encodedFunctionCall': funCall }) }; @@ -283,19 +329,10 @@ class ApproveAndCallGasRelayed extends Component { { this.state.messagingError && } + - + - - + + + + + + + + { + this.props.relayers.length > 0 ? + this.props.relayers.map((r, i) => ) + : + + } + + + + + @@ -334,7 +411,8 @@ ApproveAndCallGasRelayed.propTypes = { web3: PropTypes.object, kid: PropTypes.string, skid: PropTypes.string, - clearMessages: PropTypes.func + clearMessages: PropTypes.func, + relayers: PropTypes.array.isRequired }; export default withStyles(styles)(ApproveAndCallGasRelayed); diff --git a/test-dapp/app/components/body-sntcontroller.js b/test-dapp/app/components/body-sntcontroller.js index 5206d64..22352f1 100644 --- a/test-dapp/app/components/body-sntcontroller.js +++ b/test-dapp/app/components/body-sntcontroller.js @@ -113,7 +113,7 @@ class Body extends Component { {tab === 0 && } - {tab === 1 && } + {tab === 1 && } diff --git a/test-dapp/app/components/callgasrelayed.js b/test-dapp/app/components/callgasrelayed.js index efd11ba..a9f639a 100644 --- a/test-dapp/app/components/callgasrelayed.js +++ b/test-dapp/app/components/callgasrelayed.js @@ -45,6 +45,7 @@ class CallGasRelayed extends Component { payload: '', message: '', web3js: null, + relayer: '', transactionError: '', messagingError: '', submitting: false @@ -97,7 +98,7 @@ class CallGasRelayed extends Component { } } - sendMessage = event => { + obtainRelayers = event => { event.preventDefault(); const {web3, kid, skid} = this.props; @@ -108,6 +109,50 @@ class CallGasRelayed extends Component { }); this.props.clearMessages(); + try { + const sendOptions = { + ttl: 1000, + sig: kid, + powTarget: 1, + powTime: 20, + topic: this.state.topic, + symKeyID: skid, + payload: web3.utils.toHex({ + 'contract': this.props.identityAddress, + 'address': web3.eth.defaultAccount, + 'action': 'availability', + 'token': this.state.gasToken, + 'gasPrice': this.state.gasPrice + }) + }; + + web3.shh.post(sendOptions) + .then(() => { + this.setState({submitting: false}); + console.log("Message sent"); + return true; + }); + } catch(error){ + this.setState({messagingError: error.message, submitting: false}); + } + } + + sendTransaction = event => { + event.preventDefault(); + + const {web3, kid} = this.props; + + let relayer = this.state.relayer; + if(relayer == '' && this.props.relayers.length == 1){ + relayer = this.props.relayers[0]; + } + + this.setState({ + messagingError: '', + submitting: true + }); + this.props.clearMessages(); + try { let jsonAbi = IdentityGasRelay._jsonInterface.filter(x => x.name == "callGasRelayed")[0]; let funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ @@ -126,14 +171,15 @@ class CallGasRelayed extends Component { powTarget: 1, powTime: 20, topic: this.state.topic, - symKeyID: skid, + pubKey: relayer, payload: web3.utils.toHex({ 'contract': this.props.identityAddress, - 'encodedFunctionCall': funCall, - 'address': web3.eth.defaultAccount + 'address': web3.eth.defaultAccount, + 'action': 'transaction', + 'encodedFunctionCall': funCall }) }; - + web3.shh.post(sendOptions) .then(() => { this.setState({submitting: false}); @@ -266,19 +312,10 @@ class CallGasRelayed extends Component { { this.state.messagingError && } + - + - - + + + + + + + + + { + this.props.relayers.length > 0 ? + this.props.relayers.map((r, i) => ) + : + + } + + + + + @@ -316,7 +394,8 @@ CallGasRelayed.propTypes = { web3: PropTypes.object, kid: PropTypes.string, skid: PropTypes.string, - clearMessages: PropTypes.func + clearMessages: PropTypes.func, + relayers: PropTypes.array.isRequired }; export default withStyles(styles)(CallGasRelayed); diff --git a/test-dapp/app/components/execute.js b/test-dapp/app/components/execute.js index 87ca0b5..0dd50d6 100644 --- a/test-dapp/app/components/execute.js +++ b/test-dapp/app/components/execute.js @@ -41,6 +41,7 @@ class Execute extends Component { msgSent: '', payload: '', message: '', + relayer: '', web3js: null, transactionError: '', messagingError: '', @@ -99,11 +100,60 @@ class Execute extends Component { TestContract.methods.val().call().then(value => console.log({message: "TestContract.val(): " + value})); } - sendMessage = async event => { + obtainRelayers = async event => { event.preventDefault(); const {web3, kid, skid} = this.props; + this.setState({ + messagingError: '', + submitting: true + }); + + this.props.clearMessages(); + + const accounts = await web3.eth.getAccounts(); + + + try { + const sendOptions = { + ttl: 1000, + sig: kid, + powTarget: 1, + powTime: 20, + topic: this.state.topic, + symKeyID: skid, + payload: web3.utils.toHex({ + 'contract': SNTController.options.address, + 'address': accounts[2], + 'action': 'availability', + 'token': STT.options.address, + 'gasPrice': this.state.gasPrice + }) + }; + + web3.shh.post(sendOptions) + .then(() => { + this.setState({submitting: false}); + console.log("Message sent"); + return true; + }); + } catch(error){ + this.setState({messagingError: error.message, submitting: false}); + } + } + + sendTransaction = async event => { + event.preventDefault(); + + const {web3, kid} = this.props; + + let relayer = this.state.relayer; + if(relayer == '' && this.props.relayers.length == 1){ + relayer = this.props.relayers[0]; + } + + this.setState({ messagingError: '', submitting: true @@ -129,10 +179,11 @@ class Execute extends Component { powTarget: 1, powTime: 20, topic: this.state.topic, - symKeyID: skid, + pubKey: relayer, payload: web3.utils.toHex({ 'contract': SNTController.options.address, 'address': accounts[2], + 'action': 'transaction', 'encodedFunctionCall': funCall }) }; @@ -228,19 +279,10 @@ class Execute extends Component { { this.state.messagingError && } + - + - - + + + + + + + { + this.props.relayers.length > 0 ? + this.props.relayers.map((r, i) => ) + : + + } + + + + + @@ -278,7 +359,8 @@ Execute.propTypes = { web3: PropTypes.object, kid: PropTypes.string, skid: PropTypes.string, - clearMessages: PropTypes.func + clearMessages: PropTypes.func, + relayers: PropTypes.array.isRequired }; export default withStyles(styles)(Execute); diff --git a/test-dapp/app/components/transfersnt.js b/test-dapp/app/components/transfersnt.js index 9c91aa1..0b50550 100644 --- a/test-dapp/app/components/transfersnt.js +++ b/test-dapp/app/components/transfersnt.js @@ -41,6 +41,7 @@ class TransferSNT extends Component { msgSent: '', payload: '', message: '', + relayer: '', web3js: null, transactionError: '', messagingError: '', @@ -97,11 +98,57 @@ class TransferSNT extends Component { } } - sendMessage = async event => { + obtainRelayers = async event => { event.preventDefault(); const {web3, kid, skid} = this.props; + this.setState({ + messagingError: '', + submitting: true + }); + this.props.clearMessages(); + + const accounts = await web3.eth.getAccounts(); + + try { + const sendOptions = { + ttl: 1000, + sig: kid, + powTarget: 1, + powTime: 20, + topic: this.state.topic, + symKeyID: skid, + payload: web3.utils.toHex({ + 'contract': SNTController.options.address, + 'address': accounts[2], + 'action': 'availability', + 'token': this.state.gasToken, + 'gasPrice': this.state.gasPrice + }) + }; + + web3.shh.post(sendOptions) + .then(() => { + this.setState({submitting: false}); + console.log("Message sent"); + return true; + }); + } catch(error){ + this.setState({messagingError: error.message, submitting: false}); + } + } + + sendTransaction = async event => { + event.preventDefault(); + + const {web3, kid} = this.props; + + let relayer = this.state.relayer; + if(relayer == '' && this.props.relayers.length == 1){ + relayer = this.props.relayers[0]; + } + this.setState({ messagingError: '', submitting: true @@ -126,10 +173,11 @@ class TransferSNT extends Component { powTarget: 1, powTime: 20, topic: this.state.topic, - symKeyID: skid, + pubKey: relayer, payload: web3.utils.toHex({ 'contract': SNTController.options.address, 'address': accounts[2], + 'action': 'transaction', 'encodedFunctionCall': funCall }) }; @@ -214,19 +262,10 @@ class TransferSNT extends Component { { this.state.messagingError && } + - + - - + + + + + + + + + { + this.props.relayers.length > 0 ? + this.props.relayers.map((r, i) => ) + : + + } + + + + + @@ -264,7 +344,8 @@ TransferSNT.propTypes = { web3: PropTypes.object, kid: PropTypes.string, skid: PropTypes.string, - clearMessages: PropTypes.func + clearMessages: PropTypes.func, + relayers: PropTypes.array.isRequired }; export default withStyles(styles)(TransferSNT); From 3c136328de1e85c9bb9c8ce0954f9488bad011b6 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 24 Aug 2018 15:54:22 -0400 Subject: [PATCH 4/9] Updated the README to include the transaction details --- README.md | 64 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 77c7309..25e14b3 100644 --- a/README.md +++ b/README.md @@ -45,27 +45,59 @@ The gas relayer service needs to be running, and configured correctly to process -## Additional notes -How to send a message to this service (all accounts and privatekeys should be replaced by your own test data) +## Using the gas relayer + +### The relayer +A node that wants to act as a relayer only needs to have a geth node with whisper enabled, and an account with ether to process the transactions. This account and node need to be configured in `./config/config.js`. + +The relayer will be subscribed to receive messages in a specific symkey (this will change in the future to use ENS), and will reply back to both availability and transaction requests + +### The user + +#### Sending a message to the gas relayer network (all accounts and privatekeys should be replaced by your own test data) ``` -shh.post({symKeyID: SYM_KEY, sig: WHISPER_KEY_ID, ttl: 1000, powTarget: 1, powTime: 20, topic: TOPIC_NAME, payload: PAYLOAD_BYTES}); -``` -- `SYM_KEY` must contain the whisper symmetric key used. It is shown on the console when running the service with `node`. With the provided configuration you can use the value: -``` -0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b` +shh.post({ + symKeyID: SYM_KEY, // If requesting availability + pubKey: PUBLIC_KEY_ID, // If sending a transaction + sig: WHISPER_KEY_ID, + ttl: 1000, + powTarget: 1, + powTime: 20, + topic: TOPIC_NAME, + payload: PAYLOAD_BYTES +}).then(......) ``` +- `symKeyID: SYM_KEY` must contain the whisper symmetric key used. It is shown on the console when running the service with `node`. With the provided configuration you can use the symmetric key `0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b`. Only used when asking for relayer availability. +- `pubKey: PUBLIC_KEY_ID`. After asking for availability, once the user decides on a relayer, it needs to set the `pubKey` attribute with the relayer public key (received in the availability reply in the `sig` attribute of the message). - `WHISPER_KEY_ID` represents a keypair registered on your node, that will be used to sign the message. Can be generated with `web3W.shh.newKeyPair()` - `TOPIC_NAME` must contain one of the topic names generated based on converting the contract name to hex, and taking the first 8 bytes. For the provided configuration the following topics are available: - - IdentityGasRelay: `0x4964656e` - - SNTController: `0x534e5443` -- `PAYLOAD_BYTES` a hex string that contains the identity/contract address to invoke and the web3 encoded abi function invocation plus parameters. If we were to execute `callGasRelayed(address,uint256,bytes,uint256,uint256,uint256,address,bytes)` (part of the IdentityGasRelay) in contract `0x692a70d2e424a56d2c6c27aa97d1a86395877b3a`, with these values: `"0x11223344556677889900998877665544332211",100,"0x00",1,10,20,"0x1122334455112233445511223344551122334455"`, "0x1122334455", `PAYLOAD_BYTES` can be prepared as follows: +- `PAYLOAD_BYTES` a hex string that contains details on the operation to perform. +#### Polling for gas relayers +TODO + + + +#### Sending transaction details + +Sending a transaction is similar to the previous operation, except that we send the message to an specific node, we use the action `transaction`, and also we send a `encodedFunctionCall` with the details of the transaction to execute. + +From the list of relayers received via whisper messages, you need to extract the `message.sig` to obtain the `pubKey`. This value is used to send the transaction to that specific relayer. + +`encodedFunCall` is the hex data used obtained from `web3.eth.abi.encodeFunctionCall` for the specific function we want to invoke. + +If we were to execute `callGasRelayed(address,uint256,bytes,uint256,uint256,uint256,address,bytes)` (part of the IdentityGasRelay) in contract `0x692a70d2e424a56d2c6c27aa97d1a86395877b3a`, with these values: `"0x11223344556677889900998877665544332211",100,"0x00",1,10,20,"0x1122334455112233445511223344551122334455"`, "0x1122334455", `PAYLOAD_BYTES` can be prepared as follows: ``` - +// The following values are created obtained when polling for relayers const whisperKeyPairID = await web3W.shh.newKeyPair(); -const jsonAbi = ABIOfIdentityGasRelay.filter(x => x.name == "callGasRelayed")[0]; +const relayerPubKey = "0xRELAYER_PUBLIC_KEY_HERE"; +// ... +// ... +const jsonAbi = ABIOfIdentityGasRelay.find(x => x.name == "callGasRelayed"); const funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, [ @@ -80,7 +112,7 @@ const funCall = web3.eth.abi.encodeFunctionCall(jsonAbi, ]); const msgObj = { - symKeyID: "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b", + pubKey: relayerPubKey, sig: whisperKeyPairID, ttl: 1000, powTarget: 1, @@ -88,8 +120,9 @@ const msgObj = { topic: "0x4964656e", payload: web3.utils.toHex({ 'contract': "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a", - 'encodedFunctionCall': funCall, 'address': web3.eth.defaultAccount + 'action': 'transaction', + 'encodedFunctionCall': funCall, }) }; @@ -100,4 +133,9 @@ web3.shh.post(msgObj) console.log(err); }); -``` \ No newline at end of file +``` + + +#### Valid operations +TODO + From 2525ac062caf3bd808a5d02001da823bb316b10d Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 24 Aug 2018 16:12:17 -0400 Subject: [PATCH 5/9] Updated the readme for info on how to poll gas relayers availability --- README.md | 46 ++++++++++++++++++- .../components/approveandcallgasrelayed.js | 2 +- test-dapp/app/components/callgasrelayed.js | 2 +- test-dapp/app/components/execute.js | 2 +- test-dapp/app/components/transfersnt.js | 2 +- 5 files changed, 49 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 25e14b3..7b1cd01 100644 --- a/README.md +++ b/README.md @@ -77,9 +77,53 @@ shh.post({ #### Polling for gas relayers -TODO +The first step is asking the relayers for their availability. The message payload needs to be the hex string representation of a JSON object with a specific structure: +``` +const payload = web3.utils.toHex({ + 'contract': "0xContractToInvoke", + 'address': web3.eth.defaultAccount, + 'action': 'availability', + 'token': "0xGasTokenAddress", + 'gasPrice': 1234 + }); +``` +- `contract` is the address of the contract that will perform the operation, in this case it can be an Identity, or the SNTController. +- `address` The address that will sign the transactions. Normally it's `web3.eth.defaultAccount` +- `gasToken`: token used for paying the gas cost +- `gasPrice`: The gas price used for the transaction + +This is a example code of how to send an 'availability' message: + +``` +const whisperKeyPairID = await web3W.shh.newKeyPair(); + +const msgObj = { + symKeyId: "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b", + sig: whisperKeyPairID, + ttl: 1000, + powTarget: 1, + powTime: 20, + topic: "0x4964656e", + payload: web3.utils.toHex({ + 'contract': "0x692a70d2e424a56d2c6c27aa97d1a86395877b3a", + 'address': web3.eth.defaultAccount + 'action': 'availability', + 'gasToken': "0x744d70fdbe2ba4cf95131626614a1763df805b9e", + 'gasPrice': 40000000000 // 40 gwei equivalent in SNT + }) +}; + + +web3.shh.post(msgObj) +.then((err, result) => { + console.log(result); + console.log(err); +}); +``` + +When it replies, you need to extract the `sig` attribute to obtain the relayer's public key #### Sending transaction details diff --git a/test-dapp/app/components/approveandcallgasrelayed.js b/test-dapp/app/components/approveandcallgasrelayed.js index 45adee8..6fec346 100644 --- a/test-dapp/app/components/approveandcallgasrelayed.js +++ b/test-dapp/app/components/approveandcallgasrelayed.js @@ -117,7 +117,7 @@ class ApproveAndCallGasRelayed extends Component { 'contract': this.props.identityAddress, 'address': web3.eth.defaultAccount, 'action': 'availability', - 'token': this.state.gasToken, + 'gasToken': this.state.gasToken, 'gasPrice': this.state.gasPrice }) }; diff --git a/test-dapp/app/components/callgasrelayed.js b/test-dapp/app/components/callgasrelayed.js index a9f639a..c443cce 100644 --- a/test-dapp/app/components/callgasrelayed.js +++ b/test-dapp/app/components/callgasrelayed.js @@ -121,7 +121,7 @@ class CallGasRelayed extends Component { 'contract': this.props.identityAddress, 'address': web3.eth.defaultAccount, 'action': 'availability', - 'token': this.state.gasToken, + 'gasToken': this.state.gasToken, 'gasPrice': this.state.gasPrice }) }; diff --git a/test-dapp/app/components/execute.js b/test-dapp/app/components/execute.js index 0dd50d6..cdf5ff5 100644 --- a/test-dapp/app/components/execute.js +++ b/test-dapp/app/components/execute.js @@ -127,7 +127,7 @@ class Execute extends Component { 'contract': SNTController.options.address, 'address': accounts[2], 'action': 'availability', - 'token': STT.options.address, + 'gasToken': STT.options.address, 'gasPrice': this.state.gasPrice }) }; diff --git a/test-dapp/app/components/transfersnt.js b/test-dapp/app/components/transfersnt.js index 0b50550..e3d6abc 100644 --- a/test-dapp/app/components/transfersnt.js +++ b/test-dapp/app/components/transfersnt.js @@ -123,7 +123,7 @@ class TransferSNT extends Component { 'contract': SNTController.options.address, 'address': accounts[2], 'action': 'availability', - 'token': this.state.gasToken, + 'gasToken': this.state.gasToken, 'gasPrice': this.state.gasPrice }) }; From 4dd1004c4e14e9284d2ba0ee28d43d506ab858b8 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Mon, 27 Aug 2018 10:54:19 -0400 Subject: [PATCH 6/9] Adding availability validation --- README.md | 2 +- gas-relayer/src/contract-settings.js | 8 +++-- gas-relayer/src/message-processor.js | 24 +++++++++----- gas-relayer/src/service.js | 31 +++++++++++++------ .../src/strategy/AvailabilityStrategy.js | 24 ++++++++++++++ test-dapp/app/components/body-identity.js | 2 +- .../app/components/body-sntcontroller.js | 2 +- 7 files changed, 71 insertions(+), 22 deletions(-) create mode 100644 gas-relayer/src/strategy/AvailabilityStrategy.js diff --git a/README.md b/README.md index 7b1cd01..93eea5f 100644 --- a/README.md +++ b/README.md @@ -23,9 +23,9 @@ Before executing this program, `config/config.json` must be setup and `npm insta - Host, port and protocol Ganache will use when forking the blockchain for gas estimations and other operations - Wallet account used for processing the transactions - Symmetric key used to receive the Whisper messages -- Symmetric key used to send the heartbeats that notify the tokens and prices accepted - Accepted tokens information - Contract configuration + This program is configured with the default values for a embark installation run from 0 A `geth` node running whisper (via `-shh` option) is required. To execute the gas-relayer, you may use any of the following three methods. diff --git a/gas-relayer/src/contract-settings.js b/gas-relayer/src/contract-settings.js index 09bcad1..ec50aa4 100644 --- a/gas-relayer/src/contract-settings.js +++ b/gas-relayer/src/contract-settings.js @@ -105,8 +105,7 @@ class ContractSettings { // Obtaining strategy if(this.contracts[topicName].strategy){ - const strategy = require(this.contracts[topicName].strategy); - this.contracts[topicName].strategy = new strategy(this.web3, this.config, this, this.contracts[topicName]); + this.contracts[topicName].strategy = this.buildStrategy(this.contracts[topicName].strategy, topicName); } this._obtainContractBytecode(topicName); @@ -114,6 +113,11 @@ class ContractSettings { this._extractFunctions(topicName); } } + + buildStrategy(strategyFile, topicName){ + const strategy = require(strategyFile); + return new strategy(this.web3, this.config, this, this.contracts[topicName]); + } } diff --git a/gas-relayer/src/message-processor.js b/gas-relayer/src/message-processor.js index 1e03ae8..5dc073c 100644 --- a/gas-relayer/src/message-processor.js +++ b/gas-relayer/src/message-processor.js @@ -8,13 +8,13 @@ class MessageProcessor { } async _validateInput(contract, input){ - console.info("Processing request to: %s, %s", input.contract, input.functionName); + console.info("Processing '%s' request to contract: %s", input.action, input.contract); if(contract == undefined){ return {success: false, message: 'Unknown contract'}; } - if(!contract.functionSignatures.includes(input.functionName)){ + if(input.functionName && !contract.functionSignatures.includes(input.functionName)){ return {success: false, message: 'Function not allowed'}; } @@ -37,7 +37,7 @@ class MessageProcessor { return {success: true}; } - async process(contract, input, reply){ + async processStrategy(contract, input, reply, strategy){ const inputValidation = await this._validateInput(contract, input); if(!inputValidation.success){ // TODO Log? @@ -45,16 +45,26 @@ class MessageProcessor { return; } - let validationResult; + if(strategy || contract.strategy){ + let validationResult; + if(strategy){ + validationResult = await strategy.execute(input, reply); + } else { + validationResult = await contract.strategy.execute(input, reply); + } - if(contract.strategy){ - validationResult = await contract.strategy.execute(input, reply); if(!validationResult.success){ reply(validationResult.message); return; } - } + return validationResult; + } + } + + async processTransaction(contract, input, reply){ + const validationResult = await this.processStrategy(contract, input, reply); + let p = { from: this.config.node.blockchain.account, to: input.contract, diff --git a/gas-relayer/src/service.js b/gas-relayer/src/service.js index daf1147..ff521f9 100644 --- a/gas-relayer/src/service.js +++ b/gas-relayer/src/service.js @@ -4,9 +4,6 @@ const config = require('../config/config.js'); const ContractSettings = require('./contract-settings'); const MessageProcessor = require('./message-processor'); -// IDEA: A node should call an API (probably from a status node) to register itself as a -// token gas relayer. - console.info("Starting..."); const events = new EventEmitter(); @@ -84,7 +81,14 @@ events.on('setup:complete', async (settings) => { const replyFunction = (message) => (text, receipt) => { if(message.sig !== undefined){ - console.log(text); + + let payloadContent; + if(typeof text === 'object'){ + payloadContent = {...text, receipt}; + } else { + payloadContent = {text, receipt}; + } + web3.shh.post({ pubKey: message.sig, sig: shhOptions.kId, @@ -92,7 +96,7 @@ const replyFunction = (message) => (text, receipt) => { powTarget:config.node.whisper.minPow, powTime: config.node.whisper.powTime, topic: message.topic, - payload: web3.utils.fromAscii(JSON.stringify({message:text, receipt}, null, " ")) + payload: web3.utils.fromAscii(JSON.stringify(payloadContent, null, " ")) }).catch(console.error); } }; @@ -115,7 +119,7 @@ const extractInput = (message) => { obj.functionParameters = "0x" + parsedObj.encodedFunctionCall.slice(10); obj.payload = parsedObj.encodedFunctionCall; } else if(obj.action == 'availability') { - obj.token = parsedObj.token; + obj.gasToken = parsedObj.gasToken; obj.gasPrice = parsedObj.gasPrice; } } catch(err){ @@ -128,7 +132,7 @@ const extractInput = (message) => { events.on('server:listen', (shhOptions, settings) => { let processor = new MessageProcessor(config, settings, web3, events); - web3.shh.subscribe('messages', shhOptions, (error, message) => { + web3.shh.subscribe('messages', shhOptions, async (error, message) => { if(error){ console.error(error); return; @@ -138,15 +142,22 @@ events.on('server:listen', (shhOptions, settings) => { const input = extractInput(message); const reply = replyFunction(message); - + let validationResult; + switch(input.action){ case 'transaction': - processor.process(settings.getContractByTopic(message.topic), + processor.processTransaction(settings.getContractByTopic(message.topic), input, reply); break; case 'availability': - reply("available"); + validationResult = await processor.processStrategy(settings.getContractByTopic(message.topic), + input, + reply, + settings.buildStrategy("./strategy/AvailabilityStrategy", message.topic) + ); + if(validationResult.success) reply(validationResult.message); + break; default: reply("unknown-action"); diff --git a/gas-relayer/src/strategy/AvailabilityStrategy.js b/gas-relayer/src/strategy/AvailabilityStrategy.js new file mode 100644 index 0000000..447202e --- /dev/null +++ b/gas-relayer/src/strategy/AvailabilityStrategy.js @@ -0,0 +1,24 @@ +const Strategy = require('./BaseStrategy'); + +class AvailabilityStrategy extends Strategy { + + async execute(input){ + // Verifying if token is allowed + const token = this.settings.getToken(input.gasToken); + if(token == undefined) return {success: false, message: "Token not allowed"}; + + // TODO Validate gasPrice, and return the minPrice accepted + const minPrice = 0.00; + + return { + success: true, + message: { + message: "Available", + minPrice: minPrice + } + }; + } + +} + +module.exports = AvailabilityStrategy; diff --git a/test-dapp/app/components/body-identity.js b/test-dapp/app/components/body-identity.js index d454179..710ba1c 100644 --- a/test-dapp/app/components/body-identity.js +++ b/test-dapp/app/components/body-identity.js @@ -57,7 +57,7 @@ class Body extends Component { const msg = web3js.utils.toAscii(message.payload); const msgObj = JSON.parse(msg); - if(msgObj.message == 'available'){ + if(msgObj.message == 'Available'){ // found a relayer console.log("Relayer available: " + message.sig); diff --git a/test-dapp/app/components/body-sntcontroller.js b/test-dapp/app/components/body-sntcontroller.js index 22352f1..28c1220 100644 --- a/test-dapp/app/components/body-sntcontroller.js +++ b/test-dapp/app/components/body-sntcontroller.js @@ -59,7 +59,7 @@ class Body extends Component { const msg = web3js.utils.toAscii(message.payload); const msgObj = JSON.parse(msg); - if(msgObj.message == 'available'){ + if(msgObj.message == 'Available'){ // found a relayer console.log("Relayer available: " + message.sig); From 00a203896f531718b2d5b90fd3b2f25a0e121380 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Mon, 27 Aug 2018 11:58:52 -0400 Subject: [PATCH 7/9] Documentation for Gas Relayer pt1 --- gas-relayer/src/message-processor.js | 32 ++++++++++++++++++- .../src/strategy/AvailabilityStrategy.js | 13 ++++++-- gas-relayer/src/strategy/BaseStrategy.js | 28 ++++++++++++++++ gas-relayer/src/strategy/IdentityStrategy.js | 14 ++++++++ gas-relayer/src/strategy/SNTStrategy.js | 10 +++++- gas-relayer/src/utils.js | 0 6 files changed, 93 insertions(+), 4 deletions(-) delete mode 100644 gas-relayer/src/utils.js diff --git a/gas-relayer/src/message-processor.js b/gas-relayer/src/message-processor.js index 5dc073c..676da11 100644 --- a/gas-relayer/src/message-processor.js +++ b/gas-relayer/src/message-processor.js @@ -1,5 +1,14 @@ +/** + * Message Processor to analyze and execute strategies based on input objects + */ class MessageProcessor { + /** + * @param {object} config Configuration object obtained from `./config/config.js` + * @param {object} settings Settings obtained from parsing the configuration object + * @param {object} web3 Web3 object already configured + * @param {object} events Event emitter + */ constructor(config, settings, web3, events){ this.config = config; this.settings = settings; @@ -7,6 +16,12 @@ class MessageProcessor { this.events = events; } + /** + * Validate input message content + * @param {object} contract Contract object obtained from the settings based on the message topic + * @param {object} input Input object obtained from a message. + * @returns {object} State of validation + */ async _validateInput(contract, input){ console.info("Processing '%s' request to contract: %s", input.action, input.contract); @@ -37,6 +52,14 @@ class MessageProcessor { return {success: true}; } + /** + * Process strategy and return validation result + * @param {object} contract Contract object obtained from the settings based on the message topic + * @param {object} input Input object obtained from a message. + * @param {function} reply Reply function to return message + * @param {object} strategy Strategy to apply. If undefined, it will use a strategy based on the contract + * @returns {object} State of validation + */ async processStrategy(contract, input, reply, strategy){ const inputValidation = await this._validateInput(contract, input); if(!inputValidation.success){ @@ -62,6 +85,13 @@ class MessageProcessor { } } + /** + * Process strategy and based on its result, send a transaction to the blockchain + * @param {object} contract Contract object obtained from the settings based on the message topic + * @param {object} input Input object obtained from a message. + * @param {function} reply Reply function to return message + * @returns {undefined} + */ async processTransaction(contract, input, reply){ const validationResult = await this.processStrategy(contract, input, reply); @@ -89,7 +119,7 @@ class MessageProcessor { try { const receipt = await this.web3.eth.sendTransaction(p); // TODO: parse events - return reply("Transaction mined", receipt); + reply("Transaction mined", receipt); } catch(err){ reply("Couldn't mine transaction: " + err.message); // TODO log this? diff --git a/gas-relayer/src/strategy/AvailabilityStrategy.js b/gas-relayer/src/strategy/AvailabilityStrategy.js index 447202e..ab24d95 100644 --- a/gas-relayer/src/strategy/AvailabilityStrategy.js +++ b/gas-relayer/src/strategy/AvailabilityStrategy.js @@ -1,13 +1,22 @@ const Strategy = require('./BaseStrategy'); +/** + * Class representing a strategy to validate an 'availability' request. + * @extends Strategy + */ class AvailabilityStrategy extends Strategy { - async execute(input){ + /** + * Process availability strategy + * @param {object} input Input object obtained from an 'availability' request. It expects an object with this structure `{contract, address, action, gasToken, gasPrice}` + * @returns {object} Status of validation, and minimum price + */ + execute(input){ // Verifying if token is allowed const token = this.settings.getToken(input.gasToken); if(token == undefined) return {success: false, message: "Token not allowed"}; - // TODO Validate gasPrice, and return the minPrice accepted + // TODO: Validate gasPrice, and return the minPrice accepted const minPrice = 0.00; return { diff --git a/gas-relayer/src/strategy/BaseStrategy.js b/gas-relayer/src/strategy/BaseStrategy.js index bdd0ec7..8f3860d 100644 --- a/gas-relayer/src/strategy/BaseStrategy.js +++ b/gas-relayer/src/strategy/BaseStrategy.js @@ -2,7 +2,17 @@ const ganache = require("ganache-cli"); const Web3 = require('web3'); const erc20ABI = require('../../abi/ERC20Token.json'); +/** + * Abstract class used for validation strategies + */ class BaseStrategy { + + /** + * @param {object} web3 Web3 object already configured + * @param {object} config Configuration object obtained from `./config/config.js` + * @param {object} settings Settings obtained from parsing the configuration object + * @param {object} contract Contract object obtained from the settings based on the message topic + */ constructor(web3, config, settings, contract){ this.web3 = web3; this.settings = settings; @@ -10,6 +20,12 @@ class BaseStrategy { this.config = config; } + /** + * Obtain the balance in tokens or ETH from an address + * @param {string} address ETH address to obtain the balance from + * @param {object} token Token obtained from `settings.getToken(tokenSymbol)` + * @returns {web3.utils.BN} Balance + */ async getBalance(address, token){ // Determining balances of token used if(token.symbol == "ETH"){ @@ -21,6 +37,11 @@ class BaseStrategy { } } + /** + * Build Parameters Function + * @param {object} input Input object obtained from an `transaction` request. + * @returns {function} Function that simplifies accessing contract functions' parameters + */ _obtainParametersFunc(input){ const parameterList = this.web3.eth.abi.decodeParameters(this.contract.allowedFunctions[input.functionName].inputs, input.functionParameters); return function(parameterName){ @@ -28,6 +49,11 @@ class BaseStrategy { }; } + /** + * Estimate gas using web3 + * @param {object} input Input object obtained from an `transaction` request. + * @returns {web3.utils.toBN} Estimated gas fees + */ async _estimateGas(input){ let p = { from: this.config.node.blockchain.account, @@ -40,6 +66,8 @@ class BaseStrategy { /** * Simulate transaction using ganache. Useful for obtaining events + * @param {object} input Input object obtained from an `transaction` request. + * @returns {object} Simulated transaction receipt */ async _simulateTransaction(input){ let web3Sim = new Web3(ganache.provider({ diff --git a/gas-relayer/src/strategy/IdentityStrategy.js b/gas-relayer/src/strategy/IdentityStrategy.js index f718e9c..910f291 100644 --- a/gas-relayer/src/strategy/IdentityStrategy.js +++ b/gas-relayer/src/strategy/IdentityStrategy.js @@ -1,8 +1,17 @@ const Strategy = require('./BaseStrategy'); const erc20ABI = require('../../abi/ERC20Token.json'); +/** + * Class representing a strategy to validate a `transaction` request when the topic is related to Identities. + * @extends Strategy + */ class IdentityStrategy extends Strategy { + /** + * Validates if the contract being invoked represents an Identity instance + * @param {object} input Input object obtained from a `transaction` request. + * @returns {bool} Valid instance or not + */ async _validateInstance(input){ const instanceCodeHash = this.web3.utils.soliditySha3(await this.web3.eth.getCode(input.contract)); const kernelVerifSignature = this.web3.utils.soliditySha3(this.contract.kernelVerification).slice(0, 10); @@ -15,6 +24,11 @@ class IdentityStrategy extends Strategy { return this.web3.eth.abi.decodeParameter('bool', verificationResult); } + /** + * Process Identity strategy + * @param {object} input Input object obtained from an 'transaction' request. It expects an object with this structure `{contract, address, action, functionName, functionParameters, payload}` + * @returns {object} Status of validation and estimated gas + */ async execute(input){ if(this.contract.isIdentity){ let validInstance = await this._validateInstance(input); diff --git a/gas-relayer/src/strategy/SNTStrategy.js b/gas-relayer/src/strategy/SNTStrategy.js index 6bcc7b6..6725eab 100644 --- a/gas-relayer/src/strategy/SNTStrategy.js +++ b/gas-relayer/src/strategy/SNTStrategy.js @@ -3,9 +3,17 @@ const Strategy = require('./BaseStrategy'); const TransferSNT = "0x916b6511"; const ExecuteGasRelayed = "0x754e6ab0"; - +/** + * Class representing a strategy to validate a `transaction` request when the topic is related to SNTController. + * @extends Strategy + */ class SNTStrategy extends Strategy { + /** + * Process SNTController strategy + * @param {object} input Input object obtained from an 'transaction' request. It expects an object with this structure `{contract, address, action, functionName, functionParameters, payload}` + * @returns {object} Status of validation and estimated gas + */ async execute(input){ const params = this._obtainParametersFunc(input); diff --git a/gas-relayer/src/utils.js b/gas-relayer/src/utils.js deleted file mode 100644 index e69de29..0000000 From a26b834dab425e61b6c465f89b7bdac3840b0a49 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Mon, 27 Aug 2018 16:16:00 -0400 Subject: [PATCH 8/9] Documentation pt2 --- gas-relayer/.eslintrc | 2 +- gas-relayer/src/contract-settings.js | 54 +++++++++++++++++++ gas-relayer/src/message-processor.js | 26 ++++----- .../src/strategy/AvailabilityStrategy.js | 2 +- gas-relayer/src/strategy/BaseStrategy.js | 18 +++---- gas-relayer/src/strategy/IdentityStrategy.js | 4 +- gas-relayer/src/strategy/SNTStrategy.js | 2 +- 7 files changed, 81 insertions(+), 27 deletions(-) diff --git a/gas-relayer/.eslintrc b/gas-relayer/.eslintrc index 776e80e..13108e4 100644 --- a/gas-relayer/.eslintrc +++ b/gas-relayer/.eslintrc @@ -275,7 +275,7 @@ "error", "never" ], - "valid-jsdoc": "error", + "valid-jsdoc": "off", "vars-on-top": "off", "wrap-iife": "error", "wrap-regex": "error", diff --git a/gas-relayer/src/contract-settings.js b/gas-relayer/src/contract-settings.js index ec50aa4..6bb0744 100644 --- a/gas-relayer/src/contract-settings.js +++ b/gas-relayer/src/contract-settings.js @@ -1,5 +1,13 @@ +/** + * Configuration Settings related to contracts + */ class ContractSettings { + /** + * @param {object} config - Configuration object obtained from `./config/config.js` + * @param {object} web3 - Web3 object already configured + * @param {object} eventEmitter - Event Emitter + */ constructor(config, web3, eventEmitter){ this.tokens = config.tokens; this.topics = []; @@ -12,11 +20,17 @@ class ContractSettings { this.pendingToLoad = 0; } + /** + * Process configuration file + */ process(){ this._setTokenPricePlugin(); this._processContracts(); } + /** + * Set price plugin for token + */ _setTokenPricePlugin(){ for(let token in this.tokens){ if(this.tokens[token].pricePlugin !== undefined){ @@ -26,16 +40,30 @@ class ContractSettings { } } + /** + * Get allowed tokens + * @return {object} - Dictionary with allowed tokens (address as key) + */ getTokens(){ return this.tokens; } + /** + * Get token by address + * @param {string} - Token address + * @return {object} - Token details + */ getToken(token){ const tokenObj = this.tokens[token]; tokenObj.address = token; return tokenObj; } + /** + * Get token by symbol + * @param {string} - Token symbol + * @return {object} - Token details + */ getTokenBySymbol(symbol){ for(let token in this.tokens){ if(this.tokens[token].symbol == symbol){ @@ -46,14 +74,28 @@ class ContractSettings { } } + /** + * Get contract by topicName + * @param {string} topicName - Topic name that represents a contract + * @return {object} - Contract details + */ getContractByTopic(topicName){ return this.contracts[topicName]; } + /** + * Calculate the topic based on the contract's name + * @param {string} contractName - Name of the contract as it appears in the configuration + * @return {string} - Topic + */ getTopicName(contractName){ return this.web3.utils.toHex(contractName).slice(0, 10); } + /** + * Set contract's bytecode in the configuration + * @param {string} topicName - Topic name that represents a contract + */ async _obtainContractBytecode(topicName){ if(this.contracts[topicName].isIdentity) return; @@ -71,6 +113,10 @@ class ContractSettings { } } + /** + * Extract function details based on topicName + * @param {string} topicName - Topic name that represents a contract + */ _extractFunctions(topicName){ const contract = this.getContractByTopic(topicName); @@ -90,6 +136,9 @@ class ContractSettings { this.contracts[topicName] = contract; } + /** + * Process contracts and setup the settings object + */ _processContracts(){ for(let contractName in this.contracts){ // Obtaining the abis @@ -114,6 +163,11 @@ class ContractSettings { } } + /** + * Create strategy object based on source code and topicName + * @param {string} strategyFile - Souce code path of strategy to build + * @param {string} topicName - Hex string that represents a contract's topic + */ buildStrategy(strategyFile, topicName){ const strategy = require(strategyFile); return new strategy(this.web3, this.config, this, this.contracts[topicName]); diff --git a/gas-relayer/src/message-processor.js b/gas-relayer/src/message-processor.js index 676da11..78292ad 100644 --- a/gas-relayer/src/message-processor.js +++ b/gas-relayer/src/message-processor.js @@ -4,10 +4,10 @@ class MessageProcessor { /** - * @param {object} config Configuration object obtained from `./config/config.js` - * @param {object} settings Settings obtained from parsing the configuration object - * @param {object} web3 Web3 object already configured - * @param {object} events Event emitter + * @param {object} config - Configuration object obtained from `./config/config.js` + * @param {object} settings - Settings obtained from parsing the configuration object + * @param {object} web3 - Web3 object already configured + * @param {object} events - Event emitter */ constructor(config, settings, web3, events){ this.config = config; @@ -18,8 +18,8 @@ class MessageProcessor { /** * Validate input message content - * @param {object} contract Contract object obtained from the settings based on the message topic - * @param {object} input Input object obtained from a message. + * @param {object} contract - Object obtained from the settings based on the message topic + * @param {object} input - Object obtained from a message. * @returns {object} State of validation */ async _validateInput(contract, input){ @@ -54,10 +54,10 @@ class MessageProcessor { /** * Process strategy and return validation result - * @param {object} contract Contract object obtained from the settings based on the message topic - * @param {object} input Input object obtained from a message. - * @param {function} reply Reply function to return message - * @param {object} strategy Strategy to apply. If undefined, it will use a strategy based on the contract + * @param {object} contract - Object obtained from the settings based on the message topic + * @param {object} input - Object obtained from a message. + * @param {function} reply - Function to reply a message + * @param {object} strategy - Strategy to apply. If undefined, it will use a strategy based on the contract * @returns {object} State of validation */ async processStrategy(contract, input, reply, strategy){ @@ -87,9 +87,9 @@ class MessageProcessor { /** * Process strategy and based on its result, send a transaction to the blockchain - * @param {object} contract Contract object obtained from the settings based on the message topic - * @param {object} input Input object obtained from a message. - * @param {function} reply Reply function to return message + * @param {object} contract - Object obtained from the settings based on the message topic + * @param {object} input - Object obtained from a message. + * @param {function} reply - function to reply a message * @returns {undefined} */ async processTransaction(contract, input, reply){ diff --git a/gas-relayer/src/strategy/AvailabilityStrategy.js b/gas-relayer/src/strategy/AvailabilityStrategy.js index ab24d95..3f7e557 100644 --- a/gas-relayer/src/strategy/AvailabilityStrategy.js +++ b/gas-relayer/src/strategy/AvailabilityStrategy.js @@ -8,7 +8,7 @@ class AvailabilityStrategy extends Strategy { /** * Process availability strategy - * @param {object} input Input object obtained from an 'availability' request. It expects an object with this structure `{contract, address, action, gasToken, gasPrice}` + * @param {object} input - Object obtained from an 'availability' request. It expects an object with this structure `{contract, address, action, gasToken, gasPrice}` * @returns {object} Status of validation, and minimum price */ execute(input){ diff --git a/gas-relayer/src/strategy/BaseStrategy.js b/gas-relayer/src/strategy/BaseStrategy.js index 8f3860d..6ab5d26 100644 --- a/gas-relayer/src/strategy/BaseStrategy.js +++ b/gas-relayer/src/strategy/BaseStrategy.js @@ -8,10 +8,10 @@ const erc20ABI = require('../../abi/ERC20Token.json'); class BaseStrategy { /** - * @param {object} web3 Web3 object already configured - * @param {object} config Configuration object obtained from `./config/config.js` - * @param {object} settings Settings obtained from parsing the configuration object - * @param {object} contract Contract object obtained from the settings based on the message topic + * @param {object} web3 - Web3 object already configured + * @param {object} config - Configuration object obtained from `./config/config.js` + * @param {object} settings - Settings obtained from parsing the configuration object + * @param {object} contract - Object obtained from the settings based on the message topic */ constructor(web3, config, settings, contract){ this.web3 = web3; @@ -22,8 +22,8 @@ class BaseStrategy { /** * Obtain the balance in tokens or ETH from an address - * @param {string} address ETH address to obtain the balance from - * @param {object} token Token obtained from `settings.getToken(tokenSymbol)` + * @param {string} address - ETH address to obtain the balance from + * @param {object} token - Obtained from `settings.getToken(tokenSymbol)` * @returns {web3.utils.BN} Balance */ async getBalance(address, token){ @@ -39,7 +39,7 @@ class BaseStrategy { /** * Build Parameters Function - * @param {object} input Input object obtained from an `transaction` request. + * @param {object} input - Object obtained from an `transaction` request. * @returns {function} Function that simplifies accessing contract functions' parameters */ _obtainParametersFunc(input){ @@ -51,7 +51,7 @@ class BaseStrategy { /** * Estimate gas using web3 - * @param {object} input Input object obtained from an `transaction` request. + * @param {object} input - Object obtained from an `transaction` request. * @returns {web3.utils.toBN} Estimated gas fees */ async _estimateGas(input){ @@ -66,7 +66,7 @@ class BaseStrategy { /** * Simulate transaction using ganache. Useful for obtaining events - * @param {object} input Input object obtained from an `transaction` request. + * @param {object} input - Object obtained from an `transaction` request. * @returns {object} Simulated transaction receipt */ async _simulateTransaction(input){ diff --git a/gas-relayer/src/strategy/IdentityStrategy.js b/gas-relayer/src/strategy/IdentityStrategy.js index 910f291..a2c86d0 100644 --- a/gas-relayer/src/strategy/IdentityStrategy.js +++ b/gas-relayer/src/strategy/IdentityStrategy.js @@ -9,7 +9,7 @@ class IdentityStrategy extends Strategy { /** * Validates if the contract being invoked represents an Identity instance - * @param {object} input Input object obtained from a `transaction` request. + * @param {object} input - Object obtained from a `transaction` request. * @returns {bool} Valid instance or not */ async _validateInstance(input){ @@ -26,7 +26,7 @@ class IdentityStrategy extends Strategy { /** * Process Identity strategy - * @param {object} input Input object obtained from an 'transaction' request. It expects an object with this structure `{contract, address, action, functionName, functionParameters, payload}` + * @param {object} input - Object obtained from an 'transaction' request. It expects an object with this structure `{contract, address, action, functionName, functionParameters, payload}` * @returns {object} Status of validation and estimated gas */ async execute(input){ diff --git a/gas-relayer/src/strategy/SNTStrategy.js b/gas-relayer/src/strategy/SNTStrategy.js index 6725eab..80476d1 100644 --- a/gas-relayer/src/strategy/SNTStrategy.js +++ b/gas-relayer/src/strategy/SNTStrategy.js @@ -11,7 +11,7 @@ class SNTStrategy extends Strategy { /** * Process SNTController strategy - * @param {object} input Input object obtained from an 'transaction' request. It expects an object with this structure `{contract, address, action, functionName, functionParameters, payload}` + * @param {object} input - Object obtained from an 'transaction' request. It expects an object with this structure `{contract, address, action, functionName, functionParameters, payload}` * @returns {object} Status of validation and estimated gas */ async execute(input){ From 3b64ed81c5618d20afbada7163bcf3a1cd24aba1 Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Tue, 28 Aug 2018 10:15:45 -0400 Subject: [PATCH 9/9] Removing heartbeat from config --- gas-relayer/config/config.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/gas-relayer/config/config.js b/gas-relayer/config/config.js index 60d1703..6ca7d91 100644 --- a/gas-relayer/config/config.js +++ b/gas-relayer/config/config.js @@ -21,11 +21,6 @@ module.exports = { "powTime": 1000 } }, - - "heartbeat": { - "enabled": true, - "symKey": "0xd0d905c1c62b810b787141430417caf2b3f54cffadb395b7bb39fdeb8f17266b" - }, "tokens": { "0x0000000000000000000000000000000000000000": {