-
Notifications
You must be signed in to change notification settings - Fork 35
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
Ethereum transaction refactoring #624
base: master
Are you sure you want to change the base?
Changes from all commits
0a334e1
90de771
0881f5a
f50ceb1
c78fb26
d7b649f
9a8f962
66f1d54
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -180,7 +180,7 @@ func (s londonSigner) SenderPK(tx *EthereumTransaction) (*EthereumPublicKey, err | |
// DynamicFee txs are defined to use 0 and 1 as their recovery | ||
// id, add 27 to become equivalent to unprotected Homestead signatures. | ||
V = new(big.Int).Add(V, big.NewInt(27)) | ||
if tx.ChainId().Cmp(s.chainId) != 0 { | ||
if tx.inner.chainID().Cmp(s.chainId) != 0 { | ||
return nil, ErrInvalidChainId | ||
} | ||
return recoverEthereumPubKey(s.Hash(tx), R, S, V, true) | ||
|
@@ -262,7 +262,7 @@ func (s eip2930Signer) SenderPK(tx *EthereumTransaction) (*EthereumPublicKey, er | |
// AL txs are defined to use 0 and 1 as their recovery | ||
// id, add 27 to become equivalent to unprotected Homestead signatures. | ||
V = new(big.Int).Add(V, big.NewInt(27)) | ||
if tx.ChainId().Cmp(s.chainId) != 0 { | ||
if tx.inner.chainID().Cmp(s.chainId) != 0 { | ||
return nil, ErrInvalidChainId | ||
} | ||
return recoverEthereumPubKey(s.Hash(tx), R, S, V, true) | ||
|
@@ -342,7 +342,7 @@ func (s eip155Signer) SenderPK(tx *EthereumTransaction) (*EthereumPublicKey, err | |
if !tx.Protected() { | ||
return HomesteadSigner{}.SenderPK(tx) | ||
} | ||
if tx.ChainId().Cmp(s.chainId) != 0 { | ||
if tx.inner.chainID().Cmp(s.chainId) != 0 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same as above. |
||
return nil, ErrInvalidChainId | ||
} | ||
V, R, S := tx.RawSignatureValues() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,6 +27,12 @@ const ( | |
EthereumDynamicFeeTxType | ||
) | ||
|
||
const ( | ||
EthereumTransferWavesKind = iota + 1 | ||
EthereumTransferAssetsKind | ||
EthereumInvokeKind | ||
) | ||
|
||
const ( | ||
EthereumTransferMinFee uint64 = 100_000 | ||
EthereumScriptedAssetMinFee uint64 = 400_000 | ||
|
@@ -91,7 +97,7 @@ type EthereumTxData interface { | |
|
||
type EthereumTransactionKind interface { | ||
String() string | ||
DecodedData() *ethabi.DecodedCallData | ||
DecodedData() (*ethabi.DecodedCallData, error) | ||
} | ||
|
||
type EthereumTransferWavesTxKind struct { | ||
|
@@ -101,8 +107,8 @@ func NewEthereumTransferWavesTxKind() *EthereumTransferWavesTxKind { | |
return &EthereumTransferWavesTxKind{} | ||
} | ||
|
||
func (tx *EthereumTransferWavesTxKind) DecodedData() *ethabi.DecodedCallData { | ||
return nil | ||
func (tx *EthereumTransferWavesTxKind) DecodedData() (*ethabi.DecodedCallData, error) { | ||
return nil, errors.New("transfer waves ethereum tx kind does not have decoded call data") | ||
} | ||
|
||
func (tx *EthereumTransferWavesTxKind) String() string { | ||
|
@@ -112,42 +118,57 @@ func (tx *EthereumTransferWavesTxKind) String() string { | |
type EthereumTransferAssetsErc20TxKind struct { | ||
decodedData ethabi.DecodedCallData | ||
Arguments ethabi.ERC20TransferArguments | ||
Asset OptionalAsset | ||
OptAsset *OptionalAsset | ||
} | ||
|
||
func NewEthereumTransferAssetsErc20TxKind(decodedData ethabi.DecodedCallData, asset OptionalAsset, arguments ethabi.ERC20TransferArguments) *EthereumTransferAssetsErc20TxKind { | ||
return &EthereumTransferAssetsErc20TxKind{Asset: asset, decodedData: decodedData, Arguments: arguments} | ||
func NewEthereumTransferAssetsErc20TxKind(decodedData ethabi.DecodedCallData, asset *OptionalAsset, arguments ethabi.ERC20TransferArguments) *EthereumTransferAssetsErc20TxKind { | ||
return &EthereumTransferAssetsErc20TxKind{OptAsset: asset, decodedData: decodedData, Arguments: arguments} | ||
} | ||
|
||
func (tx *EthereumTransferAssetsErc20TxKind) DecodedData() *ethabi.DecodedCallData { | ||
return &tx.decodedData | ||
func (tx *EthereumTransferAssetsErc20TxKind) DecodedData() (*ethabi.DecodedCallData, error) { | ||
return &tx.decodedData, nil | ||
} | ||
func (tx *EthereumTransferAssetsErc20TxKind) Asset() (*OptionalAsset, error) { | ||
if tx.OptAsset == nil { | ||
return nil, errors.New("asset field of ethereum transfer assets tx is empty") | ||
} | ||
return tx.OptAsset, nil | ||
} | ||
|
||
func (tx *EthereumTransferAssetsErc20TxKind) String() string { | ||
return "EthereumTransferAssetsErc20TxKind" | ||
} | ||
|
||
type EthereumInvokeScriptTxKind struct { | ||
decodedData ethabi.DecodedCallData | ||
DecodedCallData *ethabi.DecodedCallData | ||
} | ||
|
||
func NewEthereumInvokeScriptTxKind(decodedData ethabi.DecodedCallData) *EthereumInvokeScriptTxKind { | ||
return &EthereumInvokeScriptTxKind{decodedData: decodedData} | ||
func NewEthereumInvokeScriptTxKind(decodedData *ethabi.DecodedCallData) *EthereumInvokeScriptTxKind { | ||
return &EthereumInvokeScriptTxKind{DecodedCallData: decodedData} | ||
} | ||
|
||
func (tx *EthereumInvokeScriptTxKind) DecodedData() *ethabi.DecodedCallData { | ||
return &tx.decodedData | ||
func (tx *EthereumInvokeScriptTxKind) DecodedData() (*ethabi.DecodedCallData, error) { | ||
if tx.DecodedCallData == nil { | ||
return nil, errors.New("ethereum invoke script tx has empty decoded data") | ||
} | ||
return tx.DecodedCallData, nil | ||
} | ||
|
||
func (tx *EthereumInvokeScriptTxKind) String() string { | ||
return "EthereumInvokeScriptTxKind" | ||
} | ||
|
||
type EthereumTransaction struct { | ||
inner EthereumTxData | ||
innerBinarySize int | ||
senderPK atomic.Value // *EthereumPublicKey | ||
// Ethereum representation | ||
senderPK atomic.Value // *EthereumPublicKey | ||
inner EthereumTxData // | ||
|
||
// Waves representation | ||
TxKind EthereumTransactionKind | ||
Comment on lines
+162
to
167
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use different naming for |
||
value int64 | ||
chainID int64 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
gasPrice uint64 | ||
innerBinarySize int | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is not |
||
ID *crypto.Digest | ||
} | ||
|
||
|
@@ -159,6 +180,9 @@ func NewEthereumTransaction(inner EthereumTxData, txKind EthereumTransactionKind | |
TxKind: txKind, | ||
ID: id, | ||
} | ||
res := new(big.Int).Div(tx.inner.value(), big.NewInt(int64(DiffEthWaves))) | ||
tx.value = res.Int64() | ||
|
||
Comment on lines
+183
to
+185
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should handle it res doesn't fit into |
||
tx.threadSafeSetSenderPK(senderPK) | ||
return tx | ||
} | ||
|
@@ -215,7 +239,7 @@ func (tx *EthereumTransaction) Verify() (*EthereumPublicKey, error) { | |
if senderPK := tx.threadSafeGetSenderPK(); senderPK != nil { | ||
return senderPK, nil | ||
} | ||
signer := MakeEthereumSigner(tx.ChainId()) | ||
signer := MakeEthereumSigner(tx.inner.chainID()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use |
||
senderPK, err := signer.SenderPK(tx) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to verify EthereumTransaction") | ||
|
@@ -228,9 +252,9 @@ func (tx *EthereumTransaction) Verify() (*EthereumPublicKey, error) { | |
// This method doesn't include signature verification. Use Verify method for signature verification | ||
func (tx *EthereumTransaction) Validate(scheme Scheme) (Transaction, error) { | ||
// same chainID | ||
if tx.ChainId().Cmp(big.NewInt(int64(scheme))) != 0 { | ||
if tx.ChainId() != int64(scheme) { | ||
// TODO: introduce new error type for scheme validation | ||
txChainID := tx.ChainId().Uint64() | ||
txChainID := tx.ChainId() | ||
return nil, errs.NewTxValidationError(fmt.Sprintf( | ||
"Address belongs to another network: expected: %d(%c), actual: %d(%c)", | ||
scheme, scheme, | ||
|
@@ -250,6 +274,7 @@ func (tx *EthereumTransaction) Validate(scheme Scheme) (Transaction, error) { | |
return nil, errs.NewFeeValidation("insufficient fee") | ||
} | ||
// too many waves (this check doesn't exist in scala) | ||
// TODO I'm not sure this is should be checked for all eth tx kinds. Only for transfer waves kind | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's valid. Negative wavelets amount is invalid. It must be zero or greater. |
||
wavelets, err := EthereumWeiToWavelet(tx.Value()) | ||
if err != nil { | ||
return nil, errs.NewFeeValidation(err.Error()) | ||
|
@@ -259,15 +284,15 @@ func (tx *EthereumTransaction) Validate(scheme Scheme) (Transaction, error) { | |
return nil, errs.NewNonPositiveAmount(wavelets, "waves") | ||
} | ||
// a cancel transaction: value == 0 && data == 0x | ||
if tx.Value().Cmp(big0) == 0 && len(tx.Data()) == 0 { | ||
if tx.Value() == 0 && len(tx.Data()) == 0 { | ||
return nil, errs.NewTxValidationError("Transaction cancellation is not supported") | ||
} | ||
// either data or value field is set | ||
if tx.Value().Cmp(big0) != 0 && len(tx.Data()) != 0 { | ||
if tx.Value() != 0 && len(tx.Data()) != 0 { | ||
return nil, errs.NewTxValidationError("Transaction should have either data or value") | ||
} | ||
// gasPrice == 10GWei | ||
if tx.GasPrice().Cmp(new(big.Int).SetUint64(EthereumGasPrice)) != 0 { | ||
if tx.GasPrice() != EthereumGasPrice { | ||
return nil, errs.NewTxValidationError("Gas price must be 10 Gwei") | ||
} | ||
// deny a contract creation transaction (this check doesn't exist in scala) | ||
|
@@ -375,8 +400,8 @@ func (tx *EthereumTransaction) EthereumTxType() EthereumTxType { | |
// ChainId returns the EIP155 chain ID of the transaction. The return value will always be | ||
// non-nil. For legacy transactions which are not replay-protected, the return value is | ||
// zero. | ||
func (tx *EthereumTransaction) ChainId() *big.Int { | ||
return tx.inner.chainID() | ||
func (tx *EthereumTransaction) ChainId() int64 { | ||
return tx.chainID | ||
} | ||
Comment on lines
+403
to
405
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should change return type to |
||
|
||
// Data returns the input data of the transaction. | ||
|
@@ -389,16 +414,10 @@ func (tx *EthereumTransaction) AccessList() EthereumAccessList { return tx.inner | |
func (tx *EthereumTransaction) Gas() uint64 { return tx.inner.gas() } | ||
|
||
// GasPrice returns the gas price of the transaction. | ||
func (tx *EthereumTransaction) GasPrice() *big.Int { return copyBigInt(tx.inner.gasPrice()) } | ||
|
||
// GasTipCap returns the gasTipCap per gas of the transaction. | ||
func (tx *EthereumTransaction) GasTipCap() *big.Int { return copyBigInt(tx.inner.gasTipCap()) } | ||
|
||
// GasFeeCap returns the fee cap per gas of the transaction. | ||
func (tx *EthereumTransaction) GasFeeCap() *big.Int { return copyBigInt(tx.inner.gasFeeCap()) } | ||
func (tx *EthereumTransaction) GasPrice() uint64 { return tx.gasPrice } | ||
|
||
// Value returns the ether amount of the transaction. | ||
func (tx *EthereumTransaction) Value() *big.Int { return copyBigInt(tx.inner.value()) } | ||
func (tx *EthereumTransaction) Value() int64 { return tx.value } | ||
Comment on lines
-401
to
+420
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment says that |
||
|
||
// Nonce returns the sender account nonce of the transaction. | ||
func (tx *EthereumTransaction) Nonce() uint64 { return tx.inner.nonce() } | ||
|
@@ -458,6 +477,98 @@ func (tx *EthereumTransaction) RawSignatureValues() (v, r, s *big.Int) { | |
return tx.inner.rawSignatureValues() | ||
} | ||
|
||
func validateEthereumTx(tx *EthereumTransaction) error { | ||
switch tx.TxKind.(type) { | ||
case *EthereumTransferWavesTxKind: | ||
res := new(big.Int).Div(tx.inner.value(), big.NewInt(int64(DiffEthWaves))) | ||
if ok := res.IsInt64(); !ok { | ||
return errors.Errorf("failed to convert amount from ethreum transaction (big int) to int64. value is %d", tx.Value()) | ||
} | ||
Comment on lines
+483
to
+486
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can easily use previous version for of |
||
tx.value = res.Int64() | ||
return nil | ||
case *EthereumTransferAssetsErc20TxKind: | ||
return nil | ||
case *EthereumInvokeScriptTxKind: | ||
return nil | ||
Comment on lines
+489
to
+492
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Merge this cases or rewrite whole switch to if...else. |
||
default: | ||
return errors.New("failed to check ethereum transaction, wrong kind of tx") | ||
} | ||
} | ||
|
||
func GuessEthereumTransactionKind(data []byte) (int64, error) { | ||
if len(data) == 0 { | ||
return EthereumTransferWavesKind, nil | ||
} | ||
|
||
selectorBytes := data | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is unnecessary variable |
||
if len(data) < ethabi.SelectorSize { | ||
return 0, errors.Errorf("length of data from ethereum transaction is less than %d", ethabi.SelectorSize) | ||
} | ||
selector, err := ethabi.NewSelectorFromBytes(selectorBytes[:ethabi.SelectorSize]) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
if err != nil { | ||
return 0, errors.Wrap(err, "failed to guess ethereum transaction kind") | ||
} | ||
|
||
if ethabi.IsERC20TransferSelector(selector) { | ||
return EthereumTransferAssetsKind, nil | ||
} | ||
|
||
return EthereumInvokeKind, nil | ||
} | ||
|
||
// note: in this method not all of the fields are filled | ||
func GetEthereumTransactionKind(ethTx EthereumTransaction) (EthereumTransactionKind, error) { | ||
txKind, err := GuessEthereumTransactionKind(ethTx.Data()) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to guess ethereum tx kind") | ||
} | ||
|
||
switch txKind { | ||
case EthereumTransferWavesKind: | ||
return NewEthereumTransferWavesTxKind(), nil | ||
case EthereumTransferAssetsKind: | ||
db := ethabi.NewErc20MethodsMap() | ||
decodedData, err := db.ParseCallData(ethTx.Data()) | ||
if err != nil { | ||
return nil, errors.Errorf("failed to parse ethereum data") | ||
} | ||
if len(decodedData.Inputs) != ethabi.NumberOfERC20TransferArguments { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This check is unnecessary. |
||
return nil, errors.Errorf("the number of arguments of erc20 function is %d, but expected it to be %d", len(decodedData.Inputs), ethabi.NumberOfERC20TransferArguments) | ||
} | ||
|
||
erc20Arguments, err := ethabi.GetERC20TransferArguments(decodedData) | ||
if err != nil { | ||
return nil, errors.Wrap(err, "failed to get erc20 arguments from decoded data") | ||
} | ||
return NewEthereumTransferAssetsErc20TxKind(*decodedData, nil, erc20Arguments), nil | ||
case EthereumInvokeKind: | ||
return NewEthereumInvokeScriptTxKind(nil), nil | ||
default: | ||
return nil, errors.New("unexpected ethereum tx kind") | ||
} | ||
} | ||
|
||
func (tx *EthereumTransaction) initTransactionFields() error { | ||
if ok := tx.inner.chainID().IsInt64(); !ok { | ||
return errors.Errorf("failed to recognize chainID of ethereum transaction: over int64") | ||
} | ||
tx.chainID = tx.inner.chainID().Int64() | ||
if ok := tx.inner.gasPrice().IsUint64(); !ok { | ||
return errors.Errorf("failed to recognize GasPrice of ethereum transaction: over uint64") | ||
} | ||
Comment on lines
+555
to
+558
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You should check that |
||
tx.gasPrice = tx.inner.gasPrice().Uint64() | ||
var err error | ||
tx.TxKind, err = GetEthereumTransactionKind(*tx) | ||
if err != nil { | ||
return errors.Errorf("failed to guess ethereum transaction kind, %v", err) | ||
} | ||
err = validateEthereumTx(tx) | ||
if err != nil { | ||
return errors.Errorf("validation of ethereum transaction after initialization failed , %v", err) | ||
} | ||
return nil | ||
} | ||
|
||
// DecodeCanonical decodes the canonical binary encoding of transactions. | ||
// It supports legacy RLP transactions and EIP2718 typed transactions. | ||
func (tx *EthereumTransaction) DecodeCanonical(canonicalData []byte) error { | ||
|
@@ -488,6 +599,11 @@ func (tx *EthereumTransaction) DecodeCanonical(canonicalData []byte) error { | |
tx.inner = inner | ||
} | ||
tx.innerBinarySize = len(canonicalData) | ||
|
||
err := tx.initTransactionFields() | ||
if err != nil { | ||
return errors.Wrap(err, "failed to initialize waves representation fields of ethereum transaction") | ||
} | ||
return 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.
Why did you change
tx.ChainId()
totx.inner.chainID()
? What is the reason?