Limitations of custom transaction


Whilst working on dapp (ldice) which requires transaction result to be produced during block creation I encountered couple limitations of custom transaction design. There are many use cases which are also crippled by the same limitation.

  1. There is no way to store transaction results in different asset than account.asset to enable undoAsset to work. It is the only way to enable undoAsset to reverse transactions properly.
  2. There is theoretical possibility to store result in transaction.asset however it will be rejected by node because node will attempt to validate result as part of signed body.
  3. Custom transaction db access is limited to sender, recipient and transaction itself, so there is no way to establish different db table with tx_id->result.
  4. Neither applyAsset nor undoAsset have ability to access last block signature.(results are deterministic, there is no point of saving results other than undoAsset in this specific case)

Possible solutions

  1. Amount of transactions stored in account.asset could be limited to prevent performance drop, but that would mean that, reasonably no more than 500 bets could be made by each account. Not feasible.
  2. Store only N amount of last bet results per account, while N is based on BFT finality block count. I think this could actually work? (If HQ is not willing to introduce any other alternative, like below)
  3. LiskHQ could add option for additional db access by custom transaction, perhaps limited to transaction results. Different approach is to disable validation of specific tx asset, for example - asset.results, it could be reserved for results only, not be part of signed body.

Looking forward to discuss it further.



In general, transactions are the one triggering the action to change the state, so it shouldn’t be modified during the block processing.
Therefore, saving the result into the transactin.asset wont be feasible.

Also, account.asset should be able to hold way more than 500 bets, but of course there will be some limit. If you are referencing for the search capability (for API/users), that should be handled in custom modules rather than in the protocol/custom transactions.

Removing the account asset might be possible, but the change in the account state should be deterministic. If you delete on the finality, it should be possible, but transaction don’t provide that information right now, so it will be challenging.
Also, when we remove the undo step, it should be more easy to do.

Depending on the usecase, but for this particular problem, the possible solution with the current SDK (I haven’t tried yet, so it might not work) is create an contract account to save the result.

Possible solution with the current SDK

If there is some state that needed to be persisted but not related to the sender nor recipient, you could create an account which doesn’t correspond to any public key, and use that as constant account.
This it self wont solve the problem of huge account state.
However, if you create one main contract account which saves the link to the sub contract account, you could make it dynamic.

account(main contract account).assets.contracts = [‘contract1’, ‘contract2’…];
account.(sub contract account).assets.results = [{}]

I noticed that module solution is not the best idea as inconsistency in accounts state happens from time to time. I think it would be really great if lisk-sdk would have:

  • Ability to extend block processing with custom code (like the one I am using now in module)
  • Store transaction results (accessible from custom tx and custom block processing code)

Proposed solution won’t really work in my opinion as serious bottleneck happens above 2-5k objects stored in account.asset.