-
Notifications
You must be signed in to change notification settings - Fork 1.2k
[WIP] DIP2/DIP3/DIP4 Implementation #2083
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
c1f5e15
to
8ffe44f
Compare
8ffe44f
to
7dab2df
Compare
Build is mostly fixed, but currently running into timeouts. I hope it's just Travis being slow atm. The "fix" is actually also a refactoring of how we do Travis builds. |
3bf12c2
to
d5ee817
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a complete review, only got as far as src/evo/evodb.cpp
for now.
.travis.yml
Outdated
- if [ "$RUN_TESTS" = "true" ]; then qa/pull-tester/rpc-tests.py --coverage; fi | ||
- cd ../.. | ||
- > | ||
docker run -t --rm \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it make sense to extract this to a docker-compose.yml
file? Would be the same amount of complexity, but .travis.yml
would be less cluttered.
self.sha256 = tx.sha256 | ||
self.hash = tx.hash | ||
|
||
def deserialize(self, f): | ||
self.nVersion = struct.unpack("<i", f.read(4))[0] | ||
ver32bit = struct.unpack("<i", f.read(4))[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it necessary to read as ver32bit? Since the version field is uint32 and little-endian, and backwards-compatible (meaning, there shouldn't be any current TXes with version fields except 1, 2, so this should be do-able with just:
self.nVersion = struct.unpack("<h", f.read(2))[0]
, and likewise for self.nType
, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, but I found this more explicit in what is happening there.
self.nVersion = struct.unpack("<i", f.read(4))[0] | ||
ver32bit = struct.unpack("<i", f.read(4))[0] | ||
self.nVersion = ver32bit & 0xffff | ||
self.nType = (ver32bit >> 16) & 0xffff |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this is already right-shifted 16 bits, it looks like this last bitmask shouldn't be necessary. Unless I'm missing something (which is possible).
But, if my comments above are also correct, then this should be a non-issue anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've got burned by C/C++ in the past when it comes to right shifting of (possibly) negative numbers as it extends the sign bit. This means, 1110 >> 1 = 1111 and not 0111 as someone might expect. And as I understand this, it's the same in python.
self.sha256 = None | ||
self.hash = None | ||
|
||
def serialize(self): | ||
r = b"" | ||
r += struct.pack("<i", self.nVersion) | ||
ver32bit = int(self.nVersion | (self.nType << 16)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same logic as above, but in reverse.
src/evo/deterministicmns.cpp
Outdated
|
||
auto payee = oldList.GetMNPayee(); | ||
|
||
for (const auto& dmn : newList.all_range()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this supposed to do something?
src/evo/deterministicmns.cpp
Outdated
for (const auto& dmn : newList.all_range()) { | ||
} | ||
|
||
for (int i = 1; i < (int)block.vtx.size(); i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this start at 1 to intentionally skip the coinbase? If so, I'm wondering if maybe this warrants a comment here somewhere, as an explanatory note to other developers.
a97c0d2
to
ca345ad
Compare
Allows testing of features which depend on BIP9 deployments
We need to update the CbTx's merkle root for the masternode list here as tests might add transactions into a block which cause the list to change.
As we do with ContextualCheckBlock
…licating the check
This also moves the TX version check logic into ContextualCheckTransaction
e57841e
to
483070a
Compare
A couple of patches as a result of local debugging:
|
Closing as DIP2, DIP3 and DIP4 have been merged into develop as part of separate PRs. |
Introduction
This is my current version of the DIP2/DIP3/DIP4 implementation. It is not meant to be merged for 12.3 but for a later version (most likely 12.4)
Links to DIPs:
DIP2 — Special Transactions
DIP3 — Deterministic Masternode Lists
DIP4 — Simplified Verification of Deterministic Masternode Lists
Note for previous and new reviewers
Originally, this code was part of a PR in our private dashevo repo. It is however cleaned up (squashed and rebased all the review related commits) and also contains the DIP4 implementation, which was missing in the initial PR. It also contains a few more fixes and a refactoring to optimize the performance of deterministic MN bookkeeping. I decided to not squash these new fixes and refactorings into the older commits to make life easier for reviewers who already reviewed the old version of the PR. For these reviewers, the new changes start from commit 2ffdb238676d462683739a075bfc27fa06cde8d6 ("Implement and enforce CbTx with correct block height and deprecate BIP34").
Deployment
These DIPs will be deployed through a combination of a BIP9 style deployment and a spork (SPORK_15_DETERMINISTIC_MNS_ENABLED). When the BIP9 deployment is activated, MNOs will be allowe to broadcast DIP3 special transactions and miners will be allowed to mine DIP2/DIP3 special transactions. This will also activate the bookkeeping (register, update, remove, ...) of deterministic masternodes.
It will however NOT start the use of deterministic masternodes and the old type of MNB/MNP based masternode system will keep being active. Only when spork15 gets activated, the whole network will switch over to use deterministic masternodes for all kinds of MN operations.
Between the BIP9 activation and the spork15 activation, MNOs are expected to gradually move their MNs to be ProTx based. They'd still have to issue "mn-start" on their masternodes as the old system is still active at this stage. A ProTx is internally just a normal transaction with a special payload, and thus is still a valid collateral for a MN of the old system. Between the BIP9 activation and spork15 activation, masternode broadcasts will be rejected if the collateral was created after BIP9 activation and is not a valid ProTx.
The reason for this multi stage deployment is that we must ensure that enough MNOs have switched to deterministic masternodes first before we actually switch over to use the new deterministic list. This should ensure a smooth migration without interruptions.
Spork15 is block height based and not time based (as other sporks usually are). When we see enough MNs being upgraded to ProTX, we will set spork15 to a block height in the near future. When that height is reached, all nodes in the network will drop the legacy masternode list and switch to the deterministic list. At the same time, all communication in regard to MNBs, MNWs and MNPs will be stopped. Also, mnsync will slightly change so that it completely skips the MASTERNODE_SYNC_LIST and MASTERNODE_SYNC_MNW state.
The masternode.conf file will then be ignored and masternode start-xxx won't be required anymore. As long as the MNO has -masternodeprivkey= correctly set, the node will immediately start functioning when it encounters the ProTX on-chain. If the node was started correctly and functioning as a legacy MN at the time of spork15 activation, it will switch to the deterministic mode automatically without the MNOs intervention.
Code-level compatibility between legacy and deterministic mode
As DIP3 has effects on many parts of the code and we at the same time need to support legacy and deterministic mode in the same code base, I decided to not touch too much of the legacy code and design. Instead, I tried to keep the interfaces of classes like CMasternode, CMasternodeMan, CMasternodePayments, ... as compatible as possible with the legacy code and implement the switching logic into these classes. The result is that these classes now contain some ugly if/else statements but at the same time all the other code that uses these classes is mostly untouched. IMHO, this reduces the risk of introduced bugs dramatically. After spork15 activation, my next task will be to clean up the code again and remove all the legacy code so that we have a clear codebase again.
Maintaining the deterministic MN list
Each block might result in a new version of the MN list. These lists are maintained by CDeterministicMNListManager. ProcessBlock is called for each block, which loops through all ProTx of the block and creates a new MN list based on the previous list and the freshly gathered information. It also scans for spends of ProRegTx collaterals and removes MNs if needed. The information about what has been added/updated/removed is stored in leveldb once per block (with CDeterministicMNListDiff).
Every 576 blocks, a snapshot of the list of the current block is stored in leveldb. Based on the snapshots and the diffs that follow, the list for every block height can be reconstructed (see GetListForBlock). A cache is used to avoid doing this too often.
A list is represented by CDeterministicMNList. It internally uses pure functional maps (immer::map) and shared pointers to hold the actual list and information about each entry. (Note for previous reviewers: this was different in the initial version). The reason for these data structures is that it simplifies the deduplication of slightly changed lists between blocks, which in turn dramatically reduces the memory usage.