Learning Solidity and writing a smart contract is relatively easy, but we’ve found that the harder technical challenge is designing a DApp backend infrastructure that is secure, scalable, and snappy. Unlike traditional apps, DApps rely on the inherently probabilistic nature of blockchain state and utilize Ethereum components that are relatively new and under active development. This is Part I of a series of articles about the architectural patterns and practices we’ve learned that might benefit other DApp developers.
Among the earliest lessons for anyone developing DApps on Ethereum is that logging events in smart contracts is an efficient way to report state changes and keep track of actions performed. Since events are emitted on the blockchain and can be replayed, while at the same time are technically not being stored in expensive blockchain state (i.e. memory or storage), they are a cost effective workaround for “storing” blockchain data. Events are also emitted real-time, allowing for real-time discovery and reporting of actions.
Events sound great! Everyone should use events!
Hold on, not so fast… Another early, and potentially painful, lesson for DApp developers is that event watching is unreliable: an event is only useful if your DApp detects it when needed. If you need real-time data, but event discovery is delayed or events are missed altogether, this could result in a poor user experience or complete DApp failure.
Our solution: DApp developers who need reliable event tracking should consider adding Kubernetes/Docker to their toolkit. While these technologies do come with a bit of a learning curve, they neatly solve very specific problems in the current Ethereum technology ecosystem, mainly arising from reliability.
At CoinAlpha, we have been running a lot of experiments and research on Ethereum infrastructure and reliability to support our product. We have been using different cloud providers such as AWS, Google Cloud, and Digital Ocean to host nodes in addition to running our own local nodes. We have set up node monitors that allow us to track node performance and reliability, as the sample screen shot shows.
Since our projects require as close to real-time event tracking as possible, we have had to come up with solutions to address the current issues with Ethereum infrastructure:
watch()can miss events: Nodes regularly fall behind in syncing, by a few blocks. One main reason for this is hard drive read/write speed. Paying up for high speed SSD drives is a possible mitigant, but with the current Ethereum mainnet database exceed ing100GB, cloud hosting bills begin to snowball pretty quickly. In addition, this still doesn’t address the drop in peer-dropping problem. If your node falls behind and then does a syncing catch-up, we have found that live events may be missed.
A fault-tolerant Ethereum event watching system is one that can detect failures, conduct triage, and continue operating as if nothing happened. Here’s how we design ours:
The result is we have an architecture that looks like the following, with additional discussion of components below:
Below are some explanations and discussion on some of our architecture choices. And just some of the cool things we like about Kubernetes. I understand there are many ways you can address the problems I’ve raised. If you have any comments, questions, or suggestions, you can contact our team on our CryptoBaskets telegram group or send us an email.
For our application, the benefit of using docker is that we are able to add on additional services that need access to the blockchain simply by launching new containers that can link to our parity instances. Each container runs independently, so changes, additions, and upgrades can all be completed without disrupting any of the other existing running containers/services.
In the example above, we only have three containers connecting to the parity data: (1) the node monitor, (2) an event watcher for App 1, and (3) an event watcher for App 2. But we can easily add more containers, as and when our needs change.
Managing multiple node clusters (Ethereum clients and their connected services) can get pretty complicated and messy quickly. Not only are there multiple services that are linked and dependent on each other, updating and maintaining configurations as well as managing secrets (such as API keys) requires coordination and creates potential security vulnerabilities. In addition, monitoring servers and services for failures and restarting failed containers can get challenging in increasingly larger systems. That’s where Kubernetes comes in; it handles all of these issues.
Below are some of the features that we find particularly useful:
Despite the company’s previous missteps (multi-sig hack, library hack), the one thing Parity has going for it is they have created one reliable Ethereum client (ok…let’s also ignore the consensus vulnerability for now). While most developers start out using the Go implementation of Ethereum “Geth”, a quick google/stack exchange search will reveal a lot of frustration and problems for syncing Geth. We’ve found that in practice, Geth nodes take longer to sync and fall behind more often than Parity nodes (which you can even see in our node monitor dashboard screenshot above). On the other hand, we have found that Parity nodes will sync from scratch and are available for use within a few hours to a day.
Being relatively new technologies, Ethereum clients like Parity and Geth are constantly being updated and improved, which is why we maintain both types of nodes.
Decentralized applications have a bright future, but given the nascency of the technology stack for them, substantial back-end work is necessary to make them feel as responsive and reliable as centralized web and mobile applications. Luckily, we’ve found that a careful, intentional approach to architectural design makes this feasible, and we’ll publish posts about other important aspects such as security in the future.
Create your free account to unlock your custom reading experience.