From 726f1c643d9478fcd822b1691588d47fa038ed7b Mon Sep 17 00:00:00 2001 From: Matthew Little Date: Wed, 11 Nov 2020 16:04:37 +0100 Subject: [PATCH] feat: integrate stx lock event handling into db --- .vscode/launch.json | 3 + src/api/routes/debug.ts | 137 ++++++++++++++++++ src/datastore/common.ts | 11 +- src/datastore/memory-store.ts | 28 +++- src/datastore/postgres-store.ts | 52 +++++++ src/event-stream/core-node-message.ts | 10 +- src/event-stream/event-server.ts | 28 +++- .../1605100253938_stx_lock_events.ts | 53 +++++++ src/tests/api-tests.ts | 2 + src/tests/datastore-tests.ts | 9 ++ src/tests/websocket-tests.ts | 5 + tsconfig.json | 2 +- 12 files changed, 329 insertions(+), 11 deletions(-) create mode 100644 src/migrations/1605100253938_stx_lock_events.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index a7a1862b46..441d6fe46f 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -32,6 +32,9 @@ "type": "node", "request": "launch", "name": "Launch: mocknet", + "skipFiles": [ + "/**" + ], "runtimeArgs": ["-r", "ts-node/register/transpile-only", "-r", "tsconfig-paths/register"], "args": ["${workspaceFolder}/src/index.ts"], "outputCapture": "std", diff --git a/src/api/routes/debug.ts b/src/api/routes/debug.ts index 43abe24417..379d19db51 100644 --- a/src/api/routes/debug.ts +++ b/src/api/routes/debug.ts @@ -1,5 +1,7 @@ import * as express from 'express'; import * as BN from 'bn.js'; +import * as btc from 'bitcoinjs-lib'; +import * as c32check from 'c32check'; import * as bodyParser from 'body-parser'; import { addAsync, RouterWithAsync } from '@awaitjs/express'; import { htmlEscape } from 'escape-goat'; @@ -24,6 +26,10 @@ import { TransactionVersion, AddressVersion, addressToString, + ContractCallOptions, + uintCV, + tupleCV, + bufferCV, } from '@blockstack/stacks-transactions'; import { SampleContracts } from '../../sample-data/broadcast-contract-default'; import { DataStore, DbFaucetRequestCurrency } from '../../datastore/common'; @@ -426,6 +432,137 @@ export function createDebugRouter(db: DataStore): RouterWithAsync { ); }); + const sendPoxHtml = ` + +
+ + + + ${testnetKeys.map(k => ' + + + + + ${testnetKeys.map(k => ' + + + + + + + + + + + +
+ `; + + router.getAsync('/broadcast/stack', (req, res) => { + res.set('Content-Type', 'text/html').send(sendPoxHtml); + }); + + function convertBTCAddress(btcAddress: string) { + function getAddressHashMode(btcAddress: string) { + if (btcAddress.startsWith('bc1') || btcAddress.startsWith('tb1')) { + const { data } = btc.address.fromBech32(btcAddress); + if (data.length === 32) { + return AddressHashMode.SerializeP2WSH; + } else { + return AddressHashMode.SerializeP2WPKH; + } + } else { + const { version } = btc.address.fromBase58Check(btcAddress); + switch (version) { + case 0: + return AddressHashMode.SerializeP2PKH; + case 111: + return AddressHashMode.SerializeP2PKH; + case 5: + return AddressHashMode.SerializeP2SH; + case 196: + return AddressHashMode.SerializeP2SH; + default: + throw new Error('Invalid pox address version'); + } + } + } + const hashMode = getAddressHashMode(btcAddress); + if (btcAddress.startsWith('bc1') || btcAddress.startsWith('tb1')) { + const { data } = btc.address.fromBech32(btcAddress); + return { + hashMode, + data, + }; + } else { + const { hash } = btc.address.fromBase58Check(btcAddress); + return { + hashMode, + data: hash, + }; + } + } + + router.postAsync('/broadcast/stack', async (req, res) => { + const { origin_key, recipient_address, stx_amount, memo } = req.body; + const client = new StacksCoreRpcClient(); + const coreInfo = await client.getInfo(); + const poxInfo = await client.getPox(); + const minStxAmount = BigInt(poxInfo.min_amount_ustx); + const sender = testnetKeys.filter(t => t.secretKey === origin_key)[0]; + const accountBalance = await client.getAccountBalance(sender.stacksAddress); + if (accountBalance < minStxAmount) { + throw new Error( + `Min requirement pox amount is ${minStxAmount} but account balance is only ${accountBalance}` + ); + } + const [contractAddress, contractName] = poxInfo.contract_id.split('.'); + const btcAddr = c32check.c32ToB58(sender.stacksAddress); + const { hashMode, data } = convertBTCAddress(btcAddr); + const cycles = 3; + const txOptions: ContractCallOptions = { + senderKey: sender.secretKey, + contractAddress, + contractName, + functionName: 'stack-stx', + functionArgs: [ + uintCV(minStxAmount.toString()), + tupleCV({ + hashbytes: bufferCV(data), + version: bufferCV(new BN(hashMode).toBuffer()), + }), + uintCV(coreInfo.burn_block_height), + uintCV(cycles), + ], + network: stacksNetwork, + }; + const tx = await makeContractCall(txOptions); + const expectedTxId = tx.txid(); + const serializedTx = tx.serialize(); + const { txId } = await sendCoreTx(serializedTx); + if (txId !== '0x' + expectedTxId) { + throw new Error(`Expected ${expectedTxId}, core ${txId}`); + } + res + .set('Content-Type', 'text/html') + .send( + tokenTransferHtml + + '

Broadcasted transaction:

' + + `${txId}` + ); + }); + const contractDeployHtml = `