实现IOTA新交易监控并发送邮件提醒

首先我们先来了解一下Totangle。Totangle为用户与IOTA Tangle之间进行的交互提供了一个“界面”,它将Tangle与众成业已成熟的日常应用集成在了一起,例如Zapier(1000多款应用),Google表格,亚马逊DynamoDB等。

实现IOTA新交易监控并发送邮件提醒

Totangle可以将tangle上发生的情况集成到现有的api或工作流中,因此我们可以借助Totangle与Tangle进行可视化的交互与操作。它的一个最简单实用的功能就是可以在设置触发器后,能够对指定地址上的交易情况进行监控并向用户发送电子邮件通知,此功能可以免去我们频繁登录钱包查询资金情况的需要。

Totangle还为希望能够获得长期优质服务的用户提供了收费的增值服务,这是围绕IOTA开展新商业模式的一个不错的例子。Totangle官网:https://totangle.com

本文将介绍如何利用开源lambda函数实现类似于Totangle的IOTA交易监控与邮件通知功能。本文假设您是一名已经掌握相关开发工具/函数/脚本/命令以及了解如何使用AWS的开发者,普通用户可以访问Totangle了解和尝试这项功能。

工作原理:

  • 编写一个lambda函数,每隔30分钟左右在tangle上对一个指定的address或者tag进行查询。
  • 如果lambda函数查询到新交易,它会使用SNS向指定的电子邮件地址发送一封邮件通知。
  • 电子邮件发送之后,将这个新交易保存到一个列表(数据库)中,用来在以后的查询中排除这个交易。本文使用Amazon S3做为存储服务,您也可以考虑使用DynamoDB。

第一部分:设置lambda

我们使用无服务器框架来管理AWS lambda和触发器。

下面从创建一个新项目开始:

$ npm install -g
serverless $ serverless create --template aws-nodejs --path tangle-trigger
$ cd tangle -trigger
$ npm init -y

还需要安装iota库,并设置一下环境变量:

$ touch .env
$ npm install --save iota.lib.js

编辑一下.env文件:

export AWS_PROFILE=default
export IOTA_URL="http://localhost:14265"
export IOTA_ADDRESS="9999"

使用AWS_PROFILE环境变量来控制AWS帐户的无服务器部署。我经常需要在帐户之间切换,所以这个环境变量非常有用。

两个IOTA_*环境变量只是占位符(IOTA_URL和IOTA_ADDRESS分别对应节点地址和IOTA地址,后期可自定义)。

向package.json添加部署脚本:

{
"name": "tangle-trigger",
"version": "1.0.0",
"description": "",
"main": "handler.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"deploy": "source .env && serverless deploy"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"iota.lib.js": "^0.4.7"
}
}

在github查看上述代码

接下来,需要修改Serverless.yml文件来定义lambda函数和触发函数的事件:

service: TangleTrigger
provider:
name: aws
runtime: nodejs6.10
stage: dev
region: ap-southeast-2
environment:
IOTA_URL: ${env:IOTA_URL}
IOTA_ADDRESS: ${env:IOTA_ADDRESS}

functions:
checkAddress:
handler: handler.checkAddress
events:
- schedule:
name: shedule-check-address
description: check the tangle for a given address
# rate: rate(30 minutes)
rate: rate(1 minute)
enabled: true

在Github查看上述代码

现在,我们设置为每分钟调度一次lambda函数(设置为1分钟是为了方便调试,后期可根据自己的需求自定义时间,比如30分钟)。还可以看到如何传递IOTA_*环境变量,这些变量将自动放入lambda运行时。

我们需要做的最后一件事是编写实际的lambda函数!这是一个占位文件handler.js

'use strict';

const IOTA = require('iota.lib.js');
const iota = new IOTA({provider: process.env.IOTA_URL});

