Chapter 13. Making a Dapp production ready

This chapter covers

  • How to generate event logs
  • How to upgrade your libraries after deploying them
  • How to upgrade your contracts after deploying them

In the previous chapter, you built an end-to-end Dapp from scratch for the first time, using most of the knowledge you’d acquired throughout the book and most of the available Ethereum development tools. Although it was a good exercise that helped you to consolidate your Ethereum development skills, you might not be ready to deploy your first Dapp into the public production network. Before doing so, you might want to consider strengthening your Dapp so it can cope with real-world operational requirements, such as being able to generate, store (on the blockchain), and query (from the blockchain) event logs; upgrade your libraries after deploying them; and upgrade your contracts after deploying them.

I’ve already touched on some of these topics briefly, but in this chapter, I’ll revisit them in more detail. I’ll keep my usual pragmatic approach, and I’ll walk you through examples and code. Designing an upgradeable library or contract, though, requires advanced techniques whose implementation touches Solidity syntax that’s outside the scope of this book. But I still believe you should be aware of the concepts, which I’ll try to explain by sketching some contract or sequence diagrams.

You might be surprised I haven’t included security in the list. I consider it such an important topic that I’m dedicating the entire next chapter to it.

13.1. Event logging

Let’s start with event logging. You already know how to declare and raise events on smart contracts. In the previous chapter, you saw how to handle contract events from a web UI.

For example, you refreshed the workflow status description at the top of both admin and voter pages every time you moved to a new step of the voting process. Also, you refreshed the proposals table at the top of the voter page every time a voter registered a new proposal. The entire workflow of the proposal registration, with related event handling, is illustrated in figure 13.1, which, if you remember, is similar to figure 1.8 from chapter 1. If you didn’t fully get it in chapter 1, it should definitely click now.

If you’ve done any event-driven or reactive programming before, you might think there’s nothing unusual about how contracts publish events and how a client subscribed to them, such as a web page, can handle them. But contracts differ in an important way: whereas in most languages, events are transient objects that disappear from the system after being handled, Ethereum events are logged permanently on the blockchain. As a result, you can also retrieve events that happened before starting up the UI client, if you want. Curious to see how? Keep reading!

13.1.1. Retrieving past events

You’ve already seen how to register listeners of contract events on your JavaScript with eventFilter.watch. For example, you wrote this code at the top of SimpleVoting.js (though I’m slightly simplifying it here) for registering a callback to a workflow status change event:

var workflowStatusChangeEvent  =
    contractInstance.WorkflowStatusChangeEvent();    1
 
workflowStatusChangeEvent
   .watch(function(error, result) {                  2
    if (!error)
        refreshWorkflowStatus();
    else
        console.log(error);
});

  • 1 Sets an event filter listening to events of type WorkflowStatusChangeEvent
  • 2 Registers a callback to the event filter

Figure 13.1. Event handling on the voter page. A new proposal is submitted from the web page to the voting contract. The contract then fires a ProposalRegisteredEvent event on registration completion. The event is propagated throughout the network until it reaches the node the web page is communicating with. Finally, the webpage handles the event, and a new item is added on the proposal table.

The variable workflowStatusChangeEvent is known as an event filter because it defines the set of events to listen to—in our case, all events of type WorkflowStatusChangeEvent. As you’ll see later, you can define the filter in a more restrictive way.

When you register a callback to an event filter through eventFilter.watch, you’ll handle events that take place from the moment the web page is opened. If you want to access past events—for example, to verify that all the workflow status changes took place in the correct sequence—you can do so through eventFilter.get, which accepts a similar callback as watch. First of all, you must define the filter in a slightly different way, by specifying the starting block and final block of your event search:

var pastWorkflowStatusChangeEvent =
    contractInstance.WorkflowStatusChangeEvent(
       {},
       {fromBlock: 5000, toBlock: 'latest'});        1

  • 1 Only gets the events from block 5000 onwards

Then you register the callback to the event filter with get rather than watch:

pastWorkflowStatusChangeEvent.get(
   function(error, logs) {                           1
   if (!error)
       logs.forEach(log => console.log(log.args));   2
   else
    console.log(error);
});

  • 1 Retrieves the past events
  • 2 Displays the past event arguments to the console

If you want to retrieve all past events the contract has broadcast, not only the WorkflowStatusChangeEvent events, you can create the event filter using the allEvents function, as follows:

var allEventPastEvents = contractInstance.allEvents(
    {}, {fromBlock: 1000000, toBlock: 'latest'});        1

  • 1 Filter on all event logs raised by the contract since block 1000000

Then you can register a callback on them through get, as before:

allEventPastEvents.get((error, logs) => { 
  logs.forEach(log => console.log(log.args))
});

If you want to retrieve only a subset of past events—for example, only the proposals that a certain voter registered—you can perform the search more efficiently by indexing some of the parameters of the event in question. You’ll see how in a moment.

13.1.2. Event indexing

When declaring an event on a contract, you can index up to three of its parameters. Event filters associated with these parameters will perform much better. For example, in the case of SimpleCoin, you might remember from section 3.5.1 that the Transfer event was defined as

