DEP#001 Difficulty agreement protocol
- Related to: blockchain, proof-of-work
- Requires protocol upgrade: Yes
Problem description
As of Duniter Protocol v1.0, the blockchain can be written only by members following this rule: at any moment, any member can issue a new block with the contents of its will.
This implies 3 problems:
-
Empty block easiness: an empty block is faster to compute than a full one, because the proof-of-work can be started immediately without extra computing due to the inclusion of new data (trying to include transactions, identities, certifications and check all the rules). So if a malicious node wanted to slow down the inclusion of new data in the blockchain, it would just need to compute empty blocks. Not only is this behavior annoying, but it will also have some computing advantage because of the non-inclusion of new data.
-
Empty blockchain: if a group of malicious nodes was to adopt the behavior described in 1), and was also big and powerful enough compared to the rest of the computing nodes, this group could easily break the currency by making empty blocks.
-
Deliberate forks: if a group of malicious nodes similar to 2) was trying to make blockchain forks deliberately — thanks to its high computing power, he could compute 2 valid blocks and spread each of them to half of the network, creating a fork — this could slow down the blockchain seriously.
Proposed enhancement
The proposition would be to have an additional global handicap, depending on current computing members. This additional handicap would be decreased for each agreement received by other nodes and included in the computed block.
The agreements are signed by their issuer, and are issued between each proof-of-work. Only computing members can issue them. Computing members are known to be the issuers of the last IssuersCount
block in the blockchain.
In the above example, we see Node 1 (N1) has a difficulty of 60, like N2, N3, N4. This difficulty = N1's difficulty + global handicap. Let's say that here, N1's difficulty = 56 and global handicap = 4, because we have 4 computing nodes. So the basic difficulty for N1 = 60.
N1 receives 3 agreements:
- N2's agreement
6TX:B30A
: translation « you can lower your difficulty by one if the block you compute contains at least 6 transactions, and is based on previous block #30 (closed), hashA
». - N3's agreement
4TX:B30A
: translation « you can lower your difficulty by one if the block you compute contains at least 4 transactions, and is based on previous block #30 (closed), hashA
». - N4's agreement
4TX:B30F
: translation « you can lower your difficulty by one if the block you compute contains at least 4 transactions, and is based on previous block #30 (closed), hashF
».
If we consider N1, it's final difficulty varies depending on the block it computes:
- computing
B30A
, with 6 transactions: N1'sD = 60 - 1 - 1 = 58
, because he received 2 agreements from other nodes complying with this situation. - computing
B30A
, with 4 transactions: N1'sD = 60 - 1 = 59
, because he received 1 agreement from other nodes complying with this situation. - computing
B30A
, with 3 transactions: N1'sD = 60
, because he did not receive any agreements from other nodes complying with this situation. - computing
B30F
, with 4 transactions: N1'sD = 60 - 1 = 59
- computing
B30G
, with 10 transactions: N1'sD = 60
- computing
B30G
, with 20 transactions: N1'sD = 60
- computing
B30G
, with 1000 transactions: N1'sD = 60
Consequences
-
Block easiness: making empty block, whereas most of the network is expecting 4 transactions (because they see these transactions pending in their sandbox), would require the maximum difficulty, which is equal to "personal difficulty + global handicap". So the computing members will have a word on what can be and what cannot be the next block. Note how this does not prevent a malicious node to compute an empty block, but it will be harder to go against the global agreement. The more we have computing members, the harder is to go against the global will.
-
Empty blockchain: since it is harder to make empty blocks, it is even harder to make an empty blockchain.
-
Deliberate forks: even under the hypothesis of a special task force able to create blocks on their will before anyone, and make the network fork, at least this would be costly for them. It will be even more costly as they don't integrate expected data. So we have more chances that even if they create forks, at least some data is in each fork.
-
Global difficulty decrease: with such an algorithm, it becomes possible to encourage low nodes to compute blocks. Indeed, each node is free to share its agreement with the nodes it wants. If a node or a group of node decide to favor nodes with low computing power, they can do it. They can also not favor nodes they consider malicious.
Precautions
Of course if some nodes stop sending agreements, this would globally increase the difficulty. This is very likely to happen, since all nodes are not permanent but daytime nodes.
Also, since an Agreement is issued at every block and to potentially all the computing nodes, this would make a lot of network data. We could consider a high limit for the global handicap equal to 80 (= 5*16 = 5 zeros ~= 1.048.576 times harder proof-of-work). So to have the lowest difficulty, a node would require 80 agreements: this value seems acceptable on long term run, even more as we expect thousands of transactions per block (5' block interval).
Technical impacts
This would impact protocol's difficulty rule, block document structure, create a new « Agreement » document and handling Agreement's generation and interpretation.
Difficulty rule
The rule would be changed to include a new NB_AGREEMENTS
variable.
Formula would change from:
MAX [ HEAD.powMin ; HEAD.powMin * FLOOR (percentRot * nbPreviousIssuers / (1 + nbBlocksSince)) ] + PERSONAL_HANDICAP
to:
MAX [ HEAD.powMin ; HEAD.powMin * FLOOR (percentRot * nbPreviousIssuers / (1 + nbBlocksSince)) ] + PERSONAL_HANDICAP - NB_AGREEMENTS
Agreement document
It should be defined an Agreement document, which could look like:
Version: VERSION
Type: NetworkAgreement
Currency: CURRENCY
NbJoiners: NB_JOINERS
NbActives: NB_ACTIVES
NbLeavers: NB_LEAVERS
NbRevocations: NB_REVOCATIONS
NbCertifications: NB_CERTS
NbTransactions: NB_TX
BOTTOM_SIGNATURE
It's inline format would look like:
NBJ:NBA:NBL:NBR::NBC:NBT:SIGNATURE
Where:
-
NBJ
is the number of expected records underJoiners
-
NBA
is the number of expected records underActives
-
NBL
is the number of expected records underLeavers
-
NBR
is the number of expected records underRevocations
-
NBC
is the number of expected records underCertifications
-
NBT
is the number of expected records underTransactions
Block's structure
It would be added an NetworkAgreement
list before the InnerHash
:
NetworkAgreement:
INLINE_NETWORK_AGREEMENT
...
Document handling
-
On document's reception: parse and verify the document signature and compliance with currently generated bloc.
-
On block generation: generate a new block, count the number of each entity that is expected to be written, sign the document, pick a peer selection to which send the document.