Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Block Reward Transaction Support (Coinbase Txs) #417

Merged
merged 4 commits into from
Nov 8, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Unreleased

- fixed: Improper handling of WebSocket message processing errors, causing sync-halting.
- fixed: Add support for processing coinbase inputs on block reward transactions.

## 3.4.3 (2024-10-15)

Expand Down
47 changes: 32 additions & 15 deletions src/common/utxobased/engine/UtxoEngineProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1257,43 +1257,60 @@ const processTransactionResponse = (
args: { txResponse: TransactionResponse }
): TransactionData => {
const { txResponse } = args
const inputs = txResponse.vin.map(vin => {

let outputTotalValue = '0'
const outputs: TransactionData['outputs'] = txResponse.vout.map(vout => {
const scriptPubkey =
// Note: Blockbook has empirically not sent a hex value as the
// scriptPubkey for vins. If we discover this to be changed for some
// cases, we may want to use the `hex` field as an optimization.
vout.hex ??
asMaybe(
raw => validScriptPubkeyFromAddress(raw),
'unknown'
)({
address: vin.addresses[0],
address: vout.addresses[0],
coin: common.pluginInfo.coinInfo.name
})
outputTotalValue = add(outputTotalValue, vout.value)
return {
txId: vin.txid,
outputIndex: vin.vout,
n: vin.n,
n: vout.n,
scriptPubkey,
sequence: vin.sequence,
amount: vin.value
amount: vout.value
}
})
const outputs = txResponse.vout.map(vout => {

const inputs: TransactionData['inputs'] = txResponse.vin.map(vin => {
// Handle coinbase input
if (!vin.isAddress) {
return {
amount: outputTotalValue,
n: 0,
outputIndex: 4294967295,
scriptPubkey: '', // Maybe there's a bogus scriptPubkey we can use here?
sequence: vin.sequence,
txId: '0000000000000000000000000000000000000000000000000000000000000000'
}
}

const scriptPubkey =
vout.hex ??
// Note: Blockbook has empirically not sent a hex value as the
// scriptPubkey for vins. If we discover this to be changed for some
// cases, we may want to use the `hex` field as an optimization.
asMaybe(
raw => validScriptPubkeyFromAddress(raw),
'unknown'
)({
address: vout.addresses[0],
address: vin.addresses[0],
coin: common.pluginInfo.coinInfo.name
})
return {
n: vout.n,
txId: vin.txid,
outputIndex: vin.vout,
n: vin.n,
scriptPubkey,
amount: vout.value
sequence: vin.sequence,
amount: vin.value
}
})

return {
txid: txResponse.txid,
hex: txResponse.hex,
Expand Down
76 changes: 50 additions & 26 deletions src/common/utxobased/network/blockbookApi.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import {
asArray,
asBoolean,
asEither,
asMaybe,
asNumber,
asObject,
asOptional,
asString,
asUnknown,
asValue,
Cleaner,
uncleaner
} from 'cleaners'
Expand Down Expand Up @@ -57,16 +59,24 @@ export interface BlockbookTransaction {
confirmations: number
blockTime: number
fees: string
vin: Array<{
txid: string
sequence: number
n: number
vout: number
addresses: string[]
isAddress: boolean
value: string
hex?: string
}>
vin: Array<
| {
addresses: string[]
hex?: string
isAddress: true
n: number
sequence: number
txid: string
value: string
vout: number
}
| {
coinbase: string
isAddress: false
n: number
sequence: number
}
>
vout: Array<{
n: number
value: string
Expand All @@ -85,22 +95,36 @@ export const asBlockbookTransaction = (
blockTime: asNumber,
fees: asString,
vin: asArray(
asObject({
txid: asString,
// Empirically observed omitted sequence is possible for when sequence is zero.
// Is the case for tx `19ecc679cfc7e71ad616a22bbee96fd5abe8616e4f408f1f5daaf137400ae091`.
sequence: asOptional(asNumber, 0),
n: asNumber,
// If Blockbook doesn't provide vout, assume 0. Empirically observed
// case for tx `fefac8c22ba1178df5d7c90b78cc1c203d1a9f5f5506f7b8f6f469fa821c2674`
// which has no `vout` for input in WebSocket response payload but block
// will show the input's vout value to be `0`.
vout: asOptional(asNumber, 0),
addresses: asArray(asAddress),
isAddress: asBoolean,
value: asString,
hex: asOptional(asString)
})
asEither(
// Address input:
asObject({
Comment on lines +98 to +100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optional: This cleaner is getting pretty deep. It might be easier to read if we define asAddressVin and asCoinbaseVin types on the outside, and then do asEither(asAddressVin, asCoinbaseVin) in here.

addresses: asArray(asAddress),
// `isAddress` is a boolean flag that indicates whether the input is an address.
// And therefore has `addresses` field. If `isAddress` is false, then the input is likely a coinbase input.
isAddress: asValue(true),
// This is the index of the input. Not to be confused with the index of the previous output (vout).
n: asNumber,
// Empirically observed omitted sequence is possible for when sequence is zero.
// Is the case for tx `19ecc679cfc7e71ad616a22bbee96fd5abe8616e4f408f1f5daaf137400ae091`.
sequence: asOptional(asNumber, 0),
txid: asString,
value: asString,
// If Blockbook doesn't provide vout, assume 0. Empirically observed
// case for tx `fefac8c22ba1178df5d7c90b78cc1c203d1a9f5f5506f7b8f6f469fa821c2674`
// which has no `vout` for input in WebSocket response payload but block
// will show the input's vout value to be `0`.
vout: asOptional(asNumber, 0),
hex: asOptional(asString)
}),
// Coinbase input:
asObject({
// Coinbase input is a string of hex data (see example c6e617656b7b6d9fdcf8800fb5370479e5aceea4b6fe2fd74bd7bb0f3f2c64db)
coinbase: asString,
isAddress: asValue(false),
n: asNumber,
sequence: asNumber
})
)
),
vout: asArray(
asObject({
Expand Down
28 changes: 25 additions & 3 deletions test/common/utxobased/network/Blockbook.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,14 +272,16 @@ describe('Blockbook', function () {
})

describe('fetchTransaction', function () {
const satoshiHash =
'3ed86f1b0a0a6fe180195bc1f93fd9d0801aea8c8ad5018de82c026dc21e2b15'
it('should fetch details from a transaction hash', async function () {
const satoshiHash =
'3ed86f1b0a0a6fe180195bc1f93fd9d0801aea8c8ad5018de82c026dc21e2b15'
const tx = await blockbook.fetchTransaction(satoshiHash)

tx.txid.should.equal(satoshiHash)
tx.fees.should.equal('226')
tx.blockHeight.should.equal(651329)
tx.vin[0].isAddress.should.equal(true)
if (!tx.vin[0].isAddress) throw new Error('Type assertion failed')
tx.vin[0].value.should.equal('97373')
tx.vin[0].txid.should.equal(
'fac5994d454817db2daec796cfa79cce670a372e7505fdef2a259289d5df0814'
Expand All @@ -289,12 +291,32 @@ describe('Blockbook', function () {
tx.vin[0].addresses.should.eqls([
'bc1qg6lwu6c8yqhhw7rrq69akknepxcft09agkkuqv'
])
tx.vin[0].isAddress.should.equal(true)
tx.vin[0].value.should.equal('97373')

tx.vout[1].value.should.equal('95000')
tx.should.have.property('confirmations')
tx.should.have.property('blockTime')
})

it('should fetch details from a coinbase transaction hash', async function () {
const blockRewardTxid =
'c6e617656b7b6d9fdcf8800fb5370479e5aceea4b6fe2fd74bd7bb0f3f2c64db'
const tx = await blockbook.fetchTransaction(blockRewardTxid)

tx.txid.should.equal(blockRewardTxid)
tx.fees.should.equal('0')
tx.blockHeight.should.equal(868078)
tx.vin[0].isAddress.should.equal(false)
if (tx.vin[0].isAddress) throw new Error('Type assertion failed')
tx.vin[0].coinbase.should.equal(
'03ee3e0d1e3c204f4345414e2e58595a203e0f456c656b74726f6e20456e657267790007155cb4e4ff33420eb387b0ccf1ee1567020000000000'
)
expect(tx.vin[0].sequence).to.equal(4294967295)
tx.vin[0].n.should.equal(0)

tx.vout[1].value.should.equal('135549329')
tx.should.have.property('confirmations')
tx.should.have.property('blockTime')
})
})
})
Loading