module.exports.checkAddress = (event, context, callback) => {

console.log("CHECKING ADDRESS:", process.env.IOTA_ADDRESS);

在Github查看上述代码

很简单,对吧?但它可以确保正确的传递环境变量,并确保lambda与iota.lib.js依赖项一起正确的打包。

进行部署并查看AWS控制台:

$ npm run deploy

您应该会看到如下的一些终端输出(我使用的是yarn而不是npm)。

实现IOTA新交易监控并发送邮件提醒

进入AWS控制台,我们应该可以看到一个AWS Cloudformation Stack:

实现IOTA新交易监控并发送邮件提醒

一个Lambda函数:

实现IOTA新交易监控并发送邮件提醒

还有每当lambda被触发时就会执行日志写入操作的云监控日志:

实现IOTA新交易监控并发送邮件提醒

第二部分:与Tangle通信

现在我们有了一个每n分钟运行一次的lambda函数,接下来需要一种方法来检查特定地址上的交易情况。可以使用iota js lib上的findTransactionObjects()方法来实现这一点。

然而,在实现此功能之前,我们应该设法在本地运行无服务器函数,否则调试这个函数会非常困难。

创建一个名为local.js的新文件:

const { checkAddress } = require(' ./handler ');
checkAddress();

Github:https://gist.github.com/lewisdaly/8d641d6828d89f784433e4a65d96478d#file-plt_local_1-js

运行local.js:

$ node local.js

您应该会看到如下的内容:

CHECKING ADDRESS: undefined

OK!目前只有一半可以工作。我们需要先确保能够获取.env文件。我们可以将它们放在一个npm脚本package.json中:

...
“ test ”: “ source .env && node local.js ”,
...

在Github中查看上述代码

现在,运行yarn run test(或npm run test)后得到以下内容:

yarn run v1.6.0
$ source .env && node local.js
CHECKING ADDRESS: 9999
✨ Done in 0.22s.

环境变量很好地传递了,可以开始与Tangle通信了。

与Tangle通信

首先在.env文件中添加一些正确的值 :

...
export IOTA_URL =“https://durian.iotasalad.org:14265”
export IOTA_ADDRESS =“BJSLSJNPWSM9QLO9JYJAG9A9LLAUKZAQJGYZLNN9YMBNPCUUS9E9EYE9PIKIKNYHXAPNFAMDGXVIPVKIWGDUVDALPD”
...

在Github中查看上述代码

其中IOTA_URL是节点地址,本文使用了durian.iotasalad.org这个节点。

更新handler.js来调用api:

...

module.exports.checkAddress = (event, context, callback) => {
console.log("CHECKING ADDRESS:", process.env.IOTA_ADDRESS);

const searchValues = { addresses: [process.env.IOTA_ADDRESS]};
iota.api.findTransactionObjects(searchValues, (err, res) => {
if (err) {
console.log("error finding transactions!", err);
throw err;
}

console.log("found the transactionObjects:", res);
});
};

在Github中查看上述代码

运行测试后得到如下内容:

实现IOTA新交易监控并发送邮件提醒

太棒了!可以查找到特定地址的转账信息了!现在我们需要计算转帐记录的条数,并确保过滤掉我们以前已经知道并保存过的转帐信息。为此,我们接下来使用AWS S3。

安装aws-sdk:

npm install --save aws-sdk

现在需要在serverless.yml文件中添加一个s3 bucket,以及一个新的环境变量:

serverless.yml

service: TangleTrigger
provider:
name: aws
runtime: nodejs6.10
stage: dev
region: ap-southeast-2
environment:
IOTA_URL: ${env:IOTA_URL}
IOTA_ADDRESS: ${env:IOTA_ADDRESS}
S3_BUCKET_NAME: ${env:S3_BUCKET_NAME}

functions:
checkAddress:
handler: handler.checkAddress
events:
- schedule:
name: shedule-check-address
description: check the tangle for a given address
rate: rate(30 minutes)
enabled: true

resources:
Resources:
ExistingTxs:
Type: AWS::S3::Bucket
Properties:
BucketName: ${env:S3_BUCKET_NAME}

在Github中查看上述代码

.env

...
export S3_BUCKET_NAME="tangletrigger"

好。现在需要整理一下,以及从s3读取和写入的函数。我是这样实现的:

handler.js

'use strict';

const IOTA = require('iota.lib.js');
const iota = new IOTA({provider: process.env.IOTA_URL});

var AWS = require('aws-sdk');
var s3 = new AWS.S3();

module.exports.checkAddress = (event, context, callback) => {
console.log("CHECKING ADDRESS:", process.env.IOTA_ADDRESS);

return Promise.all([
getExistingHashes(),
findTransactionsPromise()
])
.then(([existingTx, allTx]) => {
//Remove the existing from all
const newTx = Object.keys(allTx).filter(hash => existingTx[hash] !== true);
console.log("new tx are:", newTx);
//TODO: Trigger a new email/notification

return saveNewHashes(newTx);
})
.catch(err => {
console.log(err);
throw err;
});
};

/**
* Get the existing tx hashes by looking up files in s3
* This is limited to 1000, but good enough for our demo
*
* Returns an object, where the key is the hash
*/
const getExistingHashes = () => {
var params = {
Bucket: process.env.S3_BUCKET_NAME,
};

return s3.listObjects(params).promise()
.then(data => {
const existingHashList = data.Contents.map(file => file.Key);
const existingHashes = {};
existingHashList.forEach(hash => existingHashes[hash] = true);

return existingHashes;
});
}

/**
* Creates new files in s3 for each hash in the hash list
* essentially adding them to a 'seen' list
*/
const saveNewHashes = (hashList) => {
return Promise.all(hashList.map(hash => {
const params = {
Body: '\"true\"',
Bucket: process.env.S3_BUCKET_NAME,
Key: hash
};

return s3.putObject(params).promise();
}));
}

const findTransactionsPromise = () => {
const searchValues = { addresses: [process.env.IOTA_ADDRESS] };

return new Promise((resolve, reject) => {
iota.api.findTransactionObjects(searchValues, (err, res) => {
if (err) {
reject(err);
}

const foundHashList = res.map(tx => tx.hash);
const foundHashes = {};
foundHashList.forEach(hash => foundHashes[hash] = true);
resolve(foundHashes);
});
})
}

在Github中查看上述代码

在本例中,只是使用tx散列作为键向s3 bucket写入新文件。这是懒人操作,使用数据库会更好一些,但是s3是免费的,而且更容易上手。你可以使用数据库来作到这一点。

现在再次运行local.js后,就可以检查s3 bucket了:(需要安装aws cli,或者可以使用web控制台)

$ aws s3 ls s3://tangletrigger/
2018-05-30 10:05:01 6 BGTXJCVKTJZR9GVZOVJOUPQTZSFRQYTWMNBWBDNUVHLTMLNLQBNBSJEINSRJFEEXOHUBNOGXMXKEA9999

添加Email通知

最后来实现当在tangle中监控到新的交易时发送电子邮件通知的功能。AWS让SNS变得非常简单,所以首先在serverless.yml中添加一个新的SNS主题。

serverless.yml部分现在看起来像这样:

service: TangleTrigger
provider:
name: aws
runtime: nodejs6.10
stage: dev
region: ap-southeast-2
environment:
IOTA_URL: ${env:IOTA_URL}
IOTA_ADDRESS: ${env:IOTA_ADDRESS}
S3_BUCKET_NAME: ${env:S3_BUCKET_NAME}
SNS_TOPIC_NAME: ${env:SNS_TOPIC_NAME}
SNS_TOPIC_ARN:
Ref: TxNotification

functions:
checkAddress:
handler: handler.checkAddress
events:
- schedule:
name: shedule-check-address
description: check the tangle for a given address
rate: rate(30 minutes)
enabled: true

resources:
Resources:
ExistingTxs:
Type: AWS::S3::Bucket
Properties:
BucketName: ${env:S3_BUCKET_NAME}

TxNotification:
Type: AWS::SNS::Topic
Properties:
TopicName: ${env:SNS_TOPIC_NAME}
Subscription:
- Endpoint: ${env:SNS_EMAIL}
Protocol: email

在Github中查看上述代码

请注意,现在已经定义了一个SNS主题,它将向.env文件中定义的电子邮件发送通知,我们使用Ref函数将其设置为环境变量,然后将其传递给lambda。我们会在下面的publishsnessage()中用到它。

这是添加了电子邮件设置后最终的handler.js。

'use strict';

const IOTA = require('iota.lib.js');
const iota = new IOTA({provider: process.env.IOTA_URL});

const AWS = require('aws-sdk');
const s3 = new AWS.S3();
const sns = new AWS.SNS({
region: 'ap-southeast-2'
});

module.exports.checkAddress = (event, context, callback) => {
let newTx = null;

return Promise.all([
getExistingHashes(),
findTransactionsPromise()
])
.then(([existingTx, allTx]) => {
//Remove the existing from all
newTx = Object.keys(allTx).filter(hash => existingTx[hash] !== true);

return saveNewHashes(newTx);
})
.then(() => {
if (newTx.length > 0) {
const message = `You have ${newTx.length} new transactions on the tangle!`;
return publishSNSMessage(message);
}
})
.catch(err => {
console.log(err);
throw err;
});
};

/**
* Get the existing tx hashes by looking up files in s3
* This is limited to 1000, but good enough for our demo
*
* Returns an object, where the key is the hash
*/
const getExistingHashes = () => {
var params = {
Bucket: process.env.S3_BUCKET_NAME,
};

return s3.listObjects(params).promise()
.then(data => {
const existingHashList = data.Contents.map(file => file.Key);
const existingHashes = {};
existingHashList.forEach(hash => existingHashes[hash] = true);

return existingHashes;
});
}

/**
* Creates new files in s3 for each hash in the hash list
* essentially adding them to a 'seen' list
*/
const saveNewHashes = (hashList) => {
return Promise.all(hashList.map(hash => {
const params = {
Body: '\"true\"',
Bucket: process.env.S3_BUCKET_NAME,
Key: hash
};

return s3.putObject(params).promise();
}));
}

const findTransactionsPromise = () => {
const searchValues = { addresses: [process.env.IOTA_ADDRESS] };

return new Promise((resolve, reject) => {
iota.api.findTransactionObjects(searchValues, (err, res) => {
if (err) {
reject(err);
}

const foundHashList = res.map(tx => tx.hash);
const foundHashes = {};
foundHashList.forEach(hash => foundHashes[hash] = true);
resolve(foundHashes);
});
})
}

const publishSNSMessage = (message) => {

const params = {
Message: message,
Subject: 'You have new Transactions on the Tangle!',
TopicArn: process.env.SNS_TOPIC_ARN
}

return sns.publish(params).promise();
}

在Github中查看上述代码

下面是有新交易时收到的邮件通知:

实现IOTA新交易监控并发送邮件提醒

现在只需要将通知时间更改回30分钟(或者设置为你希望的检查/通知频率),就可以再次部署。

向这个地址发送一笔交易测试一下:

实现IOTA新交易监控并发送邮件提醒

接下来就是等待邮件通知,大约一个小时后收到了通知邮件:

实现IOTA新交易监控并发送邮件提醒

查看一下云监控日志,进入CloudWatch> Logs> /aws/lambda/TangleTrigger-dev-checkAddress,并选择最顶部(最新)的日志:

在日志中看到发生了一些状况,原因是Lambda函数使用的IAM角色没有与S3 bucket通信或者发布到SNS主题的权限。一个简单的解决方案:

将以下内容添加到serverless.yml的provider部分:

...
iamRoleStatements:
- Effect: "Allow"
Action:
- s3:*
Resource:
- arn:aws:s3:::${env:S3_BUCKET_NAME}
- arn:aws:s3:::${env:S3_BUCKET_NAME}/*
- Effect: "Allow"
Action:
- sns:Publish
Resource:
- ${env:SNS_TOPIC_ARN}
...

在Github中查看上述代码。

上述内容定义了Lambda函数执行的IAM角色权限,赋予它读取和写入s3 bucket以及发布到SNS主题的权限。
再执行一次部署,看看会发生什么。

实现IOTA新交易监控并发送邮件提醒

可以看出已经可以正常工作了!本文涉及的完整代码请点击此处查看,如果你在部署过程中遇到问题,可以联系作者获得帮助,作者的推特:@lewdaly,IOTA Discord:lwilld。

原文链接:https://medium.com/coinmonks/triggering-email-alerts-from-the-iota-tangle-3362219d2846

inhuman

专栏作者:inhuman

个人简介:我共发表了 189 篇文章,总计被阅读了225,765 次,共获得了 1,765 个赞。

作者邮箱 作者主页 Ta的文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注