Skip to content
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

Extend CfxBridge to be more evm compatible #192

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions config/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,13 @@ rpc:
cfxBridge:
# EVM space fullnode endpoint
ethNode: http://evmtestnet.confluxrpc.com
# core space fullnode endpoint
cfxNode: http://test.confluxrpc.com
# core space fullnode endpoint (optional)
# cfxNode: http://test.confluxrpc.com
# Implementation method for batch receipts, available options are:
# 0 - `parity_getBlockReceipts`
# 1 - `eth_getTransactionReceipt`
# 2 - `eth_getBlockReceipts`
# batchRcptImpl: 0
# Available exposed modules are `cfx`, `txpool`, `trace`, if empty all APIs will be exposed.
# exposedModules: []
# Served HTTP endpoint
Expand Down Expand Up @@ -388,4 +393,4 @@ node:
# # Switch to turn on/off pprof
# enabled: false
# # The endpoint to start a http server for pprof
# httpEndpoint: ":6060"
# httpEndpoint: ":6060"
18 changes: 12 additions & 6 deletions rpc/apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/Conflux-Chain/confura/rpc/handler"
"github.com/Conflux-Chain/confura/util/metrics/service"
"github.com/Conflux-Chain/confura/util/rpc"
sdk "github.com/Conflux-Chain/go-conflux-sdk"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -129,7 +130,8 @@ func evmSpaceApis(clientProvider *node.EthClientProvider, option ...EthAPIOption
}

