-
Notifications
You must be signed in to change notification settings - Fork 248
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
Use single codepath for sending transactions to a local and remote nodes #527
Conversation
31f4cd7
to
f4f5034
Compare
@adambabik hello, i believe this change is ready for initial review. |
Looks like i asked prematurely, e2e tests are failing with relevant error. |
db1b531
to
dcc0874
Compare
At the moment only one test fails - TestCompleteMultipleQueuedTransactions, and I am pretty sure that I know the root cause of this issue. LesBackend internally uses txPool.GetNonce method when it sets the nonce for an incoming transaction. This method looks up a recent nonce at the local map of the pool - pool.nonce. But this method is not exported, and I have to use GetTransactionCount that is not consistent with txPool.GetNonce. I tried to verify it on develop branch:
I think I can add another interceptor, to a status rpc.Client, for GetTransactionCount method which will use LesBackend and GetPoolNonce. But I am pretty sure that it is a bug in LightEthereum client. |
Hey @dshulyak. Thank you for your contribution. I skimmed through the PR and it looks great! I will submit a detailed review tomorrow. Regarding the nonce, in the current develop, the code that sends a transaction when using a remote node uses the same JSON-RPC method and arguments to get the nonce as before so the tests should pass just fine, unless these tests use a local node... In this case, a pool is used: https://github.com/ethereum/go-ethereum/blob/master/internal/ethapi/api.go#L1096-L1102 and this go-ethereum issue seems like a root cause. You can hot patch code in |
316e0f8
to
10b69eb
Compare
@adambabik i don't see a reason why it won't be possible to reproduce this bug with upstream node. maybe it will be required to increase the number of concurrent transactions |
@dshulyak you're right, this bug should be also visible when using a remote node. We may not have an automated test for the remote node, though. To reviewers: the tests were failing because they use a local node and, before changes, they were using LES backend directly instead of the public API (that is |
I think that it is possible yo retry tx on "known transaction" error, both light and full nodes return it. But, i need to understand if there are any other side effects of doing it. |
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 looks great! I added a bunch of comments but they are minor suggestions or code style.
I will create a separate issue for this effort as it's related to #338 indeed, but not exactly.
geth/txqueue/ethtxclient.go
Outdated
) | ||
|
||
// EthereumTransactor provides methods to create transactions for ethereum network. | ||
type EthereumTransactor interface { |
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.
To keep it consistent, maybe we can call it EthTransactor
, similarly to EthTxClient
?
geth/txqueue/txqueue_manager.go
Outdated
|
||
defaultGas = 90000 | ||
|
||
cancelTimeout = time.Minute |
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.
Maybe defaultTimeout
to match defaultGas
naming convention?
geth/txqueue/txqueue_manager.go
Outdated
args := queuedTx.Args | ||
|
||
var gasPrice *big.Int |
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 fine but else
can be omitted:
gasPrice := (*big.Int)(args.GasPrice)
if gasPrice == nil {
// ...
}
geth/txqueue/txqueue_manager_test.go
Outdated
}) | ||
|
||
err := txQueueManager.QueueTransaction(tx) | ||
s.NoError(err) | ||
|
||
var wg sync.WaitGroup | ||
var mu sync.Mutex | ||
completeTxErrors := make(map[error]int) | ||
completeTxErrors := map[error]int{} |
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 think it should be possible to get rid of this map and only assert errCompleteTransaction
to be always nil
.
geth/txqueue/txqueue_manager_test.go
Outdated
// As we want to avoid network connection, we mock LES with a known error | ||
// and treat as success. | ||
s.nodeManagerMock.EXPECT().LightEthereumService().Return(nil, errTxAssumedSent) | ||
nodeConfig, nodeErr := params.NewNodeConfig("/tmp", params.RopstenNetworkID, true) |
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.
As this block repeats three times, maybe we can extract it to a separate TxQueueTestSuite
method?
geth/txqueue/fake/txservice.go
Outdated
// FakePublicTxApi used to generate mock by mockgen util. | ||
// This was done because PublicTransactionPoolAPI is located in internal/ethapi module | ||
// and there is no easy way to generate mocks from internal modules. | ||
type FakePublicTxApi interface { |
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.
Maybe we can call it FakePublicTransactionPoolAPI
to make it clearer what it is?
geth/txqueue/fake/txservice.go
Outdated
|
||
import ( | ||
context "context" | ||
big "math/big" |
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.
Is big
required?
geth/txqueue/fake/txservice.go
Outdated
"github.com/ethereum/go-ethereum/common" | ||
"github.com/ethereum/go-ethereum/common/hexutil" | ||
"github.com/ethereum/go-ethereum/rpc" | ||
gomock "github.com/golang/mock/gomock" |
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 believe gomock
is not required before the package name here.
@@ -0,0 +1,91 @@ | |||
// Code generated by MockGen. DO NOT EDIT. |
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.
Please add a proper entry to mock
target: https://github.com/status-im/status-go/blob/develop/Makefile#L101-L105
@adambabik thanks for a review, i pushed an update |
Hey @dshulyak! Adam suggested that we offer a bounty for this issue because you had done such good work. I have created and funded it and linked this PR to the issue with a minor edit. Could you please log into http://openbounty.status.im/ click on your profile picture, select to |
that's awesome @andytudhope, I updated my address as you suggested |
Can someone re-trigger a job on travis? It failed due to |
@dshulyak done. |
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.
Sorry about doing this review in two iterations but I found a bit more time just today.
Minor remarks overall, just please make sure to look at the comments relating to TestInvalidPassword
and TestCompleteTransaction
.
geth/txqueue/txqueue_manager.go
Outdated
} else { | ||
hash, err = m.completeLocalTransaction(queuedTx, password) | ||
} | ||
hash, err := m.completeTransaction(selectedAccount, queuedTx, password) |
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.
Minor: I noticed that maybe this arguments grouping could be better: m.completeTransaction(queuedTx, selectedAccount, password)
geth/txqueue/txqueue_manager.go
Outdated
var emptyHash gethcommon.Hash | ||
|
||
log.Info("verifying account password for transaction", "id", queuedTx.ID) |
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 would move this log just above m.accountManager.VerifyAccountPassword
or remove at all.
geth/txqueue/txqueue_manager_test.go
Outdated
@@ -81,31 +114,30 @@ func (s *TxQueueTestSuite) TestCompleteTransaction() { | |||
s.NoError(err) | |||
|
|||
go func() { | |||
_, errCompleteTransaction := txQueueManager.CompleteTransaction(tx.ID, TestConfig.Account1.Password) | |||
s.Equal(errTxAssumedSent, errCompleteTransaction) | |||
_, errCompleteTransaction := txQueueManager.CompleteTransaction(tx.ID, password) |
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.
After these changes, we do not expect any errors so we can verify if the returned hash is equal to tx.Hash
, right?
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.
Also, a few lines above, there is txQueueManager.SetTransactionReturnHandler()
. I think we should also change it to expect err
to be nil
.
geth/txqueue/txqueue_manager_test.go
Outdated
// Transaction should be already removed from the queue. | ||
s.False(txQueueManager.TransactionQueue().Has(tx.ID)) | ||
|
||
// Wait for all CompleteTransaction calls. | ||
wg.Wait() | ||
s.Equal(completeTxErrors[errTxAssumedSent], 1) | ||
s.Equal(1, completedTx, "only 1 tx expected to be completed") | ||
s.Equal(txCount-1, inprogressTx, txCount-1, "txs expected to be reported as inprogress") |
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:
s.Equal(txCount-1, inprogressTx, "txs expected to be reported as inprogress")
?
geth/txqueue/txqueue_manager_test.go
Outdated
s.nodeManagerMock.EXPECT().LightEthereumService().Return(nil, keystore.ErrDecrypt) | ||
nonce := hexutil.Uint64(10) | ||
gas := hexutil.Big(*big.NewInt(defaultGas + 1)) | ||
s.setupTransactionPoolAPI(account, nonce, gas, keystore.ErrDecrypt) |
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 test does not test a real scenario. keystore.ErrDecrypt
is returned by SendRawTransaction
while it should be by VerifyAccountPassword
. I would recommend to change that and also make sure that SendRawTransaction
is not called.
geth/txqueue/txqueue_manager_test.go
Outdated
}) | ||
|
||
err := txQueueManager.QueueTransaction(tx) | ||
s.NoError(err) | ||
|
||
var wg sync.WaitGroup | ||
var mu sync.Mutex | ||
completeTxErrors := make(map[error]int) | ||
for i := 0; i < 3; i++ { | ||
var completedTx int |
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.
Minor: as there are multiple var
s, it's beneficial to group them with var ()
.
@@ -956,6 +956,14 @@ func (s *PublicTransactionPoolAPI) GetRawTransactionByBlockHashAndIndex(ctx cont | |||
|
|||
// GetTransactionCount returns the number of transactions the given address has sent for the given block number | |||
func (s *PublicTransactionPoolAPI) GetTransactionCount(ctx context.Context, address common.Address, blockNr rpc.BlockNumber) (*hexutil.Uint64, error) { | |||
// go-ethereum issue https://github.com/ethereum/go-ethereum/issues/2880 |
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.
We need to add this to go-ethereum patches. cc @divan
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 think there is a way to get rid of it. we can maintain nonce locally for each address that status is using. increment it on every successful transaction, and reset in case if it was changed remotely. it is easy with new addr locker and will prevent issues both with upstream and local nodes.
will do a separate pr after additional testing
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 may introduce a lot of complexity into status-go and something that potentially can break in the future if go-ethereum implementation changes. But if you find some elegant solution that would be awesome.
I have seen this PR ethereum/go-ethereum#15794 and read the whole discussion in ethereum/go-ethereum#2880 and it looks like a separate JSON-RPC method is the best solution that can preserve backward compatibility of eth_getTransactionCount
and allows to get the number of pending transactions from the pool.
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.
adding a separate method also seemed like a good idea to me, but it may take some time until it will be merged and a new geth release will be available. and even then status app needs to be always aware that upstream node is not an old geth release or another client, e.g parity.
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.
Yeah, that should be implemented via patches to keep track of our changes into the go-ethereum
. This is blocked by #492, which will be merged tomorrow (internally agreed deadline).
29c5534
to
55bcd13
Compare
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.
LGTM 👏
select { | ||
case <-c: | ||
return nil | ||
case <-timer.C: |
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.
case <-time.After(d):
return ErrTimeout
will do the same.
@@ -265,9 +307,11 @@ func (s *TxQueueTestSuite) TestDiscardTransaction() { | |||
err := txQueueManager.QueueTransaction(tx) | |||
s.NoError(err) | |||
|
|||
w := make(chan struct{}) |
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 am not a fan of one char variables. In such contexts, done := make(chan struct{})
works pretty well and indicates an intention clearly.
thanks, I will change those things in one of the pr's that is based on this one. travis failed because of #539 |
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.
Great work!
The modification to the vendored go-ethereum
should be made through patches, so this PR is currently blocked by #492. Will try to resolve this block asap.
geth/txqueue/txqueue_manager.go
Outdated
log.Info("default gas will be used. estimated gas", gas, "is lower than", defaultGas) | ||
gas = big.NewInt(defaultGas) | ||
gas := (*big.Int)(args.Gas) | ||
if args.Gas == nil { |
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 think it's good. The other way is to pass existing gas
value to EstimateGas
arguments and it will take it as upper bound, however, a lower value might be returned.
Skipping EstimateGas
call if gas is defined should be better, though.
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.
also the idea was to keep EthTxClient compatible with https://github.com/ethereum/go-ethereum/blob/master/ethclient/ethclient.go#L36 , it is not of high priority of course
As #492 has been merged, changes to The exact way of applying patches to our fork might be changed in #551, but so far patch files are in |
Previously procedure for sending transactions was different based on which type of node was used, local (light node) or remote. This change removes separate code path for working with light node, and the only difference will be backend for rpc client. In case of light node all of the communication will be done over inproc dial. Also there is a couple of related changes in this PR: - new EthereumTransactor that provides higher level API for working with ethereum network, and it is fully conformant with ethclient - new test rpc service that improves flexibility and coverage of txqueue manager tests
@divan pushed the patch |
Trying to understand why tests on Travis time outs before merging. |
@dshulyak awesome job. |
This change is not needed after the following PR was merged: #527
This change is not needed after the following PR was merged: #527
This change is not needed after the following PR was merged: #527
This change is not needed after the following PR was merged: #527
Fixes #529
Previously procedure for sending transactions was different based on
which type of node was used, local (light node) or remote.
This change removes separate code path for working with light node,
and the only difference will be backend for rpc client. In case of
light node all of the communication will be done over inproc dial.
Also there is a couple of related changes in this PR:
with ethereum network, and it is fully conformant with ethclient
txqueue manager tests
Closes: #529