EOSCommunity.org Forums

Generalized history solutions for smart contract applications?

Applications generally have the problem of making history relevant to the application available to their users. For example, a user should be able to see tokens they have sent and received in the past. Going a little further, there is also data that may not strictly be considered history but is nevertheless derived from the stream of past relevant events and is not available in the current state of the blockchain. For example, perhaps someone is interested in their current balance (which is available in the current state of the blockchain) but also in the maximum balance they ever had (which is information that is not available in the current state, does not increase without bound over time like history, but is ultimately reduced from the stream of all past transfer events related to the account in question).

One can of course build a custom system that ingests the stream of events from the blockchain (e.g. via SHiP or the dfuse Firehose) and then generate history (and any indices on data needed to make that history available) in a manner that is able to take advantage of special domain knowledge of the particular application the history solution was built for. But I am interested in exploring more general solutions within this topic for making the history (or other reduced / transformed state) of an application (including decentralized smart contracts without any particular owner) available for users.

What are the currently available options?

It appears that block explorers and the history solutions driving them display an action in an account’s history feed based on whether the account acted as authorizer of the action or if the account was a receiver of an action (i.e. the account hosting the contract of the action or any other accounts that the contracts notify via require_recipient where they have a contract deployed on them or not).

So one could design their smart contract to require_recipient any account they believe is relevant to the action and should ideally be notified of the existence of the action executing on the blockchain. This does not help with the arbitrary reduction / transformation of the stream of events (e.g. the maximum balance an account ever had), but it is a straightforward solution to the common history challenges that applications face. However, there are a few reasons it may not necessarily be desirable to do this. For one, it adds extra CPU cost to re-dispatch the action to another notified account, especially if that account has contract code on it. More importantly, a contract executing due to being notified by require_recipient can abort the transaction and therefore block the action that initiated the notification in the first place. This can potentially cause not just a denial of service for the contract but perhaps even a major flaw in the application through that denial of service if one is not careful. For example, without careful control of require_recipient (or sending inline actions to a third-party accounts for that matter), a poorly design auction contract may be abused by an attacker to prevent anyone from outbidding them until the auction expires.

But without notifications (whether via require_recipient or direct inline action notifications), general history solutions existing today wouldn’t be able to determine that a transaction has actions that might be relevant to a particular user.

So what are possible solutions to this problem. I would like to explore them in this topic. I will start it off with two ideas.

One option might be to introduce a standard that history solutions and block explorers can adopt in which a contract sends an inline action to a standard established contract account that won’t block the transaction (or better yet for efficiency reasons an account with no code deployed on it like eosio.null). This standard action would include account names which are designated as relevant to the action that sent the inline action and therefore are accounts that should be notified in some way (typically by including a reference to the transaction in their account history feed) of the existence of the transaction and specifically the action. This seems like it may open up the potential for spam and abuse (bloating the account’s history feed with noise), but I suppose it really isn’t any more abuse and noise than what is already possible with contracts using require_recipient (or for that matter just sending 0.0001 EOS to the account).

Another approach is considerably more complicated but it is also a more powerful and flexible system and it involved a bigger picture view of application history. It is also applicable to making available arbitrary reduction and transformation of the stream of events of an application. This approach is to consider that each application (which includes decentralized smart contracts not owned by any one operator, e.g. the system contracts or even individual components of it like the voting system or the PowerUp system) should have its own custom history view of the subset of actions relevant to it along with references to the EOSIO transactions including those relevant actions. It would especially be useful if that custom history view could be driven programmatically by WebAssembly code that is compiled alongside the smart contract WASM from the same smart contract source code. One can essentially think of this as a parallel blockchain driven by the subset of events on the main blockchain which allows offloading the computation and state not relevant for the main blockchain (e.g. things related to history) to some other thing that does not have as strict resource requirements as the main blockchain. This approach even allows people who did not develop and do not control the smart contract on the main chain to nevertheless implement a custom transformation / reduction / history view of the event stream for that smart contract.

One significant problem with this approach, however, is regarding what happens if that parallel WebAssembly code runs into an error and cannot continue? Then it would just stall all further progress of history processing for that application. Or what if it doesn’t halt but yet still struggles to keep up with the main blockchain because it is trying to do too much and there is no back pressure or feedback loop to throttle the driving activity happening on the main chain? Finally, there is the problem of resources and how to pay for the computational costs imposed on the history solutions running this general computation. To be fair that is already a problem with the current history solutions that do not do general computation, but it could only get worse if one can make the history component of a smart contract action arbitrarily more computationally expensive than the computation done (and paid for) on the main blockchain that everyone processes. There are probably many other challenges to overcome with this approach, but I do think it has appeal due to how powerful and general it is.

I think the first option despite its limitations offers enough value that it may be worth it for the community to seriously consider developing as some kind of standard that can be adopted by smart contract developers, history solutions, and block explorers. This could provide value in the short term while a more sophisticated solution (perhaps like the second approach outlined above) is properly explored over time.

Love the idea of WebAssembly indexers, I’ve been thinking along similar lines (but for a small modular indexer that can easily be deployed and configured for particular contracts). Very similar to a CouchDB view function if you’re familiar with those.

Even if the problems you outlined aren’t solved just having a standardized way for a contract to say “this is how you index my data” would be a huge step in the right direction IMO.

Not sure if I see much value in the first option though, maybe I’m missing something but being able to include actions into other accounts history feeds w/o them being authorizer or notified isn’t much of a improvement over status quo for decentralized apps wanting to use EOSIO.

The contract wasm may also have another entry point alongside apply(), and contain getter methods that deliver the interpretation for ship data.

But so far, Chronicle us doing a good job in decoding the ship output. But of course it cannot give a universal interpretation of it.

Also, I’m building all new projects according to this paradigm:

https://cc32d9.medium.com/building-eosio-dapps-independent-from-history-solutions-its-a-common-struggle-for-every-dapp-b588e9798756