Try   HackMD

Ultra Light Client in details

Definitions

  • TD - the total difficulty of the chain until a given block
  • LES - light Ethereum subprotocol
  • ULC - ultra light client, an option of LES
  • CHT - Canonical Hash Trie which maps historical block numbers to their canonical hashes in a Merkle trie. This allows us to discard the block headers themselves in favor of a trie root which encompasses the accumulation of their hashes, and to fetch proofs that a specific block hash is in fact the one we verified earlier [1]

Overview

ULC is a new option in LES that doesn't break compatibility with the LES protocol, but does significantly reduce the time and resources required to sync with the main Ethereum chain.
The main idea is about reducing the amount of messages and doing less client side validation.

What does ULC solves?

  1. CPU and battery consumption
  2. Time to start sync
  3. Time to finish sync

ULC in schemes

Algorithm

Created with Raphaël 2.2.0Start LES clientwith ULC optionsConnect to N Trusted LES serversHold these connectionsAsk for signed announcementsfor the block with max TDSync CHTs up to known max TDAsk for N latestsigned announcementsAre M of N announcements the same?Are all signs valid?Ask usual LES server (can be different each time)for a block headeryesno

Dataflow

Created with Raphaël 2.2.0ULCULCN_TrustedN_TrustedUntrustedUntrustedhandshakesetup LES paramshandshakeCHT syncAsk to announceHighest announceannounce:block hash, TD, numberAsk CHTsCHT "chain" syncSync headers startingfrom latest block CHT + 1Ask to announceannouncehandshakehandshakeask block headerheaderheader:block announce, logsBloom,Merkle trees roots:state, transactions, receipts

Validation of header "chain" for LES and ULC

  1. sanity check that the provided chain is actually ordered and linked. If we have a header chain of length N, for every
    ni
    and
    ni1
    ,
    iϵ[0;N]
    , conditions should hold:
    1.1.
    ni.Number=ni1.Number+1

    1.2.
    ni.ParentHash=ni1.Hash
  2. in Ethereum Yellow Paper section 4.3.4. "Block Header Validity"[2]
    2.1. The length of
    ni.Extra<32bytes

    2.2. Checks block timestamp:
    2.2.1. It shouldn't be from future more than 15 secs
    2.2.2.
    ni.Time>ni1.Time

    2.3. verify the block's difficulty based on it's timestamp and parent block's difficulty:
    ni.Difficulty=expectedDifficulty(ni)

    2.4.
    ni.gasLimit
    shouldn't overflow 2^63-1
    2.5.
    ni.gasUsed<=ni.gasLimit

    2.6. checks gas limit:
    2.6.1. should be more than MinGasLimit:
    ni.gasLimit>=5000

    2.6.2. the change of
    ni
    gas should be bounded:
    |parent.GasLimitheader.GasLimit|<parent.GasLimit/1024

    2.7. validate hard forks special fields, eg. every
    niϵ[DAOForkBlock;DAOForkBlock+10]
    should have special value in ExtraData field
    2.8. Verify a seal securing the block

Verify a seal of a block

The main improvement of ULC is that a client doesn't need to verify the seal of a block and can skip this step at all.

Ethereum light clients (actually fast and light) have slow-but-light PoW verification. Full clients have fast-but-heavy PoW verification. The main difference is that full clients generate all the data needed to verify every block in an epoch, but light clients calculate many values on-the-fly (see

generateDatasetItem below on Verify step).

The detailed algorithm can be found on the Ethereum wiki.

The verification has 2 steps: init caches and verify. Let's describe them in detail.

Init step

All numbers below are given for the Epoch 232 (a current epoch at 12 Nov 2018)
Some parts of this step can be run in parallel.
All numbers and the algorithm steps are from geth code

It runs once per epoch: epochLength = 30000 blocks ~ each 3.5 days = twice per week

It needs to generate a verification matrix of pseudo-random values (called cache).

Calculate seedHash in epochNumber steps = 232 sha3 operations
Calculate the initial cache in: cacheSize/64 steps = 51641792/64 = 806900 sha3 operations. CacheSize can be taken from table cacheSizes, for epoch=232 it equals 51641792.

At the end of the day

Ocpu(initStep)=NO(sha3), where N is a current block number.

For example, for epoch 232 (a current epoch at 12 Nov 2018)