event Transfer(address indexed from, address indexed to, uint256 value);

This allows you to efficiently filter transfer events against specific source and destination addresses when retrieving event logs:

var specificSourceAccountAddress =
    '0x8062a8850ef59dCd3469A83E04b4A40692a873Ba';
var transactionsFromSpecificAccount =
    simpleCoinInstance.Transferred(
    {from: specificSourceAccountAddress},
    {fromBlock: 5000, toBlock: 'latest'});
transactionsFromSpecificAccount.get((error, logs) => {
    logs.forEach(log => console.log(log.args));
});

In the case of SimpleVoting, you might remember that the ProposalRegisteredEvent event was defined as

event ProposalRegisteredEvent(uint proposalId);

As it stands, you can’t use this event in a meaningful way as a log. How would you modify its definition so that you could query it to provide useful information? Think about it for a few seconds.... You could amend it like this:

event ProposalRegisteredEvent(
    address author, uint proposalId, string proposalDescription);

As an exercise, here’s a question for you: How would you index this event to efficiently retrieve all the proposals that have been registered from a specific address? Take your time... Yes, you’re right! Obviously, you’d index the author parameter as

event ProposalRegisteredEvent(
    address indexed author,
    uint proposalId, string proposalDescription);

Let’s now move to a completely different topic, also important from an operational point of view: What do you do when something goes wrong in your contract? Is it possible to fix it and redeploy it as you would for a conventional software component, such as a library or a microservice? I’ll discuss the answer to that question in the next few sections.

13.2. Designing an upgradeable library

When developing a new contract, some of your requirements might be similar to those of other contracts that other developers have already deployed on the blockchain. You saw in chapter 7 how you can place common functionality into libraries and reference it from various contracts, therefore avoiding wasted reimplementation effort and code duplication. For example, you practiced how to perform safe arithmetic operations with a copy of the OpenZeppelin SafeMath library. What you did, though, is still not ideal: your deployment of OpenZeppelin libraries may be only one of the many instances present on the blockchain. Other developers, like you, may have included a copy of such libraries in their solution and may have performed their own private deployment, as shown in figure 13.2.

Figure 13.2. Duplicated deployment of an OpenZeppelin library. Only one client contract uses each privately deployed instance.

You can imagine how many instances of OpenZeppelin libraries are out there on TESTNET and MAINNET! The drawbacks of this approach include the following:

  • Unnecessary costs—Each separate deployment of the same library has to pay for its own deployment transaction gas fees.
  • Unnecessary bytecode duplication—The same bytecode is associated on the blockchain with different deployment addresses (although nodes in fact deduplicate identical bytecode).
  • Uncoordinated maintenance—If a problem in the library is detected, you can’t fix it centrally; the owner of each contract instance has to patch it independently, with obvious consequences.

The most obvious solution to these issues is to have one official golden instance of the library on the blockchain and expect all its clients to reference it at that single address, as you saw briefly in chapter 7, section 7.5.2. This solution has one major drawback: once you’ve deployed a library, you can’t patch it. If you find a problem, a new deployment is necessary, with the consequence that all its client contracts must also be repointed to the new library address and redeployed, as shown in figure 13.3. This is far from an ideal solution!

Figure 13.3. Fixing a shared library means redeploying it at a new address, which requires client contracts to be modified and redeployed.

An elegant solution that Manuel Araoz, CTO of Zeppelin, presented in an article on Medium[1] is to create a proxy to the library. His solution is a library proxy contract that acts as a layer of indirection between a client contract using the library and the library itself, as illustrated in figure 13.4.

1

See “Proxy Libraries in Solidity,” March 6, 2017, http://mng.bz/Xgd1.

Figure 13.4. When you introduce a proxy to the library, client contracts no longer directly reference an instance of the library; they communicate with the proxy (or library dispatcher), which gets from another contract the latest valid address of the library and then forwards calls to the valid instance.

These are the components of the proxy library solution:

  • A client contract references the address of a library proxy (or dispatcher) contract (SafeMathProxy) rather than directly referencing an instance of the library.
  • The library proxy (SafeMathProxy) is a contract that retrieves the address of the latest valid deployed library instance from a library proxy registry contract (SafeMathProxyRegistry) and then forwards the call to the latest deployed library instance.
  • The library proxy registry contract (SafeMathProxyRegistry) is a contract containing a list of addresses for different versions of the library. It also possibly contains a state variable holding the default address, which is typically the latest address that has been added to the list. The library owner updates the address list and the default address at each new deployment of the library.

When a client contract invokes a library function, it performs a corresponding delegatecall on the library proxy instead. This retrieves the correct library address for the client contract to use from the library proxy registry and then performs a delegatecall on the relevant instance of the library. From the point of view of the client contract, the whole call chain behaves exactly like a normal library function invocation.

Note

Library functions are implicitly invoked through the delegatecall EVM opcode, which executes in the context of the caller, as explained in section 3.2.5, and as I’ll further explain in chapter 14 on security.

