Skip to content
This repository was archived by the owner on Aug 8, 2018. It is now read-only.

Making a User Service: Tutorial

vbuterin edited this page Oct 13, 2015 · 14 revisions

Note: this tutorial assumes that you have already installed pyethapp, and running pyethapp run on the command line works

One of pyethapp's most powerful distinguishing features is its ability to easily create built-in user services: scripts written in python that run alongside pyethapp, and run code on startup and every time you receive a new block. This allows you to create "server daemons" for applications that require periodic automated support, such as RANDAO, data feeds, "decentralized dropbox" apps, alarm clocks, decentralized cloud computing services, etc.

User services are stored in the contrib sub-folder of the pyethapp data directory (eg. ~/.config/pyethapp/contrib on Ubuntu); if this folder does not yet exist then create it. To add a service, in that directory create a python script with any name, as long as that name does not conflict with another python module (eg. helloworld.py is fine, math.py is not). Now, let us create our first service. In a file called helloworld.py in the contrib directory, put the following:

import sys

def on_start(app):
    print "Hello, world!"
    sys.exit()

Now, do pyethapp run on the command line. It should start up, and then quit in about five seconds. The last lines of the output should look something like this:

INFO:app	registering service service=factory generated service 0
INFO:app	starting 
Hello world

Now, let's make the service not quit immediately, and instead add an on_block method. Let's put the following into helloworld.py:

count = 0

def on_block(blk):
    global count
    count += 1
    print "Total number of blocks seen this run: %d" % count

When you run pyethapp and start syncing, you should eventually see something like this:

Total number of blocks seen this run: 1
new blk <Block(#375193 17e5591f)>
INFO:eth.chainservice	added txs=2 gas_used=42000 block=<Block(#375193 17e5591f)>
Total number of blocks seen this run: 2
new blk <Block(#375194 d6847603)>
INFO:eth.chainservice	added txs=1 gas_used=21000 block=<Block(#375194 d6847603)>
Total number of blocks seen this run: 3
new blk <Block(#375195 c6061bfe)>

Now, let's use this to do something interesting. What we are going to do is create a centralized data feed service which essentially acts as an HTTP proxy: contracts can call a get(string) function of a particular contract passing any desired URL as an argument, and a successful call triggers a log containing the URL, a callback ID and a callback address. The data feed service listens for logs, and upon seeing a log of the right type it sends an HTTP GET request to the URL, and then sends a callback message to the address containing the HTTP GET response.

First, determine a seed to generate a private key and an address; for this example we will use qwufqhwiufyqwiugxqwqcwrqwrcqr as our seed. The privkey is c7283d309fbac6fd65d2d9664c04dc71bbbdeb945b359501e912ab007da4fd27 and the address is b3cd4c2512402047ef4ddeb1ab9f8df400ce2d3f. Now, we will write a contract as follows:

event GetRequest(url:string, callback:address, responseId:uint256, fee:uint256)
data nextResponseId

def get(url:string):
    if msg.value >= 7 * 10**15:
        self.nextResponseId = sha3(block.prevhash + sha3(url:str) + msg.sender + msg.value * 2**160)
        log(type=GetRequest, url, msg.sender, self.nextResponseId, msg.value)
        send(0xb3cd4c2512402047ef4ddeb1ab9f8df400ce2d3f, self.balance)
        return(self.nextResponseId)

This contract does several things. First, it contains a get method with the right signature, which other contracts can call. It enforces a minimum fee of 7 finney, which covers gas costs for your return transaction plus a bit more to spare. It then generates a callback ID for the request; the complicated sha3 formula for generating the callback ID is used so that if the blockchain forks and a different set of messages is made and/or in a different order then the new IDs will not match up. It is expected that contracts calling get use these IDs as a way of keeping track which function call is a callback to which request; in the future we may well see high-level programming languages for Ethereum that abstract this logic away into something that looks more like nodejs-style callbacks or promises. Finally, it logs the request data, passes along the fee to the account that will be returning the callback, and returns the callback ID to the contract that sent the request so that the contract can store it for future use.

Clone this wiki locally