-
Notifications
You must be signed in to change notification settings - Fork 108
PoS Mempool Balance Ledger #599
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
|
Current dependencies on/for this PR:
This comment was auto-generated by Graphite. |
d161b18 to
51f3b66
Compare
51f3b66 to
3b78e8c
Compare
1f3c351 to
8f65faa
Compare
tholonious
left a comment
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.
The logic LGTM overall. Just left some naming suggestions in-line. When reading through the code, I found the terms "Increase" and "Decrease" in the public interface to be ambiguous: i.e. does "Increase" reserve more coins, or does it free coins to the pk's spendable balance so they can be spent?
Since the role of the ledge is to track "reserved" balance, I suggest using more explicit verbs like "Reserve" and "Free" so it's dead-simple for readers to understand what the public functions are doing.
lib/pos_mempool_ledger.go
Outdated
| sync.RWMutex | ||
|
|
||
| // Map of public keys to reserved balances in nanos. | ||
| reservedBalancesNanos map[PublicKey]uint64 |
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.
seems like we're starting to have a bit of a pattern for having a RWLock around a map. Should we create a struct that encapsulates this so we can reuse elsewhere? we could add something like this to generics.go
type RWLockMap[K comparable, V any] struct {
sync.RWMutex
_innerMap map[K]V
}
func NewRWLockMap[K comparable, V any]() *RWLockMap[K, V] {
return &RWLockMap[K, V]{_innerMap: make(map[K]V)}
}
func (rwLockMap *RWLockMap[K, V]) Get(key K) (V, bool) {
rwLockMap.RLock()
defer rwLockMap.RUnlock()
value, exists := rwLockMap._innerMap[key]
return value, exists
}
func (rwLockMap *RWLockMap[K, V]) Set(key K, value V) {
rwLockMap.Lock()
defer rwLockMap.Unlock()
rwLockMap._innerMap[key] = value
}
func (rwLockMap *RWLockMap[K, V]) Delete(key K) {
rwLockMap.Lock()
defer rwLockMap.Unlock()
delete(rwLockMap._innerMap, key)
}
func (rwLockMap *RWLockMap[K, V]) Size() int {
rwLockMap.RLock()
defer rwLockMap.RUnlock()
return len(rwLockMap._innerMap)
}
and then BalanceLedger can be defined as
type BalanceLedger RWLockMap[PublicKey, uint64]
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.
Cool idea! Abstracting away a thread-safe map into a reusable struct is a clean solution. However, as I've started implementing this generic and using it in BalanceLedger, I've encountered some issues. The main issue occurred when I tried rewriting the IncreaseBalance function. In this case, we need to perform a read and then a write. We first read the user's balance and then write the balance + amount. The problem is that the RwLockMap functions Get and Set release locks. So, theoretically, some undesirable instruction could sneak its way in between and 180 the balance. We would need something that does a Get and a Set under a single lock.
To solve this, I came up with an Update method:
func (m *RWLockMap[K, V]) Update(key K, expression func(V, bool) V) {
m.Lock()
defer m.Unlock()
value, exists := m._innerMap[key]
newValue := expression(value, exists)
m._innerMap[key] = newValue
}
This method works nicely because we can encapsulate the "+ amount" logic inside the expression argument. But even with this function, some things become problematic. Let's say we want to check for the overflow after a read but before the write. In such a case, we want to return an error. However, this means we would need to do this inside the expression, which should, in turn, actually return (V, error). Similarly, Update should now return an error. More complexity.
Though I'm chill with complexity. What do you think? I'm happy to add this generic if you're fine with having the Update method.
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.
In this case, migrating to a generic RWLockMap data structure may be overkill. You make the call on whether to implement it or not @AeonSw4n. IMO, the code reads clearly as-is and the amount of code de-duplication we gain by implementing the RWLockMap is minimal.
869ceb6 to
21fbffc
Compare
21fbffc to
5e73693
Compare

No description provided.