-
Notifications
You must be signed in to change notification settings - Fork 370
Transaction pinning: #1599 #1632
base: dev
Are you sure you want to change the base?
Conversation
…s based on the current TxModel and rocksdb persistence layer.
- Introduced an interface for permanent storage. - Added a permanent storage PersitenceProvider - Added mergeTwo() function to genericly handle class independant merging. - Changed Tangle.load() to handle multiple persistence providers and handle with mergable results (like searching addresses in two persistence providers) - Tangle.load() didn't handle this because persistenceProvider.get never returned null, only a new (empty) instance of the requested object. The bytes() wasnt uniform in implementation and would for example throw an exception on Milestone, the MilestoneViewModel did the null check. Therefore the introduction of isEmpty() for Persistables.
- Added functions to the tangle to handle permanent storage - Added API's: * pinTransactionHashes (hashes:[]) * pinTransactionTrytes (trytes:[]) * increaseTransactionCount (hashes:[]) * decreaseTransactionCount (hashes:[]) * getTransactionsCount (hashes:[]) - or others to test this branch: set default settings for permanent storage
…isPinned is left.
# Conflicts: # src/main/java/com/iota/iri/Iota.java # src/main/java/com/iota/iri/model/StateDiff.java # src/main/java/com/iota/iri/model/persistables/Hashes.java # src/main/java/com/iota/iri/model/persistables/Milestone.java # src/main/java/com/iota/iri/model/persistables/Transaction.java # src/main/java/com/iota/iri/storage/Persistable.java # src/main/java/com/iota/iri/storage/Tangle.java
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.
This is an initial review.
Overall it looks good :-)
After you fix my few nits I will take another look.
Talk to me in private if you are being blocked
break; | ||
} | ||
default: { | ||
throw new NotImplementedException("No such database type."); |
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 prefer to use UnsupportedOperationException
, just because it doesn't have a dependency on a 3rd party library
Co-Authored-By: Gal Rogozinski <[email protected]>
Co-Authored-By: Gal Rogozinski <[email protected]>
Co-Authored-By: Gal Rogozinski <[email protected]>
Co-Authored-By: Gal Rogozinski <[email protected]>
Co-Authored-By: Gal Rogozinski <[email protected]>
Co-Authored-By: Gal Rogozinski <[email protected]>
Co-Authored-By: Gal Rogozinski <[email protected]>
Co-Authored-By: Gal Rogozinski <[email protected]>
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.
Looked at it a bit more now
The design change I am asking for is rather important from my POV because it will save us work later I believe
if (dbResult != null && dbResult.length > 0) { | ||
return new TransactionViewModel(TransactionViewModel.trits(dbResult), Hash.NULL_HASH); | ||
} | ||
return null; |
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.
Currently, we always return a new empty transaction object, which is obviously bad.
In #1591 we said we will return an instance of an immutable static dummy object instead. I prefer that you do that.
If you prefer to return null
, please explain why and make sure the code is impervious to null pointer exceptions
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.
Making an empty static object would be fine with me as well but I think that is outside of the scope for this pull request. the function getTransaction is part of the PermanentPersistenceProvider and not of the PersistenceProvider.
I chose null because it is more explicit than an empty object. In the get function of the PersistenceProvider interface this is explicitly handled.
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.
It is a possible way to go about it.
However, the reason I preferred to go about an empty object is because then you have to remember to always do null
checks. If you forget (like in safeDeleteTransaction
:-P) you later have to go back and add it...
The thing is that probably the PermanentPersistenceProvider
and PersistenceProvider
will have to be similar in this sense eventually, so might as well get it right from the beginning :-)
I only asked you to add this empty object in PermanentPersistenceProvider
and not in the old code. If you feel strongly against this then just know:
- You will have to add lots of null checks ;-)
- It will just make things messier later
@VisibleForTesting | ||
void safeDeleteTransaction(WriteBatch writeBatch, Hash key) throws Exception { | ||
byte[] keyBytes = key.bytes(); | ||
TransactionViewModel tx = getTransaction(key); |
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.
what happens if this returns null
?
See my other comment and #1591
void addToIndex(WriteBatch writeBatch, ColumnFamilyHandle column, Indexable key, Indexable indexValue) | ||
throws Exception { | ||
byte[] indexBytes = indexValue.bytes(); | ||
byte[] dbResult = db.get(column, key.bytes()); |
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.
shouldn't it be better to use the exists()
method that checks mayExists
first?
/*** | ||
* Implements the retrieval functions of a the normal persistence provider and the permanent persistence provider | ||
*/ | ||
public class RocksDBPPPImpl implements PermanentPersistenceProvider, PersistenceProvider { |
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.
Now having a deeper look here, I would like to offer a different design :-)
Currently, it is possible with the current design it is theoretically possible to implement a new PersistanceProvider
that will replace RocksDb. You have actually attempted to do it yourself :-)
Here you tightly coupled RocksDb with the PermenantPersistanceProvider...
This means that if we ever want to replace RocksDB it will be much harder (there are actually thoughts about doing this now - #1661).
Also I have noticed lots of evil copy paste... Here we are believers in DRY (don't repeat yourself).
So maybe you can create PermenantPersistanceProvider
that contains a RocksDbPersistanceProvider
?
This way if we write a new PersistnaceProvider, switching will be a breeze?
It will also adhere better to the single responsibility principle that we have a class that only deals with wrapping the RocksDb api and a class that deals with the logic of your task :-)
# Conflicts: # src/main/java/com/iota/iri/Iota.java
Hey @ovanwijk, you want another review? |
# Conflicts: # src/main/java/com/iota/iri/Iota.java
return found; | ||
} | ||
|
||
private void initDB(String path, String logPath) throws Exception { |
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.
Description
Allows IOTA Nodes to use a separate data storage in order to pinTransactions through the webapi.
A pinned transaction is a transaction that will not be deleted during snapshotting. In order to not interfere with the current implementation of the persistence layer and the snapshotting code this solution uses a separate database file to store permanently stored transactions.
It allows for arbitrary transaction storages through 'pinTransactionsTrytes' and it allows for copying transactions by 'pinTransactionHashes'.
Unpinning is also possible. This will remove the transactions from the permanent storage provider.
'isPinned' as api is introduced to check if transactions are pinned or not.
Pinned transactions can be accessed through the normal api calls like 'findTransactions' and 'getTrytes'.
Example: in order to pin all transactions from an address just call findTransactions(address) and then use the result in the 'pinTransactionHashes'.
The RocksDBPPPimpl.java code also offers inplace replacement code for updating indexes for deleting data. This could be used 1:1 for solving #1307
Fixes # (issue)
#1599
Type of change
How Has This Been Tested?
Adds a set of tests that use the database file to save, delete and retrieve data.
Checklist: