Skip to content

Commit

Permalink
fix(ref-imp): better transactions API behavior when bitcoin service i…
Browse files Browse the repository at this point in the history
…s in forked state
  • Loading branch information
thehenrytsai committed Mar 19, 2021
1 parent 5d0a14e commit c3940ad
Show file tree
Hide file tree
Showing 3 changed files with 23 additions and 26 deletions.
18 changes: 11 additions & 7 deletions lib/bitcoin/BitcoinProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,17 @@ export default class BitcoinProcessor {
};
}

// NOTE: this conditional block is technically an optional optimization,
// but it is a useful one especially when Bitcoin service's observing loop wait period is longer than that of the Core service's observing loop:
// This prevents Core from repeatedly reverting its DB after detecting a fork then repopulating its DB with forked/invalid data again.
if (!await this.verifyBlock(lastProcessedBlock.height, lastProcessedBlock.hash)) {
Logger.info('Bitcoin service in a forked state, not returning transactions until the DB is reverted to correct chain.');
return {
moreTransactions: false,
transactions: []
};
}

const [transactions, lastBlockSeen] = await this.getTransactionsSince(since, lastProcessedBlock.height);

// Add normalizedFee to transactions because internal to bitcoin, normalizedFee live in blockMetadata and have to be joined by block height
Expand All @@ -472,13 +483,6 @@ export default class BitcoinProcessor {
}
}

// make sure the last processed block hasn't changed since before getting transactions
// if changed, then a block reorg happened.
if (!await this.verifyBlock(lastProcessedBlock.height, lastProcessedBlock.hash)) {
Logger.info('Requested transactions hash mismatched blockchain');
throw new RequestError(ResponseStatus.BadRequest, SharedErrorCode.InvalidTransactionNumberOrTimeHash);
}

// if last processed block has not been seen, then there are more transactions
const moreTransactions = lastBlockSeen < lastProcessedBlock.height;

Expand Down
2 changes: 2 additions & 0 deletions lib/core/Observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ export default class Observer {
* Waits until all unresolvable transactions due for retry are processed.
*/
private async processUnresolvableTransactions () {
Logger.info(`Processing previously unresolvable transactions if any...`);

const endTimer = timeSpan();
const unresolvableTransactions = await this.unresolvableTransactionStore.getUnresolvableTransactionsDueForRetry();
Logger.info(`Fetched ${unresolvableTransactions.length} unresolvable transactions to retry in ${endTimer.rounded()} ms.`);
Expand Down
29 changes: 10 additions & 19 deletions tests/bitcoin/BitcoinProcessor.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -646,30 +646,21 @@ describe('BitcoinProcessor', () => {
}
});

it('should fail if verifyBlock fails after transactionStore query', async (done) => {
it('should return no transactions if bitcoin service is in a forked state.', async () => {
const expectedHeight = randomNumber();
const expectedHash = randomString();
const expectedTransactionNumber = TransactionNumber.construct(expectedHeight, 0);

blockMetadataStoreGetLastSpy.and.returnValues(Promise.resolve('unused'));

// The 2nd return value simulates a forked state in the bitcoin service.
const verifyMock = spyOn(bitcoinProcessor, 'verifyBlock' as any).and.returnValues(Promise.resolve(true), Promise.resolve(false));
const getTransactionsSinceMock = spyOn(bitcoinProcessor, 'getTransactionsSince' as any).and.returnValue([[], 0]);
const mockLastProcessedBlock = {
height: 1234,
hash: 'some hash',
previousHash: 'previous hash'
};
blockMetadataStoreGetLastSpy.and.returnValue(Promise.resolve(mockLastProcessedBlock));

try {
await bitcoinProcessor.transactions(expectedTransactionNumber, expectedHash);
fail('expected to throw');
} catch (error) {
expect(error.status).toEqual(httpStatus.BAD_REQUEST);
expect(error.code).toEqual(SharedErrorCode.InvalidTransactionNumberOrTimeHash);
expect(getTransactionsSinceMock).toHaveBeenCalled();
expect(verifyMock).toHaveBeenCalledTimes(2);
} finally {
done();
}
const response = await bitcoinProcessor.transactions(expectedTransactionNumber, expectedHash);
expect(blockMetadataStoreGetLastSpy).toHaveBeenCalledTimes(1);
expect(verifyMock).toHaveBeenCalledTimes(2);
expect(response.moreTransactions).toBeFalsy();
expect(response.transactions.length).toEqual(0);
});

it('should make moreTransactions true when last block processed is not reached', async (done) => {
Expand Down

0 comments on commit c3940ad

Please sign in to comment.