mirror of
https://github.com/alexgo-io/stacks-blockchain-api.git
synced 2026-01-12 22:43:34 +08:00
fix: detect name transfers and renewals in special circumstances (#1303)
* fix: take new owner from nft event * fix: remove check on nft_custody view as it is no longer required * fix: name-renewal with no zonefile hash * fix: import v1 data during replay * fix: headers already sent in redirect * fix: import names first, subdomains last
This commit is contained in:
@@ -98,7 +98,6 @@ export function createBnsNamesRouter(db: DataStore, chainId: ChainID): express.R
|
||||
return;
|
||||
}
|
||||
res.redirect(`${resolverResult.result}/v1/names${req.url}`);
|
||||
next();
|
||||
return;
|
||||
}
|
||||
res.status(404).json({ error: `cannot find subdomain ${name}` });
|
||||
|
||||
@@ -1491,12 +1491,12 @@ export class PgDataStore
|
||||
for (const smartContract of entry.smartContracts) {
|
||||
await this.updateSmartContract(client, entry.tx, smartContract);
|
||||
}
|
||||
for (const bnsName of entry.names) {
|
||||
await this.updateNames(client, entry.tx, bnsName);
|
||||
}
|
||||
for (const namespace of entry.namespaces) {
|
||||
await this.updateNamespaces(client, entry.tx, namespace);
|
||||
}
|
||||
for (const bnsName of entry.names) {
|
||||
await this.updateNames(client, entry.tx, bnsName);
|
||||
}
|
||||
}
|
||||
await this.refreshNftCustody(client, batchedTxData);
|
||||
await this.refreshMaterializedView(client, 'chain_tip');
|
||||
@@ -1701,12 +1701,12 @@ export class PgDataStore
|
||||
for (const smartContract of entry.smartContracts) {
|
||||
await this.updateSmartContract(client, entry.tx, smartContract);
|
||||
}
|
||||
for (const bnsName of entry.names) {
|
||||
await this.updateNames(client, entry.tx, bnsName);
|
||||
}
|
||||
for (const namespace of entry.namespaces) {
|
||||
await this.updateNamespaces(client, entry.tx, namespace);
|
||||
}
|
||||
for (const bnsName of entry.names) {
|
||||
await this.updateNames(client, entry.tx, bnsName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6896,8 +6896,59 @@ export class PgDataStore
|
||||
status,
|
||||
canonical,
|
||||
} = bnsName;
|
||||
// inserting remaining names information in names table
|
||||
const validZonefileHash = this.validateZonefileHash(zonefile_hash);
|
||||
// Try to figure out the name's expiration block based on its namespace's lifetime. However, if
|
||||
// the name was only transferred, keep the expiration from the last register/renewal we had.
|
||||
let expireBlock = expire_block;
|
||||
if (status === 'name-transfer') {
|
||||
const prevExpiration = await client.query<{ expire_block: number }>(
|
||||
`SELECT expire_block
|
||||
FROM names
|
||||
WHERE name = $1
|
||||
AND canonical = TRUE AND microblock_canonical = TRUE
|
||||
ORDER BY registered_at DESC, microblock_sequence DESC, tx_index DESC
|
||||
LIMIT 1`,
|
||||
[name]
|
||||
);
|
||||
if (prevExpiration.rowCount > 0) {
|
||||
expireBlock = prevExpiration.rows[0].expire_block;
|
||||
}
|
||||
} else {
|
||||
const namespaceLifetime = await client.query<{ lifetime: number }>(
|
||||
`SELECT lifetime
|
||||
FROM namespaces
|
||||
WHERE namespace_id = $1
|
||||
AND canonical = true AND microblock_canonical = true
|
||||
ORDER BY namespace_id, ready_block DESC, microblock_sequence DESC, tx_index DESC
|
||||
LIMIT 1`,
|
||||
[namespace_id]
|
||||
);
|
||||
if (namespaceLifetime.rowCount > 0) {
|
||||
expireBlock = registered_at + namespaceLifetime.rows[0].lifetime;
|
||||
}
|
||||
}
|
||||
// If we didn't receive a zonefile, keep the last valid one.
|
||||
let finalZonefile = zonefile;
|
||||
let finalZonefileHash = zonefile_hash;
|
||||
if (finalZonefileHash === '') {
|
||||
const lastZonefile = await client.query<{ zonefile: string; zonefile_hash: string }>(
|
||||
`
|
||||
SELECT z.zonefile, z.zonefile_hash
|
||||
FROM zonefiles AS z
|
||||
INNER JOIN names AS n USING (name, tx_id, index_block_hash)
|
||||
WHERE z.name = $1
|
||||
AND n.canonical = TRUE
|
||||
AND n.microblock_canonical = TRUE
|
||||
ORDER BY n.registered_at DESC, n.microblock_sequence DESC, n.tx_index DESC
|
||||
LIMIT 1
|
||||
`,
|
||||
[name]
|
||||
);
|
||||
if (lastZonefile.rowCount > 0) {
|
||||
finalZonefile = lastZonefile.rows[0].zonefile;
|
||||
finalZonefileHash = lastZonefile.rows[0].zonefile_hash;
|
||||
}
|
||||
}
|
||||
const validZonefileHash = this.validateZonefileHash(finalZonefileHash);
|
||||
await client.query(
|
||||
`
|
||||
INSERT INTO zonefiles (name, zonefile, zonefile_hash, tx_id, index_block_hash)
|
||||
@@ -6907,26 +6958,12 @@ export class PgDataStore
|
||||
`,
|
||||
[
|
||||
name,
|
||||
zonefile,
|
||||
finalZonefile,
|
||||
validZonefileHash,
|
||||
hexToBuffer(tx_id),
|
||||
hexToBuffer(blockData.index_block_hash),
|
||||
]
|
||||
);
|
||||
// Try to figure out the name's expiration block based on its namespace's lifetime.
|
||||
const namespaceLifetime = await client.query<{ lifetime: number }>(
|
||||
`SELECT lifetime
|
||||
FROM namespaces
|
||||
WHERE namespace_id = $1
|
||||
AND canonical = true AND microblock_canonical = true
|
||||
ORDER BY namespace_id, ready_block DESC, microblock_sequence DESC, tx_index DESC
|
||||
LIMIT 1`,
|
||||
[namespace_id]
|
||||
);
|
||||
const expireBlock =
|
||||
namespaceLifetime.rowCount > 0
|
||||
? registered_at + namespaceLifetime.rows[0].lifetime
|
||||
: expire_block;
|
||||
await client.query(
|
||||
`
|
||||
INSERT INTO names(
|
||||
@@ -7221,32 +7258,6 @@ export class PgDataStore
|
||||
if (nameZonefile.rowCount === 0) {
|
||||
return;
|
||||
}
|
||||
// The `names` and `zonefiles` tables only track latest zonefile changes. We need to check
|
||||
// `nft_custody` for the latest name owner, but only for names that were NOT imported from v1
|
||||
// since they did not generate an NFT event for us to track.
|
||||
if (nameZonefile.rows[0].registered_at !== 1) {
|
||||
let value: Buffer;
|
||||
try {
|
||||
value = bnsNameCV(name);
|
||||
} catch (error) {
|
||||
return;
|
||||
}
|
||||
const nameCustody = await client.query<{ recipient: string }>(
|
||||
`
|
||||
SELECT recipient
|
||||
FROM ${includeUnanchored ? 'nft_custody_unanchored' : 'nft_custody'}
|
||||
WHERE asset_identifier = $1 AND value = $2
|
||||
`,
|
||||
[getBnsSmartContractId(chainId), value]
|
||||
);
|
||||
if (nameCustody.rowCount === 0) {
|
||||
return;
|
||||
}
|
||||
return {
|
||||
...nameZonefile.rows[0],
|
||||
address: nameCustody.rows[0].recipient,
|
||||
};
|
||||
}
|
||||
return nameZonefile.rows[0];
|
||||
});
|
||||
if (queryResult) {
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { ChainID, ClarityType, hexToCV } from '@stacks/transactions';
|
||||
import { BufferCV, ChainID, ClarityType, hexToCV, StringAsciiCV } from '@stacks/transactions';
|
||||
import { hexToBuffer, hexToUtf8String } from '../../helpers';
|
||||
import { CoreNodeParsedTxMessage } from '../../event-stream/core-node-message';
|
||||
import {
|
||||
CoreNodeEvent,
|
||||
CoreNodeEventType,
|
||||
CoreNodeParsedTxMessage,
|
||||
NftTransferEvent,
|
||||
} from '../../event-stream/core-node-message';
|
||||
import { getCoreNodeEndpoint } from '../../core-rpc/client';
|
||||
import { StacksMainnet, StacksTestnet } from '@stacks/network';
|
||||
import { URIType } from 'zone-file/dist/zoneFile';
|
||||
@@ -244,10 +249,48 @@ function isEventFromBnsContract(event: SmartContractEvent): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function parseNameRenewalWithNoZonefileHashFromContractCall(
|
||||
tx: CoreNodeParsedTxMessage,
|
||||
chainId: ChainID
|
||||
): DbBnsName | undefined {
|
||||
const payload = tx.parsed_tx.payload;
|
||||
if (
|
||||
payload.type_id === TxPayloadTypeID.ContractCall &&
|
||||
payload.function_name === 'name-renewal' &&
|
||||
getBnsContractID(chainId) === `${payload.address}.${payload.contract_name}` &&
|
||||
payload.function_args.length === 5 &&
|
||||
hexToCV(payload.function_args[4].hex).type === ClarityType.OptionalNone
|
||||
) {
|
||||
const namespace = (hexToCV(payload.function_args[0].hex) as BufferCV).buffer.toString('utf8');
|
||||
const name = (hexToCV(payload.function_args[1].hex) as BufferCV).buffer.toString('utf8');
|
||||
return {
|
||||
name: `${name}.${namespace}`,
|
||||
namespace_id: namespace,
|
||||
// NOTE: We're not using the `new_owner` argument here because there's a bug in the BNS
|
||||
// contract that doesn't actually transfer the name to the given principal:
|
||||
// https://github.com/stacks-network/stacks-blockchain/issues/2680, maybe this will be fixed
|
||||
// in Stacks 2.1
|
||||
address: tx.sender_address,
|
||||
// expire_block will be calculated upon DB insert based on the namespace's lifetime.
|
||||
expire_block: 0,
|
||||
registered_at: tx.block_height,
|
||||
// Since we received no zonefile_hash, the previous one will be reused when writing to DB.
|
||||
zonefile_hash: '',
|
||||
zonefile: '',
|
||||
tx_id: tx.parsed_tx.tx_id,
|
||||
tx_index: tx.core_tx.tx_index,
|
||||
status: 'name-renewal',
|
||||
canonical: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function parseNameFromContractEvent(
|
||||
event: SmartContractEvent,
|
||||
tx: CoreNodeParsedTxMessage,
|
||||
blockHeight: number
|
||||
txEvents: CoreNodeEvent[],
|
||||
blockHeight: number,
|
||||
chainId: ChainID
|
||||
): DbBnsName | undefined {
|
||||
if (!isEventFromBnsContract(event)) {
|
||||
return;
|
||||
@@ -259,19 +302,21 @@ export function parseNameFromContractEvent(
|
||||
return;
|
||||
}
|
||||
let name_address = attachment.attachment.metadata.tx_sender.address;
|
||||
// Is this a `name-transfer` contract call? If so, record the new owner.
|
||||
if (
|
||||
attachment.attachment.metadata.op === 'name-transfer' &&
|
||||
tx.parsed_tx.payload.type_id === TxPayloadTypeID.ContractCall &&
|
||||
tx.parsed_tx.payload.function_args.length >= 3 &&
|
||||
tx.parsed_tx.payload.function_args[2].type_id === ClarityTypeID.PrincipalStandard
|
||||
) {
|
||||
const decoded = decodeClarityValue(tx.parsed_tx.payload.function_args[2].hex);
|
||||
const principal = decoded as ClarityValuePrincipalStandard;
|
||||
name_address = principal.address;
|
||||
// Is this a `name-transfer`? If so, look for the new owner in an `nft_transfer` event bundled in
|
||||
// the same transaction.
|
||||
if (attachment.attachment.metadata.op === 'name-transfer') {
|
||||
for (const txEvent of txEvents) {
|
||||
if (
|
||||
txEvent.type === CoreNodeEventType.NftTransferEvent &&
|
||||
txEvent.nft_transfer_event.asset_identifier === `${getBnsContractID(chainId)}::names`
|
||||
) {
|
||||
name_address = txEvent.nft_transfer_event.recipient;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const name: DbBnsName = {
|
||||
name: attachment.attachment.metadata.name.concat('.', attachment.attachment.metadata.namespace),
|
||||
name: `${attachment.attachment.metadata.name}.${attachment.attachment.metadata.namespace}`,
|
||||
namespace_id: attachment.attachment.metadata.namespace,
|
||||
address: name_address,
|
||||
// expire_block will be calculated upon DB insert based on the namespace's lifetime.
|
||||
|
||||
@@ -76,7 +76,7 @@ export interface StxLockEvent extends CoreNodeEventBase {
|
||||
};
|
||||
}
|
||||
|
||||
interface NftTransferEvent extends CoreNodeEventBase {
|
||||
export interface NftTransferEvent extends CoreNodeEventBase {
|
||||
type: CoreNodeEventType.NftTransferEvent;
|
||||
nft_transfer_event: {
|
||||
/** Fully qualified asset ID, e.g. "ST2ZRX0K27GW0SP3GJCEMHD95TQGJMKB7G9Y0X1MH.contract-name.asset-name" */
|
||||
|
||||
@@ -63,7 +63,11 @@ import {
|
||||
} from 'stacks-encoding-native-js';
|
||||
import { ChainID } from '@stacks/transactions';
|
||||
import { BnsContractIdentifier } from './bns/bns-constants';
|
||||
import { parseNameFromContractEvent, parseNamespaceFromContractEvent } from './bns/bns-helpers';
|
||||
import {
|
||||
parseNameFromContractEvent,
|
||||
parseNameRenewalWithNoZonefileHashFromContractCall,
|
||||
parseNamespaceFromContractEvent,
|
||||
} from './bns/bns-helpers';
|
||||
|
||||
async function handleRawEventRequest(
|
||||
eventPath: string,
|
||||
@@ -199,10 +203,15 @@ async function handleMicroblockMessage(
|
||||
});
|
||||
const updateData: DataStoreMicroblockUpdateData = {
|
||||
microblocks: dbMicroblocks,
|
||||
txs: parseDataStoreTxEventData(parsedTxs, msg.events, {
|
||||
block_height: -1, // TODO: fill during initial db insert
|
||||
index_block_hash: '',
|
||||
}),
|
||||
txs: parseDataStoreTxEventData(
|
||||
parsedTxs,
|
||||
msg.events,
|
||||
{
|
||||
block_height: -1, // TODO: fill during initial db insert
|
||||
index_block_hash: '',
|
||||
},
|
||||
chainId
|
||||
),
|
||||
};
|
||||
await db.updateMicroblocks(updateData);
|
||||
}
|
||||
@@ -299,7 +308,7 @@ async function handleBlockMessage(
|
||||
block: dbBlock,
|
||||
microblocks: dbMicroblocks,
|
||||
minerRewards: dbMinerRewards,
|
||||
txs: parseDataStoreTxEventData(parsedTxs, msg.events, msg),
|
||||
txs: parseDataStoreTxEventData(parsedTxs, msg.events, msg, chainId),
|
||||
};
|
||||
|
||||
await db.update(dbData);
|
||||
@@ -311,7 +320,8 @@ function parseDataStoreTxEventData(
|
||||
blockData: {
|
||||
block_height: number;
|
||||
index_block_hash: string;
|
||||
}
|
||||
},
|
||||
chainId: ChainID
|
||||
): DataStoreTxEventData[] {
|
||||
const dbData: DataStoreTxEventData[] = parsedTxs.map(tx => {
|
||||
const dbTx: DataStoreBlockUpdateData['txs'][number] = {
|
||||
@@ -325,16 +335,29 @@ function parseDataStoreTxEventData(
|
||||
names: [],
|
||||
namespaces: [],
|
||||
};
|
||||
if (tx.parsed_tx.payload.type_id === TxPayloadTypeID.SmartContract) {
|
||||
const contractId = `${tx.sender_address}.${tx.parsed_tx.payload.contract_name}`;
|
||||
dbTx.smartContracts.push({
|
||||
tx_id: tx.core_tx.txid,
|
||||
contract_id: contractId,
|
||||
block_height: blockData.block_height,
|
||||
source_code: tx.parsed_tx.payload.code_body,
|
||||
abi: JSON.stringify(tx.core_tx.contract_abi),
|
||||
canonical: true,
|
||||
});
|
||||
switch (tx.parsed_tx.payload.type_id) {
|
||||
case TxPayloadTypeID.SmartContract:
|
||||
const contractId = `${tx.sender_address}.${tx.parsed_tx.payload.contract_name}`;
|
||||
dbTx.smartContracts.push({
|
||||
tx_id: tx.core_tx.txid,
|
||||
contract_id: contractId,
|
||||
block_height: blockData.block_height,
|
||||
source_code: tx.parsed_tx.payload.code_body,
|
||||
abi: JSON.stringify(tx.core_tx.contract_abi),
|
||||
canonical: true,
|
||||
});
|
||||
break;
|
||||
case TxPayloadTypeID.ContractCall:
|
||||
// Name renewals can happen without a zonefile_hash. In that case, the BNS contract does NOT
|
||||
// emit a `name-renewal` contract log, causing us to miss this event. This function catches
|
||||
// those cases.
|
||||
const name = parseNameRenewalWithNoZonefileHashFromContractCall(tx, chainId);
|
||||
if (name) {
|
||||
dbTx.names.push(name);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return dbTx;
|
||||
});
|
||||
@@ -372,7 +395,13 @@ function parseDataStoreTxEventData(
|
||||
if (!parsedTx) {
|
||||
throw new Error(`Unexpected missing tx during BNS parsing by tx_id ${event.txid}`);
|
||||
}
|
||||
const name = parseNameFromContractEvent(event, parsedTx, blockData.block_height);
|
||||
const name = parseNameFromContractEvent(
|
||||
event,
|
||||
parsedTx,
|
||||
events,
|
||||
blockData.block_height,
|
||||
chainId
|
||||
);
|
||||
if (name) {
|
||||
dbTx.names.push(name);
|
||||
}
|
||||
|
||||
@@ -129,6 +129,295 @@ describe('BNS event server tests', () => {
|
||||
expect(namespace.result?.ready_block).toBe(2);
|
||||
});
|
||||
|
||||
test('name-transfer called by a contract other than BNS', async () => {
|
||||
const block = new TestBlockBuilder({
|
||||
block_height: 1,
|
||||
block_hash: '0x09458029b7c0e43e015bd3202c0f9512c2b394e0481bfd2bdd096ae7b5b862f2',
|
||||
index_block_hash: '0xad9403fc8d8eaef47816555cac51dca9d934384aa9b2581f9b9085509b2af915',
|
||||
burn_block_height: 743853,
|
||||
burn_block_hash: '0x00000000000000000008b9d65609c6b39bb89d7da35433e4b287835d7112d6d4',
|
||||
burn_block_time: 1657123396,
|
||||
})
|
||||
.addTx({
|
||||
tx_id: '0x1234',
|
||||
sender_address: 'SPP117ENNNDQVQ1G3E0N1AP178GXBTC2YNQ3H7J'
|
||||
})
|
||||
.addTxBnsNamespace({
|
||||
namespace_id: 'btc',
|
||||
lifetime: 1000
|
||||
})
|
||||
.addTxBnsName({
|
||||
name: 'dayslikewater.btc',
|
||||
namespace_id: 'btc',
|
||||
zonefile_hash: 'b472a266d0bd89c13706a4132ccfb16f7c3b9fcb',
|
||||
address: 'SPP117ENNNDQVQ1G3E0N1AP178GXBTC2YNQ3H7J'
|
||||
})
|
||||
.addTxNftEvent({
|
||||
asset_event_type_id: DbAssetEventTypeId.Mint,
|
||||
value: bnsNameCV('dayslikewater.btc'),
|
||||
asset_identifier: 'SP000000000000000000002Q6VF78.bns::names',
|
||||
recipient: 'SPP117ENNNDQVQ1G3E0N1AP178GXBTC2YNQ3H7J',
|
||||
})
|
||||
.build();
|
||||
await db.update(block);
|
||||
const microblock = new TestMicroblockStreamBuilder()
|
||||
.addMicroblock({
|
||||
microblock_hash: '0xccdd11fef1792979bc54a9b686e9cc4fc3d64f2a9b2d8ee9d34fe27bfab783a4',
|
||||
microblock_sequence: 0,
|
||||
parent_index_block_hash: '0xad9403fc8d8eaef47816555cac51dca9d934384aa9b2581f9b9085509b2af915'
|
||||
})
|
||||
.build();
|
||||
await db.updateMicroblocks(microblock);
|
||||
|
||||
const name1 = await db.getName({
|
||||
name: 'dayslikewater.btc',
|
||||
includeUnanchored: true,
|
||||
chainId: ChainID.Mainnet
|
||||
});
|
||||
expect(name1.found).toBe(true);
|
||||
expect(name1.result?.namespace_id).toBe('btc');
|
||||
expect(name1.result?.tx_id).toBe('0x1234');
|
||||
expect(name1.result?.status).toBe('name-register');
|
||||
expect(name1.result?.expire_block).toBe(1001);
|
||||
expect(name1.result?.address).toBe('SPP117ENNNDQVQ1G3E0N1AP178GXBTC2YNQ3H7J');
|
||||
|
||||
const payload = {
|
||||
"events": [
|
||||
{
|
||||
"txid": "0xa75ebee2c824c4943bf8494b101ea7ee7d44191b4a8f761582ce99ef28befb19",
|
||||
"type": "contract_event",
|
||||
"committed": true,
|
||||
"event_index": 74,
|
||||
"contract_event": {
|
||||
"topic": "print",
|
||||
"raw_value": "0x0c000000010a6174746163686d656e740c00000003106174746163686d656e742d696e646578010000000000000000000000000000e52b04686173680200000014b472a266d0bd89c13706a4132ccfb16f7c3b9fcb086d657461646174610c00000004046e616d65020000000d646179736c696b657761746572096e616d6573706163650200000003627463026f700d0000000d6e616d652d7472616e736665720974782d73656e6465720516016084eead6adbeee180dc0a855609d10eaf4c17",
|
||||
"contract_identifier": "SP000000000000000000002Q6VF78.bns"
|
||||
}
|
||||
},
|
||||
{
|
||||
"txid": "0xa75ebee2c824c4943bf8494b101ea7ee7d44191b4a8f761582ce99ef28befb19",
|
||||
"type": "nft_transfer_event",
|
||||
"committed": true,
|
||||
"event_index": 73,
|
||||
"nft_transfer_event": {
|
||||
"sender": "SPP117ENNNDQVQ1G3E0N1AP178GXBTC2YNQ3H7J",
|
||||
"raw_value": "0x0c00000002046e616d65020000000d646179736c696b657761746572096e616d6573706163650200000003627463",
|
||||
"recipient": "SP1TY00PDWJVNVEX7H7KJGS2K2YXHTQMY8C0G1NVP",
|
||||
"asset_identifier": "SP000000000000000000002Q6VF78.bns::names"
|
||||
}
|
||||
},
|
||||
{
|
||||
"txid": "0xa75ebee2c824c4943bf8494b101ea7ee7d44191b4a8f761582ce99ef28befb19",
|
||||
"type": "stx_transfer_event",
|
||||
"committed": true,
|
||||
"event_index": 71,
|
||||
"stx_transfer_event": {
|
||||
"amount": "2500",
|
||||
"sender": "SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C.bns-marketplace-v3",
|
||||
"recipient": "SP2KAF9RF86PVX3NEE27DFV1CQX0T4WGR41X3S45C"
|
||||
}
|
||||
}
|
||||
],
|
||||
"block_hash": "0x7d18920cc47f731f186fb9f731d2e8d5029bbab6d73fd012ac3e10637a8e4a37",
|
||||
"miner_txid": "0xbed35e9e7eb7f98583c87743d3860ab63f2506f7f1efe24740cd37f7708de0b4",
|
||||
"block_height": 2,
|
||||
"transactions": [
|
||||
{
|
||||
"txid": "0xa75ebee2c824c4943bf8494b101ea7ee7d44191b4a8f761582ce99ef28befb19",
|
||||
"raw_tx": "0x00000000010400016084eead6adbeee180dc0a855609d10eaf4c1700000000000000020000000000000bb80000e452e9d87e94a2a4364e89af3ab44b3ce1117afb6505721ff5b801294e1280f0616ee4d21a6ef9bcca1ea15ac65477e79df3427f7fd6c41c80938f8cca6d2cd0030200000002000316a6a7a70f41adbe8eae708ed7ec2cbf41a272182012626e732d6d61726b6574706c6163652d76330500000000000186a0020216016084eead6adbeee180dc0a855609d10eaf4c1716000000000000000000000000000000000000000003626e73056e616d65730c00000002046e616d65020000000d646179736c696b657761746572096e616d6573706163650200000003627463100216a6a7a70f41adbe8eae708ed7ec2cbf41a272182012626e732d6d61726b6574706c6163652d76330a6163636570742d626964000000030200000003627463020000000d646179736c696b6577617465720a0200000014b472a266d0bd89c13706a4132ccfb16f7c3b9fcb",
|
||||
"status": "success",
|
||||
"tx_index": 25,
|
||||
"raw_result": "0x0703",
|
||||
"contract_abi": null,
|
||||
"execution_cost": {
|
||||
"runtime": 381500,
|
||||
"read_count": 42,
|
||||
"read_length": 96314,
|
||||
"write_count": 9,
|
||||
"write_length": 359
|
||||
},
|
||||
"microblock_hash": null,
|
||||
"microblock_sequence": null,
|
||||
"microblock_parent_hash": null
|
||||
}
|
||||
],
|
||||
"anchored_cost": {
|
||||
"runtime": 44194708,
|
||||
"read_count": 4105,
|
||||
"read_length": 11476905,
|
||||
"write_count": 546,
|
||||
"write_length": 47312
|
||||
},
|
||||
"burn_block_hash": "0x00000000000000000005e28a41cdb7461953b9424b4fd44a9211a145a1c0346d",
|
||||
"burn_block_time": 1657125225,
|
||||
"index_block_hash": "0xb70205d38a8666cbd071239b4ec28ae7d12a2c32341118d7c6d4d1e22f56014e",
|
||||
"burn_block_height": 743854,
|
||||
"parent_block_hash": "0x09458029b7c0e43e015bd3202c0f9512c2b394e0481bfd2bdd096ae7b5b862f2",
|
||||
"parent_microblock": "0xccdd11fef1792979bc54a9b686e9cc4fc3d64f2a9b2d8ee9d34fe27bfab783a4",
|
||||
"matured_miner_rewards": [],
|
||||
"parent_burn_block_hash": "0x00000000000000000008b9d65609c6b39bb89d7da35433e4b287835d7112d6d4",
|
||||
"parent_index_block_hash": "0xad9403fc8d8eaef47816555cac51dca9d934384aa9b2581f9b9085509b2af915",
|
||||
"parent_burn_block_height": 743853,
|
||||
"confirmed_microblocks_cost": {
|
||||
"runtime": 48798,
|
||||
"read_count": 10,
|
||||
"read_length": 40042,
|
||||
"write_count": 3,
|
||||
"write_length": 19
|
||||
},
|
||||
"parent_microblock_sequence": 0,
|
||||
"parent_burn_block_timestamp": 1657123396
|
||||
};
|
||||
|
||||
await httpPostRequest({
|
||||
host: '127.0.0.1',
|
||||
port: eventServer.serverAddress.port,
|
||||
path: '/new_block',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: Buffer.from(JSON.stringify(payload), 'utf8'),
|
||||
throwOnNotOK: true,
|
||||
});
|
||||
|
||||
const name2 = await db.getName({
|
||||
name: 'dayslikewater.btc',
|
||||
includeUnanchored: true,
|
||||
chainId: ChainID.Mainnet
|
||||
});
|
||||
expect(name2.found).toBe(true);
|
||||
expect(name2.result?.namespace_id).toBe('btc');
|
||||
expect(name2.result?.tx_id).toBe('0xa75ebee2c824c4943bf8494b101ea7ee7d44191b4a8f761582ce99ef28befb19');
|
||||
expect(name2.result?.status).toBe('name-transfer');
|
||||
expect(name2.result?.expire_block).toBe(1001); // Unchanged as it was not renewed
|
||||
expect(name2.result?.address).toBe('SP1TY00PDWJVNVEX7H7KJGS2K2YXHTQMY8C0G1NVP');
|
||||
});
|
||||
|
||||
test('name-renewal called with no zonefile_hash', async () => {
|
||||
const block = new TestBlockBuilder({
|
||||
block_height: 1,
|
||||
block_hash: '0xf81ef7f114213b9034a4378345a931a97c781fab398c3d7a2053f0d0bf48d311',
|
||||
index_block_hash: '0xaec282925b5096c0bd98588d25a97e134bcc4f19b6600859fa267cf0ee4eaf2d',
|
||||
burn_block_height: 726955,
|
||||
burn_block_hash: '0x00000000000000000001523f01cb4304d39527454d2eec79817b50c033a5c5d9',
|
||||
burn_block_time: 1647068146,
|
||||
})
|
||||
.addTx({
|
||||
tx_id: '0x1234',
|
||||
sender_address: 'SP3GWTV1SMF9HDS4VY5NMM833CHH266W4YBASVYMZ'
|
||||
})
|
||||
.addTxBnsNamespace({
|
||||
namespace_id: 'id',
|
||||
lifetime: 1000
|
||||
})
|
||||
.addTxBnsName({
|
||||
name: 'friedger.id',
|
||||
namespace_id: 'id',
|
||||
zonefile_hash: 'b472a266d0bd89c13706a4132ccfb16f7c3b9fcb',
|
||||
address: 'SP3GWTV1SMF9HDS4VY5NMM833CHH266W4YBASVYMZ'
|
||||
})
|
||||
.addTxNftEvent({
|
||||
asset_event_type_id: DbAssetEventTypeId.Mint,
|
||||
value: bnsNameCV('friedger.id'),
|
||||
asset_identifier: 'SP000000000000000000002Q6VF78.bns::names',
|
||||
recipient: 'SP3GWTV1SMF9HDS4VY5NMM833CHH266W4YBASVYMZ',
|
||||
})
|
||||
.build();
|
||||
await db.update(block);
|
||||
const microblock = new TestMicroblockStreamBuilder()
|
||||
.addMicroblock({
|
||||
microblock_hash: '0x640362ec47c40de3337491993e42efe60d05187431633ab03c3f5d33e70d1f8e',
|
||||
microblock_sequence: 0,
|
||||
parent_index_block_hash: '0xaec282925b5096c0bd98588d25a97e134bcc4f19b6600859fa267cf0ee4eaf2d'
|
||||
})
|
||||
.build();
|
||||
await db.updateMicroblocks(microblock);
|
||||
|
||||
const name1 = await db.getName({
|
||||
name: 'friedger.id',
|
||||
includeUnanchored: true,
|
||||
chainId: ChainID.Mainnet
|
||||
});
|
||||
expect(name1.found).toBe(true);
|
||||
expect(name1.result?.namespace_id).toBe('id');
|
||||
expect(name1.result?.tx_id).toBe('0x1234');
|
||||
expect(name1.result?.status).toBe('name-register');
|
||||
expect(name1.result?.expire_block).toBe(1001);
|
||||
expect(name1.result?.address).toBe('SP3GWTV1SMF9HDS4VY5NMM833CHH266W4YBASVYMZ');
|
||||
|
||||
const payload = {
|
||||
"events": [],
|
||||
"block_hash": "0xaaee893667244adcb8581abac372f1f8c385d402b71e8e8b4ac91e8066024fd5",
|
||||
"miner_txid": "0x6ff493c6b98b9cff0638c7c5276af8e627b8ed779965a5f1c11bbc0810834b3e",
|
||||
"block_height": 2,
|
||||
"transactions": [
|
||||
{
|
||||
"txid": "0xf037c8da8210e2a348bbecd3bc44901de875d3774c5fce49cb75d95f2dc2ca4d",
|
||||
"raw_tx": "0x00000000010500e1cd6c39a3d316e49bf16b4a20636462231b84f200000000000000000000000000000000000094f2c8529dcb8a55a5cfd4434c68cae9cd54f26f01c656369585db3ba364150a4fead679adf35cf5ba1026656b3873daf3380f48ec6dcc175ada868e531decf5001d04c185cad28a3f5299d3fcbcbcbe66b2e1e227000000000000000000000000000186a0000064cc0eb565e85c0d4110c9a760c8fdad21999409f89320e355f326c144b8ada4268244f80734170cea96f683d2431b59f07f276a10efc80793d4dceef8feb2310302000000000216000000000000000000000000000000000000000003626e730c6e616d652d72656e6577616c000000050200000002696402000000086672696564676572010000000000000000000000000001a72a0909",
|
||||
"status": "success",
|
||||
"tx_index": 2,
|
||||
"raw_result": "0x0703",
|
||||
"contract_abi": null,
|
||||
"execution_cost": {
|
||||
"runtime": 184253,
|
||||
"read_count": 11,
|
||||
"read_length": 43250,
|
||||
"write_count": 1,
|
||||
"write_length": 143
|
||||
},
|
||||
"microblock_hash": null,
|
||||
"microblock_sequence": null,
|
||||
"microblock_parent_hash": null
|
||||
}
|
||||
],
|
||||
"anchored_cost": {
|
||||
"runtime": 28375070,
|
||||
"read_count": 8888,
|
||||
"read_length": 1085153,
|
||||
"write_count": 593,
|
||||
"write_length": 156284
|
||||
},
|
||||
"burn_block_hash": "0x0000000000000000000552fb5fd8c08ad8f1ef30c239369a8a3380ec1566047a",
|
||||
"burn_block_time": 1647068392,
|
||||
"index_block_hash": "0x9ff46918054b1aa94571a60e14921a56977f26af2adcbf4a7f64138566feba48",
|
||||
"burn_block_height": 726956,
|
||||
"parent_block_hash": "0xf81ef7f114213b9034a4378345a931a97c781fab398c3d7a2053f0d0bf48d311",
|
||||
"parent_microblock": "0x640362ec47c40de3337491993e42efe60d05187431633ab03c3f5d33e70d1f8e",
|
||||
"matured_miner_rewards": [],
|
||||
"parent_burn_block_hash": "0x00000000000000000001523f01cb4304d39527454d2eec79817b50c033a5c5d9",
|
||||
"parent_index_block_hash": "0xaec282925b5096c0bd98588d25a97e134bcc4f19b6600859fa267cf0ee4eaf2d",
|
||||
"parent_burn_block_height": 726955,
|
||||
"confirmed_microblocks_cost": {
|
||||
"runtime": 360206,
|
||||
"read_count": 38,
|
||||
"read_length": 95553,
|
||||
"write_count": 8,
|
||||
"write_length": 378
|
||||
},
|
||||
"parent_microblock_sequence": 0,
|
||||
"parent_burn_block_timestamp": 1647068146
|
||||
};
|
||||
|
||||
await httpPostRequest({
|
||||
host: '127.0.0.1',
|
||||
port: eventServer.serverAddress.port,
|
||||
path: '/new_block',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: Buffer.from(JSON.stringify(payload), 'utf8'),
|
||||
throwOnNotOK: true,
|
||||
});
|
||||
|
||||
const name2 = await db.getName({
|
||||
name: 'friedger.id',
|
||||
includeUnanchored: true,
|
||||
chainId: ChainID.Mainnet
|
||||
});
|
||||
expect(name2.found).toBe(true);
|
||||
expect(name2.result?.namespace_id).toBe('id');
|
||||
expect(name2.result?.tx_id).toBe('0xf037c8da8210e2a348bbecd3bc44901de875d3774c5fce49cb75d95f2dc2ca4d');
|
||||
expect(name2.result?.status).toBe('name-renewal');
|
||||
expect(name2.result?.expire_block).toBe(1002); // Updated correctly
|
||||
expect(name2.result?.address).toBe('SP3GWTV1SMF9HDS4VY5NMM833CHH266W4YBASVYMZ');
|
||||
});
|
||||
|
||||
test('/attachments/new with re-orged zonefiles', async () => {
|
||||
const block1 = new TestBlockBuilder({
|
||||
block_height: 1,
|
||||
|
||||
Reference in New Issue
Block a user