In summary, if you find that a library you’d like to use provides a proxy, use it rather than hardwiring to a specific deployed instance. You’ll gain maintainability. The catch, though, is that you must trust the contract owner won’t maliciously forward your calls to a new implementation of the library that has nothing to do with the original one.

If you find the idea of designing an upgradeable library interesting and want to learn further, I encourage you to review Araoz’s Medium article, where you can find code samples.

Warning

You should thoroughly understand the proxy library technique before you attempt to implement it; weak implementations have been found subject to malicious attacks. I recommend reading the Medium post “Malicious backdoors in Ethereum Proxies,” by Patricio Palladino, to make sure you’re aware of the weak spots: http://mng.bz/vNz1.

13.3. Designing an upgradeable contract

As you might have already guessed, contracts suffer from the same limitation as libraries: once you deploy a contract, you’re stuck with it. If you discover a bug or security vulnerability after deployment, you can at most freeze it or destroy it—if you included this kind of panic button functionality. The best bet is to plan for the worst-case scenario beforehand and design your contract with built-in upgradeability.

As in the case of proxy libraries, although this solution makes the contract safer from your side, from the point of view of the users, you’re introducing a layer of obfuscation. The most security-conscious users might not trust your good intentions and might fear the possibility that, all of a sudden, you could start redirecting client calls to a malicious contract.

I’ll illustrate for you these two main techniques for making a contract upgradeable:

  • Use the same proxy technique I’ve shown you for libraries
  • Separate state and logic into two contracts, both inherited from an abstract Upgradeable contract

13.3.1. Connecting to the upgraded contract through a proxy

Imagine various contracts, among which SimpleCrowdsale, from chapters 6 and 7, would like to use your marvelous SimpleCoin contract. You decide to make SimpleCoin upgradeable through a SimpleCoinProxy contract to avoid headaches not only to you, but also to all of your clients. As you can see in figure 13.5, the solution is identical to what you saw for SafeMath.

Figure 13.5. If you decide to make SimpleCoin upgradeable through SimpleCoinProxy, client contracts such as SimpleCrowdsale will call functions on SimpleCoinProxy, which will dispatch them to a specific deployed version of SimpleCoin, generally the latest one.

13.3.2. Inheriting from an abstract Upgradeable contract

Nick Johnson, the author of ENS, has taken a slightly different approach to making a contract upgradeable.[2] He has proposed to split the state (storage and Ether) and the

2

See “Mad blockchain science: A 100% upgradeable contract,” Reddit+r/ethereum, May 24, 2016, http://mng.bz/y1Yo.

logic of a contract into two separate contracts, both inherited from a common abstract contract called Upgradeable:

  • A dispatcher—This would hold the state and would dispatch all the calls to a target contract through the Solidity delegatecall() function (which explicitly executes the EVM delegatecall opcode). Consequently, the function would execute in the context of the dispatcher. You’ll see more about delegatecall() in chapter 14 on security.
  • A target contract—You would delegate the entire functionality to this contract, which you could upgrade later on. This contract would also control the upgrade process.

With this design, illustrated in figure 13.6, when the target contract got upgraded, the dispatcher would still keep an unaltered state while it transferred all the calls to the latest version of the target contract.

Figure 13.6. Upgradeability by splitting a contract into two contracts, both inherited from the same abstract Upgradeable contract: a dispatcher holding the state and a target contract holding the functionality. The dispatcher forwards all calls to the target contract. If an upgrade occurs, the dispatcher keeps holding the same unaltered state and forwards the calls to the latest version of the target.

If you’re eager to try out this approach, I encourage you to look at Nick’s code at http://mng.bz/4OZD. If you’re interested in learning more about solutions around library or contract upgradeability, I recommend the Medium article “Summary of Ethereum Upgradeable Smart Contract R&D,” by Jack Tanner, based on his research across all the currently known techniques: http://mng.bz/QQmR.

Warning

Designing an upgradeable contract using Nick’s technique requires a thorough understanding of the inner workings of EVM and EVM assembly, which is outside the scope of this book. Also, using an upgradeable contract poses risks, as I explained earlier.

Summary

  • Whereas in most languages events are transient objects that disappear from the system after being handled, Ethereum events are logged permanently on the blockchain.
  • You can replay and query past events by registering a handler on the event filter with get rather than watch.
  • You can improve the performance of past-event retrieval by indexing some of the event parameters when defining the event.
  • You can make a library upgradeable by introducing a proxy to it. Client contracts no longer directly reference an instance of the library, but rather communicate with the proxy (or library dispatcher), which gets the latest valid address of the library from a registry contract and then forwards calls to the valid instance.
  • You can make a contract upgradeable by using a proxy contract (as for libraries) or by separating state and logic into two contracts, both inherited from an abstract Upgradeable contract.
  • Making a contract upgradeable makes it safer on one hand; for example, if you discover a problem after deployment, you can fix the contract and route the calls to the new amended version. But it might introduce security risks if you don’t implement it correctly.
  • Using an upgradeable library raises security concerns from the point of view of the contract user, who might worry about being routed to unintended functionality.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset