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:
Rafael Cárdenas
2022-08-31 12:07:00 -05:00
committed by GitHub
parent 00e71975db
commit cd381a95b4
6 changed files with 456 additions and 83 deletions

View File

@@ -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}` });

View File

@@ -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) {

View File

@@ -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.

View File

@@ -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" */

View File

@@ -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);
}

View File

@@ -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,