You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

128 lines
4.1 KiB

/*!
* pushtx/pushtx-rest-api.js
* Copyright © 2019 – Katana Cryptographic Ltd. All Rights Reserved.
*/
'use strict'
const bitcoin = require('bitcoinjs-lib')
const Logger = require('../lib/logger')
const errors = require('../lib/errors')
const db = require('../lib/db/mysql-db-wrapper')
const network = require('../lib/bitcoin/network')
const keys = require('../keys')[network.key]
const RpcClient = require('../lib/bitcoind-rpc/rpc-client')
const pushTxProcessor = require('./pushtx-processor')
/**
* A class scheduling delayed push of transactions
*/
class TransactionsScheduler {
/**
* Constructor
*/
constructor() {
this.rpcClient = new RpcClient()
}
/**
* Schedule a set of transactions
* according to a given sequential script
* @param {object} script - scheduling script
*/
async schedule(script) {
try {
// Check script length
if (script.length > keys.txsScheduler.maxNbEntries)
throw errors.body.SCRIPTSIZE
// Order transactions by increasing hop values and nlocktime
script.sort((a,b) => a.hop - b.hop || a.nlocktime - b.nlocktime)
// Get the height of last block seen
const info = await this.rpcClient.getBlockchainInfo()
const lastHeight = info.blocks
// Get the nLockTime associated to the first transaction
const nltTx0 = script[0].nlocktime
// Check that nltTx0 is in allowed range of blocks
if (nltTx0 > lastHeight + keys.txsScheduler.maxDeltaHeight)
throw errors.pushtx.SCHEDULED_TOO_FAR
// Compute base height for this script
const baseHeight = Math.max(lastHeight, nltTx0)
// Iterate over the transactions for a few validations
let lastHopProcessed = -1
let lastLockTimeProcessed = -1
for (let entry of script) {
// Compute delta height (entry.nlocktime - nltTx0)
entry.delta = entry.nlocktime - nltTx0
// Check that delta is in allowed range
if (entry.delta > keys.txsScheduler.maxDeltaHeight)
throw errors.pushtx.SCHEDULED_TOO_FAR
// Decode the transaction
const tx = bitcoin.Transaction.fromHex(entry.tx)
// Check that nlocktimes are matching
if (!(tx.locktime && tx.locktime == entry.nlocktime)) {
const msg = `TransactionsScheduler.schedule() : nLockTime mismatch : ${tx.locktime} - ${entry.nlocktime}`
Logger.error(null, msg)
throw errors.pushtx.NLOCK_MISMATCH
}
// Check that order of hop and nlocktime values are consistent
if (entry.hop != lastHopProcessed) {
if (entry.nlocktime < lastLockTimeProcessed)
throw errors.pushtx.SCHEDULED_BAD_ORDER
}
lastHopProcessed = entry.hop
lastLockTimeProcessed = entry.nlocktime
// Update scheduled height if needed
if (baseHeight != nltTx0)
entry.nlocktime = baseHeight + entry.delta
}
let parentTxid = null
let parentNlocktime = baseHeight
// Check if first transactions should be sent immediately
while ((script.length > 0) && (script[0].nlocktime <= lastHeight) && (script[0].delta == 0)) {
await pushTxProcessor.pushTx(script[0].tx)
const tx = bitcoin.Transaction.fromHex(script[0].tx)
parentTxid = tx.getId()
parentNlocktime = script[0].nlocktime
script.splice(0,1)
}
// Store others transactions in database
let parentId = null
for (let entry of script) {
const tx = bitcoin.Transaction.fromHex(entry.tx)
const objTx = {
txid: tx.getId(),
created: null,
rawTx: entry.tx,
parentId: parentId,
parentTxid: parentTxid,
delay: entry.nlocktime - parentNlocktime, // Store delay relative to previous transaction
trigger: entry.nlocktime
}
parentId = await db.addScheduledTransaction(objTx)
Logger.info(`Registered scheduled tx ${objTx.txid} (trigger=${objTx.trigger})`)
parentTxid = tx.getId()
parentNlocktime = entry.nlocktime
}
} catch(e) {
throw e
}
}
}
module.exports = TransactionsScheduler