mirror of
https://github.com/Permissionless-Software-Foundation/psf-memo.git
synced 2026-09-21 16:52:01 -07:00
Fix txid wire encoding and add txid repair utility
Client likes, replies, poll options, and poll votes now embed the referenced txid in little-endian wire order, matching the indexer's byte reversal. Add a psf-memo-db repair library and CLI that rewrites byte-reversed references in likes, postParents, pollOptions, and pollVotes and rebuilds the postLikes and postChildren indexes, leaving correct and unknown references untouched and staying idempotent. By coder.
This commit is contained in:
@@ -11,7 +11,11 @@
|
||||
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const { hexToBytes, buildTxidTextPayload } = require('../../src/services/hex')
|
||||
const { hexToBytes, buildTxidTextPayload, txidToWireBytes } = require('../../src/services/hex')
|
||||
|
||||
// A non-palindromic txid so a missing byte reversal is observable.
|
||||
const DISPLAY_TXID = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
||||
const WIRE_HEX = 'efcdab8967452301efcdab8967452301efcdab8967452301efcdab8967452301'
|
||||
|
||||
test('hexToBytes decodes exactly 64 hex characters into 32 bytes', () => {
|
||||
const bytes = hexToBytes('ab'.repeat(32))
|
||||
@@ -61,3 +65,25 @@ test('buildTxidTextPayload prefixes the raw txid bytes', () => {
|
||||
assert.equal(buf[0], 0xab)
|
||||
assert.equal(buf.slice(32).toString('utf8'), 'hi')
|
||||
})
|
||||
|
||||
test('txidToWireBytes reverses the display txid into little-endian wire order', () => {
|
||||
const bytes = txidToWireBytes(DISPLAY_TXID)
|
||||
|
||||
assert.ok(bytes instanceof Uint8Array)
|
||||
assert.equal(Buffer.from(bytes).toString('hex'), WIRE_HEX)
|
||||
})
|
||||
|
||||
test('txidToWireBytes rejects an invalid txid', () => {
|
||||
assert.throws(
|
||||
() => txidToWireBytes('zz'.repeat(32)),
|
||||
/valid hex string/
|
||||
)
|
||||
})
|
||||
|
||||
test('buildTxidTextPayload embeds the txid in little-endian wire order', () => {
|
||||
const raw = buildTxidTextPayload(DISPLAY_TXID, 'hi')
|
||||
const buf = Buffer.from(raw)
|
||||
|
||||
assert.equal(buf.slice(0, 32).toString('hex'), WIRE_HEX)
|
||||
assert.equal(buf.slice(32).toString('utf8'), 'hi')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
Unit tests for the Memo like wire encoding.
|
||||
|
||||
A like action embeds the liked post's transaction txid as 32 raw bytes in
|
||||
little-endian wire order: the reverse of the 64-character display txid. The
|
||||
indexer reverses those bytes back into display order, so the client must
|
||||
reverse before embedding.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const MemoLike = require('../../src/services/memo-like')
|
||||
|
||||
const MY_ADDRESS = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
||||
const POST_TXID = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
||||
const WIRE_HEX = 'efcdab8967452301efcdab8967452301efcdab8967452301efcdab8967452301'
|
||||
|
||||
function makeWallet () {
|
||||
return {
|
||||
walletInfo: { cashAddress: MY_ADDRESS },
|
||||
utxos: [{ txid: 'utxo', value: 100000 }],
|
||||
broadcasts: [],
|
||||
async getUtxos () {
|
||||
return this.utxos
|
||||
},
|
||||
async sendOpReturn (msg, prefix, bchOutput = []) {
|
||||
this.broadcasts.push({ msg, prefix, bchOutput })
|
||||
return 'aa'.repeat(32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('like broadcasts the post txid in little-endian wire order', async () => {
|
||||
const wallet = makeWallet()
|
||||
const memoLike = new MemoLike({ wallet })
|
||||
|
||||
await memoLike.like(POST_TXID)
|
||||
|
||||
assert.equal(wallet.broadcasts.length, 1)
|
||||
assert.equal(wallet.broadcasts[0].prefix, MemoLike.MEMO_LIKE_PREFIX)
|
||||
const raw = wallet.broadcasts[0].msg
|
||||
assert.equal(Buffer.from(raw).toString('hex'), WIRE_HEX)
|
||||
})
|
||||
|
||||
test('like reflects the post txid in display order on the feed store', async () => {
|
||||
const wallet = makeWallet()
|
||||
const added = []
|
||||
const feed = {
|
||||
addLike (like) {
|
||||
added.push(like)
|
||||
},
|
||||
posts: []
|
||||
}
|
||||
const memoLike = new MemoLike({ wallet, feed })
|
||||
|
||||
await memoLike.like(POST_TXID)
|
||||
|
||||
assert.equal(added.length, 1)
|
||||
assert.equal(added[0].postTxid, POST_TXID)
|
||||
})
|
||||
@@ -13,7 +13,8 @@ const assert = require('node:assert/strict')
|
||||
const MemoPollOption = require('../../src/services/memo-poll-option')
|
||||
|
||||
const MY_ADDRESS = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
||||
const POLL_TXID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
// A non-palindromic txid so a missing byte reversal is observable.
|
||||
const POLL_TXID = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
||||
|
||||
function makeWallet (address = MY_ADDRESS) {
|
||||
return {
|
||||
|
||||
@@ -13,7 +13,8 @@ const assert = require('node:assert/strict')
|
||||
const MemoPollVote = require('../../src/services/memo-poll-vote')
|
||||
|
||||
const MY_ADDRESS = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
||||
const POLL_TXID = 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
|
||||
// A non-palindromic txid so a missing byte reversal is observable.
|
||||
const POLL_TXID = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
||||
|
||||
function makeWallet (address = MY_ADDRESS) {
|
||||
return {
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
Unit tests for the Memo reply wire encoding.
|
||||
|
||||
A reply action embeds the parent transaction txid as 32 raw bytes in
|
||||
little-endian wire order: the reverse of the 64-character display txid. The
|
||||
indexer reverses those bytes back into display order, so the client must
|
||||
reverse before embedding.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const MemoReply = require('../../src/services/memo-reply')
|
||||
|
||||
const MY_ADDRESS = 'bitcoincash:qqlrzp23w08434twmvr4fxw672whkjy0py26r63g3d'
|
||||
const PARENT_TXID = '0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef'
|
||||
const WIRE_HEX = 'efcdab8967452301efcdab8967452301efcdab8967452301efcdab8967452301'
|
||||
|
||||
function makeWallet () {
|
||||
return {
|
||||
walletInfo: { cashAddress: MY_ADDRESS },
|
||||
broadcasts: [],
|
||||
async getUtxos () {
|
||||
return []
|
||||
},
|
||||
async sendOpReturn (msg, prefix) {
|
||||
this.broadcasts.push({ msg, prefix })
|
||||
return 'aa'.repeat(32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test('reply broadcasts the parent txid in little-endian wire order', async () => {
|
||||
const wallet = makeWallet()
|
||||
const memoReply = new MemoReply({ wallet })
|
||||
|
||||
await memoReply.reply('hello memo', PARENT_TXID)
|
||||
|
||||
assert.equal(wallet.broadcasts.length, 1)
|
||||
assert.equal(wallet.broadcasts[0].prefix, MemoReply.MEMO_REPLY_PREFIX)
|
||||
const buf = Buffer.from(wallet.broadcasts[0].msg)
|
||||
assert.equal(buf.slice(0, 32).toString('hex'), WIRE_HEX)
|
||||
assert.equal(buf.slice(32).toString('utf8'), 'hello memo')
|
||||
})
|
||||
|
||||
test('reply reflects the parent txid in display order on the thread store', async () => {
|
||||
const wallet = makeWallet()
|
||||
const added = []
|
||||
const thread = {
|
||||
addReply (reply) {
|
||||
added.push(reply)
|
||||
}
|
||||
}
|
||||
const memoReply = new MemoReply({ wallet, thread })
|
||||
|
||||
await memoReply.reply('hello memo', PARENT_TXID)
|
||||
|
||||
assert.equal(added.length, 1)
|
||||
assert.equal(added[0].parentTxid, PARENT_TXID)
|
||||
})
|
||||
Reference in New Issue
Block a user