Skip to content

Commit

Permalink
feat: integrate stx lock event handling into db
Browse files Browse the repository at this point in the history
  • Loading branch information
zone117x committed Nov 11, 2020
1 parent 7a2750c commit 726f1c6
Show file tree
Hide file tree
Showing 12 changed files with 329 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@
"type": "node",
"request": "launch",
"name": "Launch: mocknet",
"skipFiles": [
"<node_internals>/**"
],
"runtimeArgs": ["-r", "ts-node/register/transpile-only", "-r", "tsconfig-paths/register"],
"args": ["${workspaceFolder}/src/index.ts"],
"outputCapture": "std",
Expand Down
137 changes: 137 additions & 0 deletions src/api/routes/debug.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand Down Expand Up @@ -426,6 +432,137 @@ export function createDebugRouter(db: DataStore): RouterWithAsync {
);
});

const sendPoxHtml = `
<style>
* { font-family: "Lucida Console", Monaco, monospace; }
input {
display: block;
width: 100%;
margin-bottom: 10;
}
</style>
<form action="" method="post">
<label for="origin_key">Sender key</label>
<input list="origin_keys" name="origin_key" value="${testnetKeys[0].secretKey}">
<datalist id="origin_keys">
${testnetKeys.map(k => '<option value="' + k.secretKey + '">').join('\n')}
</datalist>
<label for="recipient_address">Recipient address</label>
<input list="recipient_addresses" name="recipient_address" value="${
testnetKeys[1].stacksAddress
}">
<datalist id="recipient_addresses">
${testnetKeys.map(k => '<option value="' + k.stacksAddress + '">').join('\n')}
</datalist>
<label for="stx_amount">uSTX amount</label>
<input type="number" id="stx_amount" name="stx_amount" value="5000">
<label for="memo">Memo</label>
<input type="text" id="memo" name="memo" value="hello" maxlength="34">
<input type="checkbox" id="sponsored" name="sponsored" value="sponsored" style="display:initial;width:auto">
<label for="sponsored">Create sponsored transaction</label>
<input type="submit" value="Submit">
</form>
`;

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 +
'<h3>Broadcasted transaction:</h3>' +
`<a href="/extended/v1/tx/${txId}">${txId}</a>`
);
});

const contractDeployHtml = `
<style>
* { font-family: "Lucida Console", Monaco, monospace; }
Expand Down
11 changes: 10 additions & 1 deletion src/datastore/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export enum DbEventTypeId {
StxAsset = 2,
FungibleTokenAsset = 3,
NonFungibleTokenAsset = 4,
StxLock = 5,
}

export interface DbEventBase {
Expand All @@ -163,6 +164,13 @@ export interface DbSmartContractEvent extends DbEventBase {
value: Buffer;
}

export interface DbStxLockEvent extends DbEventBase {
event_type: DbEventTypeId.StxLock;
locked_amount: BigInt;
unlock_height: BigInt;
locked_address: string;
}

export enum DbAssetEventTypeId {
Transfer = 1,
Mint = 2,
Expand Down Expand Up @@ -196,7 +204,7 @@ export interface DbNftEvent extends DbContractAssetEvent {
value: Buffer;
}

export type DbEvent = DbSmartContractEvent | DbStxEvent | DbFtEvent | DbNftEvent;
export type DbEvent = DbSmartContractEvent | DbStxEvent | DbStxLockEvent | DbFtEvent | DbNftEvent;

export interface AddressTxUpdateInfo {
address: string;
Expand All @@ -217,6 +225,7 @@ export interface DataStoreUpdateData {
txs: {
tx: DbTx;
stxEvents: DbStxEvent[];
stxLockEvents: DbStxLockEvent[];
ftEvents: DbFtEvent[];
nftEvents: DbNftEvent[];
contractLogEvents: DbSmartContractEvent[];
Expand Down
28 changes: 27 additions & 1 deletion src/datastore/memory-store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
DbMempoolTx,
DbSearchResult,
DbStxBalance,
DbStxLockEvent,
} from './common';
import { logger, FoundOrNot } from '../helpers';
import { TransactionType } from '@blockstack/stacks-blockchain-api-types';
Expand All @@ -26,6 +27,10 @@ export class MemoryDataStore extends (EventEmitter as { new (): DataStoreEventEm
readonly blocks: Map<string, { entry: DbBlock }> = new Map();
readonly txs: Map<string, { entry: DbTx }> = new Map();
readonly txMempool: Map<string, DbMempoolTx> = new Map();
readonly stxLockEvents: Map<
string,
{ indexBlockHash: string; entry: DbStxLockEvent }
> = new Map();
readonly stxTokenEvents: Map<string, { indexBlockHash: string; entry: DbStxEvent }> = new Map();
readonly fungibleTokenEvents: Map<
string,
Expand All @@ -49,6 +54,9 @@ export class MemoryDataStore extends (EventEmitter as { new (): DataStoreEventEm
await this.updateBlock(data.block);
for (const entry of data.txs) {
await this.updateTx(entry.tx);
for (const stxLockEvent of entry.stxLockEvents) {
await this.updateStxLockEvent(entry.tx, stxLockEvent);
}
for (const stxEvent of entry.stxEvents) {
await this.updateStxEvent(entry.tx, stxEvent);
}
Expand Down Expand Up @@ -90,6 +98,7 @@ export class MemoryDataStore extends (EventEmitter as { new (): DataStoreEventEm
this.blocks,
this.txs,
this.smartContractEvents,
this.stxLockEvents,
this.stxTokenEvents,
this.fungibleTokenEvents,
this.nonFungibleTokenEvents,
Expand Down Expand Up @@ -218,6 +227,9 @@ export class MemoryDataStore extends (EventEmitter as { new (): DataStoreEventEm
}

getTxEvents(txId: string, indexBlockHash: string) {
const stxLockEvents = [...this.stxLockEvents.values()].filter(
e => e.indexBlockHash === indexBlockHash && e.entry.tx_id === txId
);
const stxEvents = [...this.stxTokenEvents.values()].filter(
e => e.indexBlockHash === indexBlockHash && e.entry.tx_id === txId
);
Expand All @@ -230,12 +242,26 @@ export class MemoryDataStore extends (EventEmitter as { new (): DataStoreEventEm
const smartContractEvents = [...this.smartContractEvents.values()].filter(
e => e.indexBlockHash === indexBlockHash && e.entry.tx_id === txId
);
const allEvents = [...stxEvents, ...ftEvents, ...nftEvents, ...smartContractEvents]
const allEvents = [
...stxLockEvents,
...stxEvents,
...ftEvents,
...nftEvents,
...smartContractEvents,
]
.sort(e => e.entry.event_index)
.map(e => e.entry);
return Promise.resolve({ results: allEvents });
}

updateStxLockEvent(tx: DbTx, event: DbStxLockEvent) {
this.stxLockEvents.set(`${event.tx_id}_${tx.index_block_hash}_${event.event_index}`, {
indexBlockHash: tx.index_block_hash,
entry: { ...event },
});
return Promise.resolve();
}

updateStxEvent(tx: DbTx, event: DbStxEvent) {
this.stxTokenEvents.set(`${event.tx_id}_${tx.index_block_hash}_${event.event_index}`, {
indexBlockHash: tx.index_block_hash,
Expand Down
Loading

0 comments on commit 726f1c6

Please sign in to comment.