diff --git a/src/event-stream/reader.ts b/src/event-stream/reader.ts index 36f9d1a904..66cd32a043 100644 --- a/src/event-stream/reader.ts +++ b/src/event-stream/reader.ts @@ -29,7 +29,11 @@ import { TxSpendingConditionSingleSigHashMode, decodeClarityValueList, } from 'stacks-encoding-native-js'; -import { DbMicroblockPartial, DbPox2DelegateStxEvent, DbPox2EventData } from '../datastore/common'; +import { + DbMicroblockPartial, + DbPox2DelegateStxEvent, + DbPox2StackStxEvent, +} from '../datastore/common'; import { NotImplementedError } from '../errors'; import { getEnumDescription, @@ -79,7 +83,8 @@ function createTransactionFromCoreBtcStxLockEvent( event: StxLockEvent, burnBlockHeight: number, txResult: string, - txId: string + txId: string, + stxStacksPox2Event: DbPox2StackStxEvent | undefined ): DecodedTxResult { const resultCv = decodeClarityValue< ClarityValueResponse< @@ -108,9 +113,14 @@ function createTransactionFromCoreBtcStxLockEvent( const contractName = event.stx_lock_event.contract_identifier?.split('.')?.[1] ?? 'pox'; + // If a pox-2 event is available then use its pox_addr, otherwise fallback to the stacker address + const poxAddrArg = stxStacksPox2Event?.pox_addr + ? poxAddressToTuple(stxStacksPox2Event.pox_addr) + : poxAddressToTuple(c32ToB58(stacker.address)); + const legacyClarityVals = [ uintCV(lockAmount.value), // amount-ustx - poxAddressToTuple(c32ToB58(stacker.address)), // pox-addr + poxAddrArg, // pox-addr uintCV(burnBlockHeight), // start-burn-height uintCV(lockPeriod), // lock-period ]; @@ -363,13 +373,15 @@ export function parseMessageTransaction( (e): e is StxLockEvent => e.type === CoreNodeEventType.StxLockEvent ); - const pox2Event = events.map(e => { - if ( - e.type === CoreNodeEventType.ContractEvent && - e.contract_event.topic === 'print' && - (e.contract_event.contract_identifier === Pox2ContractIdentifer.mainnet || - e.contract_event.contract_identifier === Pox2ContractIdentifer.testnet) - ) { + const pox2Event = events + .filter( + (e): e is SmartContractEvent => + e.type === CoreNodeEventType.ContractEvent && + e.contract_event.topic === 'print' && + (e.contract_event.contract_identifier === Pox2ContractIdentifer.mainnet || + e.contract_event.contract_identifier === Pox2ContractIdentifer.testnet) + ) + .map(e => { const network = chainId === ChainID.Mainnet ? 'mainnet' : 'testnet'; const decodedEvent = decodePox2PrintEvent(e.contract_event.raw_value, network); if (decodedEvent) { @@ -378,20 +390,24 @@ export function parseMessageTransaction( decodedEvent, }; } - } - return null; - })[0]; + }) + .find(e => !!e); if (stxTransferEvent) { rawTx = createTransactionFromCoreBtcTxEvent(chainId, stxTransferEvent, coreTx.txid); txSender = stxTransferEvent.stx_transfer_event.sender; } else if (stxLockEvent) { + const stxStacksPox2Event = + pox2Event?.decodedEvent.name === Pox2EventName.StackStx + ? pox2Event.decodedEvent + : undefined; rawTx = createTransactionFromCoreBtcStxLockEvent( chainId, stxLockEvent, blockData.burn_block_height, coreTx.raw_result, - coreTx.txid + coreTx.txid, + stxStacksPox2Event ); txSender = stxLockEvent.stx_lock_event.locked_address; } else if (pox2Event && pox2Event.decodedEvent.name === Pox2EventName.DelegateStx) { diff --git a/src/tests-2.1/pox-2-burnchain-stack-stx.ts b/src/tests-2.1/pox-2-burnchain-stack-stx.ts index 846e16166b..3c5b3a2915 100644 --- a/src/tests-2.1/pox-2-burnchain-stack-stx.ts +++ b/src/tests-2.1/pox-2-burnchain-stack-stx.ts @@ -31,6 +31,7 @@ import { RPCClient } from 'rpc-bitcoin'; import * as supertest from 'supertest'; import { Pox2ContractIdentifer } from '../pox-helpers'; import { ClarityValueUInt, decodeClarityValue } from 'stacks-encoding-native-js'; +import { decodeBtcAddress } from '@stacks/stacking'; // Perform Stack-STX operation on Bitcoin. // See https://github.com/stacksgov/sips/blob/0da29c6911c49c45e4125dbeaed58069854591eb/sips/sip-007/sip-007-stacking-consensus.md#stx-operations-on-bitcoin @@ -39,6 +40,7 @@ async function createPox2StackStx(args: { cycleCount: number; stackerAddress: string; bitcoinWif: string; + poxAddrPayout: string; }) { const btcAccount = ECPair.fromWIF(args.bitcoinWif, btc.networks.regtest); const feeAmount = 0.0001; @@ -116,7 +118,7 @@ async function createPox2StackStx(args: { }) // The second Bitcoin output will be used as the reward address for any stacking rewards. .addOutput({ - address: c32ToB58(args.stackerAddress), + address: args.poxAddrPayout, value: Math.round(outAmount1 - feeAmount * sats), }) .signInput(0, btcAccount) @@ -145,6 +147,11 @@ describe('PoX-2 - Stack using Bitcoin-chain ops', () => { const accountKey = '72e8e3725324514c38c2931ed337ab9ab8d8abaae83ed2275456790194b1fd3101'; let account: Account; + // testnet btc addr: tb1pf4x64urhdsdmadxxhv2wwjv6e3evy59auu2xaauu3vz3adxtskfschm453 + // regtest btc addr: bcrt1pf4x64urhdsdmadxxhv2wwjv6e3evy59auu2xaauu3vz3adxtskfs4w3npt + const poxAddrPayoutKey = 'c71700b07d520a8c9731e4d0f095aa6efb91e16e25fb27ce2b72e7b698f8127a01'; + let poxAddrPayoutAccount: Account; + let testAccountBalance: bigint; const testAccountBtcBalance = 5; let testStackAmount: bigint; @@ -159,6 +166,7 @@ describe('PoX-2 - Stack using Bitcoin-chain ops', () => { ({ db, api, client, stacksNetwork, bitcoinRpcClient } = testEnv); account = accountFromKey(accountKey); + poxAddrPayoutAccount = accountFromKey(poxAddrPayoutKey, 'p2tr'); const poxInfo = await client.getPox(); const [contractAddress, contractName] = poxInfo.contract_id.split('.'); @@ -246,6 +254,7 @@ describe('PoX-2 - Stack using Bitcoin-chain ops', () => { stxOpBtcTxs = await createPox2StackStx({ bitcoinWif: account.wif, stackerAddress: account.stxAddr, + poxAddrPayout: poxAddrPayoutAccount.btcAddr, stxAmount: testStackAmount, cycleCount: 6, }); @@ -298,11 +307,16 @@ describe('PoX-2 - Stack using Bitcoin-chain ops', () => { expect(callArg1.name).toBe('amount-ustx'); expect(BigInt(decodeClarityValue(callArg1.hex).value)).toBe(testStackAmount); + const expectedPoxPayoutAddr = decodeBtcAddress(poxAddrPayoutAccount.btcTestnetAddr); + const expectedPoxPayoutAddrRepr = `(tuple (hashbytes 0x${Buffer.from( + expectedPoxPayoutAddr.data + ).toString('hex')}) (version 0x${Buffer.from([expectedPoxPayoutAddr.version]).toString( + 'hex' + )}))`; const callArg2 = txObj.contract_call.function_args![1]; expect(callArg2.name).toBe('pox-addr'); - const callArg2Addr = decodePoxAddrArg(callArg2.hex); - expect(callArg2Addr.stxAddr).toBe(account.stxAddr); - expect(callArg2Addr.btcAddr).toBe(account.btcAddr); + expect(callArg2.type).toBe('(tuple (hashbytes (buff 32)) (version (buff 1)))'); + expect(callArg2.repr).toBe(expectedPoxPayoutAddrRepr); }); // TODO: this is very flaky