successfully forwarding blockchain events to matrix
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 26s
Details
Gitea Actions Demo / Explore-Gitea-Actions (push) Failing after 26s
Details
This commit is contained in:
parent
2e53252fc7
commit
fe2cbe0e27
|
@ -1,6 +1,11 @@
|
|||
PORT=3000
|
||||
DATA_DIR="./data"
|
||||
API_LISTEN_PORT=3000
|
||||
LEVEL_DATA_DIR="./data"
|
||||
SEMANTIC_SCHOLAR_API_KEY=
|
||||
ETH_NETWORK="localhost"
|
||||
ETH_PRIVATE_KEY=
|
||||
INFURA_API_KEY=
|
||||
INFURA_API_KEY=
|
||||
MATRIX_HOMESERVER_URL="https://matrix.dgov.io"
|
||||
MATRIX_USER="forum-api"
|
||||
MATRIX_PASSWORD=
|
||||
BOT_STORAGE_PATH="./data/bot-storage.json"
|
||||
BOT_CRYPTO_STORAGE_PATH="./data/bot-crypto"
|
|
@ -1,3 +1,4 @@
|
|||
node_modules/
|
||||
.env
|
||||
data/
|
||||
data/
|
||||
registration.yaml
|
|
@ -0,0 +1,17 @@
|
|||
# Setup
|
||||
|
||||
1.
|
||||
|
||||
cp .env.example .env
|
||||
|
||||
1.
|
||||
|
||||
npm install
|
||||
|
||||
1.
|
||||
|
||||
npm run registration
|
||||
|
||||
1.
|
||||
|
||||
docker compose up -d --build
|
|
@ -7,4 +7,6 @@ services:
|
|||
volumes:
|
||||
- ./data:/data
|
||||
environment:
|
||||
- DATA_DIR=/data
|
||||
- LEVEL_DATA_DIR=/data
|
||||
- BOT_STORAGE_PATH="./data/bot-storage.json"
|
||||
- BOT_CRYPTO_STORAGE_PATH="./data/bot-crypto"
|
File diff suppressed because it is too large
Load Diff
|
@ -16,7 +16,9 @@
|
|||
"ethers": "^6.12.0",
|
||||
"express": "^4.18.2",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"fastq": "^1.17.1",
|
||||
"level": "^8.0.1",
|
||||
"matrix-bot-sdk": "^0.7.1",
|
||||
"object-hash": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
const express = require('express');
|
||||
|
||||
require('express-async-errors');
|
||||
|
||||
const read = require('./read');
|
||||
const write = require('./write');
|
||||
const importFromSS = require('./import-from-ss');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.API_LISTEN_PORT || 3000;
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/write', write);
|
||||
|
||||
app.get('/read/:hash', async (req, res) => {
|
||||
const { hash } = req.params;
|
||||
console.log('read', hash);
|
||||
const data = await read(hash);
|
||||
res.json(data);
|
||||
});
|
||||
|
||||
app.post('/importFromSemanticScholar', importFromSS);
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
console.log(`404 req.path: ${req.path}`);
|
||||
res.status(404).json({ errorCode: 404 });
|
||||
});
|
||||
|
||||
app.use((err, req, res, next) => {
|
||||
const status = err.response?.status ?? err.status ?? 500;
|
||||
const message = err.response?.data?.error ?? err.message;
|
||||
console.error(`error: ${message}`, err);
|
||||
res.status(status).send(message);
|
||||
next();
|
||||
});
|
||||
|
||||
const start = () => {
|
||||
app.listen(port, () => {
|
||||
console.log(`Listening on port ${port}`);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
};
|
|
@ -2,6 +2,7 @@ const ethers = require('ethers');
|
|||
|
||||
const { getContractAddressByNetworkName } = require('./contract-config');
|
||||
const DAOArtifact = require('../contractArtifacts/DAO.json');
|
||||
const ProposalsArtifact = require('../contractArtifacts/Proposals.json');
|
||||
|
||||
const network = process.env.ETH_NETWORK;
|
||||
|
||||
|
@ -23,12 +24,15 @@ const getProvider = () => {
|
|||
|
||||
const wallet = new ethers.Wallet(process.env.ETH_PRIVATE_KEY, getProvider());
|
||||
|
||||
const getContract = (name) => new ethers.Contract(
|
||||
getContractAddressByNetworkName(process.env.ETH_NETWORK, name),
|
||||
DAOArtifact.abi,
|
||||
wallet,
|
||||
);
|
||||
|
||||
module.exports = {
|
||||
dao: getContract('DAO'),
|
||||
dao: new ethers.Contract(
|
||||
getContractAddressByNetworkName(process.env.ETH_NETWORK, 'DAO'),
|
||||
DAOArtifact.abi,
|
||||
wallet,
|
||||
),
|
||||
proposals: new ethers.Contract(
|
||||
getContractAddressByNetworkName(process.env.ETH_NETWORK, 'Proposals'),
|
||||
ProposalsArtifact.abi,
|
||||
wallet,
|
||||
),
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const { Level } = require('level');
|
||||
|
||||
const dataDir = process.env.DATA_DIR || 'data';
|
||||
const dataDir = process.env.LEVEL_DATA_DIR || 'data';
|
||||
|
||||
module.exports = {
|
||||
forum: new Level(`${dataDir}/forum`, { valueEncoding: 'json' }),
|
||||
|
|
|
@ -212,4 +212,6 @@ module.exports = async (req, res) => {
|
|||
} else {
|
||||
res.status(400).end();
|
||||
}
|
||||
|
||||
// TODO: Send matrix room event on SS import
|
||||
};
|
||||
|
|
|
@ -1,40 +1,5 @@
|
|||
const express = require('express');
|
||||
|
||||
const read = require('./read');
|
||||
const write = require('./write');
|
||||
const importFromSS = require('./import-from-ss');
|
||||
|
||||
require('dotenv').config();
|
||||
require('express-async-errors');
|
||||
|
||||
const app = express();
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
app.use(express.json());
|
||||
|
||||
app.post('/write', write);
|
||||
app.get('/read/:hash', read);
|
||||
app.post('/importFromSemanticScholar', importFromSS);
|
||||
|
||||
app.get('*', (req, res) => {
|
||||
console.log(`404 req.path: ${req.path}`);
|
||||
res.status(404).json({ errorCode: 404 });
|
||||
});
|
||||
|
||||
app.use((err, req, res, next) => {
|
||||
const status = err.response?.status ?? 500;
|
||||
const message = err.response?.data?.error ?? err.message;
|
||||
console.error(`error: ${message}`, err);
|
||||
res.status(status).send(message);
|
||||
next();
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Listening on port ${port}`);
|
||||
});
|
||||
|
||||
// TODO: Subscribe to contract events
|
||||
|
||||
// TODO: Send matrix room events for proposal events
|
||||
|
||||
// TODO: Send matrix room event on SS import
|
||||
require('./api').start();
|
||||
require('./matrix').start();
|
||||
require('./proposals').start();
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
const {
|
||||
AutojoinRoomsMixin,
|
||||
MatrixAuth,
|
||||
MatrixClient,
|
||||
RustSdkCryptoStorageProvider,
|
||||
SimpleFsStorageProvider,
|
||||
} = require('matrix-bot-sdk');
|
||||
const fastq = require('fastq');
|
||||
|
||||
const {
|
||||
MATRIX_HOMESERVER_URL,
|
||||
MATRIX_USER,
|
||||
MATRIX_PASSWORD,
|
||||
BOT_STORAGE_PATH,
|
||||
BOT_CRYPTO_STORAGE_PATH,
|
||||
} = process.env;
|
||||
|
||||
const storageProvider = new SimpleFsStorageProvider(BOT_STORAGE_PATH);
|
||||
const cryptoProvider = new RustSdkCryptoStorageProvider(BOT_CRYPTO_STORAGE_PATH);
|
||||
let client;
|
||||
let joinedRooms;
|
||||
const processOutboundQueue = async ({ text }) => {
|
||||
joinedRooms.forEach(async (roomId) => {
|
||||
await client.sendText(roomId, text);
|
||||
});
|
||||
};
|
||||
const outboundQueue = fastq(processOutboundQueue, 1);
|
||||
outboundQueue.pause();
|
||||
|
||||
const start = async () => {
|
||||
console.log('MATRIX_HOMESERVER_URL:', MATRIX_HOMESERVER_URL);
|
||||
const auth = new MatrixAuth(MATRIX_HOMESERVER_URL);
|
||||
const authClient = await auth.passwordLogin(MATRIX_USER, MATRIX_PASSWORD);
|
||||
client = new MatrixClient(
|
||||
MATRIX_HOMESERVER_URL,
|
||||
authClient.accessToken,
|
||||
storageProvider,
|
||||
cryptoProvider,
|
||||
);
|
||||
|
||||
// Automatically join a room to which we are invited
|
||||
AutojoinRoomsMixin.setupOnClient(client);
|
||||
|
||||
joinedRooms = await client.getJoinedRooms();
|
||||
console.log('joined rooms:', joinedRooms);
|
||||
|
||||
async function handleCommand(roomId, event) {
|
||||
// Don't handle unhelpful events (ones that aren't text messages, are redacted, or sent by us)
|
||||
if (event.content?.msgtype !== 'm.text') return;
|
||||
if (event.sender === await client.getUserId()) return;
|
||||
|
||||
// Check to ensure that the `!hello` command is being run
|
||||
const { body } = event.content;
|
||||
if (!body?.startsWith('!hello')) return;
|
||||
|
||||
// Now that we've passed all the checks, we can actually act upon the command
|
||||
await client.replyNotice(roomId, event, 'Hello world!');
|
||||
}
|
||||
|
||||
// Before we start the bot, register our command handler
|
||||
client.on('room.message', handleCommand);
|
||||
|
||||
client.start().then(() => {
|
||||
console.log('Bot started!');
|
||||
outboundQueue.resume();
|
||||
});
|
||||
};
|
||||
|
||||
const broadcastMessage = (text) => {
|
||||
outboundQueue.push({ text });
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
broadcastMessage,
|
||||
};
|
|
@ -0,0 +1,29 @@
|
|||
const { proposals } = require('./contracts');
|
||||
const read = require('./read');
|
||||
const { broadcastMessage } = require('./matrix');
|
||||
|
||||
// Subscribe to proposal events
|
||||
const start = () => {
|
||||
proposals.on('NewProposal', async (proposalIndex) => {
|
||||
// TODO: Cache these in leveldb so we know when we've already seen one and sent to matrix
|
||||
console.log('New Proposal, index', proposalIndex);
|
||||
|
||||
const proposal = await proposals.proposals(proposalIndex);
|
||||
console.log('postId:', proposal.postId);
|
||||
|
||||
// Read post from database
|
||||
const post = await read(proposal.postId);
|
||||
console.log('post.content:', post.content);
|
||||
|
||||
// Send matrix room event
|
||||
let message = `Proposal ${proposalIndex}\n\n${post.content}`;
|
||||
if (post.embeddedData && Object.entries(post.embeddedData).length) {
|
||||
message += `\n\n${JSON.stringify(post.embeddedData, null, 2)}`;
|
||||
}
|
||||
broadcastMessage(message);
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
start,
|
||||
};
|
|
@ -3,24 +3,14 @@ const objectHash = require('object-hash');
|
|||
const verifySignature = require('./verify-signature');
|
||||
const { forum } = require('./db');
|
||||
|
||||
module.exports = async (req, res) => {
|
||||
const { hash } = req.params;
|
||||
console.log('read', hash);
|
||||
|
||||
const read = async (hash) => {
|
||||
// Fetch content
|
||||
let data;
|
||||
try {
|
||||
data = await forum.get(req.params.hash);
|
||||
} catch (e) {
|
||||
console.log('read error:', e.message, hash);
|
||||
res.status(e.status).end();
|
||||
return;
|
||||
}
|
||||
const data = await forum.get(hash);
|
||||
|
||||
data.embeddedData = data.embeddedData || undefined;
|
||||
|
||||
const {
|
||||
authors, content, signature, embeddedData,
|
||||
authors, content, signature, embeddedData, citations,
|
||||
} = data;
|
||||
|
||||
// Verify hash
|
||||
|
@ -28,18 +18,17 @@ module.exports = async (req, res) => {
|
|||
authors, content, signature, embeddedData,
|
||||
});
|
||||
if (derivedHash !== hash) {
|
||||
console.log('error: hash mismatch');
|
||||
res.status(500).end();
|
||||
return;
|
||||
throw new Error('hash mismatch');
|
||||
}
|
||||
|
||||
// Verify signature
|
||||
if (!verifySignature(data)) {
|
||||
console.log('error: signature verificaition failed');
|
||||
res.status(500).end();
|
||||
return;
|
||||
throw new Error('signature verificaition failed');
|
||||
}
|
||||
|
||||
// Return content
|
||||
res.json(data);
|
||||
return {
|
||||
authors, content, signature, embeddedData, citations,
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = read;
|
||||
|
|
Loading…
Reference in New Issue