Skip to content

Conversation

@AeonSw4n
Copy link
Contributor

No description provided.

This was referenced Jul 14, 2023
@AeonSw4n
Copy link
Contributor Author

AeonSw4n commented Jul 14, 2023

@AeonSw4n AeonSw4n force-pushed the p/pos-mempool-balance-ledger branch from d161b18 to 51f3b66 Compare July 14, 2023 10:09
@tholonious tholonious force-pushed the p/pos-mempool-balance-ledger branch from 51f3b66 to 3b78e8c Compare July 14, 2023 13:18
@AeonSw4n AeonSw4n force-pushed the p/pos-mempool-balance-ledger branch 3 times, most recently from 1f3c351 to 8f65faa Compare July 17, 2023 02:42
@AeonSw4n AeonSw4n marked this pull request as ready for review July 17, 2023 06:46
@AeonSw4n AeonSw4n requested a review from a team as a code owner July 17, 2023 06:46
Copy link
Contributor

@tholonious tholonious left a 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.

@tholonious tholonious requested a review from lazynina July 18, 2023 17:22
Comment on lines 12 to 15
sync.RWMutex

// Map of public keys to reserved balances in nanos.
reservedBalancesNanos map[PublicKey]uint64
Copy link
Member

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]

Copy link
Contributor Author

@AeonSw4n AeonSw4n Jul 20, 2023

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.

Copy link
Contributor

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.

@AeonSw4n AeonSw4n force-pushed the p/pos-mempool-balance-ledger branch 2 times, most recently from 869ceb6 to 21fbffc Compare July 19, 2023 00:22
Base automatically changed from p/pos-mempool-main-data-structure to feature/pos-mempool July 20, 2023 00:52
@AeonSw4n AeonSw4n force-pushed the p/pos-mempool-balance-ledger branch from 21fbffc to 5e73693 Compare July 20, 2023 03:24
@AeonSw4n AeonSw4n mentioned this pull request Jul 25, 2023
This was referenced Jul 25, 2023
@AeonSw4n AeonSw4n merged commit 1d0ea29 into feature/pos-mempool Jul 25, 2023
@AeonSw4n AeonSw4n deleted the p/pos-mempool-balance-ledger branch July 25, 2023 17:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants