-
Notifications
You must be signed in to change notification settings - Fork 21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: external notifications #237
Conversation
Codecov Report
@@ Coverage Diff @@
## dev #237 +/- ##
==========================================
- Coverage 85.19% 84.87% -0.32%
==========================================
Files 24 31 +7
Lines 905 1071 +166
Branches 185 216 +31
==========================================
+ Hits 771 909 +138
- Misses 116 138 +22
- Partials 18 24 +6
Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here. |
FYI for reviewersIf you want to test the plugins there may be some setup needed. Running the plugins locallyIf the wallet headless is runing with docker, remember to enable the plugins with debug plugin# Config options
# `plugin_debug_long`: all, off
npm start -- --plugin_debug_long all This plugin requires no external tools. websocket pluginnpm start -- --plugin_ws_port 8008 We can omit the port config since the default port is 8008. We need a websocket client or a script to test this. wscat -c localhost:8008 sqs# `plugin_sqs_region`
# `plugin_sqs_queue_url`
# `plugin_sqs_endpoint_url`
npm start -- --plugin_sqs_endpoint_url http://localhost:9324 --plugin_sqs_queue_url http://localhost:9324/000000000000/queue1 --plugin_sqs_region elasticmq To avoid having to spin up an sqs queue for dev/testing we can use ElasticMQ version: "2"
services:
queue:
image: softwaremill/elasticmq:latest
ports:
- 9324:9324
- 9325:9325
volumes:
- ./elasticmq.conf:/opt/elasticmq.conf Also, we use the file
This will start a stats dashboard running on port 9325 which we can use to check the incomming messages. aws --endpoint-url http://localhost:9324 sqs list-queues While we have enough for testing, I've also created a consumer to the queue, to avoid increasing the number of unread messages on queue, and to check the contents of the messages being sent. from time import sleep
import boto3
endpoint_url = "http://localhost:9324"
# Create SQS client
sqs = boto3.client('sqs', endpoint_url=endpoint_url, region_name='elasticmq', use_ssl=False)
queue_url = 'http://localhost:9324/000000000000/queue1'
def receive_msg():
# Receive message from SQS queue
response = sqs.receive_message(
QueueUrl=queue_url,
AttributeNames=['SentTimestamp'],
MaxNumberOfMessages=1,
MessageAttributeNames=['All'],
VisibilityTimeout=0,
WaitTimeSeconds=0
)
message = response['Messages'][0]
receipt_handle = message['ReceiptHandle']
# Delete received message from queue
sqs.delete_message(QueueUrl=queue_url,ReceiptHandle=receipt_handle)
print('Received and deleted message: %s' % message)
while True:
receive_msg()
sleep(1) rabbitMQ plugin# `plugin_rabbitmq_url`
# `plugin_rabbitmq_queue`
npm start -- --plugin_rabbitmq_url amqp://localhost --plugin_rabbitmq_queue queue1 We can run rabbitMQ with docker with the command: docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.10-management And to check the messages we can use the consumer script var amqp = require('amqplib/callback_api');
var queue = 'amqp://localhost';
amqp.connect(queue, function(error0, connection) {
if (error0) {
throw error0;
}
connection.createChannel(function(error1, channel) {
if (error1) {
throw error1;
}
var queue = 'queue1';
channel.assertQueue(queue, { durable: false });
console.log(" [*] Waiting for messages in %s. To exit press CTRL+C", queue);
channel.consume(queue, function(msg) {
console.log(" [x] Received %s", msg.content.toString());
}, { noAck: true });
});
}); |
|
||
const eventBus = 'message'; | ||
|
||
const WalletEventMap = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the intention is to make a map and help the user avoid code mistakes, I think it would be better to have the key names in camelCase, without the quotes: state
, newTx
, updateTx
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I like the idea, this would remove the original event type from the map.
For instance, if we had an event "foo-bar"
which we translate to fooBar: 'wallet:foo'
a dev searching for instances of the event "foo-bar"
may miss this reference.
What do you think of this?
Obs: my opinion is that i would still change to camel case, i just want to show the full info on why the original event name is there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with you that the search by event name is the main way of finding the interactions with these events now, and that it's important to keep is searchable like this. As a short-term solution maybe just add the original event name as a comment?
const ConnectionEventMap = {
state: 'conn:state', // original lib event: 'state'
newTx: 'wallet:new-tx', // original lib event: 'new-tx'
updateTx: 'wallet:update-tx', // original lib event: 'update-tx'
Do you agree that this helps avoid confusion?
--
As a more definitive solution, I would expand my suggestion to also refactor the way events are referred on the project and on the wallet lib itself:
// On wallet-lib/src/constants.js , add the following object:
EVENT_NAMES: {
STATE: 'state',
NEW_TX: 'new-tx',
UPDATE_TX: 'update-tx',
}
// On headless/src/services/notification.service/js , simply re-use the same key names as the lib's constants object
const WalletEventMap = {
STATE: 'wallet:state',
NEW_TX: 'wallet:new-tx',
UPDATE_TX: 'wallet:update-tx',
}
// The event listeners would then have the format:
import { EVENT_NAMES as LIB_EVENTS } from '@hathor/wallet-lib/constants'
hwallet.on(LIB_EVENTS.NEW_TX, tx => {
const type = WalletEventMap.NEW_TX;
...
}
This seems material for another issue / PR on both projects, as I think it would make it simpler for developers to also use the lib's events beyond the external notifications.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created an issue to track this suggestion.
} | ||
|
||
/* istanbul ignore next */ | ||
export const init = async bus => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel this project needs at least one integration test, and maybe the websocket
plugin could be well suited for that.
A test suite containing the startup of a wallet, the real-time notification of transactions and a successful shutdown would be great also to serve as an example code to developers.
What do you think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Besides the integration test, I think a comment inside the code pointing to the Running the plugins locally
section of your comment would be great. One suggestion of a place to add it would be
hathor-wallet-headless/src/controllers/index.controller.js
Lines 182 to 183 in 50dd3a3
// XXX: subscribe to wallet events with notificationBus | |
notificationBus.subscribeHathorWallet(req.body['wallet-id'], wallet); |
// @see https://github.com/HathorNetwork/hathor-wallet-headless/pull/237#issuecomment-1253730867
That, or maybe adding a slightly adapted form of that text to the readme.md
file under a new Real-time Notifications section.
50dd3a3
to
ad9f959
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The code on this repository is approved by me.
The only thing I think is still good to have is to a complete sample of working code so that a developer can see a concrete example of how to put the plugin system to work. I just don't really see where to put this into this repository.
Maybe a tutorial text, a "Getting Started" repository ( containing integration tests ) on GitHub or a video on YouTube would be enough to give a bootstrap to a newcomer.
@@ -74,6 +74,10 @@ const config = { | |||
connectionTimeout: getIntParam('connection_timeout'), | |||
allowPassphrase: getBooleanParam('allow_passphrase'), | |||
confirmFirstAddress: getBooleanParam('confirm_first_address'), | |||
|
|||
// Plugin configuration | |||
enabled_plugins: getParam('enabled_plugins', '').split(' ').filter(x => x), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need this filter
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For the default value, since its an empty string.
After the split we will have ['']
which was confusing, so I added this filter.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe add a comment about this?
This pull request introduces 1 alert when merging 44feb33 into e5de086 - view on LGTM.com new alerts:
|
This pull request introduces 1 alert when merging d83609a into e5de086 - view on LGTM.com new alerts:
|
This pull request introduces 1 alert when merging 967ac98 into e5de086 - view on LGTM.com new alerts:
|
* | ||
* @returns {TokenBalance} | ||
*/ | ||
function getWalletBalanceForTx(wallet, tx) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm starting to think if this method should be in the lib. It seems a bit more complete than the one we have in the wallet class getTxBalance
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code approved, and I left some suggestions for future refactors on the lib
.
tokenBalance[token] = getEmptyBalance(); | ||
} | ||
|
||
if ((input.token_data & TOKEN_AUTHORITY_MASK) > 0) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel this could be added to the lib later as a isAuthorityInput(input)
method, analog to the existing isAuthorityOutput(output)
, to maintain pattern of helper methods on the lib. The isMintInput
and isMeltInput
are good candidates too.
https://github.com/HathorNetwork/hathor-wallet-lib/blob/master/src/wallet.js#L1972-L1984
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, the concept of a mint/melt input is somewhat strange to me.
The more correct idea is that the input spends a mint/melt output.
Even the token_data
I used is because the fullnode adds some metadata of the utxo being spent in the input.
if (output.decoded && output.decoded.address && wallet.isAddressMine(output.decoded.address)) { | ||
const { token } = output; | ||
if (!tokenBalance[token]) { | ||
tokenBalance[token] = getEmptyBalance(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it possible to reach this line?
If all tokens were scanned on the inputs, all of them must necessarily be present on the outputs too, right?
I'm just bringing this up because I can't see a way to make a test that covers this line and codecov will see this as a warning, but I think it may be good to keep the code this way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, a token creation transaction does not have the created token on the inputs.
Acceptance Criteria
Description
Some refactors were made to make the plugin system more reliable.
The express app configuration was moved from
index.js
toapp.js
.This was made so that we can startup the plugins and check the configuration before start listening for requests.
This can help with health and status checks, also it ensures the plugins can listen to all events.
Security Checklist