From da866a34feb56445102f292db20be9c6e0b886df Mon Sep 17 00:00:00 2001 From: zhourong Date: Wed, 31 Mar 2021 20:31:45 +0800 Subject: [PATCH] feat(rpc): add json rpc --- api/jsonrpc/apis.go | 61 +++++++ api/jsonrpc/broker.go | 82 +++++++++ api/jsonrpc/namespaces/eth/api.go | 279 +++++++++++++++++++++++++++++ api/jsonrpc/namespaces/net/api.go | 31 ++++ api/jsonrpc/namespaces/web3/api.go | 27 +++ api/jsonrpc/types/addrlock.go | 38 ++++ api/jsonrpc/types/block.go | 81 +++++++++ api/jsonrpc/types/chain_id.go | 52 ++++++ api/jsonrpc/types/types.go | 93 ++++++++++ cmd/bitxhub/start.go | 12 +- config/bitxhub.toml | 4 +- go.mod | 1 + internal/repo/config.go | 2 + scripts/cluster.sh | 1 + 14 files changed, 762 insertions(+), 2 deletions(-) create mode 100644 api/jsonrpc/apis.go create mode 100644 api/jsonrpc/broker.go create mode 100644 api/jsonrpc/namespaces/eth/api.go create mode 100644 api/jsonrpc/namespaces/net/api.go create mode 100644 api/jsonrpc/namespaces/web3/api.go create mode 100644 api/jsonrpc/types/addrlock.go create mode 100644 api/jsonrpc/types/block.go create mode 100644 api/jsonrpc/types/chain_id.go create mode 100644 api/jsonrpc/types/types.go diff --git a/api/jsonrpc/apis.go b/api/jsonrpc/apis.go new file mode 100644 index 0000000..88490a6 --- /dev/null +++ b/api/jsonrpc/apis.go @@ -0,0 +1,61 @@ +package jsonrpc + +import ( + "github.com/ethereum/go-ethereum/rpc" + "github.com/meshplus/bitxhub/api/jsonrpc/namespaces/eth" + "github.com/meshplus/bitxhub/api/jsonrpc/namespaces/net" + "github.com/meshplus/bitxhub/api/jsonrpc/namespaces/web3" + "github.com/meshplus/bitxhub/internal/coreapi/api" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/sirupsen/logrus" +) + +// RPC namespaces and API version +const ( + Web3Namespace = "web3" + EthNamespace = "eth" + PersonalNamespace = "personal" + NetNamespace = "net" + flagRPCAPI = "rpc-api" + + apiVersion = "1.0" +) + +// GetAPIs returns the list of all APIs from the Ethereum namespaces +func GetAPIs(config *repo.Config, api api.CoreAPI, logger logrus.FieldLogger) ([]rpc.API, error) { + var apis []rpc.API + + ethAPI, err := eth.NewAPI(config, api, logger) + if err != nil { + return nil, err + } + + apis = append(apis, + rpc.API{ + Namespace: EthNamespace, + Version: apiVersion, + Service: ethAPI, + Public: true, + }, + ) + + apis = append(apis, + rpc.API{ + Namespace: Web3Namespace, + Version: apiVersion, + Service: web3.NewAPI(), + Public: true, + }, + ) + + apis = append(apis, + rpc.API{ + Namespace: NetNamespace, + Version: apiVersion, + Service: net.NewAPI(config), + Public: true, + }, + ) + + return apis, nil +} diff --git a/api/jsonrpc/broker.go b/api/jsonrpc/broker.go new file mode 100644 index 0000000..b9d13f4 --- /dev/null +++ b/api/jsonrpc/broker.go @@ -0,0 +1,82 @@ +package jsonrpc + +import ( + "context" + "fmt" + "net/http" + + "github.com/ethereum/go-ethereum/rpc" + "github.com/gorilla/mux" + "github.com/meshplus/bitxhub/internal/coreapi/api" + "github.com/meshplus/bitxhub/internal/loggers" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/sirupsen/logrus" +) + +type ChainBrokerService struct { + config *repo.Config + genesis *repo.Genesis + api api.CoreAPI + server *rpc.Server + logger logrus.FieldLogger + + ctx context.Context + cancel context.CancelFunc +} + +func NewChainBrokerService(coreAPI api.CoreAPI, config *repo.Config) (*ChainBrokerService, error) { + server := rpc.NewServer() + + logger := loggers.Logger(loggers.API) + + apis, err := GetAPIs(config, coreAPI, logger) + if err != nil { + return nil, err + } + + // Register all the APIs exposed by the namespace services + for _, api := range apis { + if err := server.RegisterName(api.Namespace, api.Service); err != nil { + return nil, err + } + } + + ctx, cancel := context.WithCancel(context.Background()) + + return &ChainBrokerService{ + logger: logger, + config: config, + api: coreAPI, + server: server, + ctx: ctx, + cancel: cancel, + }, nil +} + +func (cbs *ChainBrokerService) Start() error { + router := mux.NewRouter() + router.Handle("/", cbs.server) + + go func() { + if err := http.ListenAndServe(fmt.Sprintf(":%d", cbs.config.Port.JsonRpc), router); err != nil { + cbs.logger.WithFields(logrus.Fields{ + "error": err.Error(), + }).Error("Failed to start JSON_RPC service") + return + } + + cbs.logger.WithFields(logrus.Fields{ + "port": cbs.config.Port.JsonRpc, + }).Info("JSON-RPC service started") + }() + + return nil +} + +func (cbs *ChainBrokerService) Stop() error { + cbs.cancel() + + cbs.logger.Info("GRPC service stopped") + + return nil +} diff --git a/api/jsonrpc/namespaces/eth/api.go b/api/jsonrpc/namespaces/eth/api.go new file mode 100644 index 0000000..d818f2b --- /dev/null +++ b/api/jsonrpc/namespaces/eth/api.go @@ -0,0 +1,279 @@ +package eth + +import ( + "context" + "fmt" + "math/big" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + ethtypes "github.com/ethereum/go-ethereum/core/types" + "github.com/meshplus/bitxhub-kit/types" + types2 "github.com/meshplus/bitxhub/api/jsonrpc/types" + "github.com/meshplus/bitxhub/internal/coreapi/api" + "github.com/meshplus/bitxhub/internal/repo" + "github.com/sirupsen/logrus" +) + +// PublicEthereumAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. +type PublicEthereumAPI struct { + ctx context.Context + cancel context.CancelFunc + chainIDEpoch *big.Int + logger logrus.FieldLogger + api api.CoreAPI +} + +// NewAPI creates an instance of the public ETH Web3 API. +func NewAPI(config *repo.Config, api api.CoreAPI, logger logrus.FieldLogger) (*PublicEthereumAPI, error) { + epoch, err := types2.ParseChainID(config.Genesis.ChainID) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithCancel(context.Background()) + + return &PublicEthereumAPI{ + ctx: ctx, + cancel: cancel, + chainIDEpoch: epoch, + logger: logger, + api: api, + }, nil +} + +// ProtocolVersion returns the supported Ethereum protocol version. +func (api *PublicEthereumAPI) ProtocolVersion() hexutil.Uint { + api.logger.Debug("eth_protocolVersion") + return hexutil.Uint(types2.ProtocolVersion) +} + +// ChainId returns the chain's identifier in hex format +func (api *PublicEthereumAPI) ChainId() (hexutil.Uint, error) { // nolint + api.logger.Debug("eth_chainId") + return hexutil.Uint(uint(api.chainIDEpoch.Uint64())), nil +} + +// Syncing returns whether or not the current node is syncing with other peers. Returns false if not, or a struct +// outlining the state of the sync if it is. +func (api *PublicEthereumAPI) Syncing() (interface{}, error) { + api.logger.Debug("eth_syncing") + + // TODO + + return nil, nil +} + +// Mining returns whether or not this node is currently mining. Always false. +func (api *PublicEthereumAPI) Mining() bool { + api.logger.Debug("eth_mining") + return false +} + +// Hashrate returns the current node's hashrate. Always 0. +func (api *PublicEthereumAPI) Hashrate() hexutil.Uint64 { + api.logger.Debug("eth_hashrate") + return 0 +} + +// GasPrice returns the current gas price based on Ethermint's gas price oracle. +func (api *PublicEthereumAPI) GasPrice() *hexutil.Big { + api.logger.Debug("eth_gasPrice") + out := big.NewInt(0) + return (*hexutil.Big)(out) +} + +// BlockNumber returns the current block number. +func (api *PublicEthereumAPI) BlockNumber() (hexutil.Uint64, error) { + api.logger.Debug("eth_blockNumber") + meta, err := api.api.Chain().Meta() + if err != nil { + return 0, err + } + + return hexutil.Uint64(meta.Height), nil +} + +// GetBalance returns the provided account's balance up to the provided block number. +func (api *PublicEthereumAPI) GetBalance(address common.Address, blockNum types2.BlockNumber) (*hexutil.Big, error) { + api.logger.Debug("eth_getBalance", "address", address, "block number", blockNum) + + if blockNum != types2.LatestBlockNumber { + return nil, fmt.Errorf("only support query for latest block number") + } + + account := api.api.Account().GetAccount(types.NewAddress(address.Bytes())) + + balance := account.GetBalance() + + return (*hexutil.Big)(big.NewInt(int64(balance))), nil +} + +// GetStorageAt returns the contract storage at the given address, block number, and key. +func (api *PublicEthereumAPI) GetStorageAt(address common.Address, key string, blockNum types2.BlockNumber) (hexutil.Bytes, error) { + api.logger.Debug("eth_getStorageAt", "address", address, "key", key, "block number", blockNum) + + if blockNum != types2.LatestBlockNumber { + return nil, fmt.Errorf("only support query for latest block number") + } + + account := api.api.Account().GetAccount(types.NewAddress(address.Bytes())) + + ok, val := account.GetState([]byte(key)) + if !ok { + return nil, nil + } + + return val, nil +} + +// GetTransactionCount returns the number of transactions at the given address up to the given block number. +func (api *PublicEthereumAPI) GetTransactionCount(address common.Address, blockNum types2.BlockNumber) (*hexutil.Uint64, error) { + api.logger.Debug("eth_getTransactionCount", "address", address, "block number", blockNum) + + if blockNum != types2.LatestBlockNumber { + return nil, fmt.Errorf("only support query for latest block number") + } + + account := api.api.Account().GetAccount(types.NewAddress(address.Bytes())) + + nonce := account.GetNonce() + + return (*hexutil.Uint64)(&nonce), nil +} + +// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash. +func (api *PublicEthereumAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint { + api.logger.Debug("eth_getBlockTransactionCountByHash", "hash", hash) + return nil +} + +// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by its height. +func (api *PublicEthereumAPI) GetBlockTransactionCountByNumber(blockNum uint64) *hexutil.Uint { + api.logger.Debug("eth_getBlockTransactionCountByNumber", "block number", blockNum) + + return nil +} + +// GetUncleCountByBlockHash returns the number of uncles in the block idenfied by hash. Always zero. +func (api *PublicEthereumAPI) GetUncleCountByBlockHash(_ common.Hash) hexutil.Uint { + return 0 +} + +func (api *PublicEthereumAPI) GetUncleCountByBlockNumber(_ uint64) hexutil.Uint { + return 0 +} + +// GetCode returns the contract code at the given address and block number. +func (api *PublicEthereumAPI) GetCode(address common.Address, blockNumber types2.BlockNumber) (hexutil.Bytes, error) { + api.logger.Debug("eth_getCode", "address", address, "block number", blockNumber) + + if blockNumber != types2.LatestBlockNumber { + return nil, fmt.Errorf("only support query for latest block number") + } + + account := api.api.Account().GetAccount(types.NewAddress(address.Bytes())) + + code := account.Code() + + return code, nil +} + +// GetTransactionLogs returns the logs given a transaction hash. +func (api *PublicEthereumAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) { + api.logger.Debug("eth_getTransactionLogs", "hash", txHash) + // TODO + return nil, nil +} + +// SendRawTransaction send a raw Ethereum transaction. +func (api *PublicEthereumAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) { + api.logger.Debug("eth_sendRawTransaction", "data", data) + + // TODO + + return common.Hash{}, nil +} + +// Call performs a raw contract call. +func (api *PublicEthereumAPI) Call(args types2.CallArgs, blockNr uint64, _ *map[common.Address]types2.Account) (hexutil.Bytes, error) { + api.logger.Debug("eth_call", "args", args, "block number", blockNr) + // TODO + + return nil, nil +} + +// EstimateGas returns an estimate of gas usage for the given smart contract call. +// It adds 1,000 gas to the returned value instead of using the gas adjustment +// param from the SDK. +func (api *PublicEthereumAPI) EstimateGas(args types2.CallArgs) (hexutil.Uint64, error) { + api.logger.Debug("eth_estimateGas", "args", args) + // TODO + + return hexutil.Uint64(1000), nil +} + +// GetBlockByHash returns the block identified by hash. +func (api *PublicEthereumAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) { + api.logger.Debug("eth_getBlockByHash", "hash", hash, "full", fullTx) + // TODO + + return nil, nil +} + +// GetBlockByNumber returns the block identified by number. +func (api *PublicEthereumAPI) GetBlockByNumber(blockNum uint64, fullTx bool) (map[string]interface{}, error) { + api.logger.Debug("eth_getBlockByNumber", "number", blockNum, "full", fullTx) + // TODO + + return nil, nil +} + +// GetTransactionByHash returns the transaction identified by hash. +func (api *PublicEthereumAPI) GetTransactionByHash(hash common.Hash) (*types2.Transaction, error) { + api.logger.Debug("eth_getTransactionByHash", "hash", hash) + // TODO + + return nil, nil +} + +// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index. +func (api *PublicEthereumAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*types2.Transaction, error) { + api.logger.Debug("eth_getTransactionByHashAndIndex", "hash", hash, "index", idx) + // TODO + + return nil, nil +} + +// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index. +func (api *PublicEthereumAPI) GetTransactionByBlockNumberAndIndex(blockNum uint64, idx hexutil.Uint) (*types2.Transaction, error) { + api.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx) + // TODO + + return nil, nil +} + +// GetTransactionReceipt returns the transaction receipt identified by hash. +func (api *PublicEthereumAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) { + api.logger.Debug("eth_getTransactionReceipt", "hash", hash) + // TODO + + return nil, nil +} + +// PendingTransactions returns the transactions that are in the transaction pool +// and have a from address that is one of the accounts this node manages. +func (api *PublicEthereumAPI) PendingTransactions() ([]*types2.Transaction, error) { + api.logger.Debug("eth_pendingTransactions") + return nil, nil +} + +// GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil. +func (api *PublicEthereumAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} { + return nil +} + +// GetUncleByBlockNumberAndIndex returns the uncle identified by number and index. Always returns nil. +func (api *PublicEthereumAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexutil.Uint) map[string]interface{} { + return nil +} diff --git a/api/jsonrpc/namespaces/net/api.go b/api/jsonrpc/namespaces/net/api.go new file mode 100644 index 0000000..ba8203e --- /dev/null +++ b/api/jsonrpc/namespaces/net/api.go @@ -0,0 +1,31 @@ +package net + +import ( + "fmt" + + "github.com/meshplus/bitxhub/api/jsonrpc/types" + "github.com/meshplus/bitxhub/internal/repo" +) + +// PublicNetAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. +type PublicNetAPI struct { + networkVersion uint64 +} + +// NewAPI creates an instance of the public Net Web3 API. +func NewAPI(config *repo.Config) *PublicNetAPI { + // parse the chainID from a integer string + chainIDEpoch, err := types.ParseChainID(config.ChainID) + if err != nil { + panic(err) + } + + return &PublicNetAPI{ + networkVersion: chainIDEpoch.Uint64(), + } +} + +// Version returns the current ethereum protocol version. +func (api *PublicNetAPI) Version() string { + return fmt.Sprintf("%d", api.networkVersion) +} diff --git a/api/jsonrpc/namespaces/web3/api.go b/api/jsonrpc/namespaces/web3/api.go new file mode 100644 index 0000000..e14aafb --- /dev/null +++ b/api/jsonrpc/namespaces/web3/api.go @@ -0,0 +1,27 @@ +package web3 + +import ( + "fmt" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/crypto" + "github.com/meshplus/bitxhub" +) + +// PublicWeb3API is the web3_ prefixed set of APIs in the Web3 JSON-RPC spec. +type PublicWeb3API struct{} + +// NewAPI creates an instance of the Web3 API. +func NewAPI() *PublicWeb3API { + return &PublicWeb3API{} +} + +// ClientVersion returns the client version in the Web3 user agent format. +func (PublicWeb3API) ClientVersion() string { + return fmt.Sprintf("%s-%s-%s", bitxhub.CurrentVersion, bitxhub.CurrentBranch, bitxhub.CurrentCommit) +} + +// Sha3 returns the keccak-256 hash of the passed-in input. +func (PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes { + return crypto.Keccak256(input) +} diff --git a/api/jsonrpc/types/addrlock.go b/api/jsonrpc/types/addrlock.go new file mode 100644 index 0000000..e512c03 --- /dev/null +++ b/api/jsonrpc/types/addrlock.go @@ -0,0 +1,38 @@ +package types + +import ( + "sync" + + "github.com/ethereum/go-ethereum/common" +) + +// AddrLocker is a mutex structure used to avoid querying outdated account data +type AddrLocker struct { + mu sync.Mutex + locks map[common.Address]*sync.Mutex +} + +// lock returns the lock of the given address. +func (l *AddrLocker) lock(address common.Address) *sync.Mutex { + l.mu.Lock() + defer l.mu.Unlock() + if l.locks == nil { + l.locks = make(map[common.Address]*sync.Mutex) + } + if _, ok := l.locks[address]; !ok { + l.locks[address] = new(sync.Mutex) + } + return l.locks[address] +} + +// LockAddr locks an account's mutex. This is used to prevent another tx getting the +// same nonce until the lock is released. The mutex prevents the (an identical nonce) from +// being read again during the time that the first transaction is being signed. +func (l *AddrLocker) LockAddr(address common.Address) { + l.lock(address).Lock() +} + +// UnlockAddr unlocks the mutex of the given account. +func (l *AddrLocker) UnlockAddr(address common.Address) { + l.lock(address).Unlock() +} diff --git a/api/jsonrpc/types/block.go b/api/jsonrpc/types/block.go new file mode 100644 index 0000000..a6168b4 --- /dev/null +++ b/api/jsonrpc/types/block.go @@ -0,0 +1,81 @@ +package types + +import ( + "fmt" + "math" + "math/big" + "strings" + + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// BlockNumber represents decoding hex string to block values +type BlockNumber int64 + +const ( + // LatestBlockNumber mapping from "latest" to 0 for tm query + LatestBlockNumber = BlockNumber(0) + + // EarliestBlockNumber mapping from "earliest" to 1 for tm query (earliest query not supported) + EarliestBlockNumber = BlockNumber(1) + + // PendingBlockNumber mapping from "pending" to -1 for tm query + PendingBlockNumber = BlockNumber(-1) +) + +// NewBlockNumber creates a new BlockNumber instance. +func NewBlockNumber(n *big.Int) BlockNumber { + return BlockNumber(n.Int64()) +} + +// UnmarshalJSON parses the given JSON fragment into a BlockNumber. It supports: +// - "latest", "earliest" or "pending" as string arguments +// - the block number +// Returned errors: +// - an invalid block number error when the given argument isn't a known strings +// - an out of range error when the given block number is either too little or too large +func (bn *BlockNumber) UnmarshalJSON(data []byte) error { + input := strings.TrimSpace(string(data)) + if len(input) >= 2 && input[0] == '"' && input[len(input)-1] == '"' { + input = input[1 : len(input)-1] + } + + switch input { + case "earliest": + *bn = EarliestBlockNumber + return nil + case "latest": + *bn = LatestBlockNumber + return nil + case "pending": + *bn = PendingBlockNumber + return nil + } + + blckNum, err := hexutil.DecodeUint64(input) + if err != nil { + return err + } + if blckNum > math.MaxInt64 { + return fmt.Errorf("blocknumber too high") + } + + *bn = BlockNumber(blckNum) + return nil +} + +// Int64 converts block number to primitive type +func (bn BlockNumber) Int64() int64 { + return int64(bn) +} + +// TmHeight is a util function used for the Tendermint RPC client. It returns +// nil if the block number is "latest". Otherwise, it returns the pointer of the +// int64 value of the height. +func (bn BlockNumber) TmHeight() *int64 { + if bn == LatestBlockNumber { + return nil + } + height := bn.Int64() + return &height +} diff --git a/api/jsonrpc/types/chain_id.go b/api/jsonrpc/types/chain_id.go new file mode 100644 index 0000000..0158eb0 --- /dev/null +++ b/api/jsonrpc/types/chain_id.go @@ -0,0 +1,52 @@ +package types + +import ( + "fmt" + "math/big" + "math/rand" + "regexp" + "strings" +) + +var ( + regexChainID = `[a-z]*` + regexSeparator = `-{1}` + regexEpoch = `[1-9][0-9]*` + ethermintChainID = regexp.MustCompile(fmt.Sprintf(`^(%s)%s(%s)$`, regexChainID, regexSeparator, regexEpoch)) +) + +// IsValidChainID returns false if the given chain identifier is incorrectly formatted. +func IsValidChainID(chainID string) bool { + if len(chainID) > 48 { + return false + } + + return ethermintChainID.MatchString(chainID) +} + +// ParseChainID parses a string chain identifier's epoch to an Ethereum-compatible +// chain-id in *big.Int format. The function returns an error if the chain-id has an invalid format +func ParseChainID(chainID string) (*big.Int, error) { + chainID = strings.TrimSpace(chainID) + if len(chainID) > 48 { + return nil, fmt.Errorf("chain-id '%s' cannot exceed 48 chars", chainID) + } + + matches := ethermintChainID.FindStringSubmatch(chainID) + if matches == nil || len(matches) != 3 || matches[1] == "" { + return nil, fmt.Errorf("invalid chain-id: %s", chainID) + } + + // verify that the chain-id entered is a base 10 integer + chainIDInt, ok := new(big.Int).SetString(matches[2], 10) + if !ok { + return nil, fmt.Errorf("invalid chain-id, epoch %s must be base-10 integer format", matches[2]) + } + + return chainIDInt, nil +} + +// GenerateRandomChainID returns a random chain-id in the valid format. +func GenerateRandomChainID() string { + return fmt.Sprintf("ethermint-%d", 10+rand.Intn(10000)) +} diff --git a/api/jsonrpc/types/types.go b/api/jsonrpc/types/types.go new file mode 100644 index 0000000..9553ddf --- /dev/null +++ b/api/jsonrpc/types/types.go @@ -0,0 +1,93 @@ +package types + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" +) + +// Copied the Account and StorageResult types since they are registered under an +// internal pkg on geth. + +// Constants to match up protocol versions and messages +const ( + eth65 = 65 + + // ProtocolVersion is the latest supported version of the eth protocol. + ProtocolVersion = eth65 +) + +// AccountResult struct for account proof +type AccountResult struct { + Address common.Address `json:"address"` + AccountProof []string `json:"accountProof"` + Balance *hexutil.Big `json:"balance"` + CodeHash common.Hash `json:"codeHash"` + Nonce hexutil.Uint64 `json:"nonce"` + StorageHash common.Hash `json:"storageHash"` + StorageProof []StorageResult `json:"storageProof"` +} + +// StorageResult defines the format for storage proof return +type StorageResult struct { + Key string `json:"key"` + Value *hexutil.Big `json:"value"` + Proof []string `json:"proof"` +} + +// Transaction represents a transaction returned to RPC clients. +type Transaction struct { + BlockHash *common.Hash `json:"blockHash"` + BlockNumber *hexutil.Big `json:"blockNumber"` + From common.Address `json:"from"` + Gas hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Hash common.Hash `json:"hash"` + Input hexutil.Bytes `json:"input"` + Nonce hexutil.Uint64 `json:"nonce"` + To *common.Address `json:"to"` + TransactionIndex *hexutil.Uint64 `json:"transactionIndex"` + Value *hexutil.Big `json:"value"` + V *hexutil.Big `json:"v"` + R *hexutil.Big `json:"r"` + S *hexutil.Big `json:"s"` +} + +// SendTxArgs represents the arguments to submit a new transaction into the transaction pool. +// Duplicate struct definition since geth struct is in internal package +// Ref: https://github.com/ethereum/go-ethereum/blob/release/1.9/internal/ethapi/api.go#L1346 +type SendTxArgs struct { + From common.Address `json:"from"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Value *hexutil.Big `json:"value"` + Nonce *hexutil.Uint64 `json:"nonce"` + // We accept "data" and "input" for backwards-compatibility reasons. "input" is the + // newer name and should be preferred by clients. + Data *hexutil.Bytes `json:"data"` + Input *hexutil.Bytes `json:"input"` +} + +// CallArgs represents the arguments for a call. +type CallArgs struct { + From *common.Address `json:"from"` + To *common.Address `json:"to"` + Gas *hexutil.Uint64 `json:"gas"` + GasPrice *hexutil.Big `json:"gasPrice"` + Value *hexutil.Big `json:"value"` + Data *hexutil.Bytes `json:"data"` +} + +// Account indicates the overriding fields of account during the execution of +// a message call. +// NOTE: state and stateDiff can't be specified at the same time. If state is +// set, message execution will only use the data in the given state. Otherwise +// if statDiff is set, all diff will be applied first and then execute the call +// message. +type Account struct { + Nonce *hexutil.Uint64 `json:"nonce"` + Code *hexutil.Bytes `json:"code"` + Balance **hexutil.Big `json:"balance"` + State *map[common.Hash]common.Hash `json:"state"` + StateDiff *map[common.Hash]common.Hash `json:"stateDiff"` +} diff --git a/cmd/bitxhub/start.go b/cmd/bitxhub/start.go index 9a8d4ea..8bb67b3 100755 --- a/cmd/bitxhub/start.go +++ b/cmd/bitxhub/start.go @@ -16,11 +16,11 @@ import ( "github.com/meshplus/bitxhub-kit/log" "github.com/meshplus/bitxhub/api/gateway" "github.com/meshplus/bitxhub/api/grpc" + "github.com/meshplus/bitxhub/api/jsonrpc" "github.com/meshplus/bitxhub/internal/app" "github.com/meshplus/bitxhub/internal/coreapi" "github.com/meshplus/bitxhub/internal/loggers" "github.com/meshplus/bitxhub/internal/repo" - "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/urfave/cli" ) @@ -96,6 +96,16 @@ func start(ctx *cli.Context) error { return err } + // start json-rpc service + cbs, err := jsonrpc.NewChainBrokerService(api, repo.Config) + if err != nil { + return err + } + + if err := cbs.Start(); err != nil { + return err + } + go func() { logger.WithField("port", repo.Config.Port.Gateway).Info("Gateway service started") err := gateway.Start(repo.Config) diff --git a/config/bitxhub.toml b/config/bitxhub.toml index b2525ba..4542313 100755 --- a/config/bitxhub.toml +++ b/config/bitxhub.toml @@ -3,6 +3,7 @@ title = "BitXHub configuration file" solo = false [port] + jsonrpc = 8881 grpc = 60011 gateway = 9091 pprof = 53121 @@ -44,7 +45,7 @@ solo = false consensus = "info" executor = "info" router = "info" - api = "info" + api = "debug" coreapi = "info" storage = "info" @@ -61,6 +62,7 @@ solo = false type = "serial" # opensource version only supports serial type, commercial version supports serial and parallel types [genesis] + chainid = "a-1" dider = "0xc7F999b83Af6DF9e67d0a37Ee7e900bF38b3D013" [[genesis.admins]] address = "0xc7F999b83Af6DF9e67d0a37Ee7e900bF38b3D013" diff --git a/go.mod b/go.mod index c5ae8a1..5fc47f1 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/gogo/protobuf v1.3.2 github.com/golang/mock v1.5.0 github.com/google/btree v1.0.0 + github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/go-grpc-middleware v1.2.2 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 diff --git a/internal/repo/config.go b/internal/repo/config.go index 291e6de..a129700 100755 --- a/internal/repo/config.go +++ b/internal/repo/config.go @@ -53,6 +53,7 @@ type Security struct { } type Port struct { + JsonRpc int64 `toml:"jsonrpc" json:"jsonrpc"` Grpc int64 `toml:"grpc" json:"grpc"` Gateway int64 `toml:"gateway" json:"gateway"` PProf int64 `toml:"pprof" json:"pprof"` @@ -104,6 +105,7 @@ type LogModule struct { } type Genesis struct { + ChainID string `json:"chainid" toml:"chainid"` Admins []*Admin `json:"admins" toml:"admins"` Strategy map[string]string `json:"strategy" toml:"strategy"` Dider string `json:"dider" toml:"dider"` diff --git a/scripts/cluster.sh b/scripts/cluster.sh index da1c6bd..31fbcfb 100755 --- a/scripts/cluster.sh +++ b/scripts/cluster.sh @@ -49,6 +49,7 @@ function prepare() { x_replace "s/9091/909${i}/g" "${bitxhubConfig}" x_replace "s/53121/5312${i}/g" "${bitxhubConfig}" x_replace "s/40011/4001${i}/g" "${bitxhubConfig}" + x_replace "s/8881/888${i}/g" "${bitxhubConfig}" x_replace "1s/1/${i}/" "${networkConfig}" done