mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api.git
synced 2026-09-21 16:52:00 -07:00
feat(rawtransactions): Adding full node raw transaction endpoints
This commit is contained in:
@@ -161,8 +161,7 @@ describe('#blockchain-controller.js', () => {
|
||||
it('should validate array size and call use case', async () => {
|
||||
const hash = 'a'.repeat(64)
|
||||
const req = createMockRequest({
|
||||
body: { hashes: [hash], verbose: true },
|
||||
locals: { proLimit: false }
|
||||
body: { hashes: [hash], verbose: true }
|
||||
})
|
||||
const res = createMockResponse()
|
||||
mockUseCases.blockchain.getBlockHeaders.resolves(['result'])
|
||||
@@ -172,7 +171,7 @@ describe('#blockchain-controller.js', () => {
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, ['result'])
|
||||
assert.isTrue(
|
||||
mockAdapters.fullNode.validateArraySize.calledOnceWithExactly(1, { isProUser: false })
|
||||
mockAdapters.fullNode.validateArraySize.calledOnceWithExactly(1)
|
||||
)
|
||||
assert.isTrue(
|
||||
mockUseCases.blockchain.getBlockHeaders.calledOnceWithExactly({
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
/*
|
||||
Unit tests for RawTransactionsRESTController.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import RawTransactionsRESTController from '../../../src/controllers/rest-api/full-node/rawtransactions/controller.js'
|
||||
import { createMockRequest, createMockResponse } from '../mocks/controller-mocks.js'
|
||||
|
||||
describe('#rawtransactions-controller.js', () => {
|
||||
let sandbox
|
||||
let mockAdapters
|
||||
let mockUseCases
|
||||
let uut
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
mockAdapters = {
|
||||
fullNode: {
|
||||
validateArraySize: sandbox.stub().returns(true)
|
||||
}
|
||||
}
|
||||
mockUseCases = {
|
||||
rawtransactions: {
|
||||
decodeRawTransaction: sandbox.stub().resolves({ txid: 'abc123' }),
|
||||
decodeRawTransactions: sandbox.stub().resolves([{ txid: 'abc123' }]),
|
||||
decodeScript: sandbox.stub().resolves({ asm: 'OP_DUP' }),
|
||||
decodeScripts: sandbox.stub().resolves([{ asm: 'OP_DUP' }]),
|
||||
getRawTransaction: sandbox.stub().resolves({ txid: 'abc123' }),
|
||||
getRawTransactionWithHeight: sandbox.stub().resolves({ txid: 'abc123', height: 100 }),
|
||||
getRawTransactions: sandbox.stub().resolves([{ txid: 'abc123' }]),
|
||||
sendRawTransaction: sandbox.stub().resolves('txid123'),
|
||||
sendRawTransactions: sandbox.stub().resolves(['txid1', 'txid2'])
|
||||
}
|
||||
}
|
||||
|
||||
uut = new RawTransactionsRESTController({
|
||||
adapters: mockAdapters,
|
||||
useCases: mockUseCases
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should require adapters', () => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new RawTransactionsRESTController({ useCases: mockUseCases })
|
||||
}, /Adapters library required/)
|
||||
})
|
||||
|
||||
it('should require rawtransactions use cases', () => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new RawTransactionsRESTController({ adapters: mockAdapters, useCases: {} })
|
||||
}, /RawTransactions use cases required/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#root()', () => {
|
||||
it('should return rawtransactions status', async () => {
|
||||
const req = createMockRequest()
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.root(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, { status: 'rawtransactions' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#decodeRawTransactionSingle()', () => {
|
||||
it('should return decoded transaction on success', async () => {
|
||||
const req = createMockRequest({ params: { hex: '01000000' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeRawTransactionSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, { txid: 'abc123' })
|
||||
assert.isTrue(mockUseCases.rawtransactions.decodeRawTransaction.calledOnce)
|
||||
assert.deepEqual(mockUseCases.rawtransactions.decodeRawTransaction.firstCall.args[0], { hex: '01000000' })
|
||||
})
|
||||
|
||||
it('should return 400 if hex is empty', async () => {
|
||||
const req = createMockRequest({ params: { hex: '' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeRawTransactionSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'hex can not be empty' })
|
||||
})
|
||||
|
||||
it('should handle errors via handleError', async () => {
|
||||
const error = new Error('RPC error')
|
||||
error.status = 500
|
||||
mockUseCases.rawtransactions.decodeRawTransaction.rejects(error)
|
||||
const req = createMockRequest({ params: { hex: '01000000' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeRawTransactionSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 500)
|
||||
assert.deepEqual(res.jsonData, { error: 'RPC error' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#decodeRawTransactionBulk()', () => {
|
||||
it('should return decoded transactions on success', async () => {
|
||||
const req = createMockRequest({ body: { hexes: ['hex1', 'hex2'] } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, [{ txid: 'abc123' }])
|
||||
assert.isTrue(mockUseCases.rawtransactions.decodeRawTransactions.calledOnce)
|
||||
})
|
||||
|
||||
it('should return 400 if hexes is not an array', async () => {
|
||||
const req = createMockRequest({ body: { hexes: 'not-array' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'hexes must be an array' })
|
||||
})
|
||||
|
||||
it('should return 400 if array is too large', async () => {
|
||||
mockAdapters.fullNode.validateArraySize.returns(false)
|
||||
const req = createMockRequest({ body: { hexes: new Array(25).fill('hex') } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'Array too large.' })
|
||||
})
|
||||
|
||||
it('should return 400 if empty hex encountered', async () => {
|
||||
const req = createMockRequest({ body: { hexes: ['hex1', '', 'hex2'] } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'Encountered empty hex' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#decodeScriptSingle()', () => {
|
||||
it('should return decoded script on success', async () => {
|
||||
const req = createMockRequest({ params: { hex: '76a914' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeScriptSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, { asm: 'OP_DUP' })
|
||||
assert.isTrue(mockUseCases.rawtransactions.decodeScript.calledOnce)
|
||||
})
|
||||
|
||||
it('should return 400 if hex is empty', async () => {
|
||||
const req = createMockRequest({ params: { hex: '' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeScriptSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'hex can not be empty' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#decodeScriptBulk()', () => {
|
||||
it('should return decoded scripts on success', async () => {
|
||||
const req = createMockRequest({ body: { hexes: ['script1', 'script2'] } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeScriptBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, [{ asm: 'OP_DUP' }])
|
||||
})
|
||||
|
||||
it('should return 400 if array is too large', async () => {
|
||||
mockAdapters.fullNode.validateArraySize.returns(false)
|
||||
const req = createMockRequest({ body: { hexes: new Array(25).fill('script') } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.decodeScriptBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'Array too large.' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getRawTransactionSingle()', () => {
|
||||
it('should return raw transaction on success', async () => {
|
||||
const req = createMockRequest({ params: { txid: 'a'.repeat(64) }, query: {} })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getRawTransactionSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, { txid: 'abc123', height: 100 })
|
||||
assert.isTrue(mockUseCases.rawtransactions.getRawTransactionWithHeight.calledOnce)
|
||||
})
|
||||
|
||||
it('should pass verbose=true when query param is set', async () => {
|
||||
const req = createMockRequest({ params: { txid: 'a'.repeat(64) }, query: { verbose: 'true' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getRawTransactionSingle(req, res)
|
||||
|
||||
assert.isTrue(mockUseCases.rawtransactions.getRawTransactionWithHeight.calledOnce)
|
||||
assert.deepEqual(mockUseCases.rawtransactions.getRawTransactionWithHeight.firstCall.args[0], {
|
||||
txid: 'a'.repeat(64),
|
||||
verbose: true
|
||||
})
|
||||
})
|
||||
|
||||
it('should return 400 if txid is empty', async () => {
|
||||
const req = createMockRequest({ params: { txid: '' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getRawTransactionSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'txid can not be empty' })
|
||||
})
|
||||
|
||||
it('should return 400 if txid length is not 64', async () => {
|
||||
const req = createMockRequest({ params: { txid: 'short' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getRawTransactionSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'parameter 1 must be of length 64 (not 5)' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getRawTransactionBulk()', () => {
|
||||
it('should return raw transactions on success', async () => {
|
||||
const req = createMockRequest({
|
||||
body: {
|
||||
txids: ['a'.repeat(64), 'b'.repeat(64)],
|
||||
verbose: true
|
||||
}
|
||||
})
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, [{ txid: 'abc123' }])
|
||||
assert.isTrue(mockUseCases.rawtransactions.getRawTransactions.calledOnce)
|
||||
assert.deepEqual(mockUseCases.rawtransactions.getRawTransactions.firstCall.args[0], {
|
||||
txids: ['a'.repeat(64), 'b'.repeat(64)],
|
||||
verbose: true
|
||||
})
|
||||
})
|
||||
|
||||
it('should return 400 if txids is not an array', async () => {
|
||||
const req = createMockRequest({ body: { txids: 'not-array' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'txids must be an array' })
|
||||
})
|
||||
|
||||
it('should return 400 if array is too large', async () => {
|
||||
mockAdapters.fullNode.validateArraySize.returns(false)
|
||||
const req = createMockRequest({ body: { txids: new Array(25).fill('a'.repeat(64)) } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'Array too large.' })
|
||||
})
|
||||
|
||||
it('should return 400 if empty txid encountered', async () => {
|
||||
const req = createMockRequest({ body: { txids: ['a'.repeat(64), ''] } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'Encountered empty TXID' })
|
||||
})
|
||||
|
||||
it('should return 400 if txid length is not 64', async () => {
|
||||
const req = createMockRequest({ body: { txids: ['short'] } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.getRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'parameter 1 must be of length 64 (not 5)' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#sendRawTransactionSingle()', () => {
|
||||
it('should return txid on success', async () => {
|
||||
const req = createMockRequest({ params: { hex: '01000000' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.sendRawTransactionSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.equal(res.jsonData, 'txid123')
|
||||
assert.isTrue(mockUseCases.rawtransactions.sendRawTransaction.calledOnce)
|
||||
})
|
||||
|
||||
it('should return 400 if hex is empty', async () => {
|
||||
const req = createMockRequest({ params: { hex: '' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.sendRawTransactionSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'Encountered empty hex' })
|
||||
})
|
||||
|
||||
it('should return 400 if hex is not a string', async () => {
|
||||
const req = createMockRequest({ params: { hex: 123 } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.sendRawTransactionSingle(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'hex must be a string' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#sendRawTransactionBulk()', () => {
|
||||
it('should return txids on success', async () => {
|
||||
const req = createMockRequest({ body: { hexes: ['hex1', 'hex2'] } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.sendRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 200)
|
||||
assert.deepEqual(res.jsonData, ['txid1', 'txid2'])
|
||||
assert.isTrue(mockUseCases.rawtransactions.sendRawTransactions.calledOnce)
|
||||
})
|
||||
|
||||
it('should return 400 if hexes is not an array', async () => {
|
||||
const req = createMockRequest({ body: { hexes: 'not-array' } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.sendRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'hex must be an array' })
|
||||
})
|
||||
|
||||
it('should return 400 if array is too large', async () => {
|
||||
mockAdapters.fullNode.validateArraySize.returns(false)
|
||||
const req = createMockRequest({ body: { hexes: new Array(25).fill('hex') } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.sendRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'Array too large.' })
|
||||
})
|
||||
|
||||
it('should return 400 if empty hex encountered', async () => {
|
||||
const req = createMockRequest({ body: { hexes: ['hex1', '', 'hex2'] } })
|
||||
const res = createMockResponse()
|
||||
|
||||
await uut.sendRawTransactionBulk(req, res)
|
||||
|
||||
assert.equal(res.statusValue, 400)
|
||||
assert.deepEqual(res.jsonData, { error: 'Encountered empty hex' })
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -10,6 +10,7 @@ import BlockchainRouter from '../../../src/controllers/rest-api/full-node/blockc
|
||||
import ControlRouter from '../../../src/controllers/rest-api/full-node/control/index.js'
|
||||
import DSProofRouter from '../../../src/controllers/rest-api/full-node/dsproof/index.js'
|
||||
import MiningRouter from '../../../src/controllers/rest-api/full-node/mining/index.js'
|
||||
import RawTransactionsRouter from '../../../src/controllers/rest-api/full-node/rawtransactions/index.js'
|
||||
|
||||
describe('#controllers/rest-api/index.js', () => {
|
||||
let sandbox
|
||||
@@ -56,6 +57,17 @@ describe('#controllers/rest-api/index.js', () => {
|
||||
mining: {
|
||||
getMiningInfo: () => {},
|
||||
getNetworkHashPS: () => {}
|
||||
},
|
||||
rawtransactions: {
|
||||
decodeRawTransaction: () => {},
|
||||
decodeRawTransactions: () => {},
|
||||
decodeScript: () => {},
|
||||
decodeScripts: () => {},
|
||||
getRawTransaction: () => {},
|
||||
getRawTransactionWithHeight: () => {},
|
||||
getRawTransactions: () => {},
|
||||
sendRawTransaction: () => {},
|
||||
sendRawTransactions: () => {}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -86,6 +98,7 @@ describe('#controllers/rest-api/index.js', () => {
|
||||
const controlAttachStub = sandbox.stub(ControlRouter.prototype, 'attach')
|
||||
const dsproofAttachStub = sandbox.stub(DSProofRouter.prototype, 'attach')
|
||||
const miningAttachStub = sandbox.stub(MiningRouter.prototype, 'attach')
|
||||
const rawtransactionsAttachStub = sandbox.stub(RawTransactionsRouter.prototype, 'attach')
|
||||
const restControllers = new RESTControllers({
|
||||
adapters: mockAdapters,
|
||||
useCases: mockUseCases
|
||||
@@ -102,6 +115,8 @@ describe('#controllers/rest-api/index.js', () => {
|
||||
assert.equal(dsproofAttachStub.getCall(0).args[0], app)
|
||||
assert.isTrue(miningAttachStub.calledOnce)
|
||||
assert.equal(miningAttachStub.getCall(0).args[0], app)
|
||||
assert.isTrue(rawtransactionsAttachStub.calledOnce)
|
||||
assert.equal(rawtransactionsAttachStub.getCall(0).args[0], app)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
Unit tests for RawTransactionsUseCases.
|
||||
*/
|
||||
|
||||
import { assert } from 'chai'
|
||||
|
||||
import RawTransactionsUseCases from '../../../src/use-cases/full-node-rawtransactions-use-cases.js'
|
||||
|
||||
describe('#full-node-rawtransactions-use-cases.js', () => {
|
||||
let mockAdapters
|
||||
let uut
|
||||
|
||||
beforeEach(() => {
|
||||
mockAdapters = {
|
||||
fullNode: {
|
||||
call: async () => ({})
|
||||
}
|
||||
}
|
||||
|
||||
uut = new RawTransactionsUseCases({ adapters: mockAdapters })
|
||||
})
|
||||
|
||||
describe('#constructor()', () => {
|
||||
it('should require adapters', () => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new RawTransactionsUseCases()
|
||||
}, /Adapters instance required/)
|
||||
})
|
||||
|
||||
it('should require full node adapter', () => {
|
||||
assert.throws(() => {
|
||||
// eslint-disable-next-line no-new
|
||||
new RawTransactionsUseCases({ adapters: {} })
|
||||
}, /Full node adapter required/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#decodeRawTransaction()', () => {
|
||||
it('should call full node adapter with correct method', async () => {
|
||||
let capturedMethod = ''
|
||||
let capturedParams = []
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
capturedMethod = method
|
||||
capturedParams = params
|
||||
return { txid: 'abc123', version: 2 }
|
||||
}
|
||||
|
||||
const result = await uut.decodeRawTransaction({ hex: '01000000' })
|
||||
|
||||
assert.equal(capturedMethod, 'decoderawtransaction')
|
||||
assert.deepEqual(capturedParams, ['01000000'])
|
||||
assert.deepEqual(result, { txid: 'abc123', version: 2 })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#decodeRawTransactions()', () => {
|
||||
it('should call full node adapter for each hex in parallel', async () => {
|
||||
const callCount = { count: 0 }
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
callCount.count++
|
||||
return { txid: `tx${callCount.count}`, hex: params[0] }
|
||||
}
|
||||
|
||||
const hexes = ['hex1', 'hex2', 'hex3']
|
||||
const result = await uut.decodeRawTransactions({ hexes })
|
||||
|
||||
assert.equal(callCount.count, 3)
|
||||
assert.equal(result.length, 3)
|
||||
assert.equal(result[0].txid, 'tx1')
|
||||
assert.equal(result[1].txid, 'tx2')
|
||||
assert.equal(result[2].txid, 'tx3')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#decodeScript()', () => {
|
||||
it('should call full node adapter with correct method', async () => {
|
||||
let capturedMethod = ''
|
||||
let capturedParams = []
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
capturedMethod = method
|
||||
capturedParams = params
|
||||
return { asm: 'OP_DUP OP_HASH160', type: 'pubkeyhash' }
|
||||
}
|
||||
|
||||
const result = await uut.decodeScript({ hex: '76a914' })
|
||||
|
||||
assert.equal(capturedMethod, 'decodescript')
|
||||
assert.deepEqual(capturedParams, ['76a914'])
|
||||
assert.deepEqual(result, { asm: 'OP_DUP OP_HASH160', type: 'pubkeyhash' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#decodeScripts()', () => {
|
||||
it('should call full node adapter for each hex in parallel', async () => {
|
||||
const callCount = { count: 0 }
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
callCount.count++
|
||||
return { asm: `script${callCount.count}`, hex: params[0] }
|
||||
}
|
||||
|
||||
const hexes = ['script1', 'script2']
|
||||
const result = await uut.decodeScripts({ hexes })
|
||||
|
||||
assert.equal(callCount.count, 2)
|
||||
assert.equal(result.length, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getRawTransaction()', () => {
|
||||
it('should call full node adapter with verbose=false by default', async () => {
|
||||
let capturedParams = []
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
capturedParams = params
|
||||
return '01000000'
|
||||
}
|
||||
|
||||
await uut.getRawTransaction({ txid: 'abc123' })
|
||||
|
||||
assert.deepEqual(capturedParams, ['abc123', 0])
|
||||
})
|
||||
|
||||
it('should call full node adapter with verbose=true when specified', async () => {
|
||||
let capturedParams = []
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
capturedParams = params
|
||||
return { txid: 'abc123', version: 2 }
|
||||
}
|
||||
|
||||
await uut.getRawTransaction({ txid: 'abc123', verbose: true })
|
||||
|
||||
assert.deepEqual(capturedParams, ['abc123', 1])
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getRawTransactions()', () => {
|
||||
it('should call full node adapter for each txid in parallel', async () => {
|
||||
const callCount = { count: 0 }
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
callCount.count++
|
||||
return { txid: params[0], version: 2 }
|
||||
}
|
||||
|
||||
const txids = ['tx1', 'tx2']
|
||||
const result = await uut.getRawTransactions({ txids, verbose: true })
|
||||
|
||||
assert.equal(callCount.count, 2)
|
||||
assert.equal(result.length, 2)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getRawTransactionWithHeight()', () => {
|
||||
it('should return transaction without height when verbose=false', async () => {
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
if (method === 'getrawtransaction') {
|
||||
return '01000000'
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
const result = await uut.getRawTransactionWithHeight({ txid: 'abc123', verbose: false })
|
||||
|
||||
assert.equal(result, '01000000')
|
||||
})
|
||||
|
||||
it('should fetch and append height when verbose=true and blockhash exists', async () => {
|
||||
let callCount = 0
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
callCount++
|
||||
if (method === 'getrawtransaction') {
|
||||
return { txid: 'abc123', blockhash: 'block123' }
|
||||
}
|
||||
if (method === 'getblockheader') {
|
||||
return { height: 100, hash: 'block123' }
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
const result = await uut.getRawTransactionWithHeight({ txid: 'abc123', verbose: true })
|
||||
|
||||
assert.equal(callCount, 2)
|
||||
assert.equal(result.height, 100)
|
||||
assert.equal(result.txid, 'abc123')
|
||||
})
|
||||
|
||||
it('should handle block header lookup failure gracefully', async () => {
|
||||
let callCount = 0
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
callCount++
|
||||
if (method === 'getrawtransaction') {
|
||||
return { txid: 'abc123', blockhash: 'block123' }
|
||||
}
|
||||
if (method === 'getblockheader') {
|
||||
throw new Error('Block not found')
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
const result = await uut.getRawTransactionWithHeight({ txid: 'abc123', verbose: true })
|
||||
|
||||
assert.equal(callCount, 2)
|
||||
assert.isNull(result.height)
|
||||
assert.equal(result.txid, 'abc123')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getBlockHeader()', () => {
|
||||
it('should call full node adapter with correct method', async () => {
|
||||
let capturedMethod = ''
|
||||
let capturedParams = []
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
capturedMethod = method
|
||||
capturedParams = params
|
||||
return { height: 100, hash: 'block123' }
|
||||
}
|
||||
|
||||
const result = await uut.getBlockHeader({ blockHash: 'block123', verbose: true })
|
||||
|
||||
assert.equal(capturedMethod, 'getblockheader')
|
||||
assert.deepEqual(capturedParams, ['block123', true])
|
||||
assert.deepEqual(result, { height: 100, hash: 'block123' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('#sendRawTransaction()', () => {
|
||||
it('should call full node adapter with correct method', async () => {
|
||||
let capturedMethod = ''
|
||||
let capturedParams = []
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
capturedMethod = method
|
||||
capturedParams = params
|
||||
return 'txid123'
|
||||
}
|
||||
|
||||
const result = await uut.sendRawTransaction({ hex: '01000000' })
|
||||
|
||||
assert.equal(capturedMethod, 'sendrawtransaction')
|
||||
assert.deepEqual(capturedParams, ['01000000'])
|
||||
assert.equal(result, 'txid123')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#sendRawTransactions()', () => {
|
||||
it('should send transactions serially, not in parallel', async () => {
|
||||
const callOrder = []
|
||||
mockAdapters.fullNode.call = async (method, params) => {
|
||||
callOrder.push(params[0])
|
||||
// Simulate some async work
|
||||
await new Promise(resolve => setTimeout(resolve, 10))
|
||||
return `txid-${params[0]}`
|
||||
}
|
||||
|
||||
const hexes = ['hex1', 'hex2', 'hex3']
|
||||
const startTime = Date.now()
|
||||
const result = await uut.sendRawTransactions({ hexes })
|
||||
const endTime = Date.now()
|
||||
|
||||
// Should take at least 30ms if serial (3 * 10ms)
|
||||
assert.isAtLeast(endTime - startTime, 25)
|
||||
assert.deepEqual(callOrder, ['hex1', 'hex2', 'hex3'])
|
||||
assert.equal(result.length, 3)
|
||||
assert.equal(result[0], 'txid-hex1')
|
||||
assert.equal(result[1], 'txid-hex2')
|
||||
assert.equal(result[2], 'txid-hex3')
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user