Bug & typo fixes. Smoother tx arrivals.

This commit is contained in:
Mononaut 2021-11-26 16:11:42 -06:00
parent 03c583bf59
commit f5ed1ad1d3
8 changed files with 31 additions and 16 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "bitfeed-client", "name": "bitfeed-client",
"version": "1.9.2", "version": "2.0.0",
"scripts": { "scripts": {
"build": "rollup -c", "build": "rollup -c",
"dev": "rollup -c -w", "dev": "rollup -c -w",

View File

@ -45,7 +45,7 @@ function onClose () {
Bitfeed attempts to visualise this flow of information. Bitfeed attempts to visualise this flow of information.
</p> </p>
<p> <p>
As new transactions are recieved by our nodes, they drop into the mempool to await confirmation. As new transactions are received by our nodes, they drop into the mempool to await confirmation.
</p> </p>
<p> <p>
In <i>value mode</i>, squares representing transactions in the mempool are sized according to total output value, In <i>value mode</i>, squares representing transactions in the mempool are sized according to total output value,

View File

@ -332,7 +332,7 @@ async function generateInvoice () {
<div class="qr-container" class:paid={invoicePaid} class:expired={invoiceExpired}> <div class="qr-container" class:paid={invoicePaid} class:expired={invoiceExpired}>
{#if invoicePaid } {#if invoicePaid }
<div class="invoice-icon"><Icon icon={tickIcon} color="white" /></div> <div class="invoice-icon"><Icon icon={tickIcon} color="white" /></div>
<h3 class="invoice-status">Recieved, Thanks!</h3> <h3 class="invoice-status">Received, Thanks!</h3>
{:else if invoiceExpired} {:else if invoiceExpired}
<div class="invoice-icon"><Icon icon={timerIcon} color="white" /></div> <div class="invoice-icon"><Icon icon={timerIcon} color="white" /></div>
<h3 class="invoice-status">Request Expired</h3> <h3 class="invoice-status">Request Expired</h3>

View File

@ -22,7 +22,7 @@ let settingConfig = {
label: 'Donation Info' label: 'Donation Info'
}, },
vbytes: { vbytes: {
label: 'Tx Size', label: 'Size by',
type: 'pill', type: 'pill',
falseLabel: 'value', falseLabel: 'value',
trueLabel: 'vbytes' trueLabel: 'vbytes'

View File

@ -88,11 +88,11 @@ function formatBTC (sats) {
<p class="field hash"> <p class="field hash">
TxID: { tx.id } TxID: { tx.id }
</p> </p>
{#if tx.inputs && !tx.coinbase }<p class="field inputs">{ tx.inputs.length } inputs</p> {#if tx.inputs && !tx.coinbase }<p class="field inputs">{ tx.inputs.length } input{#if tx.inputs.length != 1}s{/if}</p>
{:else if tx.coinbase } {:else if tx.coinbase }
<p class="field coinbase">Coinbase: { tx.coinbase.sigAscii }</p> <p class="field coinbase">Coinbase: { tx.coinbase.sigAscii }</p>
{/if} {/if}
{#if tx.outputs }<p class="field outputs">{ tx.outputs.length } outputs</p>{/if} {#if tx.outputs }<p class="field outputs">{ tx.outputs.length } output{#if tx.outputs.length != 1}s{/if}</p>{/if}
<p class="field vbytes">{ integerFormat.format(tx.vbytes) } vbytes</p> <p class="field vbytes">{ integerFormat.format(tx.vbytes) } vbytes</p>
<p class="field value"> <p class="field value">
Total value: { formatBTC(tx.value) } Total value: { formatBTC(tx.value) }

View File

@ -6,8 +6,9 @@ export default {
layoutHints: false, layoutHints: false,
fps: true, fps: true,
websocket_path: '/ws/txs', websocket_path: '/ws/txs',
localSocket: false,
nofeed: false, nofeed: false,
txDelay: 5000, txDelay: 10000,
blockTimeout: 10000, blockTimeout: 10000,
donationAddress: "bc1qthanksv78zs5jnmysvmuuuzj09aklf8jmm49xl", donationAddress: "bc1qthanksv78zs5jnmysvmuuuzj09aklf8jmm49xl",
donationHash: "5dfb3b419e38a1494f648337ce7052797b6fa4f2", donationHash: "5dfb3b419e38a1494f648337ce7052797b6fa4f2",

View File

@ -70,9 +70,12 @@ export default class TxController {
} }
// Dual-strategy queue processing: // Dual-strategy queue processing:
// - ensure transactions are queued for at least 2s // - ensure transactions are queued for at least txDelay
// - while queue is small, use jittered timeouts to smooth arrivals // - when queue length exceeds 500, process iteratively to avoid unbounded growth
// - when queue length exceeds 100, process iteratively to avoid unbounded growth // - while queue is small, use jittered timeouts to evenly distribute arrivals
//
// transactions tend to arrive in groups, so for smoothest
// animation the queue should stay short but never empty.
processQueue () { processQueue () {
let done let done
let delay let delay
@ -86,7 +89,11 @@ export default class TxController {
} else { } else {
const timeSince = performance.now() - this.pendingTxs[0][1] const timeSince = performance.now() - this.pendingTxs[0][1]
if (timeSince > this.txDelay) { if (timeSince > this.txDelay) {
if (this.txDelay < this.maxTxDelay) this.txDelay += 10 //process the next tx in the queue, if it arrived longer ago than txDelay
if (this.txDelay < this.maxTxDelay) {
// slowly ramp up from 0 to maxTxDelay on start, so there's no wait for the first txs on page load
this.txDelay += 50
}
const tx = this.pendingTxs.shift()[0] const tx = this.pendingTxs.shift()[0]
delete this.pendingMap[tx.id] delete this.pendingMap[tx.id]
txQueueLength.decrement() txQueueLength.decrement()
@ -94,16 +101,23 @@ export default class TxController {
this.poolScene.insert(this.txs[tx.id]) this.poolScene.insert(this.txs[tx.id])
mempoolCount.increment() mempoolCount.increment()
} else { } else {
// end the loop when the head of the queue arrived more recently than txDelay
done = true done = true
delay = 2001 - timeSince // schedule to continue processing when head of queue matures
delay = this.txDelay - timeSince
} }
if (!done && this.pendingTxs.length < 100) { if (this.pendingTxs.length < 500) {
// or the queue is under 500
done = true done = true
} }
// otherwise keep processing the queue
} }
} else done = true } else done = true
} }
this.scheduleQueue(delay || (Math.random() * Math.max(1, (250 - this.pendingTxs.length)))) // randomly jitter arrival times so that txs enter more naturally
// with jittered delay inversely proportional to size of queue
let jitter = Math.random() * Math.max(1, (500 - this.pendingTxs.length))
this.scheduleQueue(delay || jitter)
} }
scheduleQueue (delay) { scheduleQueue (delay) {

View File

@ -54,7 +54,7 @@ class TxStream {
if (this.reconnectBackoff) clearTimeout(this.reconnectBackoff) if (this.reconnectBackoff) clearTimeout(this.reconnectBackoff)
if (!this.connected) { if (!this.connected) {
console.log('......trying to reconnect websocket') console.log('......trying to reconnect websocket')
if (this.reconnectBackoff < 4000) this.reconnectBackoff *= 2 if (this.reconnectBackoff < 8000) this.reconnectBackoff *= 2
this.reconnectTimeout = setTimeout(() => { this.init() }, this.reconnectBackoff) this.reconnectTimeout = setTimeout(() => { this.init() }, this.reconnectBackoff)
} }
} }
@ -130,7 +130,7 @@ class TxStream {
} else if (msg && msg.type === 'txn') { } else if (msg && msg.type === 'txn') {
window.dispatchEvent(new CustomEvent('bitcoin_tx', { detail: msg.txn })) window.dispatchEvent(new CustomEvent('bitcoin_tx', { detail: msg.txn }))
} else if (msg && msg.type === 'block') { } else if (msg && msg.type === 'block') {
// console.log('Block recieved: ', msg.block) // console.log('Block received: ', msg.block)
window.dispatchEvent(new CustomEvent('bitcoin_block', { detail: msg.block })) window.dispatchEvent(new CustomEvent('bitcoin_block', { detail: msg.block }))
} else { } else {
// console.log('unknown message from websocket: ', msg) // console.log('unknown message from websocket: ', msg)