// nativeSpaceBridgeApis adapts evm space RPCs to core space RPCs.
func nativeSpaceBridgeApis(ethNodeURL, cfxNodeURL string) ([]API, error) {
func nativeSpaceBridgeApis(config *CfxBridgeServerConfig) ([]API, error) {
ethNodeURL, cfxNodeURL := config.EthNode, config.CfxNode
eth, err := rpc.NewEthClient(ethNodeURL)
if err != nil {
return nil, errors.WithMessage(err, "Failed to connect to eth space")
Expand All @@ -141,11 +143,15 @@ func nativeSpaceBridgeApis(ethNodeURL, cfxNodeURL string) ([]API, error) {
}
rpc.HookMiddlewares(eth.Provider(), ethNodeURL, "eth")

cfx, err := rpc.NewCfxClient(cfxNodeURL)
if err != nil {
return nil, errors.WithMessage(err, "Failed to connect to cfx space")
var cfx *sdk.Client
if len(cfxNodeURL) > 0 { // optioinal
cfx, err = rpc.NewCfxClient(cfxNodeURL)
if err != nil {
return nil, errors.WithMessage(err, "Failed to connect to cfx space")
}

rpc.HookMiddlewares(cfx.Provider(), cfxNodeURL, "cfx")
}
rpc.HookMiddlewares(cfx.Provider(), cfxNodeURL, "cfx")

// Hook an event handler to reidrect HTTP headers for the sdk client request
// because we could use `Confura` RPC service as our client endpoint for `cfxBridge`.
Expand All @@ -155,7 +161,7 @@ func nativeSpaceBridgeApis(ethNodeURL, cfxNodeURL string) ([]API, error) {
{
Namespace: "cfx",
Version: "1.0",
Service: cfxbridge.NewCfxAPI(eth, uint32(*ethChainId), cfx),
Service: cfxbridge.NewCfxAPI(eth, uint32(*ethChainId), cfx, config.BatchRcptImpl),
Public: true,
}, {
Namespace: "trace",
Expand Down
204 changes: 179 additions & 25 deletions rpc/cfxbridge/cfx_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,29 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
rpcp "github.com/openweb3/go-rpc-provider"
"github.com/openweb3/web3go"
ethTypes "github.com/openweb3/web3go/types"
"github.com/pkg/errors"
)

const (
BatchRcptImplWithParityBlockReceipts = iota
BatchRcptImplWithEthTxnReceipt
BatchRcptImplWithEthBlockReceipts
)

type CfxAPI struct {
w3c *web3go.Client
cfx *sdk.Client
ethNetworkId uint32
w3c *web3go.Client
cfx *sdk.Client // optional
ethNetworkId uint32
batchRcptImpl int
}

func NewCfxAPI(w3client *web3go.Client, ethNetId uint32, cfxClient *sdk.Client) *CfxAPI {
func NewCfxAPI(
w3client *web3go.Client, ethNetId uint32, cfxClient *sdk.Client, batchRcptImpl int) *CfxAPI {
return &CfxAPI{
w3c: w3client,
cfx: cfxClient,
ethNetworkId: ethNetId,
w3c: w3client, cfx: cfxClient, ethNetworkId: ethNetId, batchRcptImpl: batchRcptImpl,
}
}

Expand All @@ -36,7 +44,34 @@ func (api *CfxAPI) EpochNumber(ctx context.Context, epoch *types.Epoch) (*hexuti
epoch = types.EpochLatestState
}

return api.cfx.WithContext(ctx).GetEpochNumber(epoch)
if api.cfx != nil {
return api.cfx.WithContext(ctx).GetEpochNumber(epoch)
}

var blockNum ethTypes.BlockNumber
switch {
case epoch.Equals(types.EpochEarliest):
blockNum = ethTypes.EarliestBlockNumber
case epoch.Equals(types.EpochLatestConfirmed):
blockNum = ethTypes.SafeBlockNumber
case epoch.Equals(types.EpochLatestState):
blockNum = ethTypes.LatestBlockNumber
case epoch.Equals(types.EpochLatestMined):
blockNum = ethTypes.LatestBlockNumber
case epoch.Equals(types.EpochLatestFinalized):
blockNum = ethTypes.FinalizedBlockNumber
case epoch.Equals(types.EpochLatestCheckpoint):
return HexBig0, nil
default:
return nil, ErrEpochUnsupported
}

block, err := api.w3c.WithContext(ctx).Eth.BlockByNumber(blockNum, false)
if err != nil {
return nil, err
}

return (*hexutil.Big)(block.Number), nil
}

func (api *CfxAPI) GetBalance(ctx context.Context, address EthAddress, bn *EthBlockNumber) (*hexutil.Big, error) {
Expand Down Expand Up @@ -228,19 +263,66 @@ func (api *CfxAPI) GetTransactionReceipt(ctx context.Context, txHash common.Hash
}

func (api *CfxAPI) GetEpochReceipts(ctx context.Context, bnh EthBlockNumberOrHash) ([][]*types.TransactionReceipt, error) {
receipts, err := api.w3c.WithContext(ctx).Parity.BlockReceipts(bnh.ToArg())
if err != nil {
return nil, err
var receipts []*ethTypes.Receipt

switch api.batchRcptImpl {
case BatchRcptImplWithEthTxnReceipt:
block, err := api.getBlockByBlockNumberOrHash(ctx, bnh, false)
if err != nil {
return nil, err
}

for _, txn := range block.Transactions.Hashes() {
receipt, err := api.w3c.WithContext(ctx).Eth.TransactionReceipt(txn)
if err != nil {
return nil, err
}

if receipt != nil && receipt.BlockHash != block.Hash { // reorg?
return nil, errors.New("pivot reorg, please retry again")
}

receipts = append(receipts, receipt)
}

case BatchRcptImplWithParityBlockReceipts:
blockReceipts, err := api.w3c.WithContext(ctx).Parity.BlockReceipts(bnh.ToArg())
if err != nil {
return nil, err
}

for i := range blockReceipts {
receipts = append(receipts, &blockReceipts[i])
}

case BatchRcptImplWithEthBlockReceipts:
blockReceipts, err := api.w3c.WithContext(ctx).Eth.BlockReceipts(bnh.ToArg())
if err != nil {
return nil, err
}

receipts = blockReceipts
default:
return nil, errors.New("unsupported batch receipt implementation")
}

result := make([]*types.TransactionReceipt, len(receipts))
for i := range receipts {
result[i] = ConvertReceipt(&receipts[i], api.ethNetworkId)
result[i] = ConvertReceipt(receipts[i], api.ethNetworkId)
}

return [][]*types.TransactionReceipt{result}, nil
}

func (api *CfxAPI) getBlockByBlockNumberOrHash(
ctx context.Context, bnh EthBlockNumberOrHash, isFull bool) (*ethTypes.Block, error) {
if bn, ok := bnh.ToArg().Number(); ok {
return api.w3c.WithContext(ctx).Eth.BlockByNumber(bn, isFull)
}

return api.w3c.WithContext(ctx).Eth.BlockByHash(*bnh.ToArg().BlockHash, isFull)
}

func (api *CfxAPI) GetAccount(ctx context.Context, address EthAddress, bn *EthBlockNumber) (types.AccountInfo, error) {
balance, err := api.w3c.WithContext(ctx).Eth.Balance(address.value, bn.ToArg())
if err != nil {
Expand Down Expand Up @@ -277,11 +359,65 @@ func (api *CfxAPI) GetAccumulateInterestRate(ctx context.Context, bn *EthBlockNu
}

func (api *CfxAPI) GetConfirmationRiskByHash(ctx context.Context, blockHash types.Hash) (*hexutil.Big, error) {
return api.cfx.WithContext(ctx).GetRawBlockConfirmationRisk(blockHash)
if api.cfx != nil {
return api.cfx.WithContext(ctx).GetRawBlockConfirmationRisk(blockHash)
}

block, err := api.w3c.Eth.BlockByHash(*blockHash.ToCommonHash(), false)
if block == nil || err != nil {
return nil, err
}

// TODO: calculate confirmation risk based on various confirmed blocks.
return HexBig0, nil
}

func (api *CfxAPI) GetStatus(ctx context.Context) (status types.Status, err error) {
if api.cfx != nil {
return api.getCrossSpaceStatus(ctx)
}

var batchElems []rpcp.BatchElem
for _, bn := range []rpcp.BlockNumber{
ethTypes.LatestBlockNumber,
ethTypes.SafeBlockNumber,
ethTypes.FinalizedBlockNumber,
} {
batchElems = append(batchElems, rpcp.BatchElem{
Method: "eth_getBlockByNumber",
Args: []interface{}{bn, false},
Result: new(ethTypes.Block),
})
}

if err := api.w3c.Provider().BatchCallContext(ctx, batchElems); err != nil {
return types.Status{}, err
}

latestBlock := batchElems[0].Result.(*ethTypes.Block)
safeBlock := batchElems[1].Result.(*ethTypes.Block)
finBlock := batchElems[2].Result.(*ethTypes.Block)

chainID := hexutil.Uint64(api.ethNetworkId)
latestBlockNumber := hexutil.Uint64(latestBlock.Number.Uint64())

return types.Status{
BestHash: ConvertHash(latestBlock.Hash),
ChainID: chainID,
EthereumSpaceChainId: chainID,
NetworkID: chainID,
EpochNumber: latestBlockNumber,
BlockNumber: latestBlockNumber,
LatestState: latestBlockNumber,
LatestConfirmed: hexutil.Uint64(safeBlock.Number.Uint64()),
LatestFinalized: hexutil.Uint64(finBlock.Number.Uint64()),
LatestCheckpoint: 0,
PendingTxNumber: 0,
}, nil
}

func (api *CfxAPI) GetStatus(ctx context.Context) (types.Status, error) {
status, err := api.cfx.WithContext(ctx).GetStatus()
func (api *CfxAPI) getCrossSpaceStatus(ctx context.Context) (status types.Status, err error) {
status, err = api.cfx.WithContext(ctx).GetStatus()
if err != nil {
return types.Status{}, err
}
Expand All @@ -305,7 +441,13 @@ func (api *CfxAPI) GetStatus(ctx context.Context) (types.Status, error) {
}

func (api *CfxAPI) GetBlockRewardInfo(ctx context.Context, epoch types.Epoch) ([]types.RewardInfo, error) {
return api.cfx.WithContext(ctx).GetBlockRewardInfo(epoch)
if api.cfx != nil {
return api.cfx.WithContext(ctx).GetBlockRewardInfo(epoch)
}

// TODO: Calculate block reward based on the following implementation:
// https://docs.alchemy.com/docs/how-to-calculate-ethereum-miner-rewards
return []types.RewardInfo{}, nil
}

func (api *CfxAPI) ClientVersion(ctx context.Context) (string, error) {
Expand All @@ -317,15 +459,27 @@ func (api *CfxAPI) GetSupplyInfo(ctx context.Context, epoch *types.Epoch) (types
epoch = types.EpochLatestState
}

result, err := api.cfx.WithContext(ctx).GetSupplyInfo(epoch)
if err != nil {
return types.TokenSupplyInfo{}, err
}
if api.cfx != nil {
result, err := api.cfx.WithContext(ctx).GetSupplyInfo(epoch)
if err != nil {
return types.TokenSupplyInfo{}, err
}

result.TotalCirculating = result.TotalEspaceTokens
result.TotalIssued = result.TotalEspaceTokens
result.TotalStaking = HexBig0
result.TotalCollateral = HexBig0
result.TotalCirculating = result.TotalEspaceTokens
result.TotalIssued = result.TotalEspaceTokens
result.TotalStaking = HexBig0
result.TotalCollateral = HexBig0

return result, nil
return result, nil
}

// TODO: Calculate supply info based on the following implementation:
// https://github.com/lastmjs/eth-total-supply
return types.TokenSupplyInfo{
TotalCirculating: HexBig0,
TotalIssued: HexBig0,
TotalStaking: HexBig0,
TotalCollateral: HexBig0,
TotalEspaceTokens: HexBig0,
}, nil
}
11 changes: 8 additions & 3 deletions rpc/cfxbridge/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -237,16 +237,21 @@ func ConvertLog(log *ethTypes.Log, ethNetworkId uint32) *types.Log {
topics[i] = types.Hash(log.Topics[i].Hex())
}

var txnLogIdx *hexutil.Big
if log.TransactionLogIndex != nil {
txnLogIdx = types.NewBigInt(uint64(*log.TransactionLogIndex))
}

return &types.Log{
Address: ConvertAddress(log.Address, ethNetworkId),
Topics: topics,
Data: log.Data,
BlockHash: ConvertHashNullable(&log.BlockHash),
EpochNumber: types.NewBigInt(log.BlockNumber),
TransactionHash: ConvertHashNullable(&log.TxHash),
TransactionIndex: types.NewBigInt(uint64(log.TxIndex)), // tx index in block
LogIndex: types.NewBigInt(uint64(log.Index)), // log index in block
TransactionLogIndex: types.NewBigInt(uint64(*log.TransactionLogIndex)), // log index in tx
TransactionIndex: types.NewBigInt(uint64(log.TxIndex)), // tx index in block
LogIndex: types.NewBigInt(uint64(log.Index)), // log index in block
TransactionLogIndex: txnLogIdx, // log index in tx
Space: &spaceEVM,
}
}
Expand Down
3 changes: 2 additions & 1 deletion rpc/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,12 +73,13 @@ func MustNewEvmSpaceServer(
type CfxBridgeServerConfig struct {
EthNode string
CfxNode string
BatchRcptImpl int
ExposedModules []string
Endpoint string `default:":32537"`
}

func MustNewNativeSpaceBridgeServer(registry *rate.Registry, config *CfxBridgeServerConfig) *rpc.Server {
allApis, err := nativeSpaceBridgeApis(config.EthNode, config.CfxNode)
allApis, err := nativeSpaceBridgeApis(config)
if err != nil {
logrus.WithError(err).Fatal("Failed to new CFX bridge RPC server")
}
Expand Down
Loading