From 00dcb4b7c041d53926b7d5a2f87ea751a11aa9b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20Negovanovi=C4=87?= <93934272+Stefan-Ethernal@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:44:35 +0100 Subject: [PATCH 1/2] Upgrade EVM tests to the v13.2 (#161) --- tests/tests | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests b/tests/tests index 853b1e03b1..066a5878da 160000 --- a/tests/tests +++ b/tests/tests @@ -1 +1 @@ -Subproject commit 853b1e03b1078d370614002851ba1ee9803d9fcf +Subproject commit 066a5878da000bbf0ff95205c62a6c5c91ca6f52 From e6f539cf64a7d0ba8ab8b8c79fe85eabc397906b Mon Sep 17 00:00:00 2001 From: Goran Rojovic <100121253+goran-ethernal@users.noreply.github.com> Date: Wed, 20 Mar 2024 15:44:24 +0100 Subject: [PATCH 2/2] New 'Ethereum JSONRPC' client implementation (#160) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * New EthClient * Remove unused code * CallMsg * e2e test * Lint fix * Remove temporary test * Fix e2e test * Fix the tests version * Go mod tidy and use the Keccak256 from the crypto pkg * Place github.com/valyala/fastjson among the direct deps * Comments fix --------- Co-authored-by: Stefan Negovanović --- consensus/polybft/checkpoint_manager_test.go | 24 -- consensus/polybft/consensus_runtime.go | 15 - consensus/polybft/governance_manager_test.go | 21 - consensus/polybft/helpers_test.go | 2 +- consensus/polybft/polybft.go | 5 - consensus/polybft/sc_integration_test.go | 6 +- consensus/polybft/stake_manager.go | 12 - consensus/polybft/stake_manager_fuzz_test.go | 5 - consensus/polybft/state_event_getter.go | 2 - consensus/polybft/state_store_exit.go | 5 +- consensus/polybft/state_store_state_sync.go | 2 - consensus/polybft/state_sync_commitment.go | 5 - consensus/polybft/state_sync_relayer.go | 1 - consensus/polybft/stats.go | 54 --- e2e-polybft/e2e/helpers_test.go | 99 ----- e2e-polybft/e2e/jsonrpc_test.go | 272 ++++++++++++ go.mod | 2 +- jsonrpc/client.go | 178 ++++++++ jsonrpc/codec.go | 40 ++ jsonrpc/dispatcher.go | 2 +- jsonrpc/eth_endpoint.go | 39 +- jsonrpc/eth_endpoint_test.go | 2 +- jsonrpc/jsonrpc.go | 21 - jsonrpc/types.go | 173 +++++++- state/runtime/precompiled/console.go | 15 +- types/access_list_tx.go | 89 ++++ types/base_tx.go | 78 ++++ types/dynamic_fee_tx.go | 36 ++ types/json_unmarshal.go | 411 +++++++++++++++++++ types/legacy_tx.go | 16 + types/state_tx.go | 16 + types/transaction.go | 2 + 32 files changed, 1317 insertions(+), 333 deletions(-) create mode 100644 jsonrpc/client.go create mode 100644 types/json_unmarshal.go diff --git a/consensus/polybft/checkpoint_manager_test.go b/consensus/polybft/checkpoint_manager_test.go index 0f9241f981..1e0430ac73 100644 --- a/consensus/polybft/checkpoint_manager_test.go +++ b/consensus/polybft/checkpoint_manager_test.go @@ -7,13 +7,10 @@ import ( "strconv" "testing" - "github.com/umbracle/ethgo/abi" "github.com/umbracle/ethgo/jsonrpc" "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" "github.com/0xPolygon/polygon-edge/consensus/polybft/signer" - "github.com/0xPolygon/polygon-edge/contracts" - "github.com/0xPolygon/polygon-edge/helper/common" merkle "github.com/Ethernal-Tech/merkle-tree" hclog "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/mock" @@ -509,24 +506,3 @@ func getBlockNumberCheckpointSubmitInput(t *testing.T, input []byte) uint64 { return submit.Checkpoint.BlockNumber.Uint64() } - -func createTestLogForExitEvent(t *testing.T, exitEventID uint64) *types.Log { - t.Helper() - - var exitEvent contractsapi.L2StateSyncedEvent - - topics := make([]types.Hash, 4) - topics[0] = types.Hash(exitEvent.Sig()) - topics[1] = types.BytesToHash(common.EncodeUint64ToBytes(exitEventID)) - topics[2] = types.BytesToHash(types.StringToAddress("0x1111").Bytes()) - topics[3] = types.BytesToHash(types.StringToAddress("0x2222").Bytes()) - someType := abi.MustNewType("tuple(string firstName, string lastName)") - encodedData, err := someType.Encode(map[string]string{"firstName": "John", "lastName": "Doe"}) - require.NoError(t, err) - - return &types.Log{ - Address: contracts.L2StateSenderContract, - Topics: topics, - Data: encodedData, - } -} diff --git a/consensus/polybft/consensus_runtime.go b/consensus/polybft/consensus_runtime.go index 334bc4e82e..140bd847bf 100644 --- a/consensus/polybft/consensus_runtime.go +++ b/consensus/polybft/consensus_runtime.go @@ -1069,18 +1069,3 @@ func (c *consensusRuntime) getCurrentBlockTimeDrift() uint64 { return c.epoch.CurrentClientConfig.BlockTimeDrift } - -// getSealersForBlock checks who sealed a given block and updates the counter -func getSealersForBlock(sealersCounter map[types.Address]uint64, - blockExtra *Extra, validators validator.AccountSet) error { - signers, err := validators.GetFilteredValidators(blockExtra.Parent.Bitmap) - if err != nil { - return err - } - - for _, a := range signers.GetAddresses() { - sealersCounter[a]++ - } - - return nil -} diff --git a/consensus/polybft/governance_manager_test.go b/consensus/polybft/governance_manager_test.go index ca27cd48b8..b0187c6ea9 100644 --- a/consensus/polybft/governance_manager_test.go +++ b/consensus/polybft/governance_manager_test.go @@ -6,12 +6,10 @@ import ( "github.com/0xPolygon/polygon-edge/chain" "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" - "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/forkmanager" "github.com/0xPolygon/polygon-edge/types" "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" - "github.com/umbracle/ethgo/abi" ) func TestGovernanceManager_PostEpoch(t *testing.T) { @@ -143,22 +141,3 @@ func TestGovernanceManager_PostBlock(t *testing.T) { require.True(t, forkmanager.GetInstance().IsForkEnabled(newForkName, newForkBlock.Uint64())) }) } - -func createTestLogForNewEpochSizeEvent(t *testing.T, epochSize uint64) *types.Log { - t.Helper() - - var epochSizeEvent contractsapi.NewEpochSizeEvent - - topics := make([]types.Hash, 2) - topics[0] = types.Hash(epochSizeEvent.Sig()) - encodedData, err := abi.MustNewType("uint256").Encode(new(big.Int).SetUint64(epochSize)) - require.NoError(t, err) - - topics[1] = types.BytesToHash(encodedData) - - return &types.Log{ - Address: contracts.NetworkParamsContract, - Topics: topics, - Data: nil, - } -} diff --git a/consensus/polybft/helpers_test.go b/consensus/polybft/helpers_test.go index 22e038b8af..066d1a0b60 100644 --- a/consensus/polybft/helpers_test.go +++ b/consensus/polybft/helpers_test.go @@ -27,7 +27,7 @@ func createTestKey(t *testing.T) *wallet.Key { func createRandomTestKeys(t *testing.T, numberOfKeys int) []*wallet.Key { t.Helper() - result := make([]*wallet.Key, numberOfKeys, numberOfKeys) + result := make([]*wallet.Key, numberOfKeys) for i := 0; i < numberOfKeys; i++ { result[i] = wallet.NewKey(generateTestAccount(t)) diff --git a/consensus/polybft/polybft.go b/consensus/polybft/polybft.go index c08ccfeb42..acc74dade4 100644 --- a/consensus/polybft/polybft.go +++ b/consensus/polybft/polybft.go @@ -4,7 +4,6 @@ package polybft import ( "context" "encoding/json" - "errors" "fmt" "math/big" "path/filepath" @@ -35,10 +34,6 @@ const ( bridgeProto = "/bridge/0.2" ) -var ( - errMissingBridgeConfig = errors.New("invalid genesis configuration, missing bridge configuration") -) - // polybftBackend is an interface defining polybft methods needed by fsm and sync tracker type polybftBackend interface { // GetValidators retrieves validator set for the given block diff --git a/consensus/polybft/sc_integration_test.go b/consensus/polybft/sc_integration_test.go index 7d9df17c20..cd35a04a52 100644 --- a/consensus/polybft/sc_integration_test.go +++ b/consensus/polybft/sc_integration_test.go @@ -276,12 +276,12 @@ func TestIntegration_CommitEpoch(t *testing.T) { reward := uint64(math.Pow(10, 18)) // 1 token walletAddress := types.StringToAddress("1234889893") - validatorSets := make([]*validator.TestValidators, len(validatorSetSize), len(validatorSetSize)) + validatorSets := make([]*validator.TestValidators, len(validatorSetSize)) // create all validator sets which will be used in test for i, size := range validatorSetSize { - aliases := make([]string, size, size) - vps := make([]uint64, size, size) + aliases := make([]string, size) + vps := make([]uint64, size) for j := 0; j < size; j++ { aliases[j] = "v" + strconv.Itoa(j) diff --git a/consensus/polybft/stake_manager.go b/consensus/polybft/stake_manager.go index 20009c174e..b4ff5c8997 100644 --- a/consensus/polybft/stake_manager.go +++ b/consensus/polybft/stake_manager.go @@ -67,11 +67,8 @@ var _ StakeManager = (*stakeManager)(nil) type stakeManager struct { logger hclog.Logger state *State - key ethgo.Key stakeManagerContractAddr types.Address - validatorSetContract types.Address polybftBackend polybftBackend - stakeManagerContract *contract.Contract blockchain blockchainBackend } @@ -469,12 +466,3 @@ func (sc validatorStakeMap) String() string { return sb.String() } - -func getEpochID(blockchain blockchainBackend, header *types.Header) (uint64, error) { - provider, err := blockchain.GetStateProviderForBlock(header) - if err != nil { - return 0, err - } - - return blockchain.GetSystemState(provider).GetEpoch() -} diff --git a/consensus/polybft/stake_manager_fuzz_test.go b/consensus/polybft/stake_manager_fuzz_test.go index eccbf132be..4129de43f2 100644 --- a/consensus/polybft/stake_manager_fuzz_test.go +++ b/consensus/polybft/stake_manager_fuzz_test.go @@ -15,11 +15,6 @@ import ( "github.com/stretchr/testify/require" ) -type epochIDValidatorsF struct { - EpochID uint64 - Validators []*validator.ValidatorMetadata -} - type postBlockStructF struct { EpochID uint64 ValidatorID uint64 diff --git a/consensus/polybft/state_event_getter.go b/consensus/polybft/state_event_getter.go index cf5d45365e..107aa8badd 100644 --- a/consensus/polybft/state_event_getter.go +++ b/consensus/polybft/state_event_getter.go @@ -30,8 +30,6 @@ type EventProvider struct { subscriberIDCounter uint64 - blockchain blockchainBackend - subscribers map[uint64]EventSubscriber allFilters map[types.Address]map[types.Hash][]uint64 } diff --git a/consensus/polybft/state_store_exit.go b/consensus/polybft/state_store_exit.go index 97235d459a..f73673481a 100644 --- a/consensus/polybft/state_store_exit.go +++ b/consensus/polybft/state_store_exit.go @@ -3,7 +3,6 @@ package polybft import ( "bytes" "encoding/json" - "errors" "fmt" "sort" @@ -19,8 +18,6 @@ var ( exitEventsBucket = []byte("exitEvent") exitEventToEpochLookupBucket = []byte("exitIdToEpochLookup") exitRelayerEventsBucket = []byte("exitRelayerEvents") - - errNoLastSavedEntry = errors.New("there is no last saved block in last saved bucket") ) type exitEventNotFoundError struct { @@ -136,7 +133,7 @@ func getExitEventSingle(exitEventID uint64, tx *bolt.Tx) (*ExitEvent, error) { key := bytes.Join([][]byte{epochBytes, exitIDBytes}, nil) k, v := exitEventBucket.Cursor().Seek(key) - if bytes.HasPrefix(k, key) == false || v == nil { + if !bytes.HasPrefix(k, key) || v == nil { return nil, &exitEventNotFoundError{ exitID: exitEventID, epoch: common.EncodeBytesToUint64(epochBytes), diff --git a/consensus/polybft/state_store_state_sync.go b/consensus/polybft/state_store_state_sync.go index 644dd29d50..fce2891cc6 100644 --- a/consensus/polybft/state_store_state_sync.go +++ b/consensus/polybft/state_store_state_sync.go @@ -24,8 +24,6 @@ var ( // errNotEnoughStateSyncs error message errNotEnoughStateSyncs = errors.New("there is either a gap or not enough sync events") - // errCommitmentNotBuilt error message - errCommitmentNotBuilt = errors.New("there is no built commitment to register") // errNoCommitmentForStateSync error message errNoCommitmentForStateSync = errors.New("no commitment found for given state sync event") ) diff --git a/consensus/polybft/state_sync_commitment.go b/consensus/polybft/state_sync_commitment.go index dd1ade9d89..97e0637ca3 100644 --- a/consensus/polybft/state_sync_commitment.go +++ b/consensus/polybft/state_sync_commitment.go @@ -12,11 +12,6 @@ import ( merkle "github.com/Ethernal-Tech/merkle-tree" ) -const ( - stTypeBridgeCommitment = "commitment" - stTypeEndEpoch = "end-epoch" -) - // PendingCommitment holds merkle trie of bridge transactions accompanied by epoch number type PendingCommitment struct { *contractsapi.StateSyncCommitment diff --git a/consensus/polybft/state_sync_relayer.go b/consensus/polybft/state_sync_relayer.go index 5bd1eb1ecc..8007790759 100644 --- a/consensus/polybft/state_sync_relayer.go +++ b/consensus/polybft/state_sync_relayer.go @@ -17,7 +17,6 @@ import ( ) var ( - errFailedToExecuteStateSync = errors.New("failed to execute state sync") errUnknownStateSyncRelayerEvent = errors.New("unknown event from state receiver contract") commitmentEventSignature = new(contractsapi.NewCommitmentEvent).Sig() diff --git a/consensus/polybft/stats.go b/consensus/polybft/stats.go index f6e9587d54..02ae335e0c 100644 --- a/consensus/polybft/stats.go +++ b/consensus/polybft/stats.go @@ -1,14 +1,10 @@ package polybft import ( - "math/big" "time" - "github.com/0xPolygon/polygon-edge/txrelayer" "github.com/armon/go-metrics" - "github.com/hashicorp/go-hclog" "github.com/prometheus/client_golang/prometheus" - "github.com/umbracle/ethgo" ) // startStatsReleasing starts the process that releases BoltDB stats into prometheus periodically. @@ -158,53 +154,3 @@ func (s *State) startStatsReleasing() { prev = stats } } - -// publishRootchainMetrics publishes rootchain related metrics -func (p *Polybft) publishRootchainMetrics(logger hclog.Logger) { - interval := p.config.MetricsInterval - validatorAddr := p.key.Address() - bridgeCfg := p.genesisClientConfig.Bridge - - // zero means metrics are disabled - if interval <= 0 { - return - } - - relayer, err := txrelayer.NewTxRelayer(txrelayer.WithIPAddress(bridgeCfg.JSONRPCEndpoint)) - if err != nil { - logger.Error("failed to connect to the rootchain node", "err", err, "JSON RPC", bridgeCfg.JSONRPCEndpoint) - - return - } - - gweiPerWei := new(big.Float).SetInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(9), nil)) // 10^9 - - ticker := time.NewTicker(interval) - defer ticker.Stop() - - for { - select { - case <-p.closeCh: - return - case <-ticker.C: - // rootchain validator balance - balance, err := relayer.Client().Eth().GetBalance(p.key.Address(), ethgo.Latest) - if err != nil { - logger.Error("failed to query eth_getBalance", "err", err) - } else { - balanceInGwei := new(big.Float).Quo(new(big.Float).SetInt(balance), gweiPerWei) - balanceInGweiFloat, _ := balanceInGwei.Float32() - - metrics.SetGauge([]string{"bridge", "validator_root_balance_gwei", validatorAddr.String()}, balanceInGweiFloat) - } - - // rootchain current checkpoint block - checkpointBlock, err := getCurrentCheckpointBlock(relayer, bridgeCfg.CheckpointManagerAddr) - if err != nil { - logger.Error("failed to query latest checkpoint block", "err", err) - } else { - metrics.SetGauge([]string{"bridge", "checkpoint_block_number"}, float32(checkpointBlock)) - } - } - } -} diff --git a/e2e-polybft/e2e/helpers_test.go b/e2e-polybft/e2e/helpers_test.go index e5a4540dfd..36e0add4c9 100644 --- a/e2e-polybft/e2e/helpers_test.go +++ b/e2e-polybft/e2e/helpers_test.go @@ -1,13 +1,8 @@ package e2e import ( - "bytes" - "encoding/json" "errors" - "fmt" - "io" "math/big" - "net/http" "testing" "time" @@ -16,7 +11,6 @@ import ( "github.com/umbracle/ethgo/abi" "github.com/umbracle/ethgo/jsonrpc" - "github.com/0xPolygon/polygon-edge/consensus/polybft" "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" "github.com/0xPolygon/polygon-edge/contracts" "github.com/0xPolygon/polygon-edge/e2e-polybft/framework" @@ -29,53 +23,6 @@ import ( const nativeTokenNonMintableConfig = "Blade:BLD:18:false" -// getCheckpointManagerValidators queries rootchain validator set on CheckpointManager contract -func getCheckpointManagerValidators(relayer txrelayer.TxRelayer, checkpointManagerAddr ethgo.Address) ([]*polybft.ValidatorInfo, error) { - validatorsCountRaw, err := ABICall(relayer, contractsapi.CheckpointManager, - checkpointManagerAddr, ethgo.ZeroAddress, "currentValidatorSetLength") - if err != nil { - return nil, err - } - - validatorsCount, err := common.ParseUint64orHex(&validatorsCountRaw) - if err != nil { - return nil, err - } - - currentValidatorSetMethod := contractsapi.CheckpointManager.Abi.GetMethod("currentValidatorSet") - validators := make([]*polybft.ValidatorInfo, validatorsCount) - - for i := 0; i < int(validatorsCount); i++ { - validatorRaw, err := ABICall(relayer, contractsapi.CheckpointManager, - checkpointManagerAddr, ethgo.ZeroAddress, "currentValidatorSet", i) - if err != nil { - return nil, err - } - - validatorSetRaw, err := hex.DecodeString(validatorRaw[2:]) - if err != nil { - return nil, err - } - - decodedResults, err := currentValidatorSetMethod.Outputs.Decode(validatorSetRaw) - if err != nil { - return nil, err - } - - results, ok := decodedResults.(map[string]interface{}) - if !ok { - return nil, errors.New("failed to decode validator") - } - - validators[i] = &polybft.ValidatorInfo{ - Address: results["_address"].(ethgo.Address), - Stake: results["votingPower"].(*big.Int), - } - } - - return validators, nil -} - func ABICall(relayer txrelayer.TxRelayer, artifact *contracts.Artifact, contractAddress ethgo.Address, senderAddr ethgo.Address, method string, params ...interface{}) (string, error) { input, err := artifact.Abi.GetMethod(method).Encode(params) if err != nil { @@ -97,46 +44,6 @@ func ABITransaction(relayer txrelayer.TxRelayer, key ethgo.Key, artifact *contra }, key) } -func getExitProof(rpcAddress string, exitID uint64) (types.Proof, error) { - query := struct { - Jsonrpc string `json:"jsonrpc"` - Method string `json:"method"` - Params []string `json:"params"` - ID int `json:"id"` - }{ - "2.0", - "bridge_generateExitProof", - []string{fmt.Sprintf("0x%x", exitID)}, - 1, - } - - d, err := json.Marshal(query) - if err != nil { - return types.Proof{}, err - } - - resp, err := http.Post(rpcAddress, "application/json", bytes.NewReader(d)) - if err != nil { - return types.Proof{}, err - } - - s, err := io.ReadAll(resp.Body) - if err != nil { - return types.Proof{}, err - } - - rspProof := struct { - Result types.Proof `json:"result"` - }{} - - err = json.Unmarshal(s, &rspProof) - if err != nil { - return types.Proof{}, err - } - - return rspProof.Result, nil -} - // checkStateSyncResultLogs is helper function which parses given StateSyncResultEvent event's logs, // extracts status topic value and makes assertions against it. func checkStateSyncResultLogs( @@ -218,16 +125,10 @@ func setAccessListRole(t *testing.T, cluster *framework.TestCluster, precompile, switch role { case addresslist.AdminRole: updateRoleFn = addresslist.SetAdminFunc - - break case addresslist.EnabledRole: updateRoleFn = addresslist.SetEnabledFunc - - break case addresslist.NoRole: updateRoleFn = addresslist.SetNoneFunc - - break } input, err := updateRoleFn.Encode([]interface{}{account}) diff --git a/e2e-polybft/e2e/jsonrpc_test.go b/e2e-polybft/e2e/jsonrpc_test.go index f507a780f0..21ec223e91 100644 --- a/e2e-polybft/e2e/jsonrpc_test.go +++ b/e2e-polybft/e2e/jsonrpc_test.go @@ -3,15 +3,20 @@ package e2e import ( "math/big" "testing" + "time" "github.com/stretchr/testify/require" "github.com/umbracle/ethgo" "github.com/umbracle/ethgo/jsonrpc" "github.com/umbracle/ethgo/wallet" + "github.com/0xPolygon/polygon-edge/consensus/polybft" "github.com/0xPolygon/polygon-edge/consensus/polybft/contractsapi" + "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/e2e-polybft/framework" "github.com/0xPolygon/polygon-edge/helper/hex" + "github.com/0xPolygon/polygon-edge/helper/tests" + bladeRPC "github.com/0xPolygon/polygon-edge/jsonrpc" "github.com/0xPolygon/polygon-edge/state" "github.com/0xPolygon/polygon-edge/types" ) @@ -420,3 +425,270 @@ func TestE2E_JsonRPC(t *testing.T) { require.Equal(t, txReceipt.GasUsed, trace.Gas) }) } + +func TestE2E_JsonRPC_NewEthClient(t *testing.T) { + const epochSize = uint64(5) + + acct, err := wallet.GenerateKey() + require.NoError(t, err) + + cluster := framework.NewTestCluster(t, 4, + framework.WithEpochSize(int(epochSize)), + framework.WithPremine(types.Address(acct.Address())), + framework.WithBurnContract(&polybft.BurnContractInfo{BlockNumber: 0, Address: types.ZeroAddress}), + ) + defer cluster.Stop() + + cluster.WaitForReady(t) + + newEthClient, err := bladeRPC.NewEthClient(cluster.Servers[0].JSONRPCAddr()) + require.NoError(t, err) + + t.Run("eth_blockNumber", func(t *testing.T) { + require.NoError(t, cluster.WaitForBlock(epochSize, 15*time.Second)) + + blockNumber, err := newEthClient.BlockNumber() + require.NoError(t, err) + require.GreaterOrEqual(t, blockNumber, epochSize) + + require.NoError(t, cluster.WaitForBlock(blockNumber+1, 5*time.Second)) + + blockNumber, err = newEthClient.BlockNumber() + require.NoError(t, err) + require.GreaterOrEqual(t, blockNumber, epochSize) + }) + + t.Run("eth_getBlock", func(t *testing.T) { + blockByNumber, err := newEthClient.GetBlockByNumber(bladeRPC.BlockNumber(epochSize), false) + require.NoError(t, err) + require.NotNil(t, blockByNumber) + require.Equal(t, epochSize, blockByNumber.Number()) + require.Empty(t, len(blockByNumber.Transactions)) // since we did not ask for the full block + + blockByNumber, err = newEthClient.GetBlockByNumber(bladeRPC.BlockNumber(epochSize), true) + require.NoError(t, err) + require.Equal(t, epochSize, blockByNumber.Number()) + // since we asked for the full block, and epoch ending block has a transaction + require.Equal(t, 1, len(blockByNumber.Transactions)) + + blockByHash, err := newEthClient.GetBlockByHash(blockByNumber.Hash(), false) + require.NoError(t, err) + require.NotNil(t, blockByHash) + require.Equal(t, epochSize, blockByHash.Number()) + require.Equal(t, blockByNumber.Hash(), blockByHash.Hash()) + + blockByHash, err = newEthClient.GetBlockByHash(blockByNumber.Hash(), true) + require.NoError(t, err) + require.Equal(t, blockByNumber.Hash(), blockByHash.Hash()) + // since we asked for the full block, and epoch ending block has a transaction + require.Equal(t, 1, len(blockByHash.Transactions)) + + // get latest block + latestBlock, err := newEthClient.GetBlockByNumber(bladeRPC.LatestBlockNumber, false) + require.NoError(t, err) + require.NotNil(t, latestBlock) + require.GreaterOrEqual(t, latestBlock.Number(), epochSize) + + // get pending block + pendingBlock, err := newEthClient.GetBlockByNumber(bladeRPC.PendingBlockNumber, false) + require.NoError(t, err) + require.NotNil(t, pendingBlock) + require.GreaterOrEqual(t, pendingBlock.Number(), latestBlock.Number()) + + // get earliest block + earliestBlock, err := newEthClient.GetBlockByNumber(bladeRPC.EarliestBlockNumber, false) + require.NoError(t, err) + require.NotNil(t, earliestBlock) + require.Equal(t, uint64(0), earliestBlock.Number()) + }) + + t.Run("eth_getCode", func(t *testing.T) { + deployTxn := cluster.Deploy(t, acct, contractsapi.TestSimple.Bytecode) + require.NoError(t, deployTxn.Wait()) + require.True(t, deployTxn.Succeed()) + + target := deployTxn.Receipt().ContractAddress + + code, err := newEthClient.GetCode(types.Address(target), bladeRPC.LatestBlockNumberOrHash) + require.NoError(t, err) + require.NotEmpty(t, code) + }) + + t.Run("eth_getStorageAt", func(t *testing.T) { + key1, err := wallet.GenerateKey() + require.NoError(t, err) + + txn := cluster.Transfer(t, acct, types.Address(key1.Address()), one) + require.NoError(t, txn.Wait()) + require.True(t, txn.Succeed()) + + txn = cluster.Deploy(t, acct, contractsapi.TestSimple.Bytecode) + require.NoError(t, txn.Wait()) + require.True(t, txn.Succeed()) + + target := txn.Receipt().ContractAddress + + resp, err := newEthClient.GetStorageAt(types.Address(target), types.Hash{}, bladeRPC.LatestBlockNumberOrHash) + require.NoError(t, err) + require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", resp.String()) + + setValueFn := contractsapi.TestSimple.Abi.GetMethod("setValue") + + newVal := big.NewInt(1) + + input, err := setValueFn.Encode([]interface{}{newVal}) + require.NoError(t, err) + + txn = cluster.SendTxn(t, acct, ðgo.Transaction{Input: input, To: &target}) + require.NoError(t, txn.Wait()) + require.True(t, txn.Succeed()) + + resp, err = newEthClient.GetStorageAt(types.Address(target), types.Hash{}, bladeRPC.LatestBlockNumberOrHash) + require.NoError(t, err) + require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000001", resp.String()) + }) + + t.Run("eth_getTransactionByHash and eth_getTransactionReceipt", func(t *testing.T) { + txn := cluster.Transfer(t, acct, types.StringToAddress("0xDEADBEEF"), one) + require.NoError(t, txn.Wait()) + require.True(t, txn.Succeed()) + + ethTxn, err := newEthClient.GetTransactionByHash(types.Hash(txn.Receipt().TransactionHash)) + require.NoError(t, err) + + require.Equal(t, ethTxn.From(), types.Address(acct.Address())) + + receipt, err := newEthClient.GetTransactionReceipt(ethTxn.Hash()) + require.NoError(t, err) + require.NotNil(t, receipt) + require.Equal(t, ethTxn.Hash(), receipt.TxHash) + }) + + t.Run("eth_getTransactionCount", func(t *testing.T) { + nonce, err := newEthClient.GetNonce(types.Address(acct.Address()), bladeRPC.LatestBlockNumberOrHash) + require.NoError(t, err) + require.GreaterOrEqual(t, nonce, uint64(0)) // since we used this account in previous tests + + txn := cluster.Transfer(t, acct, types.StringToAddress("0xDEADBEEF"), one) + require.NoError(t, txn.Wait()) + require.True(t, txn.Succeed()) + + newNonce, err := newEthClient.GetNonce(types.Address(acct.Address()), bladeRPC.LatestBlockNumberOrHash) + require.NoError(t, err) + require.Equal(t, nonce+1, newNonce) + }) + + t.Run("eth_getBalance", func(t *testing.T) { + balance, err := newEthClient.GetBalance(types.Address(acct.Address()), bladeRPC.LatestBlockNumberOrHash) + require.NoError(t, err) + require.True(t, balance.Cmp(big.NewInt(0)) >= 0) + + receiver := types.StringToAddress("0xDEADFFFF") + + tokens := ethgo.Ether(1) + + txn := cluster.Transfer(t, acct, receiver, tokens) + require.NoError(t, txn.Wait()) + require.True(t, txn.Succeed()) + + newBalance, err := newEthClient.GetBalance(receiver, bladeRPC.LatestBlockNumberOrHash) + require.NoError(t, err) + require.Equal(t, tokens, newBalance) + }) + + t.Run("eth_estimateGas", func(t *testing.T) { + deployTxn := cluster.Deploy(t, acct, contractsapi.TestSimple.Bytecode) + require.NoError(t, deployTxn.Wait()) + require.True(t, deployTxn.Succeed()) + + target := types.Address(deployTxn.Receipt().ContractAddress) + + input := contractsapi.TestSimple.Abi.GetMethod("getValue").ID() + + estimatedGas, err := newEthClient.EstimateGas(&bladeRPC.CallMsg{ + From: types.Address(acct.Address()), + To: &target, + Data: input, + }) + require.NoError(t, err) + require.GreaterOrEqual(t, estimatedGas, uint64(0)) + }) + + t.Run("eth_gasPrice", func(t *testing.T) { + gasPrice, err := newEthClient.GasPrice() + require.NoError(t, err) + require.Greater(t, gasPrice, uint64(0)) // london fork is enabled, so gas price should be greater than 0 + }) + + t.Run("eth_call", func(t *testing.T) { + deployTxn := cluster.Deploy(t, acct, contractsapi.TestSimple.Bytecode) + require.NoError(t, deployTxn.Wait()) + require.True(t, deployTxn.Succeed()) + + target := types.Address(deployTxn.Receipt().ContractAddress) + + input := contractsapi.TestSimple.Abi.GetMethod("getValue").ID() + + acctZeroBalance, err := wallet.GenerateKey() + require.NoError(t, err) + + resp, err := newEthClient.Call(&bladeRPC.CallMsg{ + From: types.Address(acctZeroBalance.Address()), + To: &target, + Data: input, + }, bladeRPC.LatestBlockNumber, nil) + require.NoError(t, err) + require.Equal(t, "0x0000000000000000000000000000000000000000000000000000000000000000", resp) + }) + + t.Run("eth_chainID", func(t *testing.T) { + chainID, err := newEthClient.ChainID() + require.NoError(t, err) + require.Equal(t, big.NewInt(100), chainID) // default chainID + }) + + t.Run("eth_maxPriorityFeePerGas", func(t *testing.T) { + maxPriorityFeePerGas, err := newEthClient.MaxPriorityFeePerGas() + require.NoError(t, err) + // london fork is enabled, so maxPriorityFeePerGas should be greater than 0 + require.True(t, maxPriorityFeePerGas.Cmp(big.NewInt(0)) > 0) + }) + + t.Run("eth_sendRawTransaction", func(t *testing.T) { + receiver := types.StringToAddress("0xDEADFFFF") + tokens := ethgo.Ether(1) + + chainID, err := newEthClient.ChainID() + require.NoError(t, err) + + gasPrice, err := newEthClient.GasPrice() + require.NoError(t, err) + + newAccountKey, newAccountAddr := tests.GenerateKeyAndAddr(t) + + transferTxn := cluster.Transfer(t, acct, newAccountAddr, tokens) + require.NoError(t, transferTxn.Wait()) + require.True(t, transferTxn.Succeed()) + + newAccountBalance, err := newEthClient.GetBalance(newAccountAddr, bladeRPC.LatestBlockNumberOrHash) + require.NoError(t, err) + require.Equal(t, tokens, newAccountBalance) + + txn := types.NewTx(types.NewLegacyTx(types.WithNonce(0), + types.WithFrom(newAccountAddr), + types.WithTo(&receiver), + types.WithValue(ethgo.Gwei(1)), + types.WithGas(21000), + types.WithGasPrice(new(big.Int).SetUint64(gasPrice)), + )) + + signedTxn, err := crypto.NewLondonSigner(chainID.Uint64()).SignTx(txn, newAccountKey) + require.NoError(t, err) + + data := signedTxn.MarshalRLPTo(nil) + + hash, err := newEthClient.SendRawTransaction(data) + require.NoError(t, err) + require.NotEqual(t, types.ZeroHash, hash) + }) +} diff --git a/go.mod b/go.mod index b29851ad6d..996fc46a85 100644 --- a/go.mod +++ b/go.mod @@ -38,6 +38,7 @@ require ( github.com/trailofbits/go-fuzz-utils v0.0.0-20210901195358-9657fcfd256c github.com/umbracle/fastrlp v0.1.1-0.20230504065717-58a1b8a9929d github.com/umbracle/go-eth-bn256 v0.0.0-20230125114011-47cb310d9b0b + github.com/valyala/fastjson v1.6.3 go.etcd.io/bbolt v1.3.9 golang.org/x/crypto v0.20.0 golang.org/x/sync v0.6.0 @@ -193,7 +194,6 @@ require ( github.com/umbracle/ethgo v0.1.4-0.20231006072852-6b068360fc97 github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasthttp v1.50.0 // indirect - github.com/valyala/fastjson v1.6.3 // indirect go.opencensus.io v0.24.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.47.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.47.0 // indirect diff --git a/jsonrpc/client.go b/jsonrpc/client.go new file mode 100644 index 0000000000..6349745093 --- /dev/null +++ b/jsonrpc/client.go @@ -0,0 +1,178 @@ +package jsonrpc + +import ( + "math/big" + + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/helper/hex" + "github.com/0xPolygon/polygon-edge/types" + "github.com/umbracle/ethgo/jsonrpc" +) + +// EthClient is a wrapper around jsonrpc.Client +type EthClient struct { + client *jsonrpc.Client +} + +// NewEthClient creates a new EthClient +func NewEthClient(url string) (*EthClient, error) { + client, err := jsonrpc.NewClient(url) + if err != nil { + return nil, err + } + + return &EthClient{client}, nil +} + +// GetCode returns the code of a contract +func (e *EthClient) GetCode(addr types.Address, block BlockNumberOrHash) (string, error) { + var res string + if err := e.client.Call("eth_getCode", &res, addr, block.String()); err != nil { + return "", err + } + + return res, nil +} + +// GetStorageAt returns the value from a storage position at a given address +func (e *EthClient) GetStorageAt(addr types.Address, slot types.Hash, block BlockNumberOrHash) (types.Hash, error) { + var hash types.Hash + err := e.client.Call("eth_getStorageAt", &hash, addr, slot, block.String()) + + return hash, err +} + +// BlockNumber returns the number of most recent block +func (e *EthClient) BlockNumber() (uint64, error) { + var out string + if err := e.client.Call("eth_blockNumber", &out); err != nil { + return 0, err + } + + return common.ParseUint64orHex(&out) +} + +// GetBlockByNumber returns information about a block by block number. +func (e *EthClient) GetBlockByNumber(i BlockNumber, full bool) (*types.Block, error) { + var b *types.Block + if err := e.client.Call("eth_getBlockByNumber", &b, i.String(), full); err != nil { + return nil, err + } + + return b, nil +} + +// GetBlockByHash returns information about a block by hash. +func (e *EthClient) GetBlockByHash(hash types.Hash, full bool) (*types.Block, error) { + var b *types.Block + if err := e.client.Call("eth_getBlockByHash", &b, hash, full); err != nil { + return nil, err + } + + return b, nil +} + +// GetTransactionByHash returns a transaction by hash +func (e *EthClient) GetTransactionByHash(hash types.Hash) (*types.Transaction, error) { + var txn *types.Transaction + err := e.client.Call("eth_getTransactionByHash", &txn, hash) + + return txn, err +} + +// SendRawTransaction sends a signed transaction in rlp format +func (e *EthClient) SendRawTransaction(data []byte) (types.Hash, error) { + var hash types.Hash + + hexData := "0x" + hex.EncodeToString(data) + err := e.client.Call("eth_sendRawTransaction", &hash, hexData) + + return hash, err +} + +// SendTransaction creates new message call transaction or a contract creation +func (e *EthClient) SendTransaction(txn *types.Transaction) (types.Hash, error) { + var hash types.Hash + err := e.client.Call("eth_sendTransaction", &hash, txn) + + return hash, err +} + +// GetTransactionReceipt returns the receipt of a transaction by transaction hash +func (e *EthClient) GetTransactionReceipt(hash types.Hash) (*types.Receipt, error) { + var receipt *types.Receipt + err := e.client.Call("eth_getTransactionReceipt", &receipt, hash) + + return receipt, err +} + +// GetNonce returns the nonce of the account +func (e *EthClient) GetNonce(addr types.Address, blockNumber BlockNumberOrHash) (uint64, error) { + var nonce string + if err := e.client.Call("eth_getTransactionCount", &nonce, addr, blockNumber.String()); err != nil { + return 0, err + } + + return common.ParseUint64orHex(&nonce) +} + +// GetBalance returns the balance of the account of given address +func (e *EthClient) GetBalance(addr types.Address, blockNumber BlockNumberOrHash) (*big.Int, error) { + var out string + if err := e.client.Call("eth_getBalance", &out, addr, blockNumber.String()); err != nil { + return nil, err + } + + return common.ParseUint256orHex(&out) +} + +// GasPrice returns the current price per gas in wei +func (e *EthClient) GasPrice() (uint64, error) { + var out string + if err := e.client.Call("eth_gasPrice", &out); err != nil { + return 0, err + } + + return common.ParseUint64orHex(&out) +} + +// Call executes a new message call immediately without creating a transaction on the blockchain +func (e *EthClient) Call(msg *CallMsg, block BlockNumber, override *StateOverride) (string, error) { + var out string + if err := e.client.Call("eth_call", &out, msg, block.String(), override); err != nil { + return "", err + } + + return out, nil +} + +// EstimateGas generates and returns an estimate of how much gas is necessary to allow the transaction to complete +func (e *EthClient) EstimateGas(msg *CallMsg) (uint64, error) { + var out string + if err := e.client.Call("eth_estimateGas", &out, msg); err != nil { + return 0, err + } + + return common.ParseUint64orHex(&out) +} + +// ChainID returns the id of the chain +func (e *EthClient) ChainID() (*big.Int, error) { + var out string + if err := e.client.Call("eth_chainId", &out); err != nil { + return nil, err + } + + return common.ParseUint256orHex(&out) +} + +// MaxPriorityFeePerGas returns a fee per gas that is an estimate of how much you can pay as a priority fee, or 'tip', +// to get a transaction included in the current block (EIP-1559) +func (e *EthClient) MaxPriorityFeePerGas() (*big.Int, error) { + var out string + if err := e.client.Call("eth_maxPriorityFeePerGas", &out); err != nil { + return big.NewInt(0), err + } + + return common.ParseUint256orHex(&out) +} diff --git a/jsonrpc/codec.go b/jsonrpc/codec.go index b0c3128941..07445b03c4 100644 --- a/jsonrpc/codec.go +++ b/jsonrpc/codec.go @@ -9,6 +9,17 @@ import ( "github.com/0xPolygon/polygon-edge/types" ) +func init() { + pendingBN := PendingBlockNumber + PendingBlockNumberOrHash = BlockNumberOrHash{BlockNumber: &pendingBN} + + latestBN := LatestBlockNumber + LatestBlockNumberOrHash = BlockNumberOrHash{BlockNumber: &latestBN} + + earliestBN := EarliestBlockNumber + EarliestBlockNumberOrHash = BlockNumberOrHash{BlockNumber: &earliestBN} +} + // Request is a jsonrpc request type Request struct { ID interface{} `json:"id"` @@ -127,13 +138,42 @@ const ( EarliestBlockNumber = BlockNumber(-1) ) +var ( + PendingBlockNumberOrHash BlockNumberOrHash + LatestBlockNumberOrHash BlockNumberOrHash + EarliestBlockNumberOrHash BlockNumberOrHash +) + type BlockNumber int64 +// String returns the string representation of the block number +func (b BlockNumber) String() string { + switch b { + case PendingBlockNumber: + return pending + case LatestBlockNumber: + return latest + case EarliestBlockNumber: + return earliest + } + + return fmt.Sprintf("0x%x", uint64(b)) +} + type BlockNumberOrHash struct { BlockNumber *BlockNumber `json:"blockNumber,omitempty"` BlockHash *types.Hash `json:"blockHash,omitempty"` } +// String returns the string representation of the block number or hash +func (bnh BlockNumberOrHash) String() string { + if bnh.BlockNumber != nil { + return bnh.BlockNumber.String() + } + + return bnh.BlockHash.String() +} + // UnmarshalJSON will try to extract the filter's data. // Here are the possible input formats : // diff --git a/jsonrpc/dispatcher.go b/jsonrpc/dispatcher.go index 7f66ef6559..6199deca3c 100644 --- a/jsonrpc/dispatcher.go +++ b/jsonrpc/dispatcher.go @@ -475,7 +475,7 @@ func (d *Dispatcher) registerService(serviceName string, service interface{}) er st := reflect.TypeOf(service) if st.Kind() == reflect.Struct { - return errors.New(fmt.Sprintf("jsonrpc: service '%s' must be a pointer to struct", serviceName)) + return fmt.Errorf("jsonrpc: service '%s' must be a pointer to struct", serviceName) } funcMap := make(map[string]*funcData) diff --git a/jsonrpc/eth_endpoint.go b/jsonrpc/eth_endpoint.go index 194101a5bd..9b4119087b 100644 --- a/jsonrpc/eth_endpoint.go +++ b/jsonrpc/eth_endpoint.go @@ -448,45 +448,8 @@ func (e *Eth) fillTransactionGasPrice(tx *types.Transaction) error { return nil } -type overrideAccount struct { - Nonce *argUint64 `json:"nonce"` - Code *argBytes `json:"code"` - Balance *argUint64 `json:"balance"` - State *map[types.Hash]types.Hash `json:"state"` - StateDiff *map[types.Hash]types.Hash `json:"stateDiff"` -} - -func (o *overrideAccount) ToType() types.OverrideAccount { - res := types.OverrideAccount{} - - if o.Nonce != nil { - res.Nonce = (*uint64)(o.Nonce) - } - - if o.Code != nil { - res.Code = *o.Code - } - - if o.Balance != nil { - res.Balance = new(big.Int).SetUint64(*(*uint64)(o.Balance)) - } - - if o.State != nil { - res.State = *o.State - } - - if o.StateDiff != nil { - res.StateDiff = *o.StateDiff - } - - return res -} - -// StateOverride is the collection of overridden accounts. -type stateOverride map[types.Address]overrideAccount - // Call executes a smart contract call using the transaction object data -func (e *Eth) Call(arg *txnArgs, filter BlockNumberOrHash, apiOverride *stateOverride) (interface{}, error) { +func (e *Eth) Call(arg *txnArgs, filter BlockNumberOrHash, apiOverride *StateOverride) (interface{}, error) { header, err := GetHeaderFromBlockNumberOrHash(filter, e.store) if err != nil { return nil, err diff --git a/jsonrpc/eth_endpoint_test.go b/jsonrpc/eth_endpoint_test.go index c35dd3694d..1dc5d905fe 100644 --- a/jsonrpc/eth_endpoint_test.go +++ b/jsonrpc/eth_endpoint_test.go @@ -364,7 +364,7 @@ func TestOverrideAccount_ToType(t *testing.T) { state := map[types.Hash]types.Hash{types.StringToHash("1"): types.StringToHash("2")} stateDiff := map[types.Hash]types.Hash{types.StringToHash("3"): types.StringToHash("4")} - overrideAcc := &overrideAccount{ + overrideAcc := &OverrideAccount{ Nonce: toArgUint64Ptr(nonce), Code: toArgBytesPtr(code), Balance: toArgUint64Ptr(balance), diff --git a/jsonrpc/jsonrpc.go b/jsonrpc/jsonrpc.go index ef64bbfd0d..c478eab397 100644 --- a/jsonrpc/jsonrpc.go +++ b/jsonrpc/jsonrpc.go @@ -14,27 +14,6 @@ import ( "github.com/hashicorp/go-hclog" ) -type serverType int - -const ( - serverIPC serverType = iota - serverHTTP - serverWS -) - -func (s serverType) String() string { - switch s { - case serverIPC: - return "ipc" - case serverHTTP: - return "http" - case serverWS: - return "ws" - default: - panic("BUG: Not expected") //nolint:gocritic - } -} - // JSONRPC is an API consensus type JSONRPC struct { logger hclog.Logger diff --git a/jsonrpc/types.go b/jsonrpc/types.go index fdb34b7323..46b98b7cfd 100644 --- a/jsonrpc/types.go +++ b/jsonrpc/types.go @@ -1,6 +1,7 @@ package jsonrpc import ( + "fmt" "math/big" "strconv" "strings" @@ -8,8 +9,11 @@ import ( "github.com/0xPolygon/polygon-edge/helper/common" "github.com/0xPolygon/polygon-edge/helper/hex" "github.com/0xPolygon/polygon-edge/types" + "github.com/valyala/fastjson" ) +var defaultArena fastjson.ArenaPool + const jsonRPCMetric = "json_rpc" // For union type of transaction and types.Hash @@ -382,18 +386,19 @@ func encodeToHex(b []byte) []byte { // txnArgs is the transaction argument for the rpc endpoints type txnArgs struct { - From *types.Address - To *types.Address - Gas *argUint64 - GasPrice *argBytes - GasTipCap *argBytes - GasFeeCap *argBytes - Value *argBytes - Data *argBytes - Input *argBytes - Nonce *argUint64 - Type *argUint64 - AccessList *types.TxAccessList + From *types.Address `json:"from"` + To *types.Address `json:"to"` + Gas *argUint64 `json:"gas"` + GasPrice *argBytes `json:"gasPrice,omitempty"` + GasTipCap *argBytes `json:"maxFeePerGas,omitempty"` + GasFeeCap *argBytes `json:"maxPriorityFeePerGas,omitempty"` + Value *argBytes `json:"value"` + Data *argBytes `json:"data"` + Input *argBytes `json:"input"` + Nonce *argUint64 `json:"nonce"` + Type *argUint64 `json:"type"` + AccessList *types.TxAccessList `json:"accessList,omitempty"` + ChainID *argUint64 `json:"chainId,omitempty"` } type progression struct { @@ -427,3 +432,147 @@ func convertToArgUint64SliceSlice(slice [][]uint64) [][]argUint64 { return argSlice } + +type OverrideAccount struct { + Nonce *argUint64 `json:"nonce"` + Code *argBytes `json:"code"` + Balance *argUint64 `json:"balance"` + State *map[types.Hash]types.Hash `json:"state"` + StateDiff *map[types.Hash]types.Hash `json:"stateDiff"` +} + +func (o *OverrideAccount) ToType() types.OverrideAccount { + res := types.OverrideAccount{} + + if o.Nonce != nil { + res.Nonce = (*uint64)(o.Nonce) + } + + if o.Code != nil { + res.Code = *o.Code + } + + if o.Balance != nil { + res.Balance = new(big.Int).SetUint64(*(*uint64)(o.Balance)) + } + + if o.State != nil { + res.State = *o.State + } + + if o.StateDiff != nil { + res.StateDiff = *o.StateDiff + } + + return res +} + +// StateOverride is the collection of overridden accounts +type StateOverride map[types.Address]OverrideAccount + +// MarshalJSON marshals the StateOverride to JSON +func (s StateOverride) MarshalJSON() ([]byte, error) { + a := defaultArena.Get() + defer a.Reset() + + o := a.NewObject() + + for addr, obj := range s { + oo := a.NewObject() + if obj.Nonce != nil { + oo.Set("nonce", a.NewString(fmt.Sprintf("0x%x", *obj.Nonce))) + } + + if obj.Balance != nil { + oo.Set("balance", a.NewString(fmt.Sprintf("0x%x", obj.Balance))) + } + + if obj.Code != nil { + oo.Set("code", a.NewString("0x"+hex.EncodeToString(*obj.Code))) + } + + if obj.State != nil { + ooo := a.NewObject() + for k, v := range *obj.State { + ooo.Set(k.String(), a.NewString(v.String())) + } + + oo.Set("state", ooo) + } + + if obj.StateDiff != nil { + ooo := a.NewObject() + for k, v := range *obj.StateDiff { + ooo.Set(k.String(), a.NewString(v.String())) + } + + oo.Set("stateDiff", ooo) + } + + o.Set(addr.String(), oo) + } + + res := o.MarshalTo(nil) + + defaultArena.Put(a) + + return res, nil +} + +// CallMsg contains parameters for contract calls +type CallMsg struct { + From types.Address // the sender of the 'transaction' + To *types.Address // the destination contract (nil for contract creation) + Gas uint64 // if 0, the call executes with near-infinite gas + GasPrice *big.Int // wei <-> gas exchange ratio + GasFeeCap *big.Int // EIP-1559 fee cap per gas + GasTipCap *big.Int // EIP-1559 tip per gas + Value *big.Int // amount of wei sent along with the call + Data []byte // input data, usually an ABI-encoded contract method invocation + + AccessList types.TxAccessList // EIP-2930 access list +} + +// MarshalJSON implements the Marshal interface. +func (c *CallMsg) MarshalJSON() ([]byte, error) { + a := defaultArena.Get() + defer a.Reset() + + o := a.NewObject() + o.Set("from", a.NewString(c.From.String())) + o.Set("gas", a.NewString(fmt.Sprintf("0x%x", c.Gas))) + + if c.To != nil { + o.Set("to", a.NewString(c.To.String())) + } + + if len(c.Data) != 0 { + o.Set("data", a.NewString("0x"+hex.EncodeToString(c.Data))) + } + + if c.GasPrice != nil { + o.Set("gasPrice", a.NewString(fmt.Sprintf("0x%x", c.GasPrice))) + } + + if c.Value != nil { + o.Set("value", a.NewString(fmt.Sprintf("0x%x", c.Value))) + } + + if c.GasFeeCap != nil { + o.Set("maxFeePerGas", a.NewString(fmt.Sprintf("0x%x", c.GasFeeCap))) + } + + if c.GasTipCap != nil { + o.Set("maxPriorityFeePerGas", a.NewString(fmt.Sprintf("0x%x", c.GasTipCap))) + } + + if c.AccessList != nil { + o.Set("accessList", c.AccessList.MarshalJSONWith(a)) + } + + res := o.MarshalTo(nil) + + defaultArena.Put(a) + + return res, nil +} diff --git a/state/runtime/precompiled/console.go b/state/runtime/precompiled/console.go index da9e531d43..404c257dae 100644 --- a/state/runtime/precompiled/console.go +++ b/state/runtime/precompiled/console.go @@ -7,17 +7,20 @@ import ( "log" "regexp" + "github.com/umbracle/ethgo/abi" + "github.com/0xPolygon/polygon-edge/chain" + "github.com/0xPolygon/polygon-edge/crypto" "github.com/0xPolygon/polygon-edge/state/runtime" "github.com/0xPolygon/polygon-edge/types" - "github.com/umbracle/ethgo" - "github.com/umbracle/ethgo/abi" ) -//go:embed console.sol -var consoleContract string +var ( + //go:embed console.sol + consoleContract string -var logOverloads = map[string]*abi.Type{} + logOverloads = map[string]*abi.Type{} +) func init() { rxp := regexp.MustCompile("abi.encodeWithSignature\\(\"log(.*)\"") @@ -34,7 +37,7 @@ func init() { } // signature of the call. Use the version without the bytes in 'uint'. - sig := ethgo.Keccak256([]byte("log" + match[1]))[:4] + sig := crypto.Keccak256([]byte("log" + match[1]))[:4] logOverloads[hex.EncodeToString(sig)] = typ } } diff --git a/types/access_list_tx.go b/types/access_list_tx.go index 1df0b26bf7..2c601704f3 100644 --- a/types/access_list_tx.go +++ b/types/access_list_tx.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/umbracle/fastrlp" + "github.com/valyala/fastjson" ) type TxAccessList []AccessTuple @@ -104,6 +105,66 @@ func (al TxAccessList) MarshallRLPWith(arena *fastrlp.Arena) *fastrlp.Value { return accessListVV } +func (t *TxAccessList) MarshalJSONWith(a *fastjson.Arena) *fastjson.Value { + arr := a.NewArray() + + for i, tuple := range *t { + arrElem := a.NewObject() + arrElem.Set("address", a.NewString(tuple.Address.String())) + + strg := a.NewArray() + + for j, key := range tuple.StorageKeys { + strg.SetArrayItem(j, a.NewString(key.String())) + } + + arrElem.Set("storageKeys", strg) + arr.SetArrayItem(i, arrElem) + } + + return arr +} + +func (t *TxAccessList) unmarshalJSON(v *fastjson.Value) error { + elems, err := v.Array() + if err != nil { + return err + } + + for _, elem := range elems { + accessTuple := AccessTuple{} + + addr, err := unmarshalJSONAddr(elem, "address") + if err != nil { + return err + } + + accessTuple.Address = addr + + storage, err := elem.Get("storageKeys").Array() + if err != nil { + return err + } + + accessTuple.StorageKeys = make([]Hash, len(storage)) + + for indx, stg := range storage { + b, err := stg.StringBytes() + if err != nil { + return err + } + + if err := accessTuple.StorageKeys[indx].UnmarshalText(b); err != nil { + return err + } + } + + *t = append(*t, accessTuple) + } + + return nil +} + type AccessListTxn struct { *BaseTx GasPrice *big.Int @@ -328,3 +389,31 @@ func (tx *AccessListTxn) copy() TxData { return cpy } + +func (tx *AccessListTxn) unmarshalJSON(v *fastjson.Value) error { + if err := tx.BaseTx.unmarshalJSON(v); err != nil { + return err + } + + gasPrice, err := unmarshalJSONBigInt(v, "gasPrice") + if err != nil { + return err + } + + tx.setGasPrice(gasPrice) + + chainID, err := unmarshalJSONBigInt(v, "chainId") + if err != nil { + return err + } + + tx.setChainID(chainID) + + if hasKey(v, "accessList") { + if err := tx.AccessList.unmarshalJSON(v.Get("accessList")); err != nil { + return err + } + } + + return nil +} diff --git a/types/base_tx.go b/types/base_tx.go index 0356d2a777..982c68e4c6 100644 --- a/types/base_tx.go +++ b/types/base_tx.go @@ -2,6 +2,8 @@ package types import ( "math/big" + + "github.com/valyala/fastjson" ) // BaseTx represents a base abstract transaction in the blockchain, @@ -105,3 +107,79 @@ func (tx *BaseTx) copy() *BaseTx { return cpy } + +func (tx *BaseTx) unmarshalJSON(v *fastjson.Value) error { + hash, err := unmarshalJSONHash(v, "hash") + if err != nil { + return err + } + + tx.setHash(hash) + + from, err := unmarshalJSONAddr(v, "from") + if err != nil { + return err + } + + tx.setFrom(from) + + // Do not decode 'to' if it doesn't exist. + if hasKey(v, "to") { + if v.Get("to").String() != "null" { + var to Address + + if to, err = unmarshalJSONAddr(v, "to"); err != nil { + return err + } + + tx.setTo(&to) + } + } + + input, err := unmarshalJSONBytes(v, "input") + if err != nil { + return err + } + + tx.setInput(input) + + value, err := unmarshalJSONBigInt(v, "value") + if err != nil { + return err + } + + tx.setValue(value) + + nonce, err := unmarshalJSONUint64(v, "nonce") + if err != nil { + return err + } + + tx.setNonce(nonce) + + vParity, err := unmarshalJSONBigInt(v, "v") + if err != nil { + return err + } + + r, err := unmarshalJSONBigInt(v, "r") + if err != nil { + return err + } + + s, err := unmarshalJSONBigInt(v, "s") + if err != nil { + return err + } + + tx.setSignatureValues(vParity, r, s) + + gas, err := unmarshalJSONUint64(v, "gas") + if err != nil { + return err + } + + tx.setGas(gas) + + return nil +} diff --git a/types/dynamic_fee_tx.go b/types/dynamic_fee_tx.go index b5c33eefd8..a90c650fd2 100644 --- a/types/dynamic_fee_tx.go +++ b/types/dynamic_fee_tx.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/umbracle/fastrlp" + "github.com/valyala/fastjson" ) type DynamicFeeTx struct { @@ -246,3 +247,38 @@ func (tx *DynamicFeeTx) copy() TxData { return cpy } + +func (tx *DynamicFeeTx) unmarshalJSON(v *fastjson.Value) error { + if err := tx.BaseTx.unmarshalJSON(v); err != nil { + return err + } + + gasTipCap, err := unmarshalJSONBigInt(v, "maxPriorityFeePerGas") + if err != nil { + return err + } + + tx.setGasTipCap(gasTipCap) + + gasFeeCap, err := unmarshalJSONBigInt(v, "maxFeePerGas") + if err != nil { + return err + } + + tx.setGasFeeCap(gasFeeCap) + + chainID, err := unmarshalJSONBigInt(v, "chainId") + if err != nil { + return err + } + + tx.setChainID(chainID) + + if hasKey(v, "accessList") { + if err := tx.AccessList.unmarshalJSON(v.Get("accessList")); err != nil { + return err + } + } + + return nil +} diff --git a/types/json_unmarshal.go b/types/json_unmarshal.go new file mode 100644 index 0000000000..3d57b89df9 --- /dev/null +++ b/types/json_unmarshal.go @@ -0,0 +1,411 @@ +package types + +import ( + "fmt" + "math/big" + "strings" + + "github.com/0xPolygon/polygon-edge/helper/common" + "github.com/0xPolygon/polygon-edge/helper/hex" + "github.com/valyala/fastjson" +) + +var defaultPool fastjson.ParserPool + +// UnmarshalJSON implements the unmarshal interface +func (b *Block) UnmarshalJSON(buf []byte) error { + p := defaultPool.Get() + defer defaultPool.Put(p) + + v, err := p.Parse(string(buf)) + if err != nil { + return err + } + + // header + b.Header = &Header{} + if err := b.Header.unmarshalJSON(v); err != nil { + return err + } + + // transactions + b.Transactions = b.Transactions[:0] + + elems := v.GetArray("transactions") + if len(elems) != 0 && elems[0].Type() != fastjson.TypeString { + for _, elem := range elems { + txn := new(Transaction) + if err := txn.unmarshalJSON(elem); err != nil { + return err + } + + b.Transactions = append(b.Transactions, txn) + } + } + + // uncles + b.Uncles = b.Uncles[:0] + + uncles := v.GetArray("uncles") + if len(uncles) != 0 && uncles[0].Type() != fastjson.TypeString { + for _, elem := range uncles { + h := new(Header) + if err := h.unmarshalJSON(elem); err != nil { + return err + } + + b.Uncles = append(b.Uncles, h) + } + } + + return nil +} + +func (h *Header) UnmarshalJSON(buf []byte) error { + p := defaultPool.Get() + defer defaultPool.Put(p) + + v, err := p.Parse(string(buf)) + if err != nil { + return err + } + + return h.unmarshalJSON(v) +} + +func (h *Header) unmarshalJSON(v *fastjson.Value) error { + var err error + + if h.Hash, err = unmarshalJSONHash(v, "hash"); err != nil { + return err + } + + if h.ParentHash, err = unmarshalJSONHash(v, "parentHash"); err != nil { + return err + } + + if h.Sha3Uncles, err = unmarshalJSONHash(v, "sha3Uncles"); err != nil { + return err + } + + if h.TxRoot, err = unmarshalJSONHash(v, "transactionsRoot"); err != nil { + return err + } + + if h.StateRoot, err = unmarshalJSONHash(v, "stateRoot"); err != nil { + return err + } + + if h.ReceiptsRoot, err = unmarshalJSONHash(v, "receiptsRoot"); err != nil { + return err + } + + if h.Miner, err = unmarshalJSONBytes(v, "miner"); err != nil { + return err + } + + if h.Number, err = unmarshalJSONUint64(v, "number"); err != nil { + return err + } + + if h.GasLimit, err = unmarshalJSONUint64(v, "gasLimit"); err != nil { + return err + } + + if h.GasUsed, err = unmarshalJSONUint64(v, "gasUsed"); err != nil { + return err + } + + if h.MixHash, err = unmarshalJSONHash(v, "mixHash"); err != nil { + return err + } + + if err = unmarshalJSONNonce(&h.Nonce, v, "nonce"); err != nil { + return err + } + + if h.Timestamp, err = unmarshalJSONUint64(v, "timestamp"); err != nil { + return err + } + + if h.Difficulty, err = unmarshalJSONUint64(v, "difficulty"); err != nil { + return err + } + + if h.ExtraData, err = unmarshalJSONBytes(v, "extraData"); err != nil { + return err + } + + if h.BaseFee, err = unmarshalJSONUint64(v, "baseFee"); err != nil { + if err.Error() != "field 'baseFee' not found" { + return err + } + } + + return nil +} + +// UnmarshalJSON implements the unmarshal interface +func (t *Transaction) UnmarshalJSON(buf []byte) error { + p := defaultPool.Get() + defer defaultPool.Put(p) + + v, err := p.Parse(string(buf)) + if err != nil { + return err + } + + return t.unmarshalJSON(v) +} + +func (t *Transaction) unmarshalJSON(v *fastjson.Value) error { + if hasKey(v, "type") { + txnType, err := unmarshalJSONUint64(v, "type") + if err != nil { + return err + } + + t.InitInnerData(TxType(txnType)) + } else { + if hasKey(v, "chainId") { + if hasKey(v, "maxFeePerGas") { + t.InitInnerData(DynamicFeeTxType) + } else { + t.InitInnerData(AccessListTxType) + } + } else { + t.InitInnerData(LegacyTxType) + } + } + + return t.Inner.unmarshalJSON(v) +} + +// UnmarshalJSON implements the unmarshal interface +func (r *Receipt) UnmarshalJSON(buf []byte) error { + p := defaultPool.Get() + defer defaultPool.Put(p) + + v, err := p.Parse(string(buf)) + if err != nil { + return nil + } + + if hasKey(v, "contractAddress") { + contractAddr, err := unmarshalJSONAddr(v, "contractAddress") + if err != nil { + return err + } + + r.ContractAddress = &contractAddr + } + + if r.TxHash, err = unmarshalJSONHash(v, "transactionHash"); err != nil { + return err + } + + if r.GasUsed, err = unmarshalJSONUint64(v, "gasUsed"); err != nil { + return err + } + + if r.CumulativeGasUsed, err = unmarshalJSONUint64(v, "cumulativeGasUsed"); err != nil { + return err + } + + if err = unmarshalJSONBloom(&r.LogsBloom, v, "logsBloom"); err != nil { + return err + } + + if r.Root, err = unmarshalJSONHash(v, "root"); err != nil { + return err + } + + if hasKey(v, "status") { + // post-byzantium fork + status, err := unmarshalJSONUint64(v, "status") + if err != nil { + return err + } + + r.SetStatus(ReceiptStatus(status)) + } + + // logs + r.Logs = r.Logs[:0] + + for _, elem := range v.GetArray("logs") { + log := new(Log) + if err := log.unmarshalJSON(elem); err != nil { + return err + } + + r.Logs = append(r.Logs, log) + } + + return nil +} + +// UnmarshalJSON implements the unmarshal interface +func (r *Log) UnmarshalJSON(buf []byte) error { + p := defaultPool.Get() + defer defaultPool.Put(p) + + v, err := p.Parse(string(buf)) + if err != nil { + return nil + } + + return r.unmarshalJSON(v) +} + +func (r *Log) unmarshalJSON(v *fastjson.Value) error { + var err error + + if r.Address, err = unmarshalJSONAddr(v, "address"); err != nil { + return err + } + + if r.Data, err = unmarshalJSONBytes(v, "data"); err != nil { + return err + } + + r.Topics = r.Topics[:0] + + for _, topic := range v.GetArray("topics") { + b, err := topic.StringBytes() + if err != nil { + return err + } + + var t Hash + if err := t.UnmarshalText(b); err != nil { + return err + } + + r.Topics = append(r.Topics, t) + } + + return nil +} + +func unmarshalJSONHash(v *fastjson.Value, key string) (Hash, error) { + hash := Hash{} + + b := v.GetStringBytes(key) + if len(b) == 0 { + return ZeroHash, fmt.Errorf("field '%s' not found", key) + } + + err := hash.UnmarshalText(b) + + return hash, err +} + +func unmarshalJSONAddr(v *fastjson.Value, key string) (Address, error) { + b := v.GetStringBytes(key) + if len(b) == 0 { + return ZeroAddress, fmt.Errorf("field '%s' not found", key) + } + + a := Address{} + err := a.UnmarshalText(b) + + return a, err +} + +func unmarshalJSONBytes(v *fastjson.Value, key string, bits ...int) ([]byte, error) { + vv := v.Get(key) + if vv == nil { + return nil, fmt.Errorf("field '%s' not found", key) + } + + str := vv.String() + str = strings.Trim(str, "\"") + + if !strings.HasPrefix(str, "0x") { + return nil, fmt.Errorf("field '%s' does not have 0x prefix: '%s'", key, str) + } + + str = str[2:] + if len(str)%2 != 0 { + str = "0" + str + } + + buf, err := hex.DecodeString(str) + if err != nil { + return nil, err + } + + if len(bits) > 0 && bits[0] != len(buf) { + return nil, fmt.Errorf("field '%s' invalid length, expected %d but found %d: %s", key, bits[0], len(buf), str) + } + + return buf, nil +} + +func unmarshalJSONUint64(v *fastjson.Value, key string) (uint64, error) { + vv := v.Get(key) + if vv == nil { + return 0, fmt.Errorf("field '%s' not found", key) + } + + str := vv.String() + str = strings.Trim(str, "\"") + + return common.ParseUint64orHex(&str) +} + +func unmarshalJSONBigInt(v *fastjson.Value, key string) (*big.Int, error) { + vv := v.Get(key) + if vv == nil { + return nil, fmt.Errorf("field '%s' not found", key) + } + + str := vv.String() + str = strings.Trim(str, "\"") + + return common.ParseUint256orHex(&str) +} + +func unmarshalJSONNonce(n *Nonce, v *fastjson.Value, key string) error { + b := v.GetStringBytes(key) + if len(b) == 0 { + return fmt.Errorf("field '%s' not found", key) + } + + return unmarshalTextByte(n[:], b, 8) +} + +func unmarshalJSONBloom(bloom *Bloom, v *fastjson.Value, key string) error { + b := v.GetStringBytes(key) + if len(b) == 0 { + return fmt.Errorf("field '%s' not found", key) + } + + return unmarshalTextByte(bloom[:], b, BloomByteLength) +} + +func unmarshalTextByte(dst, src []byte, size int) error { + str := string(src) + str = strings.Trim(str, "\"") + + b, err := hex.DecodeHex(str) + if err != nil { + return err + } + + if len(b) != size { + return fmt.Errorf("length %d is not correct, expected %d", len(b), size) + } + + copy(dst, b) + + return nil +} + +// hasKey is a helper function for checking if given key exists in json +func hasKey(v *fastjson.Value, key string) bool { + value := v.Get(key) + + return value != nil && value.Type() != fastjson.TypeNull +} diff --git a/types/legacy_tx.go b/types/legacy_tx.go index 668d905e3b..f78aca36ee 100644 --- a/types/legacy_tx.go +++ b/types/legacy_tx.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/umbracle/fastrlp" + "github.com/valyala/fastjson" ) type LegacyTx struct { @@ -198,3 +199,18 @@ func deriveChainID(v *big.Int) *big.Int { return v.Div(v, big.NewInt(2)) } + +func (tx *LegacyTx) unmarshalJSON(v *fastjson.Value) error { + if err := tx.BaseTx.unmarshalJSON(v); err != nil { + return err + } + + gasPrice, err := unmarshalJSONBigInt(v, "gasPrice") + if err != nil { + return err + } + + tx.setGasPrice(gasPrice) + + return nil +} diff --git a/types/state_tx.go b/types/state_tx.go index 0ff7c48fa2..ff62671a49 100644 --- a/types/state_tx.go +++ b/types/state_tx.go @@ -5,6 +5,7 @@ import ( "math/big" "github.com/umbracle/fastrlp" + "github.com/valyala/fastjson" ) type StateTx struct { @@ -198,3 +199,18 @@ func (tx *StateTx) copy() TxData { //nolint:dupl return cpy } + +func (tx *StateTx) unmarshalJSON(v *fastjson.Value) error { + if err := tx.BaseTx.unmarshalJSON(v); err != nil { + return err + } + + gasPrice, err := unmarshalJSONBigInt(v, "gasPrice") + if err != nil { + return err + } + + tx.setGasPrice(gasPrice) + + return nil +} diff --git a/types/transaction.go b/types/transaction.go index e5bb986234..248ee979e1 100644 --- a/types/transaction.go +++ b/types/transaction.go @@ -8,6 +8,7 @@ import ( "github.com/0xPolygon/polygon-edge/helper/common" "github.com/0xPolygon/polygon-edge/helper/keccak" "github.com/umbracle/fastrlp" + "github.com/valyala/fastjson" ) const ( @@ -117,6 +118,7 @@ type TxData interface { setSignatureValues(v, r, s *big.Int) unmarshalRLPFrom(p *fastrlp.Parser, v *fastrlp.Value) error marshalRLPWith(arena *fastrlp.Arena) *fastrlp.Value + unmarshalJSON(v *fastjson.Value) error copy() TxData }