-
Notifications
You must be signed in to change notification settings - Fork 607
Making a User Service: Tutorial
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.