Welcome to Ethereum Alarm Clock’s documentation!¶
The Ethereum Alarm Clock is a service that allows scheduling transactions to be executed at a later time on the ethereum blockchain. This is accomplished by specifying all of the details for the transaction you wish to send, as well as providing up-front payment for gas costs, allowing your transaction to be executed for you at a later time.
The service is completely trustless, meaning that the entire service operates as smart contracts on the Ethereum blockchain, with no priviledged access given to any party.
The code for this service is open source under the MIT license and can be viewed on the github repository. Each release of the alarm service includes details on verifying the contract source code.
For a more complete explanation of what this service does check out the Introduction.
If you are a smart contract developer and would like to start scheduling transactions now then check out the Quickstart.
If you are looking to build a lower level integration with the service then our ./TODO is a good place to start.
Contents:
Introduction¶
What problem does this solve¶
The simplest way to explain the utility of the Alarm service is to explain the problem it solves.
First, you need to understand the difference between private key based accounts and contract accounts. There are two types of accounts on the Ethereum blockchain.
- Accounts that have a private key.
- Contracts (which do not have a private key)
Private key accounts are the accounts that humans operate, where as contract accounts are deployed pieces of code capable of executing some computer program. Contract accounts cannot however trigger their own code execution.
All code execution in the Ethreum Virtual Machine, or EVM must be triggered by a private key based account. This is done by sending a transaction, which may do something simple like transfering ether, or it may do something more complex like calling a function on a contract account.
The second part of the problem is that when you send a transaction it is executed as soon as it is included in a block. The Ethereum protocol does not provide any way to create a transaction to be executed at a later time.
This leads us to the problem that the Alarm service solves. With the functionality provided by this service, transactions can be securely scheduled to be executed at a later time.
How transactions are executed¶
When a transaction is scheduled a new smart contract is created that holds all of the information needed to execute the transaction. It may be useful to think of this as an order on an exchange. When called during the specified execution window, this contract will send the transaction as specified and then pay the account that triggered the execution.
These contracts are referred to as TransactionRequest
contracts and
are written to provide strong guarantees of correctness to both parties.
The creator of the TransactionRequest
contract can know that their
transaction will only be sent during the window they specified and that the
transaction parameters will be sent exactly as specified.
Similarly, the account that executes the TransactionRequest
contract
can know that no matter what occurs during the execution of the transaction
that they will receive full gas reimbursement as well as their payment for
execution.
Execution guarantees¶
You may have noted at this point that this service relies on external parties to initiate the execution of these transactions. This means that it is possible that your transaction will not be executed at all.
In an ideal situation, there is a sufficient volume of scheduled transactions that operating a server to execute these transactions is a profitable endeavor. The reality is that I operate between 3-5 execution servers dedicated filling this role until there is sufficient volume that I am confident I can turn those servers off or until it is no longer feasible for me to continue paying their costs.
How scheduling transactions works¶
A transaction is scheduled by providing some or all of the following information.
- Details about the transaction itself such as which address the transaction should be sent to, or how much ether should be sent with the transaction.
- Details about when the transaction can be executed. This includes things like the window of time or blocks during which this transaction can be executed.
- Ether to pay for the transaction gas costs as well as the payment that will be paid to the account that triggers the transaction.
Scheduling is done by calling a Scheduler
contract which handles
creation of the individual TransactionRequest
contract.
Quickstart¶
Scheduling your first transaction¶
The first step is to establish how we will interact with the Alarm service’s
Scheduler
contract. Lets create an abstract contract to accomplish
this.
contract SchedulerInterface {
//
// params:
// - uintArgs[0] callGas
// - uintArgs[1] callValue
// - uintArgs[2] windowStart
// - uint8 windowSize
// - bytes callData
// - address toAddress
//
function scheduleTransaction(address toAddress,
bytes callData,
uint8 windowSize,
uint[3] uintArgs) public returns (address);
}
This abstract contract exposes the function scheduleTransaction
which will
return the address of the newly created TransactionRequest
contract.
Now lets write a simple contract that can use the scheduling service.
contract DelayedPayment {
SchedulerInterface constant scheduler = SchedulerInterface(0xTODO);
uint lockedUntil;
address recipient;
function DelayedPayment(address _recipient, uint numBlocks) {
// set the time that the funds are locked up
lockedUntil = block.number + numBlocks;
recipient = _recipient;
uint[3] memory uintArgs = [
200000, // the amount of gas that will be sent with the txn.
0, // the amount of ether (in wei) that will be sent with the txn
lockedUntil, // the first block number on which the transaction can be executed.
];
scheduler.scheduleTransaction.value(2 ether)(
address(this), // The address that the transaction will be sent to.
"", // The call data that will be sent with the transaction.
255, // The number of blocks this will be executable.
uintArgs, // The tree args defined above
)
}
function() {
if (this.balance > 0) {
payout();
}
}
function payout() public returns (bool) {
if (now < lockedUntil) return false;
return recipient.call.value(this.balance)();
}
}
The contract above is designed to lock away whatever ether it is given for
numBlocks
blocks. In its constructor, it makes a call to the
scheduleTransaction
method on the scheduler
contract. The function
takes a total of 6 parameters, 3 of which are passed in as an array. Lets
briefly go over what each of these parameters are.
-
scheduleTransaction(address toAddress,
-
bytes callData,
-
uint8 windowSize,
-
[uint callGas, uint callValue, uint windowStart])
address toAddress
: Theaddress
which the transaction will be sent to.bytes callData
: Thebytes
that will be used as the data for the transaction.uint callGas
: The amount of gas that will be sent with the transaction.uint callValue
: The amount of ether (in wei) that will be sent with the transaction.uint windowStart
: The first block number that the transaction will be executable.uint8 windowSize
: The number of blocks afterwindowSize
during which the transaction will still be executable.
TODO: more
Architecture¶
Overview¶
The Alarm service is made of the following contracts.
TransactionRequest
: Represents a single scheduled transaction.RequestFactory
: Low level API for creatingTransactionRequest
contracts.RequestTracker
: Tracks the scheduled transactions.BlockScheduler
: High level API for creatingTransactionRequest
contracts configured to be executed at a specified block number.TimestampScheduler
: High level API for creatingTransactionRequest
contracts configured to be executed at a certain time, as specified by a timestamp.
Note
Actual functionality of most of the contracts is housed separately in various libraries.
-
class
RequestTracker
RequestTracker¶
The RequestTracker
is a database contract which tracks upcoming
transaction requests. It exposes an API suitable for someone wishing to
execute transaction requests to be able to query which requests are scheduled
next as well as other common needs.
This database tracks requests based on the address that submits them. This
allows the RequestTracker
to be un-permissioned allowing any address
to report scheduled transactions and to have them stored in their own personal
index. The address which submits the transaction request is referred to as the
scheduler address.
This also enables those executing transaction requests to choose which scheduler addresses they wish to execute transactions for.
-
class
RequestFactory
RequestFactory¶
The RequestFactory
contract is designed to be a low-level interface
for developers who need fine-grained control over all of the various
parameters that the TransactionRequest
can be configured with.
Parameter validation is available, but not mandatory.
It provides an API for creating new TransactionRequest
contracts.
-
class
BlockScheduler
¶
-
class
TimestampScheduler
¶
BlockScheduler and TimestampScheduler¶
The BlockScheduler
and TimestampScheduler
contracts are a
higher-level interface that most developers should want to use in order to
schedule a transaction for a future block or timestamp.
Both contracts present an identical API for creating new
TransactionRequest
contracts. Different from RequestFactory
,
request parameters are always validated.
BlockScheduler
treats all of the scheduling parameters as meaning
block numbers, while TimestampScheduler
treats them as meaning
timestamps and seconds.
Transaction Request¶
-
class
TransactionRequest
¶
Each TransactionRequest
contract represents one transaction that has
been scheduled for future execution. This contract is not intended to be used
directly as the RequestFactory
contract can be used to create new
TransactionRequest
contracts with full control over all of the
parameters.
Interface¶
//pragma solidity 0.4.1;
contract TransactionRequestInterface {
/*
* Primary actions
*/
function execute() public returns (bool);
function cancel() public returns (bool);
function claim() public returns (bool);
/*
* Data accessors
*/
function requestData() constant returns (address[6],
bool[3],
uint[15],
uint8[1]);
function callData() constant returns (bytes);
/*
* Pull mechanisms for payments.
*/
function refundClaimDeposit() public returns (bool);
function sendDonation() public returns (bool);
function sendPayment() public returns (bool);
function sendOwnerEther() public returns (bool);
}
Events¶
-
TransactionRequest.
Cancelled
(uint rewardPayment, uint measuredGasConsumption)¶
When a request is cancelled, the Cancelled
event will be logged. The
rewardPayment
is the amount that was paid to the party that cancelled the
request. This will always be 0 when the owner of the request cancels the
request.
-
TransactionRequest.
Claimed
()¶
When a request is claimed this event is logged.
-
TransactionRequest.Aborted(uint8 reason);
When an attempt is made to execute a request but one of the pre-execution
checks fails, this event is logged. The reason
is an error code which maps
to the following errors.
0 => WasCancelled
1 => AlreadyCalled
2 => BeforeCallWindow
3 => AfterCallWindow
4 => ReservedForClaimer
5 => StackTooDeep
6 => InsufficientGas
-
TransactionRequest.
Executed
(uint payment, uint donation, uint measuredGasConsumption)¶
When a request is successfully executed this event is logged. The payment
is the total payment amount that was awarded for execution. The donation
is the amount that was awarded to the donationBenefactor
. The
measuredGasConsumption
is the amount of gas that was reimbursed which
should always be slightly greater than the actual gas consumption.
Data Model¶
The data for the transaction request is split into 5 main sections.
- Transaction Data: Information specific to the execution of the transaction.
- Payment Data: Information related to the payment and donation associated with this request.
- Claim Data: Information about the claim status for this request.
- Schedule Data: Information about when this request should be executed.
- Meta Data: Information about the result of the request as well as which address owns this request and which address created this request.
Retrieving Data¶
The data for a request can be retrieved using two methods.
-
TransactionRequest.
requestData
()¶
This function returns the serialized request data (excluding the callData
)
in a compact format spread across four arrays. The data is returned
alphabetical, first by type, and then by section, then by field.
The return value of this function is four arrays.
address[6] addressValues
bool[3] boolValues
uint256[15] uintValues
uint8[1] uint8Values
These arrays then map to the following data fields on the request.
- Addresses (
address
) addressValues[0] => claimData.claimedBy
addressValues[1] => meta.createdBy
addressValues[2] => meta.owner
addressValues[3] => paymentData.donationBenefactor
addressValues[4] => paymentData.paymentBenefactor
addressValues[5] => txnData.toAddress
- Addresses (
- Booleans (
bool
) boolValues[0] => meta.isCancelled
boolValues[1] => meta.wasCalled
boolValues[2] => meta.wasSuccessful
- Booleans (
- Unsigned 256 bit Integers (
uint
akauint256
) uintValues[0] => claimData.claimDeposit
uintValues[1] => paymentData.anchorGasPrice
uintValues[2] => paymentData.donation
uintValues[3] => paymentData.donationOwed
uintValues[4] => paymentData.payment
uintValues[5] => paymentData.paymentOwed
uintValues[6] => schedule.claimWindowSize
uintValues[7] => schedule.freezePeriod
uintValues[8] => schedule.reservedWindowSize
uintValues[9] => schedule.temporalUnit)
uintValues[10] => schedule.windowStart
uintValues[11] => schedule.windowSize
uintValues[12] => txnData.callGas
uintValues[13] => txnData.callValue
uintValues[14] => txnData.requiredStackDepth
- Unsigned 256 bit Integers (
- Unsigned 8 bit Integers (
uint8
) uint8Values[0] => claimData.paymentModifier
- Unsigned 8 bit Integers (
-
TransactionRequest.
callData
()¶
Returns the bytes
value of the callData
from the request’s transaction
data.
Transaction Data¶
This portion of the request data deals specifically with the transaction that has been requested to be sent at a future block or time. It has the following fields.
-
address toAddress
The address that the transaction will be sent to.
-
bytes callData
The bytes that will be sent as the
data
section of the transaction.
-
uint callValue
The amount of ether, in wei, that will be sent with the transaction.
-
uint callGas
The amount of gas that will be sent with the transaction.
-
uint requiredStackDepth
The number of stack frames required by this transaction.
Payment Data¶
Information surrounding the payment and donation for this request.
-
uint anchorGasPrice
The gas price that was used during creation of this request. This is used to incentivise the use of an adequately low gas price during execution.
See Gas Multiplier for more information on how this is used.
-
uint payment
The amount of ether in wei that will be paid to the account that executes this transaction at the scheduled time.
-
address paymentBenefactor
The address that the payment will be sent to. This is set during execution.
-
uint paymentOwed
The amount of ether in wei that is owed to the
paymentBenefactor
. In most situations this will be zero at the end of execution, however, in the event that sending the payment fails the payment amount will be stored here and retrievable via thesendPayment()
function.
-
uint donation
The amount of ether, in wei, that will be sent to the donationBenefactor upon execution.
-
address donationBenefactor
The address that the donation will be sent to.
-
uint donationOwed
The amount of ether in wei that is owed to the
donationBenefactor
. In most situations this will be zero at the end of execution, however, in the event that sending the donation fails the donation amount will be stored here and retrievable via thesendDonation()
function.
Claim Data¶
Information surrounding the claiming of this request. See Claiming for more information.
-
address claimedBy
The address that has claimed this request. If unclaimed this value will be set to the zero address
0x0000000000000000000000000000000000000000
-
uint claimDeposit
The amount of ether, in wei, that has been put down as a deposit towards claiming. This amount is included in the payment that is sent during request execution.
-
uint8 paymentModifier
A number constrained between 0 and 100 (inclusive) which will be applied to the payment for this request. This value is determined based on the time or block that the request is claimed.
Schedule Data¶
Information related to the window of time during which this request is scheduled to be executed.
-
uint temporalUnit
Determines if this request is scheduled based on block numbers or timestamps.
- Set to
1
for block based scheduling. - Set to
2
for timestamp based scheduling.
All other values are interpreted as being blocks or timestamps depending on what this value is set as.
- Set to
-
uint windowStart
The block number or timestamp on which this request may first be executed.
-
uint windowSize
The number of blocks or seconds after the
windowStart
during which the request may still be executed. This period of time is referred to as the execution window. This period is inclusive of it’s endpoints meaning that the request may be executed on the block or timestampwindowStart + windowSize
.
-
uint freezePeriod
The number of blocks or seconds prior to the
windowStart
during which no activity may occur.
-
uint reservedWindowSize
The number of blocks or seconds during the first portion of the the execution window during which the request may only be executed by the address that address that claimed the call. If the call is not claimed, then this window of time is treated no differently.
-
uint claimWindowSize
The number of blocks prior to the
freezePeriod
during which the call may be claimed.
Meta Data¶
Information about ownership, creation, and the result of the transaction request.
-
address owner
The address that scheduled this transaction request.
-
address createdBy
The address that created this transaction request. This value is set by the
RequestFactory
meaning that if the request is known by the request factory then this value can be trusted to be the address that created the contract. When using either theBlockScheduler
orTimestampScheduler
this address will be set to the respective scheduler contract..
-
bool isCancelled
Whether or not this request has been cancelled.
-
bool wasCalled
Whether or not this request was executed.
-
bool wasSuccessful
Whether or not the execution of this request returned
true
orfalse
. In most cases this can be an indicator that an execption was thrown if set tofalse
but there are also certain cases due to quirks in the EVM where this value may betrue
even though the call technically failed.
Actions¶
The TransactionRequest
contract has three primary actions that can be performed.
- Cancellation: Cancels the request.
- Claiming: Reserves exclusive execution rights during a portion of the execution window.
- Execution: Sends the requested transaction.
Cancellation¶
-
TransactionRequest.
cancel
()¶
Cancellation can occur if either of the two are true.
- The current block or time is before the freeze period and the request has not been claimed.
- The current block or time is after the execution window and the request was not executed.
When cancelling prior to the execution window, only the owner
of the call
may trigger cancellation.
When cancelling after the execution window, anyone may trigger cancellation.
To ensure that funds are not forever left to rot in these contracts, there is
an incentive layer for this function to be called by others whenever a request
fails to be executed. When cancellation is executed by someone other than the
owner
of the contract, 1%
of what would have been paid to someone for
execution is paid to the account that triggers cancellation.
Claiming¶
-
TransactionRequest.
claim
()¶
Claiming may occur during the claimWindowSize
number of blocks or seconds
prior to the freeze period. For example, if a request was configured as
follows:
windowStart
: block #500freezePeriod
: 10 blocksclaimWindowSize
: 100 blocks
In this case, the call would first be claimable at block 390. The last block in which it could be claimed would be block 489.
See the Claiming section of the documentation for details about the claiming process.
Execution¶
-
TransactionRequest.
execute
()¶
Execution may happen beginning at the block or timestamp denoted by the
windowStart
value all the way through and including the block or timestamp
denoted by windowStart + windowSize
.
See the Execution section of the documentation for details about the execution process.
Retrieval of Ether¶
All payments are automatically returned as part of normal request execution and cancellation. Since it is possible for these payments to fail, there are backup methods that can be called individually to retrieve these different payment or deposit values.
All of these functions may be called by anyone.
Returning the Claim Deposit¶
-
TransactionRequest.
refundClaimDeposit
()¶
This method will return the claim deposit if either of the following conditions are met.
- The request was cancelled.
- The execution window has passed.
Retrieving the Payment¶
-
TransactionRequest.
sendPayment
()¶
This function will send the paymentOwed
value to the
paymentBenefactor
. This is only callable after the execution window has
passed.
Retrieving the Donation¶
-
TransactionRequest.
sendDonation
()¶
This function will send the donationOwed
value to the
donationBenefactor
. This is only callable after the execution window has
passed.
Return any extra Ether¶
This function will send any exta ether in the contract that is not owed as a
donation or payment and that is not part of the claim deposit back to the
owner
of the request. This is only callable if one of the following
conditions is met.
- The request was cancelled.
- The execution window has passed.
Claiming¶
-
class
TransactionRequest
The Problem¶
To understand the claiming mechanism it is important to understand the problem it solves.
Consider a situation where there are two people Alice and Bob competing to execute the same request that will issue a payment of 100 wei to whomever executes it.
Suppose that Alice and Bob both send their execution transactions at approximately the same time, but out of luck, Alice’s transaction is included before Bob’s.
Alice will receive the 100 wei payment, while Bob will receive no payment as well as having paid the gas costs for his execution transaction that was rejected. Suppose that the gas cost Bob has now incurred are 25 wei.
In this situation we could assume that Alice and bob have a roughly 50% chance of successfully executing any given transaction request, but since 50% of their attempts end up costing them money, their overall profits are being reduced by each failed attempt.
In this model, their expected payout is 75 wei for every two transaction requests they try to execute.
Now suppose that we add more competition via three additional people attempting to execute each transaction. Now Bob and Alice will only end up executing an average of 1 out of every 5 transaction requests, with the other 4 costing them 25 wei each. Now nobody is making a profit because the cost of the failed transactions now cancels out any profit they are making.
The Solution¶
The claiming process is the current solution to this issue.
Prior to the execution window there is a section of time referred to as the claim window during which the request may be claimed by a single party for execution. Part of claiming includes putting down a deposit.
When a request has been claimed, the claimer is granted exclusive rights to execute the request during a window of blocks at the beginning of the execution window.
Whomever ends up executing the request receives the claim deposit as part of their payment. This means that if the claimer fulfills their commitment to execute the request their deposit is returned to them intact. Otherwise, if someone else executes the request then they will receive the deposit as an additional reward.
Claim Deposit¶
In order to claim a request you must put down a deposit. This deposit amount
is equal to twice the payment
amount associated with this request.
The deposit is returned during execution, or when the call is cancelled.
How claiming effects payment¶
A claimed request does not pay the same as an unclaimed request. The earlier the request is claimed, the less it will pay, and conversely, the later the request is claimed, the more it pays.
This is a linear transition from getting paid 0% of the total payment if the request is claimed at the earliest possible time up to 100% of the total payment at the very end of the claim window. This multiplier is referred to as the payment modifier.
It is important to note that the payment modifier does not apply to gas reimbursements which are always paid in full. No matter when a call is claimed, or how it is executed, it will always provide a full gas reimbursement. The only case where this may end up not being true is in cases where the gas price has changed drastically since the time the request was scheduled and the contract’s endowment is now sufficiently low that it is not longer funded with sufficient ether to cover these costs.
For example, if the request has a payment
of 2000 wei, a
claimWindowSize
of 255 blocks, a freezePeriod
of 10 blocks, and a
windowStart
set at block 500. In this case, the request would have a
payment of 0 at block 235. At block 235 it would provide a payment of 20 wei.
At block 245 it would pay 220 wei or 11% of the total payment. At block 489 it
would pay 2000 wei or 100% of the total payment.
Gas Costs¶
The gas costs for claim transactions are not reimbursed. They are considered
the cost of doing business and should be taken into consideration when claiming
a request. If the request is claimed sufficiently early in the claim window it
is possible that the payment
will not fully offset the transaction costs of
claiming the request.
Execution¶
-
class
TransactionRequest
Warning
Anyone wishing to write their own execution client should be sure they fully understand all of the intricacies related to the execution of transaction requests. The guarantees in place for those executing requests are only in place if the executing client is written appropriately.
Important Windows of Blocks/Time¶
Freeze Window¶
Each request may specify a freezePeriod
. This defines a number of blocks
or seconds prior to the windowStart
during which no actions may be
performed against the request. This is primarily in place to provide some
level of guarantee to those executing the request. For anyone executing
requests, once the request enters the freezePeriod
they can know that it
will not be cancelled and that they can send the executing transaction without
fear of it being cancelled at the last moment before the execution window
starts.
The Execution Window¶
The execution window is the range of blocks or timestamps during which the
request may be executed. This window is defined as the range of blocks or
timestamps from windowStart
till windowStart + windowSize
.
For example, if a request was scheduled with a windowStart
of block 2100
and a windowSize
of 255 blocks, the request would be allowed to be executed
on any block such that windowStart <= block.number <= windowStart +
windowSize
.
As another example, if a request was scheduled with a windowStart
of block 2100
and a windowSize
of 0 blocks, the request would only be allowed to be
executed at block 2100.
Very short windowSize
configurations likely lower the chances of your
request being executed at the desired time since it is not possible to force a
transaction to be included in a specific block and thus the party executing
your request may either fail to get the transaction included in the correct
block or they may choose to not try for fear that their transaction will not
be included in the correct block and thus they will not recieve a reimbursment
for their gas costs.
Similarly, very short ranges of time for timestamp based calls may even make it
impossible to execute the call. For example, if you were to specify a
windowStart
at 1480000010 and a windowSize
of 5 seconds then the
request would only be executable on blocks whose block.timestamp
satisfied
the conditions 1480000010 <= block.timestamp <= 1480000015
. Given that it
is entirely possible that no blocks are mined within this small range of
timestamps there would never be a valid block for your request to be executed.
Note
It is worth pointing out that actual size of the execution window will
always be windowSize + 1
since the bounds are inclusive.
Reserved Execution Window¶
Each request may specify a claimWindowSize
which defines a number of blocks
or seconds at the beginning of the execution window during which the request
may only be executed by the address which has claimed the request. Once this
window has passed the request may be executed by anyone.
Note
If the request has not been claimed this window is treated no differently than the remainder of the execution window.
For example, if a request specifies a windowStart
of block 2100, a
windowSize
of 100 blocks, and a reservedWindowSize
of 25 blocks then in
the case that the request was claimed then the request would only be executable
by the claimer for blocks satisfying the condition 2100 <= block.number <
2125
.
Note
It is worth pointing out that unlike the execution window the reserved execution window is not inclusive of it’s righthand bound.
If the reservedWindowSize
is set to 0, then there will be no window of
blocks during which the execution rights are exclusive to the claimer.
Similarly, if the reservedWindowSize
is set to be equal to the full size of
the execution window or windowSize + 1
then there will be not window
after the reserved execution window during which execution can be triggered
by anyone.
The RequestFactory
will allow a reservedWindowSize
of any value
from 0 up to windowSize
+ 1, however, it is highly recommended that you
pick a number around 16 blocks or 270 seconds, leaving at least the same amount
of time unreserved during the second portion of the execution window. This
ensures that there is sufficient motivation for your call to be claimed because
the person claiming the call knows that they will have ample opportunity to
execute it when the execution window comes around. Conversely, leaving at
least as much time unreserved ensures that in the event that your request is
claimed but the claimer fails to execute the request that someone else has
plenty of of time to fulfill the execution before the execution window ends.
The Execution Lifecycle¶
When the :method:`TransactionRequest.execute()` function is called the contract goes through three main sections of logic which are referred to as a whole as the execution lifecycle.
- Validation: Handles all of the checks that must be done to ensure that all of the conditions are correct for the requested transaction to be executed.
- Execution: The actual sending of the requested transaction.
- Accounting: Computing and sending of all payments to the necessary parties.
Part 1: Validation¶
During the validation phase all of the following validation checks must pass.
Check #1: Not already called¶
Requires the wasCalled
attribute of the transaction request to
be false
.
Check #2: Not Cancelled¶
Requires the isCancelled
attribute of the transaction request to
be false
.
Check #3: Not before execution window¶
Requires block.number
or block.timestamp
to be greater than or equal to
the windowStart
attribute.
Check #4: Not after execution window¶
Requires block.number
or block.timestamp
to be less than or equal to
windowStart + windowSize
.
Check #5 and #6: Within the execution window and authorized¶
- If the request is claimed
- If the current time is within the reserved execution window
- Requires that
msg.sender
to be theclaimedBy
address
- Requires that
- Otherwise during the remainder of the execution window
- Always passes.
- If the request is not claimed.
- Always passes if the current time is within the execution window
Check #7: Stack Depth Check¶
In order to understand this check you need to understand the problem it solves.
One of the more subtle attacks that can be executed against a requested
transaction is to force it to fail by ensuring that it will encounter the EVM
stack limit. Without this check the executor of a transaction request could
force any request to fail by arbitrarily increasing the stack depth prior to
execution such that when the transaction is sent it encounters the maximum
stack depth and fails. From the perspective of the TransactionRequest
contract this sort of failure is indistinguishable from any other exception.
In order to prevent this, prior to execution, the TransactionRequest
contract will ensure that the stack can be extended by a number of stack frames
equal to requiredStackDepth
. This check passes if the stack can be
extended by this amount.
This check will be skipped if msg.sender == tx.origin
since in this case it
is not possible for the stack to have been arbitrarily extended prior to
execution.
Check #8: Sufficient Call Gas¶
Requires that the current value of msg.gas
be greater than the minimum
call gas. See minimum-call-gas for details on how to compute this
value as it includes both the callGas
amount as well as some extra for the
overhead involved in execution.
Part 2: Execution¶
The execution phase is very minimalistic. It marks the request as having been
called and then dispatches the requested transaction, storing the success or
failure on the wasSuccessful
attribute.
Part 3: Accounting¶
The accounting phase accounts for all of the payments and reimbursements that need to be sent.
The donation payment is the mechanism through which developers can earn a
return on their development efforts on the Alarm service. For the official
scheduler deployed as part of the alarm service this defaults to 1% of the
default payment. This value is multiplied by the gas multiplier (see
Gas Multiplier) and sent to the donationBenefactor
address.
Next the payment for the actual execution is computed. The formula for this is as follows:
totalPayment = payment * gasMultiplier + gasUsed * tx.gasprice + claimDeposit
The three components of the totalPayment
are as follows.
payment * gasMultiplier
: The actual payment for execution.gasUsed * tx.gasprice
: The reimbursement for the gas costs of execution. This is not going to exactly match the actual gas costs, but it will always err on the side of overpaying slightly for gas consumption.claimDeposit
: If the request is not claimed this will be 0. Otherwise, theclaimDeposit
is always given to the executor of the request.
After these payments have been calculated and sent, the Executed
event is
logged, and any remaining ether that is not allocated to be paid to any party
is sent back to the address that scheduled the request.
Gas Multiplier¶
To understand the gas multiplier you must understand the problem it solves.
Transactions requests always provide a 100% reimbursment of gas costs. This is
implemented by requiring the scheduler to provide sufficient funds up-front to
cover the future gas costs of their transaction. Ideally we want the sender of
the transaction that executes the request to be motivated to use a gasPrice
that is as low as possible while still allowing the transaction to be included
in a block in a timely manner.
A naive approach would be to specify a maximum gas price that the scheduler is willing to pay. This might be possible for requests that will be processed a short time in the future, but for transactions that are scheduled sufficiently far in the future it isn’t feasible to set a gas price that is going to reliably reflect the current normal gas prices at that time.
In order to mitigate this issue, we instead provide a financial incentive to the party executing the request to provide as low a gas cost as possible while still getting their transaction included in a timely manner.
Those executing the request are already sufficiently motivated to provide a gas price that is high enough to get the transaction mined in a reasonable time since if the price they specify is too low it is likely that someone else will execute the request before them, or that their transaction will not be included before the execution window closes.
So, to provide incentive to keep the gas cost reasonably low, the gas
multiplier concept was introduced. Simply put, the multiplier produces a
number between 0 and 2 which is applid to the payment
that will be sent for
fulfilling the request.
At the time of scheduling, the gasPrice
of the scheduling transaction is
stored. We refer to this as the anchorGasPrice
as we can assume with some
reliability that this value is a reasonable gas cost that the scheduler is
willing to pay.
At the time of execution, the following will occur based on the gasPrice
used for the executing transaction:
- If
gasPrice
is equal to theanchorGasPrice
then the gas multiplier will be 1, meaning that thepayment
will be issued as is.- When the
gasPrice
is greater than theanchorGasPrice
, the gas multiplier will approach 0 meaning that the payment will steadily get smaller for higher gas prices.- When the
gasPrice
is less than theanchorGasPrice
, the gas multiplier will approach 2 meaning that the payment will steadily get larger for lower gas prices.
The formula used is the following.
If the execution
gasPrice
is greater thananchorGasPrice
:gasMultiplier = anchorGasPrice / tx.gasprice
Else (if the execution
gasPrice
is less than or equal to theanchorGasPrice
:gasMultiplier = 2 - (anchorGasPrice / (2 * anchorGasPrice - tx.gasprice))
For example, if at the time of scheduling the gas price was 100 wei and the
executing transaction uses a gasPrice
of 200 wei, then the gas multiplier
would be 100 / 200 => 0.5
.
Alternatively, if the transaction used a gasPrice
of 75 wei then the gas
multiplier would be 2 - (100 / (2 * 100 - 75)) => 1.2
.
Sending the Execution Transaction¶
In addition to the pre-execution validation checks, the following things should be taken into considuration when sending the executing transaction for a request.
Gas Reimbursement¶
If the gasPrice
of the network has increased significantly since the
request was scheduled it is possible that it no longer has sufficient ether to
pay for gas costs. The following formula can be used to compute the maximum
amount of gas that a request is capable of paying:
(request.balance - 2 * (payment + donation)) / tx.gasprice
If you provide a gas value above this amount for the executing transaction then you are not guaranteed to be fully reimbursed for gas costs.
Minimum ExecutionGas¶
When sending the execution transaction, you should use the following rules to determine the minimum gas to be sent with the transaction:
- Start with a baseline of the
callGas
attribute. - Add
180000
gas to account for execution overhead. - If you are proxying the execution through another contract such that during
execution
msg.sender != tx.origin
then you need to provide an additional700 * requiredStackDepth
gas for the stack depth checking.
For example, if you are sending the execution transaction directly from a
private key based address, and the request specified a callGas
value of
120000 gas then you would need to provide 120000 + 180000 => 300000
gas.
If you were executing the same request, except the execution transaction was
being proxied through a contract, and the request specified a
requiredStackDepth
of 10 then you would need to provide 120000 + 180000 +
700 * 10 => 307000
gas.
Request Factory¶
-
class
RequestFactory
¶
Introduction¶
The RequestFactory
contract is the lowest level API for creating
transaction requests. It handles:
- Validation and Deployment of
TransactionRequest
contracts- Tracking of all addresses that it has deployed.
This contract is designed to allow tuning of all transaction parameters and is probably the wrong API to integrate with if your goal is to simply schedule transactions for later execution. The Request Factory API is likely the right solution for these use cases.
Interface¶
//pragma solidity 0.4.1;
contract RequestFactoryInterface {
event RequestCreated(address request);
function createRequest(address[3] addressArgs,
uint[11] uintArgs,
bytes callData) returns (address);
function validateRequestParams(address[3] addressArgs,
uint[11] uintArgs,
bytes callData,
uint endowment) returns (bool[7]);
function createValidatedRequest(address[3] addressArgs,
uint[11] uintArgs,
bytes callData) returns (address);
function isKnownRequest(address _address) returns (bool);
}
Events¶
-
RequestFactory.
RequestCreated
(address request)¶
The RequestCreated
event will be logged for each newly created
TransactionRequest
.
-
RequestFactory.
ValidationError
(uint8 error)¶
The ValidationError
event will be logged when an attempt is made to create
a new TransactionRequest
which fails due to validation errors. The error
represents an error code that maps to the following errors.
0 => InsufficientEndowment
0 => ReservedWindowBiggerThanExecutionWindow
0 => InvalidTemporalUnit
0 => ExecutionWindowTooSoon
0 => InvalidRequiredStackDepth
0 => CallGasTooHigh
0 => EmptyToAddress
Function Arguments¶
Because of the call stack limitations imposed by the EVM, all of the following
functions on the RequestFactory
contract take their arguments in the
form of the following form.
address[3] addressArgs
uint256[11] uintArgs
bytes callData
The arrays map to to the following TransactionRequest
attributes.
- Addresses (
address
) addressArgs[0] => meta.owner
addressArgs[1] => paymentData.donationBenefactor
addressArgs[2] => txnData.toAddress
- Addresses (
- Unsigned Integers (
uint
akauint256
) uintArgs[0] => paymentData.donation
uintArgs[1] => paymentData.payment
uintArgs[2] => schedule.claimWindowSize
uintArgs[3] => schedule.freezePeriod
uintArgs[4] => schedule.reservedWindowSize
uintArgs[5] => schedule.temporalUnit
uintArgs[6] => schedule.windowStart
uintArgs[7] => schedule.windowSize
uintArgs[8] => txnData.callGas
uintArgs[9] => txnData.callValue
uintArgs[10] => txnData.requiredStackDepth
- Unsigned Integers (
Validation¶
-
RequestFactory.
validateRequestParams
(address[3] addressArgs, uint[11] uintArgs, bytes callData, uint endowment) returns (bool[7] result)¶
The validateRequestParams
function can be used to validate the parameters
to both createRequest
and createValidatedRequest
. The additional
parameter endowment
should be the amount in wei that will be sent during
contract creation.
This function returns an array of bool
values. A true
means that the
validation check succeeded. A false
means that the check failed. The
result
array’s values map to the following validation checks.
Check #1: Insufficient Endowment¶
result[0]
Checks that the provided endowment
is sufficient to pay for the donation
and payment as well as gas reimbursment.
The required minimum endowment can be computed as the sum of the following:
callValue
to provide the ether that will be sent with the transaction.2 * payment
to pay for maximum possible payment2 * donation
to pay for maximum possible donation2 * callGas * tx.gasprice
to pay forcallGas
with up to a 2x increase in the network gas price.2 * 700 * requiredStackDepth * tx.gasprice
to pay gas for the stack depth checking with up to a 2x increase in network gas costs.2 * 180000 * tx.gasprice
to pay for the gas overhead involved in transaction execution.
Check #2: Invalid Reserved Window¶
result[1]
Checks that the reservedWindowSize
is less than or equal to windowSize +
1
.
Check #3: Invalid Temporal Unit¶
result[2]
Checks that the temporalUnit
is either 1
to specify block based scheduling,
or 2
to specify timestamp based scheduling.
Check #4: Execution Window Too Soon¶
result[3]
Checks that the current now
value is not greater than windowStart -
freezePeriod
.
- When using block based scheduling,
block.number
is used for thenow
value. - When using timestamp based scheduling,
block.timestamp
is used.
Check #5: Invalid Stack Depth Check¶
result[4]
Checks that the requiredStackDepth
is less than or equal to 1000.
Check #6: Call Gas too high¶
result[5]
Check that the specified callGas
value is not greater than the current
gasLimit - 140000
where 140000
is the gas overhead of request
execution.
Check #7: Empty To Address¶
result[6]
Checks that the toAddress
is not the null address
0x0000000000000000000000000000000000000000
.
Creation of Transaction Requests¶
-
RequestFactory.
createRequest
(address[3] addressArgs, uint[11] uintArgs, bytes callData) returns (address)¶
This function deploys a new TransactionRequest
contract. This
function does not perform any validation and merely directly deploys the new
contract.
Upon successful creation the RequestCreated
event will be logged.
-
RequestFactory.
createValidatedRequest
(address[3] addressArgs, uint[11] uintArgs, bytes callData) returns (address)¶
This function first performs validation of the provided arguments and then
deploys the new TransactionRequest
contract when validation succeeds.
When validation fails, a ValidationError
event will be logged for each
validation error that occured.
Tracking API¶
-
RequestFactory.
isKnownRequest
(address _address) returns (bool)¶
This method will return true
if the address is a
TransactionRequest
that was created from this contract.
Request Tracker¶
-
class
RequestTracker
¶
Introduction¶
The RequestTracker
contract is a simple database contract that exposes
an API suitable for querying for scheduled transaction requests. This database
is permissionless in so much as it partitiions transaction requests by the
address that reported them. This means that anyone can deploy a new request
scheduler that conforms to whatever specific rules they may need for their use
case and configure it to report any requests it schedules with this tracker
contract.
Assuming that such a scheduler was written to still use the
RequestFactory
contract for creation of transaction requests, the
standard execution client will pickup and execute any requests that this
scheduler creates.
Interface¶
//pragma solidity 0.4.1;
contract RequestTrackerInterface {
function getWindowStart(address factory, address request) constant returns (uint);
function getPreviousRequest(address factory, address request) constant returns (address);
function getNextRequest(address factory, address request) constant returns (address);
function addRequest(address request, uint startWindow) constant returns (bool);
function removeRequest(address request) constant returns (bool);
function isKnownRequest(address factory, address request) constant returns (bool);
function query(address factory, bytes2 operator, uint value) constant returns (address);
}
Database Structure¶
All functions exposed by the RequestTracker
take an address
as the
first argument. This is the address that reported the request into the
tracker. This address is referred to as the scheduling address which merely
means that it is the address that reported this request into the tracker. Each
scheduling address effectively receives it’s own database.
All requests are tracked and ordered by their windowStart
value. The
tracker does not distinguish between block based scheduling and timestamp based
scheduling.
It is possible for a single TransactionRequest
contract to be listed
under multiple scheduling addresses since any address may report a request into
the database.
Chain of Trust¶
Since this database is permissionless, if you plan to consume data from it, you should validate the following things.
- Check with the
RequestFactory
that therequest
address is known using the :method:`RequestFactory.isKnownRequest()` function. - Check that the
windowStart
attribute of theTransactionRequest
contract matches the registeredwindowStart
value from theRequestTracker
.
Any request created by the RequestFactory
contract regardless of how
it was created should be safe to execute using the provided execution client.
API¶
-
RequestTracker.
isKnownRequest
(address scheduler, address request) constant returns (bool)¶
Returns true
or false
depending on whether this address has been
registered under this scheduler address.
-
RequestTracker.
getWindowStart
(address scheduler, address request) constant returns (uint)¶
Returns the registered windowStart
value for the request. A return value
of 0 indicates that this address is not known.
-
RequestTracker.
getPreviousRequest
(address scheduler, address request) constant returns (address)¶
Returns the address of the request who’s windowStart
comes directly before
this one.
-
RequestTracker.
getNextRequest
(address scheduler, address request) constant returns (address)¶
Returns the address of the request who’s windowStart
comes directly after
this one.
-
RequestTracker.
addRequest
(address request, uint startWindow) constant returns (bool)¶
Add an address into the tracker. The msg.sender
address will be used as
the scheduler address to determine which database to use.
-
RequestTracker.
removeRequest
(address request) constant returns (bool)¶
Remove an address from the tracker. The msg.sender
address will be used as
the scheduler address to determine which database to use.
-
RequestTracker.
query
(address scheduler, bytes2 operator, uint value) constant returns (address)¶
Query the database for the given scheduler. Returns the address of the 1st
record which evaluates to true
for the given query.
Allowed values for the operator
parameter are:
'>'
: For strictly greater than.'>='
: For greater than or equal to.'<'
: For strictly less than.'<='
: For less than or equal to.'=='
: For less than or equal to.
The value
parameter is what the windowSize
for each record will be
compared to.
If the return address is the null address
0x0000000000000000000000000000000000000000
then no records matched.
Request Factory¶
-
class
Scheduler
¶
Introduction¶
The Scheduler
contract is the high level API for scheduling
transaction requests. It exposes a very minimal subset of the full parameters
that can be specified for a TransactionRequest
in order to provide a
simplified scheduling API with fewer foot-guns.
The Alarm service exposes two schedulers.
BlockScheduler
for block based scheduling.TimestampScheduler
for timestamp based scheduling.
Both of these contracts present an identical API. The only difference is which
temporalUnit
that each created TransactionRequest
contract is
configured with.
Interface¶
//pragma solidity 0.4.1;
import {RequestScheduleLib} from "contracts/RequestScheduleLib.sol";
import {SchedulerLib} from "contracts/SchedulerLib.sol";
contract SchedulerInterface {
using SchedulerLib for SchedulerLib.FutureTransaction;
address public factoryAddress;
RequestScheduleLib.TemporalUnit public temporalUnit;
/*
* Local storage variable used to house the data for transaction
* scheduling.
*/
SchedulerLib.FutureTransaction futureTransaction;
/*
* When applied to a function, causes the local futureTransaction to
* get reset to it's defaults on each function call.
*
* TODO: Compare to actual enum values when solidity compiler error is fixed.
* https://github.com/ethereum/solidity/issues/1116
*/
modifier doReset {
if (uint(temporalUnit) == 1) {
futureTransaction.resetAsBlock();
} else if (uint(temporalUnit) == 2) {
futureTransaction.resetAsTimestamp();
} else {
throw;
}
_
}
/*
* Full scheduling API exposing all fields.
*
* uintArgs[0] callGas
* uintArgs[1] callValue
* uintArgs[2] windowSize
* uintArgs[3] windowStart
* bytes callData;
* address toAddress;
*/
function scheduleTransaction(address toAddress,
bytes callData,
uint[4] uintArgs) doReset public returns (address);
/*
* Full scheduling API exposing all fields.
*
* uintArgs[0] callGas
* uintArgs[1] callValue
* uintArgs[2] donation
* uintArgs[3] payment
* uintArgs[4] requiredStackDepth
* uintArgs[5] windowSize
* uintArgs[6] windowStart
* bytes callData;
* address toAddress;
*/
function scheduleTransaction(address toAddress,
bytes callData,
uint[7] uintArgs) doReset public returns (address);
}
Defaults¶
The following defaults are used when creating a new TransactionRequest
contract via either Scheduler
contract.
donationBenefactor
:0xd3cda913deb6f67967b99d67acdfa1712c293601
which is the address of Piper Merriam, the creator of this project.payment
:1000000 * tx.gasprice
set at the time of scheduling.donation
:10000 * tx.gasprice
or 1/100th of the default payment.reservedWindowSize
: 16 blocks or 5 minutes.freezePeriod
: 10 blocks or 3 minutesclaimWindowSize
: 255 blocks or 60 minutes.requiredStackDepth
: 10 stack frames.
API¶
There are two scheduleTransaction
methods on each Scheduler
contract with different call signatures.
-
Scheduler.
scheduleTransaction
(address toAddress, bytes callData, uint[4] uintArgs) returns (address)¶
This method allows for configuration of the most common parameters needed for
transaction scheduling. Due to EVM restrictions, all of the unsigned integer
arguments are passed in as an array. The array values are mapped to the
TransactionRequest
attributes as follows.
uintArgs[0] => callGas
uintArgs[1] => callValue
uintArgs[2] => windowSize
uintArgs[3] => windowStart
-
Scheduler.
scheduleTransaction
(address toAddress, bytes callData, uint[4] uintArgs) returns (address)
This method presents three extra fields allowing more fine controll for
transaction scheduling. Due to EVM restrictions, all of the unsigned integer
arguments are passed in as an array. The array values are mapped to the
TransactionRequest
attributes as follows.
uintArgs[0] => callGas
uintArgs[1] => callValue
uintArgs[2] => donation
uintArgs[3] => payment
uintArgs[4] => requiredStackDepth
uintArgs[5] => windowSize
uintArgs[6] => windowStart
Endowments¶
When scheduling a transaction, you must provide sufficient ether to cover all of the execution costs with some buffer to account for possible changes in the network gas price. See Check #1: Insufficient Endowment for more information on how to compute the endowment.
CLI Interface¶
The alarm service ships with a command line interface that can be used to interact with the service in various ways.
Requirements¶
The ethereum-alarm-clock-client
python package requires some system
dependencies to install. Please see the pyethereum documentation for more
information on how to install these.
This package is only tested against Python 3.5. It may work on other versions but they are explicitely not supported.
This package is only tested on unix based platforms (OSX and Linux). It may work on other platforms but they are explicitely not supported.
Installation¶
The ethereum-alarm-clock-client
package can be installed using pip
like this.
$ pip install ethereum-alarm-clock-client
Or directly from source like this.
$ python setup.py install
If you are planning on modifying the code or developing a new feature you should instead install like this.
$ python setup.py develop
The eth_alarm
executable¶
Once you’ve installed the package you should have the eth_alarm
executable
available on your command line.
$ eth_alarm
Usage: eth_alarm [OPTIONS] COMMAND [ARGS]...
Options:
-t, --tracker-address TEXT The address of the RequestTracker contract
that should be used.
-f, --factory-address TEXT The address of the RequestFactory contract
that should be used.
--payment-lib-address TEXT The address of the PaymentLib contract that
should be used.
-r, --request-lib-address TEXT The address of the RequestLib contract that
should be used.
-l, --log-level INTEGER Integer logging level - 10:DEBUG 20:INFO
30:WARNING 40:ERROR
-p, --provider TEXT Web3.py provider type to use to connect to
the chain. Supported values are 'rpc',
'ipc', or any dot-separated python path to a
web3 provider class
--ipc-path TEXT Path to the IPC socket that the IPCProvider
will connect to.
--rpc-host TEXT Hostname or IP address of the RPC server
--rpc-port INTEGER The port to use when connecting to the RPC
server
-a, --compiled-assets-path PATH
Path to JSON file which contains the
compiled contract assets
--back-scan-seconds INTEGER Number of seconds to scan into the past for
timestamp based calls
--forward-scan-seconds INTEGER Number of seconds to scan into the future
for timestamp based calls
--back-scan-blocks INTEGER Number of blocks to scan into the past for
block based calls
--forward-scan-blocks INTEGER Number of blocks to scan into the future for
block based calls
--help Show this message and exit.
Commands:
client:monitor Scan the blockchain for events from the alarm...
client:run
repl Drop into a debugger shell with most of what...
request:create Schedule a transaction to be executed at a...
Rollbar Integration¶
Monitoring these sorts of things can be difficult. I am a big fan of the rollbar service which provides what I feel is a very solid monitoring and log management solution.
To enable rollbar logging with the eth_alarm
client you’ll need to do the
following.
- Install the python rollbar package.
*
$ pip install rollbar
- Run
eth_alarm
with the following environment variables set. *ROLLBAR_SECRET
set to the server side token that rollbar provides. *ROLLBAR_ENVIRONMENT
set to a string such as ‘production’ or ‘ec2-instance-abcdefg’`.
Running a server¶
The scheduler runs nicely on the small AWS EC2 instance size. The following steps should get an EC2 instance provisioned with the scheduler running.
1. Setup an EC2 Instance¶
- Setup an EC2 instance running Ubuntu. The smallest instance size works fine.
- Add an extra volume to store your blockchain data. 20GB should be sufficient for a short while (after April 2017) if storing the entire history, block-for-block, is not required. Otherwise, a much larger size should be used.
- Optionally mark this volume to persist past termination of the instance so that you can reuse your blockchain data.
- Make sure that the security policy leaves 30303 open to connections from the outside world.
2. Provision the Server¶
sudo apt-get update --fix-missing
sudo apt-get install -y supervisor
sudo apt-get install -y python3-dev python build-essential libreadline-gplv2-dev libncursesw5-dev libssl-dev libsqlite3-dev tk-dev libgdbm-dev libc6-dev libbz2-dev python-virtualenv libffi-dev autoconf
3. Mount the extra volume¶
The following comes from the AWS Documentation and will only work verbatim
if your additional volume is /dev/xvdb
.
sudo mkfs -t ext4 /dev/xvdb
sudo mkdir -p /data
sudo mount /dev/xvdb /data
sudo mkdir -p /data/ethereum
sudo chown ubuntu /data/ethereum
Modify /etc/fstab to look like the following. This ensures the extra volume will persist through restarts.
#/etc/fstab
LABEL=cloudimg-rootfs / ext4 defaults,discard 0 0
/dev/xvdb /data ext4 defaults,nofail 0 2
Run sudo mount -a
If you don’t get any errors then you haven’t borked your
etc/fstab
4. Install Geth or Parity¶
Install the go-ethereum client.
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:ethereum/ethereum
sudo apt-get update
sudo apt-get install -y ethereum
or install the parity client.
bash <(curl https://get.parity.io -Lk)
5. Install the Alarm Client¶
Install the Alarm client.
mkdir -p ~/alarm-0.8.0
cd ~/alarm-0.8.0
virtualenv -p /usr/bin/python3.5 env && source env/bin/activate
pip install setuptools --upgrade
pip install ethereum-alarm-clock-client==8.0.0b1
6. Configure Supervisord¶
Supervisord will be used to manage both geth
and eth_alarm
.
If you are using Go-Ethereum, put the following in /etc/supervisord/conf.d/geth.conf
[program:geth]
command=geth --datadir /data/ethereum --unlock 0 --password /home/ubuntu/scheduler_password --fast
user=ubuntu
stdout_logfile=/var/log/supervisor/geth-stdout.log
stderr_logfile=/var/log/supervisor/geth-stderr.log
autorestart=true
autostart=false
and the following in /etc/supervisord/conf.d/scheduler-v8.conf
[program:scheduler-v8]
user=ubuntu
command=/home/ubuntu/alarm-0.8.0/env/bin/eth_alarm --ipc-path /data/ethereum/geth.ipc client:run
directory=/home/ubuntu/alarm-0.8.0/
environment=PATH="/home/ubuntu/alarm-0.8.0/env/bin"
stdout_logfile=/var/log/supervisor/scheduler-v8-stdout.log
stderr_logfile=/var/log/supervisor/scheduler-v8-stderr.log
autorestart=true
autostart=false
If you are using Parity, put the following in /etc/supervisord/conf.d/parity.conf
[program:parity]
command=parity --db-path /data/ethereum --unlock <your-account-address> --password /home/ubuntu/scheduler_password
user=ubuntu
stdout_logfile=/var/log/supervisor/parity-stdout.log
stderr_logfile=/var/log/supervisor/parity-stderr.log
autorestart=true
autostart=false
and the following in /etc/supervisord/conf.d/scheduler-v8.conf
[program:scheduler-v8]
user=ubuntu
command=/home/ubuntu/alarm-0.8.0/env/bin/eth_alarm --ipc-path /home/ubuntu/.parity/jsonrpc.ipc client:run
directory=/home/ubuntu/alarm-0.8.0/
environment=PATH="/home/ubuntu/alarm-0.8.0/env/bin"
stdout_logfile=/var/log/supervisor/scheduler-v8-stdout.log
stderr_logfile=/var/log/supervisor/scheduler-v8-stderr.log
autorestart=true
autostart=false
7. Generate an account¶
For Go-Ethereum
$ geth --datadir /data/ethereum account new
For parity
$ parity account new
Place the password for that account in /home/ubuntu/scheduler_password
.
You will also need to send this account a few ether. A few times the maximum transaction cost should be sufficient as this account should always trend upwards as it executes requests and receives payment for them.
Don’t forget to back up the key file! Go-Ethereum should have put it in
/data/ethereum/keystore/
and Parity in
/home/ubuntu/.local/share/io.parity.ethereum/keys/
8. Turn it on¶
Reload supervisord so that it finds the two new config files.
sudo supervisord reload
You’ll want to wait for Go-Ethereum or Parity to fully sync with the network
before you start the scheduler-v8
process.
9. Monitoring¶
You can monitor these processes with tail
tail -f /var/log/supervisor/geth*.log
tail -f /var/log/supervisor/parity*.log
tail -f /var/log/supervisor/scheduler-v8*.log
10. System Clock¶
You might want to add the following line to your crontab. This keeps your system clock up to date. I’ve had issues with my servers drifting.
0 0 * * * /usr/sbin/ntpdate ntp.ubuntu.com
Changelog¶
0.8.0 (unreleased)¶
- Full rewrite of all contracts.
- Support for both time and block based scheduling.
- New permissionless call tracker now used to track scheduled calls.
- Donation address can now be configured.
- Request execution window size is now configurable.
- Reserved claim window size is now configurable.
- Freeze period is now configurable.
- Claim window size is now configurable.
- All payments now support pull mechanism for retrieving payments.
0.7.0¶
- Scheduled calls can now specify a required gas amount. This takes place of
the
suggestedGas
api from 0.6.0 - Scheduled calls can now send value along with the transaction.
- Calls now protect against stack depth attacks. This is configurable via the
requiredStackDepth
option. - Calls can now be scheduled as soon as 10 blocks in the future.
- Experimental implementation of market-based value for the
defaultPayment
scheduleCall
now has 31 different call signatures.
0.6.0¶
- Each scheduled call now exists as it’s own contract, referred to as a call contract.
- Removal of the Caller Pool
- Introduction of the claim api for call.
- Call Portability. Scheduled calls can now be trustlessly imported into future versions of the service.
0.5.0¶
- Each scheduled call now exists as it’s own contract, referred to as a call contract.
- The authorization API has been removed. It is now possible for the contract
being called to look up
msg.sender
on the scheduling contract and find out who scheduled the call. - The account management API has been removed. Each call contract now manages it’s own gas money, the remainder of which is given back to the scheduler after the call is executed.
- All of the information that used to be stored about the call execution is now placed in event logs (gasUsed, wasSuccessful, wasCalled, etc)
0.4.0¶
- Convert Alarm service to use library contracts for all functionality.
- CallerPool contract API is now integrated into the Alarm API
0.3.0¶
- Convert Alarm service to use Grove for tracking scheduled call ordering.
- Enable logging most notable Alarm service events.
- Two additional convenience functions for invoking
scheduleCall
with gracePeriod and nonce as optional parameters.
0.2.0¶
- Fix for Issue 42. Make the free-for-all bond bonus restrict itself to the correct set of callers.
- Re-enable the right tree rotation in favor of removing three
getLastX
function. This is related to the pi-million gas limit which is restricting the code size of the contract.
0.1.0¶
- Initial release.