The Raiden services (:ref:`MS` and :ref:`PFS`) require a set of smart contracts to function. There are two general smart contracts:
- UserDeposit, used as a pool for tokens used in service fee payments
- ServiceRegistry, where service providers can register themselves so they can use the service fee payment contracts
and one additional contract for each of the services
- MonitoringService, used as integral part of how the MS functions
- OneToN, a minimal one-to-N payment solution used to pay fees to the PFS
which depend on the former two contracts.
There might also be an additional contract to facilitate the onboarding of new Raiden users, which has been called "Hub Contract" in some discussions. There are no detailed plans for that contract, yet.
The ServiceRegistry provides a registry in which services have to register before becoming a full part of the Raiden services system. Services have to deposit RDN tokens in this contract for a successful registration. This avoids attacks using a large number of services and increases the incentive for service provider to not harm the Raiden ecosystem.
- Need to stake for these slots by the Raiden Service Providers (RSPs), but there will be no slashing
- RSPs must be allowed to withdraw the deposited stake after their registration ends
- An "auction" mechanism that allows for competition over those slots
- There must be a way for new RSPs to get a slot
- We want an easy MVP to get real user feedback after our release
- Need to provide a way of setting the minimal deposit amount - the cost of becoming an RSP should be high enough to deter users from double-spending IOUs
- We want to reduce the amount of work to be done, so try to avoid periodical deployments and / or coordination stakeholders
- There must be a way for other smart contracts to check if an address is registered as a service provider
- Avoid new implementations if possible
- The deployer should be able to choose whether it wants to control the parameters
- The controller should be able to deprecate the ServiceRegistry
- When the ServiceRegistry is deprecated, no new deposits are accepted
- When the ServiceRegistry is deprecated, already held deposits can be immediately withdrawn
In order to avoid periodical deployments, ServiceRegistry does not have a deadline for joining. ServiceRegistry allows new deposits anytime. The required amount of deposit (called the price) changes over time so as to regulate the inflow of the deposits. The price movement depends only on the timing of incoming deposits. Upon receiving new deposit, the price jumps by some ratio (called the bump ratio). Otherwise the price decreases slowly following some exponential decay, dictated by the decay constant.
- Somebody who wants to provide services registers themselves as a RSP.
- Service smart contracts look up whether an Ethereum address is registered as a RSP.
- A registered RSP posts a URL.
- Users of services look up URLs of registered RSPs.
- Users of services choose one of the registered RSPs randomly.
During and after the deployment, a ServiceRegistry has the following entries in its state.
controller
the address that can change some parameters of ServiceRegistryset_price
last recorded priceset_price_at
the timestamp whenset_price
was recordeddecay_constant
the number of seconds till the price decreases by roughly 1/2.7min_price
the minimum price, under which the price does not decayprice_bump_numerator
andprice_bump_denominator
the ratio of the price dump when a deposit is maderegistration_duration
the duration in seconds of a service registration (or its extension)deprecated
non-zero value indicates that the controller has turned on the deprecation switchtoken
the address of the ERC20 token account with which registration deposits are madeservice_valid_till
the timestamp when the registration of every address expires (zero if the address has never made a deposit)urls
the URL that a registered account submittedever_made_deposits
the list of addresses that have ever made deposits
The deployer of a ServiceRegistry instance chooses
- the token for registration
- the controller
- the initial price
- the price bump ratio
- the decay constant
- the minimum price
The other entries start empty.
Anybody can register themselves as a service (with enough ETH to cover the gas costs, and with enough tokens). The registration runs for the registration duration. If they are already registered their registration extends by the registration duration.
Before calling deposit()
, the service provider candidate must have called approve()
function on the ERC20 token smart contract, so that the ServiceRegistry can send tokens. The approval must cover the current price.
The current price might be higher than what the service provider candidate has seen because another party might have made a deposit meanwhile. The service provider candidate must set a limit amount, indicating the biggest amount of tokens it's willing to deposit. If the current price is bigger than the limit amount, the service provider candidate still pays the gas costs in ETH, but its tokens stay.
If the service provider candidate approved more than the current price (both in the ERC20 token smart contract and in the limit amount parameter), its tokens are transferred into a newly created Deposit smart contract. The ServiceRegistry smart contract records the new deadline of the service provider's registration. The new deposits cannot be withdrawn until this deadline. After the deadline, the registered service provider can withdraw the deposit. The address of the newly created Deposit contract can be seen as the fourth parameter of RegisteredService(msg.sender, valid_till, amount, depo)
. Extension of an existing registration does not affect the deposits made in the past. In other words, the old deposits can be withdrawn after the originally scheduled deadline.
If the deadline calculation overflows, the deposit does not succeed.
In case the deposit is made successfully, the ServiceRegistry contract remembers the amount as set_price
, and the current timestamp as set_price_at
.
A registered service provider can set a URL (with enough ETH to cover the gas costs). If it has already set a URL, the new URL overwrites the old URL.
The list ever_made_deposits
is public so anybody can call ever_made_deposits()
to see addresses that have ever made deposits. They can call hasValidRegistration()
on element of the list, and determine the set of currently registered services.
This function is not intended for onchain use because the operation might exceed the block gas limit.
The controller can at any time turn on the deprecation switch. Once the deprecation switch is on, it cannot be turned off, no new deposits can be made, and the already made deposits can immediately be withdrawn.
The controller can at any time change the parameters
- the price bump ratio
- the decay constant
- the minimum price
- the registration duration
When the parameters are changed, the ServiceRegistry contract calculates and records the current price using the old parameters. From then on, the price changes according to the new parameters.
The Raiden services will ask for payment in RDN. The Monitoring Service and the Pathfinding Service require deposits to be made in advance of service usage. These deposits are handled by the User Deposit Contract. Usage of the deposit for payments is not safe from double spending, but measures can be taken to reduce the likelihood to acceptable levels. This is a good trade off as long as the money lost on double spending is less than the savings in gas cost.
- Users can deposit and withdraw tokens.
- Tokens can be deposited to the benefit of other users. This could facilitate onboarding of new Raiden users and allow a MS to defer the monitoring to another MS.
- Tokens can't be withdrawn immediately, but only after a certain delay. This allows services to claim their deserved payments before the withdraw takes place.
- Services can read the effective balance of a user (current balance - planned withdrawals)
- Service contracts are trusted and can claim tokens for the service providers.
- Services can listen to events which notify them of decreasing user balances. A service can then claim payments before double spending becomes too likely.
The MS is promised a reward for each settlement in which it took part on behalf of the non-closing participant. Before accepting a monitor request, the MS checks if enough tokens are deposited in the UDC. The MS that has submit the latest BP upon settlement will receive the promised tokens on it's UDC balance.
The PFS will be paid with signed IOUs, roughly a simplified uRaiden adapted to 1-n payments. The IOU contains the amount of tokens that can be claimed from the signer's UDC balance. See OneToN for details.
The OneToN contract handles payments for the PFS. It has been chosen with the following properties in mind:
- easy to implement
- low initial gas cost even when fees are paid to many PFSs
- a certain risk of double spends is accepted
The concept is based on the idea to use a user's single deposit in the
UDC as a security deposit for off-chain payments to all PFSs. The client
sends an IOU consisting of (sender, receiver, amount, expiration,
signature) to the PFS with every path finding request. The PFS verifies
the IOU and checks that amount >= prev_amount + pfs_fee
. At any
time, the PFS can claim the payment by submitting the IOU on-chain.
Afterwards, no further IOU with the same (sender, receiver, expiration)
can be claimed.
Related:
- https://github.com/raiden-network/team/issues/257
- https://github.com/raiden-network/team/issues/256
- https://gist.github.com/heikoheiko/214dbbd954e0f97e0e13b2fefdc7c753
- low latency (<1s)
- reliability, high probability of success (P > 0.99)
- low cost overhead (<5% of transferred amount)
- low fraud rate (< 3%, i.e. some fraud is tolerable)
- can be implemented quickly
When requesting a route, the IOU is added as new JSON object to the :ref:`existing parameters <path_args>` when requesting paths. The IOU object has the following properties:
Field Name | Field Type | Description |
---|---|---|
sender | address | Sender of the payment (Ethereum address of client) |
receiver | address | Receiver of the payment (Ethereum address of PFS) |
amount | uint256 | Total amount of tokens transferred to the receiver within this session (sender, receiver, expiration_block) |
expiration_block | uint256 | Last block in which the IOU can be claimed |
one_to_n_address | address | The OneToN contract for which this IOU is valid |
chain_id | uint256 | Chain identifier as defined in EIP155 |
signature | bytes | Signature of the payment arguments [1] |
The PFS then thoroughly checks the IOU:
- Is the PFS the receiver?
- Did the amount increase enough to make the request profitable for the
PFS (
amount >= prev_amount + pfs_fee
) - Is
expiration_block
far enough in the future to potentially accumulate a reasonable amount of fees and claim the payment - Is the IOU for (sender, receiver, expiration) still unclaimed
- Did the client create too many small IOU instead of increasing the value of an existing one? This would make claiming the IOU unprofitable for the PFS
- Is the signature valid
- Is the deposit much larger than
amount
If one of the conditions is not met, a corresponding error message is returned and the client can try to submit a request with a proper IOU or try a different PFS. Otherwise, the PFS returns the requested routes as described in the current spec and saves the latest IOU for this (sender, expiration_block).
[1] | The signature is calculated by ecdsa_recoverable( privkey, sha3_keccak("\x19Ethereum Signed Message:\n188" || one_to_n_address || chain_id || uint256(5) || sender || receiver || amount || expiration_block) ) You can use |
A OneToN contract (OTNC) which is trusted by the UDC accepts IOUs (see
table above for parameters) and uses the UDC to transfer amount
from
sender
to receiver
. The OTNC stores a mapping
hash(receiver, sender, expiration_block) => expiration_block
to make
sure that each IOU can only be claimed once. To make claims more gas
efficient, multiple claims can be done in a single transaction and
expired claims can be removed from the storage. receiver
has to be
registered in the ServiceRegistry, or otherwise the claiming fails.
Having the field expiration_block
as part of the IOU serves multiple
purposes:
- Combined with the
sender
andreceiver
fields it identifies a single payment session. Under this identifier, multiple payments are aggregated by continuously increasing theamount
and only a single on-chain transaction is needed to claim the total payment sum. After claiming, the identifier is stored on-chain and used to prevent the receiver from claiming the same payments, again. - When old IOUs have expired (
current_block > expiration_block
), the sender can be sure that he won't have to pay this IOU. So after waiting for expiry, the sender knows that IOUs which have been lost for some reason (e.g. disk failure) won't be redeemed and does not have to prepare for unpredictable claims of very old IOUs. - Entries can be deleted from the
hash(receiver, sender, expiration_block) => expiration_block
mapping which is used to prevent double claims after expiry. This frees blockchain storage and thereby reduces gas costs.
Since the same deposit is used for payments to multiple parties, it is possible that the deposit is drained before each party has been paid. This is an accepted trade-off, because the amounts are small and low gas costs are more important, as long as the actual double spending does not reach a high level. To somewhat reduce the risks of double spends, the following precautions are taken:
- Users can't immediately withdraw tokens from the UDC. They first have to announce their intention and then wait until a withdraw delay has elapsed.
- The PFS demands a higher deposit than it's currently owed
amount
to give it some safety margin when other parties claim tokens - Only PFSs registered in the ServiceRegistry are allowed to claim IOUs. This is important because claims allow circumventing the UDC's withdraw delay.
A user and a PFS can theoretically collude to quickly withdraw the complete deposit (via a claim) before other services are paid. This should be unlikely due to the following aspects:
- The savings achieved by cheating the other services are low compared to the coordination cost for the collusion
- The PFS is itself a party receiving payments of services and does not want to promote cheating against services
- If this becomes widespread, cheating users can theoretically be blacklisted by PFSs. This will require them to close their existing channels and reopen new channels at a cost which will most likely be higher than the profit gained by cheating
The :ref:`MS` submits an up-to-date :term:`balance proof` on behalf of users who
are offline when a channel is closed to prevent them from losing tokens. This
could be done without a dedicated contract by calling
TokenNetwork.updateNonClosingBalanceProof <update-channel> but then the MS
would not be able to claim a reward for its work. To handle the rewards, the
MonitoringService contract provides two functions. monitor()
for wrapping
updateNonClosingBalanceProof and creating the reward and claimReward()
for
claiming the reward after the settlement. monitor()
only works for service
providers that are registered in ServiceRegistry:
function monitor(address closing_participant, address non_closing_participant, bytes32 balance_hash, uint256 nonce, bytes32 additional_hash, bytes memory closing_signature, bytes memory non_closing_signature, uint256 reward_amount, address token_network_address, bytes memory reward_proof_signature) public Called by a registered MS, when providing a new balance proof to a monitored channel. Can be called multiple times by different registered MSs as long as the BP provided is newer than the current newest registered BP. Parameters: nonce – Strictly monotonic value used to order BPs omitting PB specific params, since these will not be provided in the future reward_amount – Amount of tokens to be rewarded token_network_address – Address of the Token Network in which the channel being monitored exists. reward_proof_signature – The signature of the signed reward proof function claimReward(uint256 channel_identifier, address token_network_address, address closing_participant, address non_closing_participant) public returns (bool) Called after a monitored channel is settled in order for MS to claim the reward Can be called once per settled channel by everyone on behalf of MS. Parameters: token_network_address – Address of the Token Network in which the channel exists closing_participant – Address of the participant of the channel that called close non_closing_participant – The other participant of the channel
In order to avoid an unproductive race between service providers, for the same participant on the same channel, different service providers have different block numbers from which they can call monitor()
. Here we are not trying to get randomness, but merely trying to make differences in a fair way.
When the :ref:`MS` calls monitor()
with a :term:`balance proof`, MonitoringService
smart contract submits the balance proof on behalf of the :ref:`MS`. If that succeeds, MonitoringService
smart contract records the fact that the :ref:`MS` has submitted the balance proof.
monitor()
refuses callers that are not registered inServiceRegistry
.