Ocpu(initStep)=Ocpu(seedHash)+Ocpu(initCahce)=807133O(sha3)

This is theoretical lower bound. The Ethash Design Rationale mentions that "a light client should become fully operational and be able to verify blocks within 40 seconds in Javascript".

Verify step

Light mode seal verification doesn't store the entire dataset for block verification, but generates necessary items on-the-fly. For a single block it runs the hashimotoLight algorithm which takes:

generateDatasetItem=(2sha3+512fnv)

More details about FNV32-1 hash function can be found here.

hashimotoLight=loopAccessesmixBytes/hashBytes(generateDatasetItem+fnvHash)
hashimotoLight=64128/64(generateDatasetItem+fnv)

hashimotoLight=128(generateDatasetItem+fnv)

hashimotoLight=256sha3+65664fnv

This is the difference between ULC and LES clients for each block. Because the CHT is generated once per 32767 blocks, the total difference is

[1;32767]Cost(hashimotoLight)=[256O(sha3)+65664O(fnv);8.388.352O(sha3)+2.151.612.288O(fnv)]. The growth is linear.

This is the theoretical lower bound. As noted in the Ethash Design Rationale, a single block verification step should take '0.1 seconds in Python'. In practice it takes ~200ms in Geth.

LES vs ULC

So ULC saves 807133*O(sha3) at init stage, which happens each epoch and [256O(sha3) + 65664O(fnv) ; 8.388.352O(sha3) + 2.151.612.288O(fnv)] for each block while syncing. Because the difficulty of block verification grows linearly, the total difficulty of syncing N blocks grows as

N2.

ULC in Roles

Trusted LES servers

Trusted LES servers are needed only to send announcements (in Geth code it has name announce(block hash, TD, number)) to LES(ULC) clients. All announcements should be signed. Trusted servers don't know whether they have been chosen as trusted or not by any given client. Such servers can be started with an onlyAnnounce flag, which ensures that the LES server operates under the rule "only send announcements to my peers, do not process any other requests".

LES servers (untrusted)

LES servers - usual LES servers, a header chain is synchronised with them. Helps to prevent attacks on trusted servers.

ULC client

  1. has some CHT root at the start; has a CHTs "chain", that can be synced from LES servers; CHT chain allows to request any historical information (block, transaction, receipt) from LES server

  2. trusts announcements received from N Trusted LES servers. Announcements should be signed by Trusted LES servers. There should be at least M identical announcements to trust.

  3. Asks for announcements with the biggest TD

  4. ULC client starts CHT sync before syncing header chain. ULC client requests newer CHTs from LES servers.

  5. requests headers from untrusted LES server, starting from the highest block is known to latest CHT + 1 up to latest block number known from trusted announce

  6. ULC client validates:

    6.1. announcements checking are there M the same announcements from N received from Trusted LES servers

    6.2. validates headers, as usual, LES client except VerifySeal. ULC doesn't run VerifySeal at all.

  7. doesn't validate CHT. If we get incorrect CHT, it'll be clear later after receiving block headers.

ULC client resources

Network

  • CHT is received from a single LES server that is considered "best to sync" at the moment
  • headers are received from a single LES server
  • announcements are received from N Trusted LES servers

Network connections

  • tries to be always connected to N Trusted LES servers, in case of disconnection it reconnects
  • handles a usual number of connections to LES servers

Storage

  • at least one CHT, but we can have several consecutive CHTs
  • headers chain of blocks of the latest Epoch

Security

Main issues

  1. Mostly are inherited from LES
  2. Too few LES servers in the Network
  3. Trusted servers discovery (?)
  4. DoS on trusted nodes

Sybill attack on ULC (client)

Prevented because it is already prevented in a classic LES model and we only download headers with trusted announcements.

Sybill attack on trusted servers

Even less possible than in LES model because it's needed to attack at least M servers.

DoS on trusted servers

Possible. ULC makes it much less possible by hiding what nodes are trusted for each user. A user doesn't send any unusual for LES information to LES servers trusted or untrusted. Any trusted for a user LES server doesn't know that it has been chosen by the user to be trusted LES server.

MITM

Prevented because all announcements must be signed by according LES server.

What other security guarantees does ULC give and what is it comparable with?

Some math

There're 2 kinds of security guarantees:

  1. reducing the probability of failure to perform a correct request due to the failure of remote servers - failure and censorship resistance
  2. reducing the need to trust in data coming from malicious nodes

