feat(nano): implement storage module [part 4]#1279
Conversation
3691e76 to
8c6b9d4
Compare
ccbd984 to
646105d
Compare
Codecov ReportAttention: Patch coverage is
❌ Your project status has failed because the head coverage (81.49%) is below the target coverage (82.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## master #1279 +/- ##
==========================================
- Coverage 82.90% 81.49% -1.41%
==========================================
Files 362 373 +11
Lines 25838 26697 +859
Branches 3956 4060 +104
==========================================
+ Hits 21420 21758 +338
- Misses 3691 4202 +511
- Partials 727 737 +10 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
8c6b9d4 to
d29507c
Compare
646105d to
5a6ca04
Compare
5a6ca04 to
ded9471
Compare
|
| Branch | feat/nano/storages |
| Testbed | ubuntu-22.04 |
Click to view all benchmark results
| Benchmark | Latency | Benchmark Result minutes (m) (Result Δ%) | Lower Boundary minutes (m) (Limit %) | Upper Boundary minutes (m) (Limit %) |
|---|---|---|---|---|
| sync-v2 (up to 20000 blocks) | 📈 view plot 🚷 view threshold | 1.58 m(-2.78%)Baseline: 1.63 m | 1.47 m (92.57%) | 1.79 m (88.38%) |
| raise NotImplementedError | ||
|
|
||
|
|
||
| class MemoryNodeTrieStore(NodeTrieStore): |
There was a problem hiding this comment.
Should we remove this memory trie store? Or we should make it serialize and deserialize objects to behave as the RocksDB trie store.
|
|
||
| last_match: bytes = b'' | ||
|
|
||
| while True: |
There was a problem hiding this comment.
Should we add a maximum number of iterations to prevent an infinite loop caused by a bug?
| self._trie: PatriciaTrie = trie | ||
|
|
||
| # Nano contract id | ||
| self.nc_id = nc_id |
| key: bytes | ||
|
|
||
| def __bytes__(self) -> bytes: | ||
| return _Tag.ATTR.value + hashlib.sha1(self.key).digest() |
There was a problem hiding this comment.
Should we keep using sha1? In fact, do we need to hash at all? I guess so because it is what guarantee that the tree will be balanced.
| token_uid: bytes | ||
|
|
||
| def __bytes__(self) -> bytes: | ||
| return _Tag.BALANCE.value + self.token_uid |
There was a problem hiding this comment.
Should we assert that len(self.token_uid) in {1, 32}?
| key: bytes | ||
|
|
||
| def __bytes__(self) -> bytes: | ||
| return _Tag.METADATA.value + hashlib.sha1(self.key).digest() |
There was a problem hiding this comment.
Same here. Should we keep using sha1? In fact, do we need to hash at all?
| token_uid: bytes | ||
|
|
||
| def __bytes__(self) -> bytes: | ||
| return _Tag.BALANCE.value + self.token_uid |
There was a problem hiding this comment.
Should we hash the token_uid? Or is it random enough to guarantee that the tree will be balanced?
|
|
||
|
|
||
| @dataclass(frozen=True, slots=True) | ||
| class MetadataKey(TrieKey): |
There was a problem hiding this comment.
Move this TrieKey closer to the others.
cf70f19 to
72d6938
Compare
72d6938 to
d2eaf78
Compare
| def __len__(self) -> int: | ||
| it = self._db.iterkeys() | ||
| it.seek_to_first() | ||
| return sum(1 for _ in it) |
There was a problem hiding this comment.
Is this used anywhere? If not, it could be removed because it runs in O(n). If we do need it, we should implement a stats column that keeps a count.
| class TrieKey(ABC): | ||
| @abstractmethod | ||
| def __bytes__(self) -> bytes: | ||
| raise NotImplementedError |
There was a problem hiding this comment.
The block storage doesn't use this and it uses NamedTuples intead of dataclasses. We should probably unify this.
| assert isinstance(balance, MutableBalance) | ||
| return balance | ||
|
|
||
| def get_all_balances(self) -> dict[BalanceKey, Balance]: |
There was a problem hiding this comment.
This is using internal methods from the trie, review whether they should be public or if this method should be moved.
f65c9a2 to
24fa883
Compare
|
LGTM ✅ |
There was a problem hiding this comment.
We had talked about shaving off a byte from this class' serialization by removing the tag and using an "empty byte sequence" to represent a DeletedKey. It's possible to do that "outside" of the NCType (de)serialization system. It changes the root-id hash, but for now that is acceptable.
I think this isn't important just noting so we don't forget.
| return self.melt is _NCAuthorityState.REVOKED | ||
|
|
||
|
|
||
| class NCChangesTracker(NCContractStorage): |
There was a problem hiding this comment.
Or rather a NCContractStorageInterface (not Protocol), but yeah, I agree.
| @override | ||
| def check_if_locked(self) -> None: | ||
| if self.has_been_commited: | ||
| raise RuntimeError('you cannot change any value after the commit has been executed') | ||
| elif self.has_been_blocked: | ||
| raise RuntimeError('you cannot change any value after the changes have been blocked') |
There was a problem hiding this comment.
I think we could use type-states for this, similar to the pattern used by Balance/MutableBalance, we could expose a NCContractStorageLocked which only has read methods (which it forwards to its nc_storage: NCContractStorageInterface) and a NCContractStorageUnlocked which also exposes methods with side effects, lock and unlock methods can convert one into the other (and even prevent double-lock/double-unlock).
| def __len__(self) -> int: | ||
| it = self._db.iterkeys() | ||
| it.seek_to_first() | ||
| return sum(1 for _ in it) |
| def get_root_id(self) -> bytes: | ||
| """Return the current merkle root id of the trie.""" | ||
| return self._trie.root.id |
There was a problem hiding this comment.
I think we need a combinations of tests to cover the "stability" of this method. Even though breaking it currently doesn't break the network (in the sense that the blockchain can still sink and has the same observable state), it will break the storage and require some sort of migration. So we have to make it very clear when a PR can cause that.
| # XXX: id is not optional, we're indicating that only nodes with id can be stored | ||
| _id: NCType[NodeId] |
There was a problem hiding this comment.
This is currently the only reasons for this class to be "manual", maybe some tweaks to Node could simplify this and get rid of this module.
Motivation
Implement the storage module.
Acceptance Criteria
Checklist
master, confirm this code is production-ready and can be included in future releases as soon as it gets merged