-
Notifications
You must be signed in to change notification settings - Fork 92
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
client/eth: refactor eth client #1301
Conversation
client/asset/eth/nodeclient.go
Outdated
nonce, err := n.ec.NonceAt(ctx, n.creds.addr, nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("error getting nonce: %v", err) | ||
} |
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's not impossible that this is insufficient to guarantee a unique nonce. I haven't verified that this is incremented before the transaction is mined.
f4b38e9
to
ff5f9ef
Compare
I went ahead and removed the rest of the |
return b, weiToGwei(tip), nil | ||
} | ||
|
||
// getCodeAt retrieves the runtime bytecode at a certain address. | ||
func (n *nodeClient) getCodeAt(ctx context.Context, contractAddr *common.Address) ([]byte, error) { | ||
bn, err := n.ec.BlockNumber(ctx) | ||
func (n *nodeClient) getCodeAt(ctx context.Context, contractAddr common.Address) ([]byte, error) { |
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 one's busted too and I don't know why yet. It's busted if I use the ethclient.Client
method too, but that's not ruling out some other mistake I made.
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.
Works fine for me.. maybe try restarting the eth harness?
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.
Hmm. I'm still failing. What version of geth are you running?
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.
Nevermind. I found the problem.
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.
What was wrong?
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 I've forgotten now. Sorry I missed this question.
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.
dex/networks/eth/params.go
Outdated
// called and the gas used for each. (◍•﹏•) | ||
InitGas = 158000 // gas | ||
|
||
RedeemGas = 63000 // gas |
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.
Since the contracts are versioned now, these gas estimates will also need to be versioned.
client/asset/eth/eth.go
Outdated
func (eth *ExchangeWallet) Withdraw(addr string, value, _ uint64) (asset.Coin, error) { | ||
_, err := eth.sendToAddr(common.HexToAddress(addr), value) |
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 should revisit this at some point. If a user tries to withdraw their entire balance, it will fail and they'll lose the tx fees, I believe.
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.
Yikes, how does one sweep an ETH wallet completely, leaving nothing left if gas doesn't eat up whatever was allocated to gas?
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 the wallet won't allow the transaction to be sent the way I've described. I'm not certain.
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.
for ref: https://www.reddit.com/r/ethdev/comments/pits6s/sweep_all_eth_using_dynamic_fees_eip1559_and/
EIP-1559 does make this a headache if not impossible to do without leaving dust
Out of curiosity I tried out a popular eth wallet and despite being eip-1559 aware, it's support for sweep is non-existent. A tx draft wavers between leaving dust and having insufficient funds, while the base fee fluctuates.
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.
Haha, basically the way to sweep it is to give the dust to the miners.
For the original issue of the failing transaction, I would think that the node would have a check that refuses to submit a transaction where the gasLimit*gasPrice + value > balance
, but I haven't tried it out yet.
|
||
state := dexeth.SwapStep(swap.State) | ||
if test.success && state != dexeth.SSInitiated { | ||
t.Fatalf("unexpected swap state for test %v: want %s got %s", test.name, dexeth.SSInitiated, state) |
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 cannot figure out why this isn't passing. I'm verifying the secret hash matches thoughout. There are no errors. I can see the transaction with eth.getTransaction
afterwards, and I can see the secret hash in the tx data, but the ETHSwapSwap
is completely zeroed.
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've been trying to figure it out what's wrong.. haven't had any luck yet.
I also saw that you removed the addSignerToOpts
calls from the functions that submit transactions (initiate
, redeem
, etc.) and you're calling them from the test code, but in some cases you forgot to call it. What's wrong with this going into intiate
and redeem
?
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.
Here's a clue.
https://github.com/decred/dcrdex/compare/master...buck54321:fail-init-real-balance?expand=1
That fails too.
Nevermind. That failed because I wasn't setting the TransactOpts
value
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 branch is failing for > 1 swap. Maybe another manifestation of #1304?
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.
Update: I've got everything passing except for TestReplayAttack
.
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.
Nice, what was wrong? I was getting a headache from that.
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 I was handling a *big.Int
wrong. Not making a copy when I should have or something.
196a375
to
145481a
Compare
Rebased and un-renamed the rpcclient*.go files back to the original names in an attempt to provide a usable diff for review. Will rename again later. I'm still banging my head against the wall on the swap state issue, but I'm gonna step away for a minute. @martonp, if you have a sec to take a look, I'd really appreciate it. |
1cb84e8
to
c2b1f43
Compare
@@ -82,7 +82,6 @@ if [ "${CHAIN_ADDRESS}" != "_" ]; then | |||
|
|||
[Eth.Miner] | |||
Etherbase = "0x${CHAIN_ADDRESS}" | |||
GasPrice = 81000000000 |
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, sorry I couldn't answer you somewhere where you asked about this, I don't know. I'm not up to speed on the base fee, thing. The miners can set the gas price over time is all I think I know. Also, I think geth complained without setting this, maybe it doesn't now.
c2b1f43
to
b975ead
Compare
So, the reentry test, it doesn't look like it's communicating with the contract. The balances aren't the simnet's contract's balance, and the initiations aren't being sent to the contract. Or, did you just fix it? |
b975ead
to
5a7d8cf
Compare
Thanks, @JoeGruffins. The contract address must've been part of it. It's partly passing now, but still seeing an unexpected change in the contract balance. Still missing something. |
Passing for me. Please see the rpcclient.balance comment I made. |
dbc278b
to
4bf6adc
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.
The Contractor interface looks good.
I'm not completely sold on the rpcclient changes. I think it was less work for us before. But it's fine I guess.
Also, some conflicts with #1309 so hopefully mine goes in first :P
client/asset/eth/rpcclient.go
Outdated
node, err := prepareNode(&nodeConfig{ | ||
net: net, | ||
appDir: dir, | ||
logger: log.SubLogger("NODE"), |
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 like you already make this logger at the call site.
client/asset/eth/rpcclient.go
Outdated
// balance gets the current balance of an address. | ||
func (c *rpcclient) balance(ctx context.Context, addr *common.Address) (*big.Int, error) { | ||
return c.ec.BalanceAt(ctx, *addr, nil) | ||
func (n *nodeClient) balance(ctx context.Context) (*big.Int, error) { | ||
return n.addressBalance(ctx, n.creds.addr) | ||
} |
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 the balance for the wallet only correct? Can change doc.
client/asset/eth/rpcclient.go
Outdated
return status != "Unlocked" | ||
} | ||
|
||
// sendToAddr sends funds from acct to addr. |
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.
acct is no longer a parameter, so from the internal account maybe?
client/asset/eth/rpcclient.go
Outdated
}), n.chainID) | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("signing error: %v", err) |
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.
Depending on whether eth increments the nonce, this could cause problems if erroring if it does increment. I guess it would increment maybe when the tx is sent though? In that case it would be fine to let this error. But if it already marked it as used I don't know if it would get stuck trying to send transactions/swaps with an out of order nonce.
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.
See other comment about nonce too, but go-ethereum seems to operate with the assumption that GetPoolNonce
is accurate as soon as SendTx
returns.
dex/networks/eth/contractor.go
Outdated
Secret: secret, | ||
SecretHash: sha256.Sum256(secret[:]), |
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.
These couldn't be fake? I think there was some discussion about this but I forget. EstimateInit is using fake secret hashes.
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.
Apparently not.
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 was wrong with random bytes? I would expect only the length to be important, not the content.
transactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) | ||
sendTransaction(ctx context.Context, tx map[string]string) (common.Hash, error) | ||
connect(ctx context.Context) error | ||
initiate(ctx context.Context, contracts []*asset.Contract, maxFeeRate uint64, contractVer uint32) (*types.Transaction, error) |
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 contractVer
doesn't need to map precisely to the dex.Asset.Version
, but as long as we make a rule that a change in contract always accompanies a bump in dex.Asset.Version
, then we can find a scheme to get the contract version from the asset version and we don't need any new data.
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.
A few quick comments, will have a proper review later today.
dex/networks/eth/contractor.go
Outdated
// contractorv0 is the Contractor for contract version 0. | ||
// Redeem and Refund methods of ETHSwap already have suitable return types. | ||
type contractorV0 struct { | ||
*ETHSwap |
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 should save the following suggestion for another day, but since ETHSwap
is the v0 contract API compiled from it's solidity, we'll probably want to move dex/networks/eth/contract.go -> dex/networks/eth/contracts/v0.go or dex/networks/eth/contracts/apis/v0.go
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.
A bit more. Sorry I'm reviewing this in 20 minute stints.
// state of a swap. | ||
SSNone SwapStep = iota | ||
// SSInitiated indicates that the swap has been initiated. | ||
SSInitiated |
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.
Just a note that like contract.go, these SwapStep
s are potentially contract version-specific and we'll have to address that when we move contract.go into subfolders for the different contract api versions as described in my other comment. Same deal with SwapState
. The v0 Contractor
uses these types, but it's not clear they will be the same for all future contract versions.
We're taking a leap with the definition of Swap(ctx context.Context, secretHash [32]byte) (*SwapState, error)
in the Contractor interface
that the swap state will always have the same definition, or that we can shoe-horn any version's swap "state" into this definition. I hope that's the case.
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 SwapState
is already a mirror of the ETHSwapState
, which is the contract-specific struct. The Contractor
is translating. SwapStep
and SwapState
are meant to defined for the needs of the DEX protocol, so in theory, shouldn't need modification with future contract changes. I wouldn't rule out that we do need to modify this stuff someday though, esp. SwapState
, but it's a struct so we can always make it work, I think.
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.
Understood that it's supposed to be a version-independent translation of ETHSwapState
. I'm just crossing my fingers that it will always be adequate (all fields applicable and/or sufficient).
But meh, we can change the struct in the future so a translation is good.
_, err := eth.sendToAddr(common.HexToAddress(addr), | ||
bigVal.Mul(bigVal, gweiFactorBig), big.NewInt(0).SetUint64(defaultGasFee)) | ||
func (eth *ExchangeWallet) Withdraw(addr string, value, _ uint64) (asset.Coin, error) { | ||
_, err := eth.node.sendToAddr(eth.ctx, common.HexToAddress(addr), value) |
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 seem to be deviating from the withdraw semantics we have been following with the other assets. That is, this gives to addr
a certain value
, whereas with the other assets we've had it reduce the wallet balance by value
, giving addr
something less than value
by network fees. I'm basing this comment on my understanding of (*nodeClient).sendToAddr
.
It does not seem to be an issue with this PR, but something we should resolve before production.
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.
Continuation of previous conversation, I think, but yes. We need to address this.
19d93a0
to
616fd74
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.
Looks good. Holding out for #1248, and for the outstanding reviewers to circle back.
// GweiToWei converts *big.Int Wei to uint64 Gwei. | ||
func WeiToGwei(v *big.Int) uint64 { | ||
return new(big.Int).Div(v, BigGweiFactor).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.
The original value could potentially be negative or overflow the returned uint64. There is also already a ToGwei in cointypes.go
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
The client/asset/eth package was using geth like an RPC client. That's not crazy, considering we have still an *rpc.Client, but in reality, there is no RPC server, and the client is just a shortcut to RPC handlers that call functions that we already have access to through e.g. *les.LightEthereum or *keystore.KeyStore. This removes most "rpc" calls in favor of calling the underlying methods directly. Moves some common types to dex/networks/eth. As a general rule, if the client/asset/ imports from server/asset, we should just move it to dex/networks. Creates the Contractor interface, which serves as a clear boundary between dex-space and abigen-space. This also enables clean upgrades, implemented here. Remove extraneous methods from the ethFetcher interface. Convert Contractor redemption gas estimation to batch. Move node.Node and KeyStore initialization to nodeClient constructor and remove unused ExchangeWallet fields. Move Contractor to client/asset/eth and unexport. Add params tests. Use new(big.Int) instead of bit.NewInt(0). Version the gas values. rename rpcclient files add nonce-send mutex fix atomic whoopsie
695ac56
to
920fc78
Compare
@@ -1085,58 +941,9 @@ func TestSwap(t *testing.T) { | |||
testName, receipt.Contract(), contract.SecretHash) | |||
} | |||
|
|||
if !bytes.Equal(node.lastInitiation.hash.Bytes(), receipt.Coin().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.
Why did you remove these? Shouldn't we be checking that initiate
is called with correct arguments?
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.
Good call. You probably see the PR, but I'll link for all: #1318
The client/asset/eth package was using go-ethereum like an RPC client. That's
not crazy, considering we have still an
*rpc.Client
, but in reality,there is no RPC server, and the client is just a shortcut to RPC handlers
that call functions that we already have access to through e.g.
*les.LightEthereum
or*keystore.KeyStore
. This PR removes allCallContext
calls in favor of calling the underlying methods directly.There are probably more
*rpc.Client
calls we could bypass as well.Moves some common types to dex/networks/eth. As a general rule, if
the client/asset/ imports from server/asset, we should just move it
to dex/networks.
Creates the
Contractor
interface, which serves as a clear boundarybetween dex-space and abigen-space. Implements contract versions.
Switches to EIP1559 transactions and removes hard-coded base fee rate
from harness. Due to this change, some live tests are still busted because
balance changes need to be fixes. Leaving in draft until that's done.
I realize this a big change in an area where we have a lot of hands right now,
but I'd rather do it earlier than later.