Inconsistent tx_info response

Hi all,

While testing my app built on top of AE, I noticed something weird today. I have some sort of middleware implemented in nodejs which broadcasts transaction to node, and then polls for transaction status. After status is resolved (mined/failed) middleware will cache tx details in a regular psql database. So then it happened today that my middleware queried testnet for transaction with hash


and in logs I saw following tx info response:

2020-09-09T13:54:33.519Z info [18b70cef-2256-4ca5-94e6-f2cc851eae58]: [service/transaction-processor.js] Fetched tx info 
{ callerId: 'ak_Mmho6EMjk7xrPa7K1pCWtqHmiUc7x1jxh4oZwuqAN5vvF7aM4',
  callerNonce: 1,
  contractId: 'ct_2NfX6HyJHN9xHsbwtXaV965wmbxYd64cx9ocVK1gXMELZYLnYA',
  gasPrice: 1000000000,
  gasUsed: 6248,
  height: 311926,
   [ { address: 'ct_2NfX6HyJHN9xHsbwtXaV965wmbxYd64cx9ocVK1gXMELZYLnYA',
       data: 'cb_Xfbg4g==',
        [ '64845357342007104325519235815942072832295243175538055423444320696061087366731',
          [length]: 3 ] },
     [length]: 1 ],
  returnType: 'ok',
  returnValue: 'cb_P4fvHVw=' }

So it looked just fine to me, everything as expected. Tx mined at height 311926 with return type ‘OK’. Then I noticed that my platform behaves oddly. I won’t go into details but it was in an inconsistent state. I started digging to find the issue and it was this transaction. When I manually fetched transaction info using AE testate explorer I actually received different response:

This time, transaction was mined at height 311927 with return type ‘revert’.
Basically, I was given two different tx info responses for a transaction with the same hash. I don’t understand how this happened. What’s going on here?

I think you experienced a micro-fork. Due to the nature or the BitcoinNG consensus algorithm it is often the case that a couple of microblocks from a generation gets discarded when a new keyblock is mined. This means that a transaction appears to be “moved” from one generation to the next - normally the result of the transaction doesn’t change because of this, but a contract call could definitely go from ok to revert (maybe because of height or some state change).


As far as I know, there seems to be some bugs in the middleware system, and some data is not trusted, so it is recommended to query directly from the node

Yes, micro forks are to be expected for sure. That’s why one should wait a few generations in order to achieve finality of the transition.

Ok I understand the issue here. Wouldn’t it be great for .getTxInfo(hash) sdk function to accept one more argument - number of blocks to wait until node is queried for info?

I know querying directly from a node is best way to go since it’s the only real source of truth, but query time is slow and kills my UX. That’s why I cache everything in a regular database so I can provide better response time. But it looks to me now that I cannot just wait for txInfo when processing one tx hash, because obviously it happened that one transaction went from ok to revert, and my middleware cached the first response it got - ok state. Maybe I’ll just await height before every getTxInfo call and wait for a few blocks to increase my chances of having correct transaction state provided from node.

at the time you query the node this is considered the correct transaction state

Unfortunately I can’t query node all the time (to have correct transaction state all the time). I’ll have to make a trade-off here: wait for a few blocks, read transaction state and cache, pray to blockchain gods that chain won’t fork or something which will in turn change my transaction state

yes, 3 options:

  • query node directly
  • wait for finality
  • detect forks and correct your data

Agreed. Out of curiosity, how does one detect forks?

There could be different approaches there but most likely the simplest one would be on every new top change, check the last known what-used-to-be-top-block if It is still in the blockchain or it had been kicked out. This is not necessarily the most optimal one, though :smiley:

1 Like

@karol.chain how is the middleware detecting this? or does this come “out of the box” as the data of the chain itself is being reused?

AE_MDW is detecting forks at the key block granularity. If the fork is detected, transactions are rollbacked in the DB down to the common ancestor keyblock (after which the chains diverge), and then syncs transactions again from that common keyblock.