Skip to content

Commit

Permalink
[action] support pre-EIP155 unprotected transaction
Browse files Browse the repository at this point in the history
  • Loading branch information
dustinxie committed Nov 14, 2023
1 parent be3a09a commit 9a9d640
Show file tree
Hide file tree
Showing 13 changed files with 489 additions and 24 deletions.
72 changes: 68 additions & 4 deletions action/rlp_tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,6 @@ func RawTxToSignedTx(rawTx *types.Transaction, signer types.Signer, sig []byte)
sc[64] -= 27
}

// TODO: currently all our web3 tx are EIP-155 protected tx
// in the future release, use proper signer for other supported tx types (EIP-1559, EIP-2930)
signedTx, err := rawTx.WithSignature(signer, sc)
if err != nil {
return nil, err
Expand Down Expand Up @@ -87,14 +85,80 @@ func DecodeRawTx(rawData string, chainID uint32) (tx *types.Transaction, sig []b

// NewEthSigner returns the proper signer for Eth-compatible tx
func NewEthSigner(txType iotextypes.Encoding, chainID uint32) (types.Signer, error) {
// TODO: use proper signer according to tx type
switch txType {
case iotextypes.Encoding_IOTEX_PROTOBUF:
// native tx use same signature format as that of Homestead
return types.HomesteadSigner{}, nil
case iotextypes.Encoding_ETHEREUM_RLP:
case iotextypes.Encoding_ETHEREUM_EIP155:
return types.NewEIP155Signer(big.NewInt(int64(chainID))), nil
case iotextypes.Encoding_ETHEREUM_UNPROTECTED:
return types.HomesteadSigner{}, nil
default:
return nil, ErrInvalidAct
}
}

// DecodeEtherTx decodes raw data string into eth tx
func DecodeEtherTx(rawData string) (*types.Transaction, error) {
//remove Hex prefix and decode string to byte
if strings.HasPrefix(rawData, "0x") || strings.HasPrefix(rawData, "0X") {
rawData = rawData[2:]
}
rawTxBytes, err := hex.DecodeString(rawData)
if err != nil {
return nil, err
}

// decode raw data into eth tx
tx := types.Transaction{}
if err = tx.UnmarshalBinary(rawTxBytes); err != nil {
return nil, err
}
return &tx, nil
}

// ExtractTypeSigPubkey extracts tx type, signature, and pubkey
func ExtractTypeSigPubkey(tx *types.Transaction) (iotextypes.Encoding, []byte, crypto.PublicKey, error) {
var (
encoding iotextypes.Encoding
signer types.Signer
V, R, S = tx.RawSignatureValues()
)
// extract correct V value
switch tx.Type() {
case types.LegacyTxType:
if tx.Protected() {
chainIDMul := tx.ChainId()
V = new(big.Int).Sub(V, chainIDMul.Lsh(chainIDMul, 1))
V.Sub(V, big.NewInt(8))
encoding = iotextypes.Encoding_ETHEREUM_EIP155
signer = types.NewEIP155Signer(tx.ChainId())
} else {
// tx has pre-EIP155 signature
encoding = iotextypes.Encoding_ETHEREUM_UNPROTECTED
signer = types.HomesteadSigner{}
}
default:
return encoding, nil, nil, ErrNotSupported
}

// construct signature
if V.BitLen() > 8 {
return encoding, nil, nil, ErrNotSupported
}

var (
r, s = R.Bytes(), S.Bytes()
sig = make([]byte, 65)
pubkey crypto.PublicKey
err error
)
copy(sig[32-len(r):32], r)
copy(sig[64-len(s):64], s)
sig[64] = byte(V.Uint64())

// recover public key
rawHash := signer.Hash(tx)
pubkey, err = crypto.RecoverPubkey(rawHash[:], sig)
return encoding, sig, pubkey, err
}
343 changes: 339 additions & 4 deletions action/rlp_tx_test.go

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions action/sealedenvelope.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ type SealedEnvelope struct {
// an all-0 return value means the transaction is invalid
func (sealed *SealedEnvelope) envelopeHash() (hash.Hash256, error) {
switch sealed.encoding {
case iotextypes.Encoding_ETHEREUM_RLP:
case iotextypes.Encoding_ETHEREUM_EIP155, iotextypes.Encoding_ETHEREUM_UNPROTECTED:
act, ok := sealed.Action().(EthCompatibleAction)
if !ok {
return hash.ZeroHash256, ErrInvalidAct
Expand Down Expand Up @@ -66,7 +66,7 @@ func (sealed *SealedEnvelope) Hash() (hash.Hash256, error) {

func (sealed *SealedEnvelope) calcHash() (hash.Hash256, error) {
switch sealed.encoding {
case iotextypes.Encoding_ETHEREUM_RLP:
case iotextypes.Encoding_ETHEREUM_EIP155, iotextypes.Encoding_ETHEREUM_UNPROTECTED:
act, ok := sealed.Action().(EthCompatibleAction)
if !ok {
return hash.ZeroHash256, ErrInvalidAct
Expand Down Expand Up @@ -144,7 +144,7 @@ func (sealed *SealedEnvelope) loadProto(pbAct *iotextypes.Action, evmID uint32)
}
encoding := pbAct.GetEncoding()
switch encoding {
case iotextypes.Encoding_ETHEREUM_RLP:
case iotextypes.Encoding_ETHEREUM_EIP155, iotextypes.Encoding_ETHEREUM_UNPROTECTED:
// verify action type can support RLP-encoding
act, ok := elp.Action().(EthCompatibleAction)
if !ok {
Expand Down
2 changes: 1 addition & 1 deletion action/sealedenvelope_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func TestSealedEnvelope_InvalidType(t *testing.T) {
SetAction(r).
SetGasLimit(100000).Build()
selp := FakeSeal(elp, identityset.PrivateKey(27).PublicKey())
selp.encoding = iotextypes.Encoding_ETHEREUM_RLP
selp.encoding = iotextypes.Encoding_ETHEREUM_EIP155
hash1, err := selp.envelopeHash()
require.Equal(hash1, hash.ZeroHash256)
require.Contains(err.Error(), "invalid action type")
Expand Down
7 changes: 7 additions & 0 deletions api/coreservice.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ type (
LogsInBlockByHash(filter *logfilter.LogFilter, blockHash hash.Hash256) ([]*action.Log, error)
// LogsInRange filter logs among [start, end] blocks
LogsInRange(filter *logfilter.LogFilter, start, end, paginationSize uint64) ([]*action.Log, []hash.Hash256, error)
// Genesis returns the genesis of the chain
Genesis() genesis.Genesis
// EVMNetworkID returns the network id of evm
EVMNetworkID() uint32
// ChainID returns the chain id of evm
Expand Down Expand Up @@ -1589,6 +1591,11 @@ func (core *coreService) ActionsInActPool(actHashes []string) ([]action.SealedEn
return ret, nil
}

// Genesis returns the genesis of the chain
func (core *coreService) Genesis() genesis.Genesis {
return core.bc.Genesis()
}

// EVMNetworkID returns the network id of evm
func (core *coreService) EVMNetworkID() uint32 {
return core.bc.EvmNetworkID()
Expand Down
36 changes: 31 additions & 5 deletions api/web3server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (

"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/iotexproject/go-pkgs/crypto"
"github.com/iotexproject/go-pkgs/hash"
"github.com/iotexproject/go-pkgs/util"
"github.com/iotexproject/iotex-address/address"
Expand Down Expand Up @@ -73,6 +74,7 @@ var (
errInvalidFormat = errors.New("invalid format of request")
errNotImplemented = errors.New("method not implemented")
errInvalidFilterID = errors.New("filter not found")
errInvalidEvmChainID = errors.New("invalid EVM chain ID")
errInvalidBlock = errors.New("invalid block")
errUnsupportedAction = errors.New("the type of action is not supported")
errMsgBatchTooLarge = errors.New("batch too large")
Expand Down Expand Up @@ -433,9 +435,33 @@ func (svr *web3Handler) sendRawTransaction(in *gjson.Result) (interface{}, error
return nil, errInvalidFormat
}
// parse raw data string from json request
tx, sig, pubkey, err := action.DecodeRawTx(dataStr.String(), svr.coreService.EVMNetworkID())
if err != nil {
return nil, err
var (
cs = svr.coreService
tx *types.Transaction
encoding iotextypes.Encoding
sig []byte
pubkey crypto.PublicKey
err error
)
if g := cs.Genesis(); g.IsSumatra(cs.TipHeight()) {
tx, err = action.DecodeEtherTx(dataStr.String())
if err != nil {
return nil, err
}
if tx.Protected() && tx.ChainId().Uint64() != uint64(cs.EVMNetworkID()) {
return nil, errors.Wrapf(errInvalidEvmChainID, "expect chainID = %d, got %d", cs.EVMNetworkID(), tx.ChainId().Uint64())
}
encoding, sig, pubkey, err = action.ExtractTypeSigPubkey(tx)
if err != nil {
return nil, err
}
} else {
tx, sig, pubkey, err = action.DecodeRawTx(dataStr.String(), cs.EVMNetworkID())
if err != nil {
return nil, err
}
// before Sumatra height, all tx are EIP-155 format
encoding = iotextypes.Encoding_ETHEREUM_EIP155
}
elp, err := svr.ethTxToEnvelope(tx)
if err != nil {
Expand All @@ -445,9 +471,9 @@ func (svr *web3Handler) sendRawTransaction(in *gjson.Result) (interface{}, error
Core: elp.Proto(),
SenderPubKey: pubkey.Bytes(),
Signature: sig,
Encoding: iotextypes.Encoding_ETHEREUM_RLP,
Encoding: encoding,
}
actionHash, err := svr.coreService.SendAction(context.Background(), req)
actionHash, err := cs.SendAction(context.Background(), req)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion api/web3server_integrity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ func web3Staking(t *testing.T, handler *hTTPHandler) {
big.NewInt(0),
test.data,
)
signer, err := action.NewEthSigner(iotextypes.Encoding_ETHEREUM_RLP, _evmNetworkID)
signer, err := action.NewEthSigner(iotextypes.Encoding_ETHEREUM_EIP155, _evmNetworkID)
require.NoError(err)
tx, err := types.SignTx(rawTx, signer, ecdsaPvk)
require.NoError(err)
Expand Down
7 changes: 4 additions & 3 deletions api/web3server_marshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ func TestBlockObjectMarshal(t *testing.T) {

t.Run("BlockWithDetail", func(t *testing.T) {
raw := types.NewContractCreation(2, unit.ConvertIotxToRau(1000), 21000, unit.ConvertIotxToRau(1), []byte{})
signer, err := action.NewEthSigner(iotextypes.Encoding_ETHEREUM_RLP, 0)
signer, err := action.NewEthSigner(iotextypes.Encoding_ETHEREUM_EIP155, 0)
require.NoError(err)
ethTx, err := action.RawTxToSignedTx(raw, signer, sevlp.Signature())
require.NoError(err)
Expand Down Expand Up @@ -233,9 +233,10 @@ func TestTransactionObjectMarshal(t *testing.T) {
t.Run("ContractCreation", func(t *testing.T) {
raw := types.NewContractCreation(1, big.NewInt(10), 21000, big.NewInt(0), []byte{})
sig, _ := hex.DecodeString("363964383961306166323764636161363766316236326133383335393464393735393961616464326237623136346362343131326161386464666434326638391b")
signer, err := action.NewEthSigner(iotextypes.Encoding_ETHEREUM_RLP, 4690)
signer, err := action.NewEthSigner(iotextypes.Encoding_ETHEREUM_EIP155, 4690)
require.NoError(err)
tx, err := action.RawTxToSignedTx(raw, signer, sig)
require.NoError(err)
res, err := json.Marshal(&getTransactionResult{
blockHash: _testBlkHash,
to: nil,
Expand Down Expand Up @@ -271,7 +272,7 @@ func TestTransactionObjectMarshal(t *testing.T) {
pubkey, _ := crypto.HexStringToPublicKey("04806b217cb0b6a675974689fd99549e525d967287eee9a62dc4e598eea981b8158acfe026da7bf58397108abd0607672832c28ef3bc7b5855077f6e67ab5fc096")
actHash, _ := hash.HexStringToHash256("cbc2560d986d79a46bfd96a08d18c6045b29f97352c1360289e371d9cffd6b6a")
raw := types.NewContractCreation(305, big.NewInt(0), 297131, big.NewInt(1000000000000), data)
signer, err := action.NewEthSigner(iotextypes.Encoding_ETHEREUM_RLP, 0)
signer, err := action.NewEthSigner(iotextypes.Encoding_ETHEREUM_EIP155, 0)
require.NoError(err)
tx, err := action.RawTxToSignedTx(raw, signer, sig)
require.NoError(err)
Expand Down
3 changes: 3 additions & 0 deletions api/web3server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/iotexproject/iotex-core/action"
apitypes "github.com/iotexproject/iotex-core/api/types"
"github.com/iotexproject/iotex-core/blockchain/block"
"github.com/iotexproject/iotex-core/blockchain/genesis"
"github.com/iotexproject/iotex-core/test/identityset"
"github.com/iotexproject/iotex-core/test/mock/mock_apicoreservice"
mock_apitypes "github.com/iotexproject/iotex-core/test/mock/mock_apiresponder"
Expand Down Expand Up @@ -383,6 +384,8 @@ func TestSendRawTransaction(t *testing.T) {
defer ctrl.Finish()
core := mock_apicoreservice.NewMockCoreService(ctrl)
web3svr := &web3Handler{core, nil, _defaultBatchRequestLimit}
core.EXPECT().Genesis().Return(genesis.Default)
core.EXPECT().TipHeight().Return(uint64(0))
core.EXPECT().EVMNetworkID().Return(uint32(1))
core.EXPECT().ChainID().Return(uint32(1))
core.EXPECT().Account(gomock.Any()).Return(&iotextypes.AccountMeta{IsContract: true}, nil, nil)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ require (
github.com/iotexproject/iotex-address v0.2.8
github.com/iotexproject/iotex-antenna-go/v2 v2.5.1
github.com/iotexproject/iotex-election v0.3.5-0.20210611041425-20ddf674363d
github.com/iotexproject/iotex-proto v0.5.14
github.com/iotexproject/iotex-proto v0.5.15-0.20231108184801-bc89358f2344
github.com/libp2p/go-libp2p-core v0.8.5
github.com/mackerelio/go-osstat v0.2.4
github.com/miguelmota/go-ethereum-hdwallet v0.1.1
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -511,8 +511,8 @@ github.com/iotexproject/iotex-antenna-go/v2 v2.5.1/go.mod h1:8pDZcM45M0gY6jm3PoM
github.com/iotexproject/iotex-election v0.3.5-0.20210611041425-20ddf674363d h1:/j1xCAC9YiG/8UKqYvycS/v3ddVsb1G7AMyLXOjeYI0=
github.com/iotexproject/iotex-election v0.3.5-0.20210611041425-20ddf674363d/go.mod h1:GRWevxtqQ4gPMrd7Qxhr29/7aTgvjiTp+rFI9KMMZEo=
github.com/iotexproject/iotex-proto v0.5.0/go.mod h1:Xg6REkv+nTZN+OC22xXIQuqKdTWWHwOAJEXCoMpDwtI=
github.com/iotexproject/iotex-proto v0.5.14 h1:03UuHO5M1Dr6mjPk+72zoJfY2OEm93k9Z0oggHuzA6s=
github.com/iotexproject/iotex-proto v0.5.14/go.mod h1:wQpCk3Df0fPID+K8ohiICGj+cWRmcQ3wanT+aSrnIPo=
github.com/iotexproject/iotex-proto v0.5.15-0.20231108184801-bc89358f2344 h1:Io64R5K/xpDIvvRXVzRq8Z1QQr/C+MN9780Jj2aj6u8=
github.com/iotexproject/iotex-proto v0.5.15-0.20231108184801-bc89358f2344/go.mod h1:wQpCk3Df0fPID+K8ohiICGj+cWRmcQ3wanT+aSrnIPo=
github.com/ipfs/go-cid v0.0.1/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
github.com/ipfs/go-cid v0.0.2/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
github.com/ipfs/go-cid v0.0.3/go.mod h1:GHWU/WuQdMPmIosc4Yn1bcCT7dSeX4lBafM7iqUPQvM=
Expand Down
15 changes: 15 additions & 0 deletions test/mock/mock_apicoreservice/mock_apicoreservice.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 14 additions & 0 deletions test/mock/mock_blocksync/mock_blocksync.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 9a9d640

Please sign in to comment.