-
Notifications
You must be signed in to change notification settings - Fork 97
test(mempool): add integration test #512
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
Just to confirm — in Geth’s mempool, when two txs with the same account/nonce are submitted, the one with the higher gas price replaces the earlier one, right? |
Yes, more specifically, the following conditions apply, and these conditions are also applied in the same way to both geth(list.go:307-325) and cosmos/evm (list.go:307-325).
|
This only actually happens in a test environment due to the fact that the antehandler's sequence incrementing is being persisted. In production, the increment would only be saved during finalization and inclusion in the block, and not during the inclusion in the mempool. I wrote a simple system test to confirm this, which you can add to the suite. func TestPriorityPendingReplacement(t *testing.T) {
sut := systemtests.Sut
sut.ResetChain(t)
StartChain(t, sut)
sut.AwaitNBlocks(t, 10)
// get the directory of the counter project to run commands from
_, filename, _, _ := runtime.Caller(0)
testDir := filepath.Dir(filename)
counterDir := filepath.Join(testDir, "Counter")
// deploy the contract
cmd := exec.Command(
"forge",
"create", "src/Counter.sol:Counter",
"--rpc-url", "http://127.0.0.1:8545",
"--broadcast",
"--private-key", pk,
)
cmd.Dir = counterDir
res, err := cmd.CombinedOutput()
require.NoError(t, err)
require.NotEmpty(t, string(res))
// get contract address
contractAddr := parseContractAddress(string(res))
require.NotEmpty(t, contractAddr)
wg := sync.WaitGroup{}
wg.Add(1)
var lowPrioRes []byte
go func() {
defer wg.Done()
var prioErr error
lowPrioRes, prioErr = exec.Command(
"cast", "send",
contractAddr,
"increment()",
"--rpc-url", "http://127.0.0.1:8545",
"--private-key", pk,
"--gas-price", "100000000000",
"--nonce", "1",
).CombinedOutput()
require.Error(t, prioErr)
}()
var highPrioRes []byte
wg.Add(1)
go func() {
defer wg.Done()
var prioErr error
highPrioRes, prioErr = exec.Command(
"cast", "send",
contractAddr,
"increment()",
"--rpc-url", "http://127.0.0.1:8545",
"--private-key", pk,
"--gas-price", "100000000000000",
"--priority-gas-price", "100",
"--nonce", "1",
).CombinedOutput()
require.NoError(t, prioErr)
}()
wg.Wait()
lowPrioReceipt, err := parseReceipt(string(lowPrioRes))
require.NoError(t, err)
highPrioReceipt, err := parseReceipt(string(highPrioRes))
require.NoError(t, err)
// 1 = success, 0 = failure.
require.Equal(t, highPrioReceipt.Status, uint64(1))
require.Equal(t, lowPrioReceipt.Status, uint64(0))
} |
@vladjdk In fact,
If you add a slight sleep to the goroutine that sends
This matches the results we live-tested in yesterday’s remote meeting. |
You can reproduce with this test code. func TestPriorityPendingReplacement(t *testing.T) {
sut := systemtests.Sut
sut.ResetChain(t)
StartChain(t, sut)
sut.AwaitNBlocks(t, 10)
// get the directory of the counter project to run commands from
_, filename, _, _ := runtime.Caller(0)
testDir := filepath.Dir(filename)
counterDir := filepath.Join(testDir, "Counter")
// deploy the contract
cmd := exec.Command(
"forge",
"create", "src/Counter.sol:Counter",
"--rpc-url", "http://127.0.0.1:8545",
"--broadcast",
"--private-key", pk,
)
cmd.Dir = counterDir
res, err := cmd.CombinedOutput()
require.NoError(t, err)
require.NotEmpty(t, string(res))
// get contract address
contractAddr := parseContractAddress(string(res))
require.NotEmpty(t, contractAddr)
wg := sync.WaitGroup{}
wg.Add(1)
var lowPrioRes []byte
go func() {
defer wg.Done()
var prioErr error
lowPrioRes, prioErr = exec.Command(
"cast", "send",
contractAddr,
"increment()",
"--rpc-url", "http://127.0.0.1:8545",
"--private-key", pk,
"--gas-price", "100000000000",
"--nonce", "1",
).CombinedOutput()
require.Error(t, prioErr)
}()
var highPrioRes []byte
wg.Add(1)
go func() {
defer wg.Done()
var prioErr error
time.Sleep(100 * time.Millisecond)
highPrioRes, prioErr = exec.Command(
"cast", "send",
contractAddr,
"increment()",
"--rpc-url", "http://127.0.0.1:8545",
"--private-key", pk,
"--gas-price", "100000000000000",
"--priority-gas-price", "100",
"--nonce", "1",
).CombinedOutput()
require.NoError(t, prioErr)
}()
wg.Wait()
lowPrioReceipt, err := parseReceipt(string(lowPrioRes))
fmt.Println("DEBUG - lowPrioRes: ", string(lowPrioRes))
fmt.Println("DEBUG - lowPrioReceipt: ", lowPrioReceipt)
require.NoError(t, err)
highPrioReceipt, err := parseReceipt(string(highPrioRes))
fmt.Println("DEBUG - highPrioRes: ", string(highPrioRes))
fmt.Println("DEBUG - highPrioReceipt: ", highPrioReceipt)
require.NoError(t, err)
// 1 = success, 0 = failure.
require.Equal(t, highPrioReceipt.Status, uint64(1))
require.Equal(t, lowPrioReceipt.Status, uint64(0))
} |
Description
Integration test added for appside mempool
Found Issues
CheckTx reject valid tx for some case
When two transactions (
Tx1
andTx2
) with the same account and the same nonce but different GasPrice sequentially go through the CheckTx logic, replacement does not occur, and the later transaction is treated as an error.Tx2
, which has a higher GasPrice, should replaceTx1
, which has a lower GasPrice, already in the mempool.Tx1
remains in the mempool.Consideration for code review
some test cases are bypassed for the found issue above "CheckTx reject valid tx for some case" issue. After fixing the issue, that test cases should be tested.
mempool.Remove()
can remove EVM transactoin only in case that tx is invalid After fixing tx gernerating helper functions to generate valida transaction, false positive test case that tries to remove tx from mempool was deleted.Closes: #516
Author Checklist
All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.
I have...
main
branch