The very mechanism of blockchain synchronisation of the ULC is the same as that of the LES. Therefore, comparing the security guarantees of ULC with full, fast and LES does not make sense. It is more important to compare the guarantees of a private RPC server or Infura with ULC.

If the probability of failure* or hacking Infura or RPC server is taken as P, then with the ULC consensus M/N trusted LES nodes, the probability of its failure can be considered as Bernoulli process:

PULC_failure=i=MNCNiPi(1P)Ni

*Failure - in the sense of Bezianteene failure so that it can be either node crush or malicious actions. Such a failure for a trusted LES node can be sending fake announcements. So trusted LES node can try to "switch" a user to a malicious chain.

For example, let's calculate the failure probability

PULC_failure while syncing or getting an incorrect state, given N=10, M=6 and the failure probability of a single node P:

PULC_failure=C106P6(1P)4+C107P7(1P)3+C108P8(1P)2+C109P9(1P)+C1010P10

PULC_failure=210(1P)4P6+120(1P)3P7+45(1P)2P8+10(1P)P9+P10

Let's take several values of the LES server failure probability and see what the probability of ULC breakage of the client is:
|

Pserver_failure | 5% | 1% | 0.1% | 0.01% |
|-|-|-|-|-|-|-|-|-|-|-|
| ~
PULC_failure
|
106%
|
1010%
|
1016%
|
1022%
|

0-1

0-0.001

So ULC drastically increases censorship resistance of an Ethereum client. We can develop a far more reliable system using unreliable nodes.

A few N/M, given

Pserver_failure=0.01%:

N M ~
PULC_failure
3 2
3108%
4 2
6108%
4 3
41012%
5 2
107%
5 3
1011%
5 4
51016%
6 2
1.5107%
6 3
21011%
6 4
1.51015%
6 5
61020%

Values 2/3, 3/4, 3/5, 4/5 look like reasonable values to use in ULC client.

N M ~
PULC_failure
3 1
3108%
4 1
6108%
5 1
107%
5 2
1011%
6 1
1.5107%
6 2
21011%

Trusted nodes

ULC clients need a set of trusted LES servers to get the current Chain state. It should be said that only ULC clients knows their own trusted list, LES servers don't know whether they've been chosen as trusted by some ULC client. The key difference is that ULC clients request and accept only the signed announcements needed to trust some nonce (PoW) without performing their own check.

We're going to provide, predefined in the App, a trusted LES servers list. This also means that an application wanting to use ULC can define their own such trusted list of clients and do load balancing with a simple random choice.

One of the major drawbacks currently, however, is that LES servers can handle only a limited number of clients. At the moment it's

LES_limit=25.

So if we want 3(M) out of 4(N) ULC consensus, in average we have 1000 users online, so we need minimum

Servers=Max(AverageUsersN/LES_limit;N)=Max(10004/25;4)=160

It is for this reason that a new option was added in ULC for LES servers: --onlyAnnounce. This flag ensures LES servers only handle get announce requests, which increases the possible number of simultaneous users to about ~250 (should be stress tested).

With --onlyAnnounce the formula looks like:

Servers=Max(AverageUsersN/LES_only_announce_limit;N)=Max(10004/250;4)=Max(16;4)=16

Users online N Server w/o onlyAnnounce With onlyAnnounce
1000 4 160 16
1000 5 200 20
1000 6 240 24
1000 7 280 28
5000 4 800 80
5000 5 1000 100
5000 6 1200 120
5000 7 1400 140
10000 4 1600 160
10000 5 2000 200
10000 6 2400 240
10000 7 2800 280

It's obvious that scaling due to server expansion inside the service is strictly limited. A prerequisite for the operation of ULC at large scales is an increase in the percentage of LES of servers relative to all Ethereum servers.

At the moment there are 15000 nodes. If 30% of them would use the LES server option, more than 300 000 ULC users could be handled simultaneously.

Benchmarks

Our beta test showed that ULC sync is ~10x times faster than LES

Plans

Short-Term

Status.im is going to start using ULC to achieve greater censorship resistance and enable all possible web3 features for DApps and developers.

Long-Term

  • On ULC incentives
  • Ethereum services are going to have microtransactions and this will make possible to create a market of LES server quotas using the proposed LES service model