This post aims to cover the technical details we went through along the implementation process. To read more about the announcement please go here.
For those who haven't heard about Aragon Court before, it is a dispute resolution protocol formed by jurors to handle subjective disputes that cannot be resolved by smart contracts. If you want to read more details about how the protocol works, you can jump to the technical documentation.
During the whole year, huge and amazing things have been going on along the process. Here is a detailed chronicle about the whole process we went through:
During the implementation of the protocol, multiple technical challenges came up. Here are some of the difficulties we went through and the solutions we carried out to solve them.
One of the key parts of Aragon Court is guaranteeing randomness when drafting jurors for a dispute. As we all know, implementing randomness in Ethereum is not an easy task. The strategy we picked to solve this problem was to use block hashes of future blocks, which means using information that cannot be known beforehand, to make sure miners cannot manipulate it easily and that users can not speculate on when it's better to open a dispute.
Aragon Court is a window-based protocol and these periods are called "terms". This allows us to guarantee to all the participants that certain attributes will remain constant during a court term. For example, drafting should produce the same output for the same input, i.e. drafting a group of jurors for a particular dispute should always return the same outcome if computing it during the same term. That said, the block number used for randomness is one of these things that are set at the beginning of each term and remain constant.
We are aware of the cons of this pattern, and it is, in fact, one of the crypto-economics considerations we declare. The only way it could be manipulated is by jurors that are also miners. They could decide to drop a block that includes a term transition in case the following block number doesn't result in them being elected for a dispute. However, this juror will need to know which are going to be the transactions that will be mined in the following block to compute that since we are relying on the block hash. Note that this could only occur if the miner has the chance to mine the current and the following block too. An alternative would be for the malicious miner to be "listening" to each block mining a heartbeat function in order to mine the next block forcing a specific set of transactions to make sure a certain juror is elected for the draft.
Another key problem to solve was being able to guarantee a pure drafting function while keeping a lower gas cost. Let's start by analyzing a bit how the drafting algorithm works.
The way jurors are selected for a dispute is based on the number of ANJ activated: the more tokens a juror has activated, the higher the chance to get drafted. It can be seen as a segment of ANJ balance where different chunks are assigned to each juror based on their ANJ active balance:
The image above illustrates how a draft of three jurors would work. In this case, three positions of the segment are selected randomly, using the court term randomness seed. Each picked position belongs to one of the jurors of the system.
In order to optimize the searching process, we were highly inspired by the idea of using a sum-tree data structure explored by the Kleros team, to store all these balances. Otherwise, performing a binary search for a huge amount of jurors would have been more expensive. The way sum-trees searches work is pretty similar to how a binary search works in an array. The idea of sum-trees is that all the items (in this case balances) of the tree are stored in the leaves while the parent nodes only hold the sum of the values of their children. Let's see an example of how can we look up balance #2 (130) using a binary sum-tree:
Additionally, in order to guarantee a constant outcome of this algorithm during each court term, we need to know how did this segment look like at the beginning of each term. Since storing that would require huge amounts of gas, we used a checkpointing process to be able to fetch historic information of each node. Instead of updating the whole tree on every single term transition, we only update it when a node is modified or if a new node is inserted to the tree.
The cherry on top is a superb optimization we implemented for this search algorithm where all the balances are searched at the same time, which allows us to visit each node in the tree only once. Based on some gas cost tests, this optimization allows to draft +100 jurors in a tree with 10,000 jurors in a single transaction which is more than enough. However, Aragon Court also supports drafting in batches, but that hopefully won't be needed for most of the disputes.
There were many other technical challenges we went through and in fact we started writing a blog post series about them. If you want to read more about these, please jump to Bingen's wonderful Rage Against the (EV)Machine series.
Another key part of the whole protocol was to build a robust architecture that allows us to support all the functionality we wanted to provide along with some "background" features to guarantee the correct operation of the court. To implement this we carried out a module-based architecture where each module is in charge of a specific section of the protocol. The glue between all these modules is what we called the
Controller which also aims to provide basic functionality to each of these modules like accessing the Court config and terms-related functionality.
The following image illustrates how the final architecture for the Court looks like:
As you can see, there are five main modules:
Subscription. To have a better understanding of the responsibilities of each module check out the technical documentation where you will find a thorough specification about the whole architecture and each of the modules.
The two main features we wanted to support at an architecture level were:
- Having the chance to tweak the Court config
- Having the flexibility to change the different modules of the Court
Of course, this required a trusted entity allowed to handle this information, and we decided to leverage the Aragon Network DAO for it. This DAO will be formed initially by a council of trusted members from the community and will be transitioned to the ANT holders as explained in the Aragon Court launch process. We proposed an AGP to the community declaring the group of members that will form the initial council of the Aragon Network DAO, feel free to come and vote for it!
Beforehand, we already knew this wasn't going to be an easy task, there were plenty of edge cases we thought about during the process, several numbers of attacks we wanted to prevent, and much more. Initially, we explored different strategies like simulations or stress testing, we definitely wanted to have a solid and robust automated process that could verify our assumptions and guarantee the proper behavior of the protocol.
However, after digging around for some weeks about tools or libraries that could help us bootstrapping these ideas, we finally decided to delay this for a while. Unfortunately, most of the well-known tools to perform these kinds of tests would have required to build custom connectors to make them work with an Ethereum blockchain. This would have taken a considerable amount of time and probably a lot of effort that we would have had to decrease from other parts of the project.
Finally, we ended up deciding to work on a thorough unit and integration test suite, which obviously didn't require a learning curve. Based on the architecture explained above, it was super straightforward to work on modular tests. Besides, we tried to cover every single corner of the whole codebase, without mocking unnecessarily to make sure we were testing real logic. A great part of what we were focused on covering was:
- Permissions and authorization flows
- Funds accountancy
- Protocol lifecycle
- Input validation
- Error handling
- Gas costs tests
As a result, we ended up having a test suite composed of +2,300 tests!
The first version of the protocol was already released and deployed to the Ethereum mainnet. This version was audited by Georgios Konstantopoulos, you can read the report here. As you can see, there were only three findings where two of them are part of our trust assumptions. All the related recommendations were already addressed and audited by Georgios as well.
Additionally, we do think security is a continuous and ongoing process. Therefore, the Aragon Association has already launched a bug bounty program for the Aragon Network including the Aragon Court contracts in it. Feel free to take a look at it and start reviewing the codebase!
I want to publicly thank the entire Aragon One R&D team specially to Jorge, Bingen, and Luke for their full support and continuous contribution to the project. I feel really proud of being part of such an amazing team.