-
Notifications
You must be signed in to change notification settings - Fork 195
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
test(eth-rpc): more tests for types dir
- Loading branch information
1 parent
176b6c6
commit ac0701a
Showing
7 changed files
with
245 additions
and
63 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
// Copyright (c) 2023-2024 Nibi, Inc. | ||
package types | ||
|
||
import ( | ||
"sync" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
) | ||
|
||
// AddrLocker is a mutex (mutual exclusion lock) structure used to avoid querying | ||
// outdated account data. It prevents data races by allowing only one goroutine | ||
// to access critical sections at a time. | ||
type AddrLocker struct { | ||
// mu protects access to the locks map | ||
mu sync.Mutex | ||
locks map[common.Address]*sync.Mutex | ||
} | ||
|
||
// lock returns the mutex lock of the given Ethereum address. If no mutex exists | ||
// for the address, it creates a new one. This function ensures that each address | ||
// has exactly one mutex associated with it, and it is thread-safe. | ||
// | ||
// The returned mutex is not locked; callers are responsible for locking and | ||
// unlocking it as necessary. | ||
func (l *AddrLocker) lock(address common.Address) *sync.Mutex { | ||
l.mu.Lock() | ||
defer l.mu.Unlock() | ||
if l.locks == nil { | ||
l.locks = make(map[common.Address]*sync.Mutex) | ||
} | ||
if _, ok := l.locks[address]; !ok { | ||
l.locks[address] = new(sync.Mutex) | ||
} | ||
return l.locks[address] | ||
} | ||
|
||
// LockAddr acquires the mutex for a specific address, blocking if it is already | ||
// held by another goroutine. The mutex lock prevents an identical nonce from | ||
// being read again during the time that the first transaction is being signed. | ||
func (l *AddrLocker) LockAddr(address common.Address) { | ||
l.lock(address).Lock() | ||
} | ||
|
||
// UnlockAddr unlocks the mutex for a specific address, allowing other goroutines | ||
// to acquire it. | ||
func (l *AddrLocker) UnlockAddr(address common.Address) { | ||
l.lock(address).Unlock() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
package types_test | ||
|
||
import ( | ||
"sync" | ||
"testing" | ||
"time" | ||
|
||
"github.com/ethereum/go-ethereum/common" | ||
"github.com/stretchr/testify/suite" | ||
|
||
rpc "github.com/NibiruChain/nibiru/eth/rpc/types" | ||
) | ||
|
||
type SuiteAddrLocker struct { | ||
suite.Suite | ||
} | ||
|
||
func TestSuiteAddrLocker(t *testing.T) { | ||
suite.Run(t, new(SuiteAddrLocker)) | ||
} | ||
|
||
// TestLockAddr: This test checks that the lock mechanism prevents multiple | ||
// goroutines from entering critical sections of code simultaneously for the same | ||
// address. | ||
func (s *SuiteAddrLocker) TestLockAddr() { | ||
// Setup: Lock the address | ||
locker := &rpc.AddrLocker{} | ||
addr := common.HexToAddress("0x123") | ||
locker.LockAddr(addr) | ||
|
||
// Concurrent Lock Attempt: Attempt to lock again in a separate goroutine. If | ||
// the initial lock is effective, this attempt should block and not complete | ||
// immediately. | ||
done := make(chan bool) | ||
go func() { | ||
locker.LockAddr(addr) // This should block if the first lock is effective | ||
done <- true | ||
}() | ||
|
||
// Assertion: A select statement is used to check if the channel receives a | ||
// value, which would indicate the lock did not block as expected. | ||
select { | ||
case <-done: | ||
s.Fail("LockAddr did not block the second call as expected") | ||
default: | ||
// expected behavior, continue test | ||
} | ||
|
||
// Cleanup: Unlock and allow the goroutine to proceed | ||
locker.UnlockAddr(addr) | ||
<-done // Ensure goroutine completes | ||
} | ||
|
||
func (s *SuiteAddrLocker) TestUnlockAddr() { | ||
// Setup: Lock the address | ||
locker := &rpc.AddrLocker{} | ||
addr := common.HexToAddress("0x123") | ||
locker.LockAddr(addr) | ||
|
||
locker.UnlockAddr(addr) | ||
|
||
// Try re-locking to test if unlock was successful | ||
locked := make(chan bool) | ||
go func() { | ||
locker.LockAddr(addr) // This should not block if unlock worked | ||
locked <- true | ||
locker.UnlockAddr(addr) | ||
}() | ||
|
||
select { | ||
case <-locked: | ||
// expected behavior, continue | ||
case <-time.After(time.Second): | ||
s.Fail("UnlockAddr did not effectively unlock the mutex") | ||
} | ||
} | ||
|
||
func (s *SuiteAddrLocker) TestMultipleAddresses() { | ||
locker := &rpc.AddrLocker{} | ||
addr1 := common.HexToAddress("0x123") | ||
addr2 := common.HexToAddress("0x456") | ||
|
||
locker.LockAddr(addr1) | ||
locked := make(chan bool) | ||
|
||
go func() { | ||
locker.LockAddr(addr2) // This should not block if locks are address-specific | ||
locked <- true | ||
locker.UnlockAddr(addr2) | ||
}() | ||
|
||
select { | ||
case <-locked: | ||
// expected behavior, continue | ||
case <-time.After(time.Second): | ||
s.Fail("Locks are not address-specific as expected") | ||
} | ||
|
||
locker.UnlockAddr(addr1) | ||
} | ||
|
||
// TestConcurrentAccess: Tests the system's behavior under high concurrency, | ||
// specifically ensuring that the lock can handle multiple locking and unlocking | ||
// operations on the same address without leading to race conditions or | ||
// deadlocks. | ||
func (s *SuiteAddrLocker) TestConcurrentAccess() { | ||
locker := &rpc.AddrLocker{} | ||
addr := common.HexToAddress("0x789") | ||
var wg sync.WaitGroup | ||
|
||
// Spawn 100 goroutines, each locking and unlocking the same address. | ||
// Each routine will hod the lock briefly to simulate work done during the | ||
// lock (like an Ethereum query). | ||
for i := 0; i < 100; i++ { | ||
wg.Add(1) | ||
go func() { | ||
locker.LockAddr(addr) | ||
time.Sleep(time.Millisecond * 5) // Simulate work | ||
locker.UnlockAddr(addr) | ||
wg.Done() | ||
}() | ||
} | ||
|
||
// Cleanup: Wait for all goroutines to complete | ||
wg.Wait() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters