mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-23 05:06:43 +00:00
internal/restapi: implement trustless REST API
This commit is contained in:
parent
1487a8577d
commit
55265be62b
22 changed files with 598 additions and 111 deletions
25
beacon/blsync/server.go
Normal file
25
beacon/blsync/server.go
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package blsync
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (c *Client) NewAPIServer() func(mux *http.ServeMux, maxResponseSize int) {
|
||||||
|
return func(mux *http.ServeMux, maxResponseSize int) {}
|
||||||
|
}
|
||||||
|
|
@ -737,7 +737,7 @@ func signer(c *cli.Context) error {
|
||||||
|
|
||||||
srv := rpc.NewServer()
|
srv := rpc.NewServer()
|
||||||
srv.SetBatchLimits(node.DefaultConfig.BatchRequestLimit, node.DefaultConfig.BatchResponseMaxSize)
|
srv.SetBatchLimits(node.DefaultConfig.BatchRequestLimit, node.DefaultConfig.BatchResponseMaxSize)
|
||||||
err := node.RegisterApis(rpcAPI, []string{"account"}, srv)
|
err := node.RegisterRpcAPIs(rpcAPI, []string{"account"}, srv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.Fatalf("Could not register API: %w", err)
|
utils.Fatalf("Could not register API: %w", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -300,6 +300,9 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
||||||
srv.RegisterName("engine", catalyst.NewConsensusAPI(eth))
|
srv.RegisterName("engine", catalyst.NewConsensusAPI(eth))
|
||||||
blsyncer := blsync.NewClient(utils.MakeBeaconLightConfig(ctx))
|
blsyncer := blsync.NewClient(utils.MakeBeaconLightConfig(ctx))
|
||||||
blsyncer.SetEngineRPC(rpc.DialInProc(srv))
|
blsyncer.SetEngineRPC(rpc.DialInProc(srv))
|
||||||
|
if eth != nil {
|
||||||
|
eth.Blsync = blsyncer
|
||||||
|
}
|
||||||
stack.RegisterLifecycle(blsyncer)
|
stack.RegisterLifecycle(blsyncer)
|
||||||
} else {
|
} else {
|
||||||
// Launch the engine API for interacting with external consensus client.
|
// Launch the engine API for interacting with external consensus client.
|
||||||
|
|
|
||||||
|
|
@ -2017,7 +2017,7 @@ func RegisterEthService(stack *node.Node, cfg *ethconfig.Config) (*eth.EthAPIBac
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Fatalf("Failed to register the Ethereum service: %v", err)
|
Fatalf("Failed to register the Ethereum service: %v", err)
|
||||||
}
|
}
|
||||||
stack.RegisterAPIs(tracers.APIs(backend.APIBackend))
|
stack.RegisterRpcAPIs(tracers.APIs(backend.APIBackend))
|
||||||
return backend.APIBackend, backend
|
return backend.APIBackend, backend
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2042,7 +2042,7 @@ func RegisterFilterAPI(stack *node.Node, backend ethapi.Backend, ethcfg *ethconf
|
||||||
LogCacheSize: ethcfg.FilterLogCacheSize,
|
LogCacheSize: ethcfg.FilterLogCacheSize,
|
||||||
LogQueryLimit: ethcfg.LogQueryLimit,
|
LogQueryLimit: ethcfg.LogQueryLimit,
|
||||||
})
|
})
|
||||||
stack.RegisterAPIs([]rpc.API{{
|
stack.RegisterRpcAPIs([]rpc.API{{
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
Service: filters.NewFilterAPI(filterSystem),
|
Service: filters.NewFilterAPI(filterSystem),
|
||||||
}})
|
}})
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum"
|
"github.com/ethereum/go-ethereum"
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/blsync"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
|
||||||
|
|
@ -431,6 +432,10 @@ func (b *EthAPIBackend) BlobBaseFee(ctx context.Context) *big.Int {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *EthAPIBackend) Blsync() *blsync.Client {
|
||||||
|
return b.eth.Blsync
|
||||||
|
}
|
||||||
|
|
||||||
func (b *EthAPIBackend) ChainDb() ethdb.Database {
|
func (b *EthAPIBackend) ChainDb() ethdb.Database {
|
||||||
return b.eth.ChainDb()
|
return b.eth.ChainDb()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts"
|
"github.com/ethereum/go-ethereum/accounts"
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/blsync"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
"github.com/ethereum/go-ethereum/consensus"
|
"github.com/ethereum/go-ethereum/consensus"
|
||||||
|
|
@ -50,6 +51,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/ethdb"
|
"github.com/ethereum/go-ethereum/ethdb"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/internal/ethapi"
|
"github.com/ethereum/go-ethereum/internal/ethapi"
|
||||||
|
"github.com/ethereum/go-ethereum/internal/restapi"
|
||||||
"github.com/ethereum/go-ethereum/internal/shutdowncheck"
|
"github.com/ethereum/go-ethereum/internal/shutdowncheck"
|
||||||
"github.com/ethereum/go-ethereum/internal/version"
|
"github.com/ethereum/go-ethereum/internal/version"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
|
@ -59,6 +61,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/p2p/dnsdisc"
|
"github.com/ethereum/go-ethereum/p2p/dnsdisc"
|
||||||
"github.com/ethereum/go-ethereum/p2p/enode"
|
"github.com/ethereum/go-ethereum/p2p/enode"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rest"
|
||||||
"github.com/ethereum/go-ethereum/rlp"
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
gethversion "github.com/ethereum/go-ethereum/version"
|
gethversion "github.com/ethereum/go-ethereum/version"
|
||||||
|
|
@ -113,6 +116,8 @@ type Ethereum struct {
|
||||||
|
|
||||||
APIBackend *EthAPIBackend
|
APIBackend *EthAPIBackend
|
||||||
|
|
||||||
|
Blsync *blsync.Client
|
||||||
|
|
||||||
miner *miner.Miner
|
miner *miner.Miner
|
||||||
gasPrice *big.Int
|
gasPrice *big.Int
|
||||||
|
|
||||||
|
|
@ -346,7 +351,11 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
eth.miner.SetExtra(makeExtraData(config.Miner.ExtraData))
|
||||||
eth.miner.SetPrioAddresses(config.TxPool.Locals)
|
eth.miner.SetPrioAddresses(config.TxPool.Locals)
|
||||||
|
|
||||||
eth.APIBackend = &EthAPIBackend{stack.Config().ExtRPCEnabled(), stack.Config().AllowUnprotectedTxs, eth, nil}
|
eth.APIBackend = &EthAPIBackend{
|
||||||
|
extRPCEnabled: stack.Config().ExtRPCEnabled(),
|
||||||
|
allowUnprotectedTxs: stack.Config().AllowUnprotectedTxs,
|
||||||
|
eth: eth,
|
||||||
|
}
|
||||||
if eth.APIBackend.allowUnprotectedTxs {
|
if eth.APIBackend.allowUnprotectedTxs {
|
||||||
log.Info("Unprotected transactions allowed")
|
log.Info("Unprotected transactions allowed")
|
||||||
}
|
}
|
||||||
|
|
@ -356,7 +365,8 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
||||||
eth.netRPCService = ethapi.NewNetAPI(eth.p2pServer, networkID)
|
eth.netRPCService = ethapi.NewNetAPI(eth.p2pServer, networkID)
|
||||||
|
|
||||||
// Register the backend on the node
|
// Register the backend on the node
|
||||||
stack.RegisterAPIs(eth.APIs())
|
stack.RegisterRpcAPIs(eth.RpcAPIs())
|
||||||
|
stack.RegisterRestAPIs(eth.RestAPIs())
|
||||||
stack.RegisterProtocols(eth.Protocols())
|
stack.RegisterProtocols(eth.Protocols())
|
||||||
stack.RegisterLifecycle(eth)
|
stack.RegisterLifecycle(eth)
|
||||||
|
|
||||||
|
|
@ -383,9 +393,9 @@ func makeExtraData(extra []byte) []byte {
|
||||||
return extra
|
return extra
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIs return the collection of RPC services the ethereum package offers.
|
// RpcAPIs return the collection of RPC services the ethereum package offers.
|
||||||
// NOTE, some of these services probably need to be moved to somewhere else.
|
// NOTE, some of these services probably need to be moved to somewhere else.
|
||||||
func (s *Ethereum) APIs() []rpc.API {
|
func (s *Ethereum) RpcAPIs() []rpc.API {
|
||||||
apis := ethapi.GetAPIs(s.APIBackend)
|
apis := ethapi.GetAPIs(s.APIBackend)
|
||||||
|
|
||||||
// Append all the local APIs and return
|
// Append all the local APIs and return
|
||||||
|
|
@ -409,6 +419,11 @@ func (s *Ethereum) APIs() []rpc.API {
|
||||||
}...)
|
}...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RestAPIs return the collection of REST API services the ethereum package offers.
|
||||||
|
func (s *Ethereum) RestAPIs() []rest.API {
|
||||||
|
return restapi.GetAPIs(s.APIBackend)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
|
func (s *Ethereum) ResetWithGenesisBlock(gb *types.Block) {
|
||||||
s.blockchain.ResetWithGenesisBlock(gb)
|
s.blockchain.ResetWithGenesisBlock(gb)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ import (
|
||||||
// Register adds the engine API to the full node.
|
// Register adds the engine API to the full node.
|
||||||
func Register(stack *node.Node, backend *eth.Ethereum) error {
|
func Register(stack *node.Node, backend *eth.Ethereum) error {
|
||||||
log.Warn("Engine API enabled", "protocol", "eth")
|
log.Warn("Engine API enabled", "protocol", "eth")
|
||||||
stack.RegisterAPIs([]rpc.API{
|
stack.RegisterRpcAPIs([]rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: "engine",
|
Namespace: "engine",
|
||||||
Service: NewConsensusAPI(backend),
|
Service: NewConsensusAPI(backend),
|
||||||
|
|
|
||||||
|
|
@ -359,7 +359,7 @@ func (c *SimulatedBeacon) AdjustTime(adjustment time.Duration) error {
|
||||||
// stack.
|
// stack.
|
||||||
func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) {
|
func RegisterSimulatedBeaconAPIs(stack *node.Node, sim *SimulatedBeacon) {
|
||||||
api := newSimulatedBeaconAPI(sim)
|
api := newSimulatedBeaconAPI(sim)
|
||||||
stack.RegisterAPIs([]rpc.API{
|
stack.RegisterRpcAPIs([]rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: "dev",
|
Namespace: "dev",
|
||||||
Service: api,
|
Service: api,
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ func Register(stack *node.Node, backend *eth.Ethereum, target common.Hash, exitW
|
||||||
closed: make(chan struct{}),
|
closed: make(chan struct{}),
|
||||||
exitWhenSynced: exitWhenSynced,
|
exitWhenSynced: exitWhenSynced,
|
||||||
}
|
}
|
||||||
stack.RegisterAPIs(s.APIs())
|
stack.RegisterRpcAPIs(s.APIs())
|
||||||
stack.RegisterLifecycle(s)
|
stack.RegisterLifecycle(s)
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,10 +66,10 @@ func newTestBackend(t *testing.T) (*node.Node, []*types.Block, []common.Hash) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("can't create new ethereum service: %v", err)
|
t.Fatalf("can't create new ethereum service: %v", err)
|
||||||
}
|
}
|
||||||
n.RegisterAPIs(tracers.APIs(ethservice.APIBackend))
|
n.RegisterRpcAPIs(tracers.APIs(ethservice.APIBackend))
|
||||||
|
|
||||||
filterSystem := filters.NewFilterSystem(ethservice.APIBackend, filters.Config{})
|
filterSystem := filters.NewFilterSystem(ethservice.APIBackend, filters.Config{})
|
||||||
n.RegisterAPIs([]rpc.API{{
|
n.RegisterRpcAPIs([]rpc.API{{
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
Service: filters.NewFilterAPI(filterSystem),
|
Service: filters.NewFilterAPI(filterSystem),
|
||||||
}})
|
}})
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,7 @@ func newWithNode(stack *node.Node, conf *eth.Config, blockPeriod uint64) (*Backe
|
||||||
}
|
}
|
||||||
// Register the filter system
|
// Register the filter system
|
||||||
filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{})
|
filterSystem := filters.NewFilterSystem(backend.APIBackend, filters.Config{})
|
||||||
stack.RegisterAPIs([]rpc.API{{
|
stack.RegisterRpcAPIs([]rpc.API{{
|
||||||
Namespace: "eth",
|
Namespace: "eth",
|
||||||
Service: filters.NewFilterAPI(filterSystem),
|
Service: filters.NewFilterAPI(filterSystem),
|
||||||
}})
|
}})
|
||||||
|
|
|
||||||
51
internal/restapi/backend.go
Normal file
51
internal/restapi/backend.go
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package restapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/beacon/blsync"
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/rest"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Backend interface {
|
||||||
|
HeaderByNumber(ctx context.Context, number rpc.BlockNumber) (*types.Header, error)
|
||||||
|
HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error)
|
||||||
|
ChainConfig() *params.ChainConfig
|
||||||
|
Blsync() *blsync.Client
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetAPIs(apiBackend Backend) []rest.API {
|
||||||
|
apis := []rest.API{
|
||||||
|
{
|
||||||
|
Namespace: "exec",
|
||||||
|
Register: NewExecutionRestAPI(apiBackend),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if apiBackend.Blsync() != nil {
|
||||||
|
apis = append(apis, rest.API{
|
||||||
|
Namespace: "beacon",
|
||||||
|
Register: apiBackend.Blsync().NewAPIServer(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return apis
|
||||||
|
}
|
||||||
230
internal/restapi/exec_api.go
Normal file
230
internal/restapi/exec_api.go
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package restapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
|
"github.com/ethereum/go-ethereum/params/forks"
|
||||||
|
"github.com/ethereum/go-ethereum/rlp"
|
||||||
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
urlHeaders = "/eth/v1/exec/headers/"
|
||||||
|
urlBlocks = "/eth/v1/exec/blocks"
|
||||||
|
urlBlockReceipts = "/eth/v1/exec/block_receipts"
|
||||||
|
urlTransaction = "/eth/v1/exec/transaction"
|
||||||
|
urlTxByIndex = "/eth/v1/exec/transaction_by_index"
|
||||||
|
urlReceiptByIndex = "/eth/v1/exec/receipt_by_index"
|
||||||
|
urlState = "/eth/v1/exec/state"
|
||||||
|
urlCall = "/eth/v1/exec/call"
|
||||||
|
// Requires EIP-7745
|
||||||
|
urlHistory = "/eth/v1/exec/history"
|
||||||
|
urlTxPosition = "/eth/v1/exec/transaction_position"
|
||||||
|
urlLogs = "/eth/v1/exec/logs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type execApiServer struct {
|
||||||
|
apiBackend Backend
|
||||||
|
maxAmount, maxResponseSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewExecutionRestAPI(apiBackend Backend) func(mux *http.ServeMux, maxResponseSize int) {
|
||||||
|
return func(mux *http.ServeMux, maxResponseSize int) {
|
||||||
|
s := &execApiServer{
|
||||||
|
apiBackend: apiBackend,
|
||||||
|
maxResponseSize: maxResponseSize,
|
||||||
|
}
|
||||||
|
mux.HandleFunc(urlHeaders, s.handleHeaders)
|
||||||
|
mux.HandleFunc(urlBlocks, s.handleBlocks)
|
||||||
|
mux.HandleFunc(urlBlockReceipts, s.handleBlockReceipts)
|
||||||
|
mux.HandleFunc(urlTransaction, s.handleTransaction)
|
||||||
|
mux.HandleFunc(urlTxByIndex, s.handleTxByIndex)
|
||||||
|
mux.HandleFunc(urlReceiptByIndex, s.handleReceiptByIndex)
|
||||||
|
mux.HandleFunc(urlState, s.handleState)
|
||||||
|
mux.HandleFunc(urlCall, s.handleCall)
|
||||||
|
// Requires EIP-7745
|
||||||
|
mux.HandleFunc(urlHistory, s.handleHistory)
|
||||||
|
mux.HandleFunc(urlTxPosition, s.handleTxPosition)
|
||||||
|
mux.HandleFunc(urlLogs, s.handleLogs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type blockId struct {
|
||||||
|
hash common.Hash
|
||||||
|
number uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *blockId) isHash() bool {
|
||||||
|
return b.hash != (common.Hash{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeBlockId(id string) (blockId, bool) {
|
||||||
|
if hex, err := hexutil.Decode(id); err == nil {
|
||||||
|
if len(hex) != common.HashLength {
|
||||||
|
return blockId{}, false
|
||||||
|
}
|
||||||
|
var b blockId
|
||||||
|
copy(b.hash[:], hex)
|
||||||
|
return b, true
|
||||||
|
}
|
||||||
|
if number, err := strconv.ParseUint(id, 10, 64); err == nil {
|
||||||
|
return blockId{number: number}, true
|
||||||
|
}
|
||||||
|
return blockId{}, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// forkId returns the fork corresponding to the given header.
|
||||||
|
// Note that frontier thawing and difficulty bomb adjustments are ignored according
|
||||||
|
// to the API specification as they do not affect the interpretation of the
|
||||||
|
// returned data structures.
|
||||||
|
func (s *execApiServer) forkId(header *types.Header) forks.Fork {
|
||||||
|
c := s.apiBackend.ChainConfig()
|
||||||
|
switch {
|
||||||
|
case header.Difficulty.Sign() == 0:
|
||||||
|
return c.LatestFork(header.Time)
|
||||||
|
case c.IsLondon(header.Number):
|
||||||
|
return forks.London
|
||||||
|
case c.IsBerlin(header.Number):
|
||||||
|
return forks.Berlin
|
||||||
|
case c.IsIstanbul(header.Number):
|
||||||
|
return forks.Istanbul
|
||||||
|
case c.IsPetersburg(header.Number):
|
||||||
|
return forks.Petersburg
|
||||||
|
case c.IsConstantinople(header.Number):
|
||||||
|
return forks.Constantinople
|
||||||
|
case c.IsByzantium(header.Number):
|
||||||
|
return forks.Byzantium
|
||||||
|
case c.IsEIP155(header.Number):
|
||||||
|
return forks.SpuriousDragon
|
||||||
|
case c.IsEIP150(header.Number):
|
||||||
|
return forks.TangerineWhistle
|
||||||
|
case c.IsDAOFork(header.Number):
|
||||||
|
return forks.DAO
|
||||||
|
case c.IsHomestead(header.Number):
|
||||||
|
return forks.Homestead
|
||||||
|
default:
|
||||||
|
return forks.Frontier
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *execApiServer) forkName(header *types.Header) string {
|
||||||
|
return strings.ToLower(s.forkId(header).String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *execApiServer) handleHeaders(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
type headerResponse struct {
|
||||||
|
Version string `json:"version"`
|
||||||
|
Data *types.Header `json:"data"`
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
amount int
|
||||||
|
response []headerResponse
|
||||||
|
binary bool
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
|
||||||
|
if mt, _, err := mime.ParseMediaType(req.Header.Get("accept")); err == nil {
|
||||||
|
switch mt {
|
||||||
|
case "application/json":
|
||||||
|
case "application/octet-stream":
|
||||||
|
binary = true
|
||||||
|
default:
|
||||||
|
http.Error(resp, "invalid accepted media type", http.StatusNotAcceptable)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
id, ok := decodeBlockId(req.URL.Path[len(urlHeaders):])
|
||||||
|
if !ok {
|
||||||
|
http.Error(resp, "invalid block id", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if s := req.URL.Query().Get("amount"); s != "" {
|
||||||
|
amount, err = strconv.Atoi(s)
|
||||||
|
if err != nil || amount <= 0 {
|
||||||
|
http.Error(resp, "invalid amount", http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
amount = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
response = make([]headerResponse, amount)
|
||||||
|
for i := amount - 1; i >= 0; i-- {
|
||||||
|
if id.isHash() {
|
||||||
|
response[i].Data, err = s.apiBackend.HeaderByHash(req.Context(), id.hash)
|
||||||
|
} else {
|
||||||
|
response[i].Data, err = s.apiBackend.HeaderByNumber(req.Context(), rpc.BlockNumber(id.number))
|
||||||
|
}
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
http.Error(resp, "request timeout", http.StatusRequestTimeout)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if response[i].Data == nil {
|
||||||
|
http.Error(resp, "not available", http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
response[i].Version = s.forkName(response[i].Data)
|
||||||
|
if response[i].Data.Number.Uint64() == 0 {
|
||||||
|
response = response[i:]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
id = blockId{hash: response[i].Data.ParentHash}
|
||||||
|
}
|
||||||
|
|
||||||
|
if binary {
|
||||||
|
respRlp, err := rlp.EncodeToBytes(response)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(resp, "response encoding error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Header().Set("content-type", "application/octet-stream")
|
||||||
|
resp.Write(respRlp)
|
||||||
|
} else {
|
||||||
|
respJson, err := json.Marshal(response)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(resp, "response encoding error", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resp.Header().Set("content-type", "application/json")
|
||||||
|
resp.Write(respJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *execApiServer) handleBlocks(resp http.ResponseWriter, req *http.Request) { panic("TODO") }
|
||||||
|
func (s *execApiServer) handleBlockReceipts(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
func (s *execApiServer) handleTransaction(resp http.ResponseWriter, req *http.Request) { panic("TODO") }
|
||||||
|
func (s *execApiServer) handleTxByIndex(resp http.ResponseWriter, req *http.Request) { panic("TODO") }
|
||||||
|
func (s *execApiServer) handleReceiptByIndex(resp http.ResponseWriter, req *http.Request) {
|
||||||
|
panic("TODO")
|
||||||
|
}
|
||||||
|
func (s *execApiServer) handleState(resp http.ResponseWriter, req *http.Request) { panic("TODO") }
|
||||||
|
func (s *execApiServer) handleCall(resp http.ResponseWriter, req *http.Request) { panic("TODO") }
|
||||||
|
func (s *execApiServer) handleHistory(resp http.ResponseWriter, req *http.Request) { panic("TODO") } // Requires EIP-7745
|
||||||
|
func (s *execApiServer) handleTxPosition(resp http.ResponseWriter, req *http.Request) { panic("TODO") } // Requires EIP-7745
|
||||||
|
func (s *execApiServer) handleLogs(resp http.ResponseWriter, req *http.Request) { panic("TODO") } // Requires EIP-7745
|
||||||
|
|
@ -178,7 +178,7 @@ func (api *adminAPI) StartHTTP(host *string, port *int, cors *string, apis *stri
|
||||||
CorsAllowedOrigins: api.node.config.HTTPCors,
|
CorsAllowedOrigins: api.node.config.HTTPCors,
|
||||||
Vhosts: api.node.config.HTTPVirtualHosts,
|
Vhosts: api.node.config.HTTPVirtualHosts,
|
||||||
Modules: api.node.config.HTTPModules,
|
Modules: api.node.config.HTTPModules,
|
||||||
rpcEndpointConfig: rpcEndpointConfig{
|
apiEndpointConfig: apiEndpointConfig{
|
||||||
batchItemLimit: api.node.config.BatchRequestLimit,
|
batchItemLimit: api.node.config.BatchRequestLimit,
|
||||||
batchResponseSizeLimit: api.node.config.BatchResponseMaxSize,
|
batchResponseSizeLimit: api.node.config.BatchResponseMaxSize,
|
||||||
},
|
},
|
||||||
|
|
@ -205,7 +205,7 @@ func (api *adminAPI) StartHTTP(host *string, port *int, cors *string, apis *stri
|
||||||
if err := api.node.http.setListenAddr(*host, *port); err != nil {
|
if err := api.node.http.setListenAddr(*host, *port); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if err := api.node.http.enableRPC(api.node.rpcAPIs, config); err != nil {
|
if err := api.node.http.enableHTTP(api.node.rpcAPIs, api.node.restAPIs, config); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if err := api.node.http.start(); err != nil {
|
if err := api.node.http.start(); err != nil {
|
||||||
|
|
@ -256,7 +256,7 @@ func (api *adminAPI) StartWS(host *string, port *int, allowedOrigins *string, ap
|
||||||
Modules: api.node.config.WSModules,
|
Modules: api.node.config.WSModules,
|
||||||
Origins: api.node.config.WSOrigins,
|
Origins: api.node.config.WSOrigins,
|
||||||
// ExposeAll: api.node.config.WSExposeAll,
|
// ExposeAll: api.node.config.WSExposeAll,
|
||||||
rpcEndpointConfig: rpcEndpointConfig{
|
apiEndpointConfig: apiEndpointConfig{
|
||||||
batchItemLimit: api.node.config.BatchRequestLimit,
|
batchItemLimit: api.node.config.BatchRequestLimit,
|
||||||
batchResponseSizeLimit: api.node.config.BatchResponseMaxSize,
|
batchResponseSizeLimit: api.node.config.BatchResponseMaxSize,
|
||||||
},
|
},
|
||||||
|
|
@ -280,7 +280,7 @@ func (api *adminAPI) StartWS(host *string, port *int, allowedOrigins *string, ap
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
openApis, _ := api.node.getAPIs()
|
openApis, _ := api.node.getAPIs()
|
||||||
if err := server.enableWS(openApis, config); err != nil {
|
if err := server.enableWS(openApis, api.node.restAPIs, config); err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
if err := server.start(); err != nil {
|
if err := server.start(); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -32,38 +32,40 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rest"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/rs/cors"
|
"github.com/rs/cors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// httpConfig is the JSON-RPC/HTTP configuration.
|
// httpConfig is the JSON-RPC/HTTP configuration.
|
||||||
type httpConfig struct {
|
type httpConfig struct {
|
||||||
Modules []string
|
Modules []string
|
||||||
CorsAllowedOrigins []string
|
CorsAllowedOrigins []string
|
||||||
Vhosts []string
|
Vhosts []string
|
||||||
prefix string // path prefix on which to mount http handler
|
rpcPrefix, restPrefix string // path prefix on which to mount http handler
|
||||||
rpcEndpointConfig
|
apiEndpointConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
// wsConfig is the JSON-RPC/Websocket configuration
|
// wsConfig is the JSON-RPC/Websocket configuration
|
||||||
type wsConfig struct {
|
type wsConfig struct {
|
||||||
Origins []string
|
Origins []string
|
||||||
Modules []string
|
Modules []string
|
||||||
prefix string // path prefix on which to mount ws handler
|
rpcPrefix, restPrefix string // path prefix on which to mount ws handler
|
||||||
rpcEndpointConfig
|
apiEndpointConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type rpcEndpointConfig struct {
|
type apiEndpointConfig struct {
|
||||||
jwtSecret []byte // optional JWT secret
|
jwtSecret []byte // optional JWT secret
|
||||||
batchItemLimit int
|
batchItemLimit int
|
||||||
batchResponseSizeLimit int
|
batchResponseSizeLimit int
|
||||||
httpBodyLimit int
|
httpBodyLimit int
|
||||||
}
|
}
|
||||||
|
|
||||||
type rpcHandler struct {
|
type apiHandler struct {
|
||||||
http.Handler
|
rpcHandler, restHandler http.Handler
|
||||||
prefix string
|
rpcPrefix, restPrefix string
|
||||||
server *rpc.Server
|
rpcServer *rpc.Server
|
||||||
|
restServer *rest.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
type httpServer struct {
|
type httpServer struct {
|
||||||
|
|
@ -78,11 +80,11 @@ type httpServer struct {
|
||||||
// HTTP RPC handler things.
|
// HTTP RPC handler things.
|
||||||
|
|
||||||
httpConfig httpConfig
|
httpConfig httpConfig
|
||||||
httpHandler atomic.Pointer[rpcHandler]
|
httpHandler atomic.Pointer[apiHandler]
|
||||||
|
|
||||||
// WebSocket handler things.
|
// WebSocket handler things.
|
||||||
wsConfig wsConfig
|
wsConfig wsConfig
|
||||||
wsHandler atomic.Pointer[rpcHandler]
|
wsHandler atomic.Pointer[apiHandler]
|
||||||
|
|
||||||
// These are set by setListenAddr.
|
// These are set by setListenAddr.
|
||||||
endpoint string
|
endpoint string
|
||||||
|
|
@ -151,7 +153,7 @@ func (h *httpServer) start() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// If the server fails to start, we need to clear out the RPC and WS
|
// If the server fails to start, we need to clear out the RPC and WS
|
||||||
// configuration so they can be configured another time.
|
// configuration so they can be configured another time.
|
||||||
h.disableRPC()
|
h.disableHTTP()
|
||||||
h.disableWS()
|
h.disableWS()
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -160,19 +162,17 @@ func (h *httpServer) start() error {
|
||||||
|
|
||||||
if h.wsAllowed() {
|
if h.wsAllowed() {
|
||||||
url := fmt.Sprintf("ws://%v", listener.Addr())
|
url := fmt.Sprintf("ws://%v", listener.Addr())
|
||||||
if h.wsConfig.prefix != "" {
|
h.log.Info("WebSocket enabled", "RPC URL", url+h.wsConfig.rpcPrefix, "REST URL", url+h.wsConfig.restPrefix)
|
||||||
url += h.wsConfig.prefix
|
|
||||||
}
|
|
||||||
h.log.Info("WebSocket enabled", "url", url)
|
|
||||||
}
|
}
|
||||||
// if server is websocket only, return after logging
|
// if server is websocket only, return after logging
|
||||||
if !h.rpcAllowed() {
|
if !h.httpAllowed() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Log http endpoint.
|
// Log http endpoint.
|
||||||
h.log.Info("HTTP server started",
|
h.log.Info("HTTP server started",
|
||||||
"endpoint", listener.Addr(), "auth", h.httpConfig.jwtSecret != nil,
|
"endpoint", listener.Addr(), "auth", h.httpConfig.jwtSecret != nil,
|
||||||
"prefix", h.httpConfig.prefix,
|
"RPC prefix", h.httpConfig.rpcPrefix,
|
||||||
|
"REST prefix", h.httpConfig.restPrefix,
|
||||||
"cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","),
|
"cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","),
|
||||||
"vhosts", strings.Join(h.httpConfig.Vhosts, ","),
|
"vhosts", strings.Join(h.httpConfig.Vhosts, ","),
|
||||||
)
|
)
|
||||||
|
|
@ -198,15 +198,19 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
// check if ws request and serve if ws enabled
|
// check if ws request and serve if ws enabled
|
||||||
ws := h.wsHandler.Load()
|
ws := h.wsHandler.Load()
|
||||||
if ws != nil && isWebsocket(r) {
|
if ws != nil && isWebsocket(r) {
|
||||||
if checkPath(r, ws.prefix) {
|
if checkPath(r, ws.restPrefix) {
|
||||||
ws.ServeHTTP(w, r)
|
ws.restHandler.ServeHTTP(w, r)
|
||||||
|
return //TODO is this in the right place?
|
||||||
|
}
|
||||||
|
if checkPath(r, ws.rpcPrefix) {
|
||||||
|
ws.rpcHandler.ServeHTTP(w, r)
|
||||||
|
return //TODO is this in the right place?
|
||||||
}
|
}
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// if http-rpc is enabled, try to serve request
|
// if http-rpc is enabled, try to serve request
|
||||||
rpc := h.httpHandler.Load()
|
api := h.httpHandler.Load()
|
||||||
if rpc != nil {
|
if api != nil {
|
||||||
// First try to route in the mux.
|
// First try to route in the mux.
|
||||||
// Requests to a path below root are handled by the mux,
|
// Requests to a path below root are handled by the mux,
|
||||||
// which has all the handlers registered via Node.RegisterHandler.
|
// which has all the handlers registered via Node.RegisterHandler.
|
||||||
|
|
@ -217,8 +221,12 @@ func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if checkPath(r, rpc.prefix) {
|
if checkPath(r, api.restPrefix) {
|
||||||
rpc.ServeHTTP(w, r)
|
api.restHandler.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if checkPath(r, api.rpcPrefix) {
|
||||||
|
api.rpcHandler.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -269,11 +277,13 @@ func (h *httpServer) doStop() {
|
||||||
wsHandler := h.wsHandler.Load()
|
wsHandler := h.wsHandler.Load()
|
||||||
if httpHandler != nil {
|
if httpHandler != nil {
|
||||||
h.httpHandler.Store(nil)
|
h.httpHandler.Store(nil)
|
||||||
httpHandler.server.Stop()
|
httpHandler.rpcServer.Stop()
|
||||||
|
httpHandler.restServer.Stop()
|
||||||
}
|
}
|
||||||
if wsHandler != nil {
|
if wsHandler != nil {
|
||||||
h.wsHandler.Store(nil)
|
h.wsHandler.Store(nil)
|
||||||
wsHandler.server.Stop()
|
wsHandler.rpcServer.Stop()
|
||||||
|
wsHandler.restServer.Stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
|
||||||
|
|
@ -292,65 +302,76 @@ func (h *httpServer) doStop() {
|
||||||
h.server, h.listener = nil, nil
|
h.server, h.listener = nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// enableRPC turns on JSON-RPC over HTTP on the server.
|
// enableHTTP turns on JSON-RPC and REST API over HTTP on the server.
|
||||||
func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
|
func (h *httpServer) enableHTTP(rpcApis []rpc.API, restApis []rest.API, config httpConfig) error {
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
if h.rpcAllowed() {
|
if h.httpAllowed() {
|
||||||
return errors.New("JSON-RPC over HTTP is already enabled")
|
return errors.New("JSON-RPC over HTTP is already enabled")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create RPC server and handler.
|
// Create servers and handlers.
|
||||||
srv := rpc.NewServer()
|
rpcServer := rpc.NewServer()
|
||||||
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
|
restServer := rest.NewServer()
|
||||||
|
rpcServer.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
|
||||||
if config.httpBodyLimit > 0 {
|
if config.httpBodyLimit > 0 {
|
||||||
srv.SetHTTPBodyLimit(config.httpBodyLimit)
|
rpcServer.SetHTTPBodyLimit(config.httpBodyLimit)
|
||||||
}
|
}
|
||||||
if err := RegisterApis(apis, config.Modules, srv); err != nil {
|
//TODO REST server limits
|
||||||
|
if err := RegisterAPIs(rpcApis, restApis, config.Modules, rpcServer, restServer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
h.httpConfig = config
|
h.httpConfig = config
|
||||||
h.httpHandler.Store(&rpcHandler{
|
h.httpHandler.Store(&apiHandler{
|
||||||
Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts, config.jwtSecret),
|
rpcHandler: NewHTTPHandlerStack(rpcServer, config.CorsAllowedOrigins, config.Vhosts, config.jwtSecret),
|
||||||
prefix: config.prefix,
|
restHandler: NewHTTPHandlerStack(restServer, config.CorsAllowedOrigins, config.Vhosts, config.jwtSecret),
|
||||||
server: srv,
|
rpcPrefix: config.rpcPrefix,
|
||||||
|
restPrefix: config.restPrefix,
|
||||||
|
rpcServer: rpcServer,
|
||||||
|
restServer: restServer,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// disableRPC stops the HTTP RPC handler. This is internal, the caller must hold h.mu.
|
// disableHTTP stops the HTTP RPC and REST API handlers. This is internal, the caller must hold h.mu.
|
||||||
func (h *httpServer) disableRPC() bool {
|
func (h *httpServer) disableHTTP() bool {
|
||||||
handler := h.httpHandler.Load()
|
handler := h.httpHandler.Load()
|
||||||
if handler != nil {
|
if handler != nil {
|
||||||
h.httpHandler.Store(nil)
|
h.httpHandler.Store(nil)
|
||||||
handler.server.Stop()
|
handler.rpcServer.Stop()
|
||||||
|
handler.restServer.Stop()
|
||||||
}
|
}
|
||||||
return handler != nil
|
return handler != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// enableWS turns on JSON-RPC over WebSocket on the server.
|
// enableWS turns on JSON-RPC over WebSocket on the server.
|
||||||
func (h *httpServer) enableWS(apis []rpc.API, config wsConfig) error {
|
func (h *httpServer) enableWS(rpcApis []rpc.API, restApis []rest.API, config wsConfig) error {
|
||||||
h.mu.Lock()
|
h.mu.Lock()
|
||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
if h.wsAllowed() {
|
if h.wsAllowed() {
|
||||||
return errors.New("JSON-RPC over WebSocket is already enabled")
|
return errors.New("JSON-RPC over WebSocket is already enabled")
|
||||||
}
|
}
|
||||||
// Create RPC server and handler.
|
// Create servers and handlers.
|
||||||
srv := rpc.NewServer()
|
rpcServer := rpc.NewServer()
|
||||||
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
|
restServer := rest.NewServer()
|
||||||
|
rpcServer.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
|
||||||
if config.httpBodyLimit > 0 {
|
if config.httpBodyLimit > 0 {
|
||||||
srv.SetHTTPBodyLimit(config.httpBodyLimit)
|
rpcServer.SetHTTPBodyLimit(config.httpBodyLimit)
|
||||||
}
|
}
|
||||||
if err := RegisterApis(apis, config.Modules, srv); err != nil {
|
//TODO REST server limits
|
||||||
|
if err := RegisterAPIs(rpcApis, restApis, config.Modules, rpcServer, restServer); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
h.wsConfig = config
|
h.wsConfig = config
|
||||||
h.wsHandler.Store(&rpcHandler{
|
h.wsHandler.Store(&apiHandler{
|
||||||
Handler: NewWSHandlerStack(srv.WebsocketHandler(config.Origins), config.jwtSecret),
|
rpcHandler: NewWSHandlerStack(rpcServer.WebsocketHandler(config.Origins), config.jwtSecret),
|
||||||
prefix: config.prefix,
|
//TODO restHandler: NewWSHandlerStack(restServer.WebsocketHandler(config.Origins), config.jwtSecret),
|
||||||
server: srv,
|
rpcPrefix: config.rpcPrefix,
|
||||||
|
restPrefix: config.restPrefix,
|
||||||
|
rpcServer: rpcServer,
|
||||||
|
restServer: restServer,
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -361,7 +382,7 @@ func (h *httpServer) stopWS() {
|
||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
if h.disableWS() {
|
if h.disableWS() {
|
||||||
if !h.rpcAllowed() {
|
if !h.httpAllowed() {
|
||||||
h.doStop()
|
h.doStop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -372,13 +393,14 @@ func (h *httpServer) disableWS() bool {
|
||||||
ws := h.wsHandler.Load()
|
ws := h.wsHandler.Load()
|
||||||
if ws != nil {
|
if ws != nil {
|
||||||
h.wsHandler.Store(nil)
|
h.wsHandler.Store(nil)
|
||||||
ws.server.Stop()
|
ws.rpcServer.Stop()
|
||||||
|
ws.restServer.Stop()
|
||||||
}
|
}
|
||||||
return ws != nil
|
return ws != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// rpcAllowed returns true when JSON-RPC over HTTP is enabled.
|
// rpcAllowed returns true when JSON-RPC over HTTP is enabled.
|
||||||
func (h *httpServer) rpcAllowed() bool {
|
func (h *httpServer) httpAllowed() bool {
|
||||||
return h.httpHandler.Load() != nil
|
return h.httpHandler.Load() != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -629,10 +651,10 @@ func (is *ipcServer) stop() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterApis checks the given modules' availability, generates an allowlist based on the allowed modules,
|
// RegisterRpcAPIs checks the given modules' availability, generates an allowlist based on the allowed modules,
|
||||||
// and then registers all of the APIs exposed by the services.
|
// and then registers all of the APIs exposed by the services.
|
||||||
func RegisterApis(apis []rpc.API, modules []string, srv *rpc.Server) error {
|
func RegisterAPIs(rpcApis []rpc.API, restApis []rest.API, modules []string, rpcServer *rpc.Server, restServer *rest.Server) error {
|
||||||
if bad, available := checkModuleAvailability(modules, apis); len(bad) > 0 {
|
if bad, available := checkModuleAvailability(modules, rpcApis, restApis); len(bad) > 0 {
|
||||||
log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available)
|
log.Error("Unavailable modules in HTTP API list", "unavailable", bad, "available", available)
|
||||||
}
|
}
|
||||||
// Generate the allow list based on the allowed modules
|
// Generate the allow list based on the allowed modules
|
||||||
|
|
@ -641,12 +663,17 @@ func RegisterApis(apis []rpc.API, modules []string, srv *rpc.Server) error {
|
||||||
allowList[module] = true
|
allowList[module] = true
|
||||||
}
|
}
|
||||||
// Register all the APIs exposed by the services
|
// Register all the APIs exposed by the services
|
||||||
for _, api := range apis {
|
for _, api := range rpcApis {
|
||||||
if allowList[api.Namespace] || len(allowList) == 0 {
|
if allowList[api.Namespace] || len(allowList) == 0 {
|
||||||
if err := srv.RegisterName(api.Namespace, api.Service); err != nil {
|
if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for _, api := range restApis {
|
||||||
|
if allowList[api.Namespace] || len(allowList) == 0 {
|
||||||
|
restServer.Register(api)
|
||||||
|
}
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
@ -242,7 +242,7 @@ func createAndStartServer(t *testing.T, conf *httpConfig, ws bool, wsConf *wsCon
|
||||||
timeouts = &rpc.DefaultHTTPTimeouts
|
timeouts = &rpc.DefaultHTTPTimeouts
|
||||||
}
|
}
|
||||||
srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), *timeouts)
|
srv := newHTTPServer(testlog.Logger(t, log.LvlDebug), *timeouts)
|
||||||
assert.NoError(t, srv.enableRPC(apis(), *conf))
|
assert.NoError(t, srv.enableHTTP(apis(), *conf))
|
||||||
if ws {
|
if ws {
|
||||||
assert.NoError(t, srv.enableWS(nil, *wsConf))
|
assert.NoError(t, srv.enableWS(nil, *wsConf))
|
||||||
}
|
}
|
||||||
|
|
@ -339,9 +339,9 @@ func TestJWT(t *testing.T) {
|
||||||
ss, _ := jwt.NewWithClaims(method, testClaim(input)).SignedString(secret)
|
ss, _ := jwt.NewWithClaims(method, testClaim(input)).SignedString(secret)
|
||||||
return ss
|
return ss
|
||||||
}
|
}
|
||||||
cfg := rpcEndpointConfig{jwtSecret: []byte("secret")}
|
cfg := apiEndpointConfig{jwtSecret: []byte("secret")}
|
||||||
httpcfg := &httpConfig{rpcEndpointConfig: cfg}
|
httpcfg := &httpConfig{apiEndpointConfig: cfg}
|
||||||
wscfg := &wsConfig{Origins: []string{"*"}, rpcEndpointConfig: cfg}
|
wscfg := &wsConfig{Origins: []string{"*"}, apiEndpointConfig: cfg}
|
||||||
srv := createAndStartServer(t, httpcfg, true, wscfg, nil)
|
srv := createAndStartServer(t, httpcfg, true, wscfg, nil)
|
||||||
wsUrl := fmt.Sprintf("ws://%v", srv.listenAddr())
|
wsUrl := fmt.Sprintf("ws://%v", srv.listenAddr())
|
||||||
htUrl := fmt.Sprintf("http://%v", srv.listenAddr())
|
htUrl := fmt.Sprintf("http://%v", srv.listenAddr())
|
||||||
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/ethereum/go-ethereum/rest"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -52,9 +53,15 @@ func StartHTTPEndpoint(endpoint string, timeouts rpc.HTTPTimeouts, handler http.
|
||||||
// checkModuleAvailability checks that all names given in modules are actually
|
// checkModuleAvailability checks that all names given in modules are actually
|
||||||
// available API services. It assumes that the MetadataApi module ("rpc") is always available;
|
// available API services. It assumes that the MetadataApi module ("rpc") is always available;
|
||||||
// the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints.
|
// the registration of this "rpc" module happens in NewServer() and is thus common to all endpoints.
|
||||||
func checkModuleAvailability(modules []string, apis []rpc.API) (bad, available []string) {
|
func checkModuleAvailability(modules []string, rpcApis []rpc.API, restApis []rest.API) (bad, available []string) {
|
||||||
availableSet := make(map[string]struct{})
|
availableSet := make(map[string]struct{})
|
||||||
for _, api := range apis {
|
for _, api := range rpcApis {
|
||||||
|
if _, ok := availableSet[api.Namespace]; !ok {
|
||||||
|
availableSet[api.Namespace] = struct{}{}
|
||||||
|
available = append(available, api.Namespace)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, api := range restApis {
|
||||||
if _, ok := availableSet[api.Namespace]; !ok {
|
if _, ok := availableSet[api.Namespace]; !ok {
|
||||||
availableSet[api.Namespace] = struct{}{}
|
availableSet[api.Namespace] = struct{}{}
|
||||||
available = append(available, api.Namespace)
|
available = append(available, api.Namespace)
|
||||||
|
|
|
||||||
61
node/node.go
61
node/node.go
|
|
@ -38,6 +38,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
"github.com/ethereum/go-ethereum/p2p"
|
"github.com/ethereum/go-ethereum/p2p"
|
||||||
|
"github.com/ethereum/go-ethereum/rest"
|
||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/gofrs/flock"
|
"github.com/gofrs/flock"
|
||||||
)
|
)
|
||||||
|
|
@ -58,7 +59,8 @@ type Node struct {
|
||||||
|
|
||||||
lock sync.Mutex
|
lock sync.Mutex
|
||||||
lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle
|
lifecycles []Lifecycle // All registered backends, services, and auxiliary services that have a lifecycle
|
||||||
rpcAPIs []rpc.API // List of APIs currently provided by the node
|
rpcAPIs []rpc.API // List of RPC APIs currently provided by the node
|
||||||
|
restAPIs []rest.API // List of REST APIs currently provided by the node
|
||||||
http *httpServer //
|
http *httpServer //
|
||||||
ws *httpServer //
|
ws *httpServer //
|
||||||
httpAuth *httpServer //
|
httpAuth *httpServer //
|
||||||
|
|
@ -75,6 +77,8 @@ const (
|
||||||
closedState
|
closedState
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var restApiPrefix = "/eth/" //TODO
|
||||||
|
|
||||||
// New creates a new P2P node, ready for protocol registration.
|
// New creates a new P2P node, ready for protocol registration.
|
||||||
func New(conf *Config) (*Node, error) {
|
func New(conf *Config) (*Node, error) {
|
||||||
// Copy config and resolve the datadir so future changes to the current
|
// Copy config and resolve the datadir so future changes to the current
|
||||||
|
|
@ -390,7 +394,7 @@ func (n *Node) startRPC() error {
|
||||||
openAPIs, allAPIs = n.getAPIs()
|
openAPIs, allAPIs = n.getAPIs()
|
||||||
)
|
)
|
||||||
|
|
||||||
rpcConfig := rpcEndpointConfig{
|
rpcConfig := apiEndpointConfig{
|
||||||
batchItemLimit: n.config.BatchRequestLimit,
|
batchItemLimit: n.config.BatchRequestLimit,
|
||||||
batchResponseSizeLimit: n.config.BatchResponseMaxSize,
|
batchResponseSizeLimit: n.config.BatchResponseMaxSize,
|
||||||
}
|
}
|
||||||
|
|
@ -399,12 +403,13 @@ func (n *Node) startRPC() error {
|
||||||
if err := server.setListenAddr(n.config.HTTPHost, port); err != nil {
|
if err := server.setListenAddr(n.config.HTTPHost, port); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := server.enableRPC(openAPIs, httpConfig{
|
if err := server.enableHTTP(openAPIs, n.restAPIs, httpConfig{
|
||||||
CorsAllowedOrigins: n.config.HTTPCors,
|
CorsAllowedOrigins: n.config.HTTPCors,
|
||||||
Vhosts: n.config.HTTPVirtualHosts,
|
Vhosts: n.config.HTTPVirtualHosts,
|
||||||
Modules: n.config.HTTPModules,
|
Modules: n.config.HTTPModules,
|
||||||
prefix: n.config.HTTPPathPrefix,
|
rpcPrefix: n.config.HTTPPathPrefix,
|
||||||
rpcEndpointConfig: rpcConfig,
|
restPrefix: restApiPrefix,
|
||||||
|
apiEndpointConfig: rpcConfig,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -417,11 +422,12 @@ func (n *Node) startRPC() error {
|
||||||
if err := server.setListenAddr(n.config.WSHost, port); err != nil {
|
if err := server.setListenAddr(n.config.WSHost, port); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := server.enableWS(openAPIs, wsConfig{
|
if err := server.enableWS(openAPIs, n.restAPIs, wsConfig{
|
||||||
Modules: n.config.WSModules,
|
Modules: n.config.WSModules,
|
||||||
Origins: n.config.WSOrigins,
|
Origins: n.config.WSOrigins,
|
||||||
prefix: n.config.WSPathPrefix,
|
rpcPrefix: n.config.WSPathPrefix,
|
||||||
rpcEndpointConfig: rpcConfig,
|
restPrefix: restApiPrefix,
|
||||||
|
apiEndpointConfig: rpcConfig,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -435,18 +441,19 @@ func (n *Node) startRPC() error {
|
||||||
if err := server.setListenAddr(n.config.AuthAddr, port); err != nil {
|
if err := server.setListenAddr(n.config.AuthAddr, port); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
sharedConfig := rpcEndpointConfig{
|
sharedConfig := apiEndpointConfig{
|
||||||
jwtSecret: secret,
|
jwtSecret: secret,
|
||||||
batchItemLimit: engineAPIBatchItemLimit,
|
batchItemLimit: engineAPIBatchItemLimit,
|
||||||
batchResponseSizeLimit: engineAPIBatchResponseSizeLimit,
|
batchResponseSizeLimit: engineAPIBatchResponseSizeLimit,
|
||||||
httpBodyLimit: engineAPIBodyLimit,
|
httpBodyLimit: engineAPIBodyLimit,
|
||||||
}
|
}
|
||||||
err := server.enableRPC(allAPIs, httpConfig{
|
err := server.enableHTTP(allAPIs, n.restAPIs, httpConfig{
|
||||||
CorsAllowedOrigins: DefaultAuthCors,
|
CorsAllowedOrigins: DefaultAuthCors,
|
||||||
Vhosts: n.config.AuthVirtualHosts,
|
Vhosts: n.config.AuthVirtualHosts,
|
||||||
Modules: DefaultAuthModules,
|
Modules: DefaultAuthModules,
|
||||||
prefix: DefaultAuthPrefix,
|
rpcPrefix: DefaultAuthPrefix,
|
||||||
rpcEndpointConfig: sharedConfig,
|
restPrefix: restApiPrefix,
|
||||||
|
apiEndpointConfig: sharedConfig,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -458,11 +465,12 @@ func (n *Node) startRPC() error {
|
||||||
if err := server.setListenAddr(n.config.AuthAddr, port); err != nil {
|
if err := server.setListenAddr(n.config.AuthAddr, port); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := server.enableWS(allAPIs, wsConfig{
|
if err := server.enableWS(allAPIs, n.restAPIs, wsConfig{
|
||||||
Modules: DefaultAuthModules,
|
Modules: DefaultAuthModules,
|
||||||
Origins: DefaultAuthOrigins,
|
Origins: DefaultAuthOrigins,
|
||||||
prefix: DefaultAuthPrefix,
|
rpcPrefix: DefaultAuthPrefix,
|
||||||
rpcEndpointConfig: sharedConfig,
|
restPrefix: restApiPrefix,
|
||||||
|
apiEndpointConfig: sharedConfig,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -568,8 +576,8 @@ func (n *Node) RegisterProtocols(protocols []p2p.Protocol) {
|
||||||
n.server.Protocols = append(n.server.Protocols, protocols...)
|
n.server.Protocols = append(n.server.Protocols, protocols...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterAPIs registers the APIs a service provides on the node.
|
// RegisterRpcAPIs registers the RPC APIs a service provides on the node.
|
||||||
func (n *Node) RegisterAPIs(apis []rpc.API) {
|
func (n *Node) RegisterRpcAPIs(apis []rpc.API) {
|
||||||
n.lock.Lock()
|
n.lock.Lock()
|
||||||
defer n.lock.Unlock()
|
defer n.lock.Unlock()
|
||||||
|
|
||||||
|
|
@ -579,6 +587,17 @@ func (n *Node) RegisterAPIs(apis []rpc.API) {
|
||||||
n.rpcAPIs = append(n.rpcAPIs, apis...)
|
n.rpcAPIs = append(n.rpcAPIs, apis...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RegisterRestAPIs registers the RPC APIs a service provides on the node.
|
||||||
|
func (n *Node) RegisterRestAPIs(apis []rest.API) {
|
||||||
|
n.lock.Lock()
|
||||||
|
defer n.lock.Unlock()
|
||||||
|
|
||||||
|
if n.state != initializingState {
|
||||||
|
panic("can't register APIs on running/stopped node")
|
||||||
|
}
|
||||||
|
n.restAPIs = append(n.restAPIs, apis...)
|
||||||
|
}
|
||||||
|
|
||||||
// getAPIs return two sets of APIs, both the ones that do not require
|
// getAPIs return two sets of APIs, both the ones that do not require
|
||||||
// authentication, and the complete set
|
// authentication, and the complete set
|
||||||
func (n *Node) getAPIs() (unauthenticated, all []rpc.API) {
|
func (n *Node) getAPIs() (unauthenticated, all []rpc.API) {
|
||||||
|
|
@ -672,9 +691,9 @@ func (n *Node) HTTPEndpoint() string {
|
||||||
// WSEndpoint returns the current JSON-RPC over WebSocket endpoint.
|
// WSEndpoint returns the current JSON-RPC over WebSocket endpoint.
|
||||||
func (n *Node) WSEndpoint() string {
|
func (n *Node) WSEndpoint() string {
|
||||||
if n.http.wsAllowed() {
|
if n.http.wsAllowed() {
|
||||||
return "ws://" + n.http.listenAddr() + n.http.wsConfig.prefix
|
return "ws://" + n.http.listenAddr() + n.http.wsConfig.rpcPrefix //TODO ???
|
||||||
}
|
}
|
||||||
return "ws://" + n.ws.listenAddr() + n.ws.wsConfig.prefix
|
return "ws://" + n.ws.listenAddr() + n.ws.wsConfig.rpcPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
// HTTPAuthEndpoint returns the URL of the authenticated HTTP server.
|
// HTTPAuthEndpoint returns the URL of the authenticated HTTP server.
|
||||||
|
|
@ -685,9 +704,9 @@ func (n *Node) HTTPAuthEndpoint() string {
|
||||||
// WSAuthEndpoint returns the current authenticated JSON-RPC over WebSocket endpoint.
|
// WSAuthEndpoint returns the current authenticated JSON-RPC over WebSocket endpoint.
|
||||||
func (n *Node) WSAuthEndpoint() string {
|
func (n *Node) WSAuthEndpoint() string {
|
||||||
if n.httpAuth.wsAllowed() {
|
if n.httpAuth.wsAllowed() {
|
||||||
return "ws://" + n.httpAuth.listenAddr() + n.httpAuth.wsConfig.prefix
|
return "ws://" + n.httpAuth.listenAddr() + n.httpAuth.wsConfig.rpcPrefix
|
||||||
}
|
}
|
||||||
return "ws://" + n.wsAuth.listenAddr() + n.wsAuth.wsConfig.prefix
|
return "ws://" + n.wsAuth.listenAddr() + n.wsAuth.wsConfig.rpcPrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
// EventMux retrieves the event multiplexer used by all the network services in
|
// EventMux retrieves the event multiplexer used by all the network services in
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ func TestAuthEndpoints(t *testing.T) {
|
||||||
t.Fatalf("could not create a new node: %v", err)
|
t.Fatalf("could not create a new node: %v", err)
|
||||||
}
|
}
|
||||||
// register dummy apis so we can test the modules are available and reachable with authentication
|
// register dummy apis so we can test the modules are available and reachable with authentication
|
||||||
node.RegisterAPIs([]rpc.API{
|
node.RegisterRpcAPIs([]rpc.API{
|
||||||
{
|
{
|
||||||
Namespace: "engine",
|
Namespace: "engine",
|
||||||
Version: "1.0",
|
Version: "1.0",
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,7 @@ func NewFullService(stack *Node) (*FullService, error) {
|
||||||
fs := new(FullService)
|
fs := new(FullService)
|
||||||
|
|
||||||
stack.RegisterProtocols(fs.Protocols())
|
stack.RegisterProtocols(fs.Protocols())
|
||||||
stack.RegisterAPIs(fs.APIs())
|
stack.RegisterRpcAPIs(fs.APIs())
|
||||||
stack.RegisterLifecycle(fs)
|
stack.RegisterLifecycle(fs)
|
||||||
return fs, nil
|
return fs, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
78
rest/server.go
Normal file
78
rest/server.go
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultRequestLimit = 5 * 1024 * 1024
|
||||||
|
defaultResponseLimit = 5 * 1024 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
// Server is a REST API server.
|
||||||
|
type Server struct {
|
||||||
|
itemLimit, requestLimit, responseLimit int
|
||||||
|
mux http.ServeMux
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer creates a new server instance with no registered handlers.
|
||||||
|
func NewServer() *Server {
|
||||||
|
return &Server{
|
||||||
|
requestLimit: defaultRequestLimit,
|
||||||
|
responseLimit: defaultResponseLimit,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Server) Stop() {} //TODO is this required?
|
||||||
|
|
||||||
|
func (s *Server) Register(api API) {
|
||||||
|
api.Register(&s.mux, s.responseLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBatchLimits sets limits applied to batch requests. There are two limits: 'itemLimit'
|
||||||
|
// is the maximum number of items in a batch. 'maxResponseSize' is the maximum number of
|
||||||
|
// response bytes across all requests in a batch.
|
||||||
|
//
|
||||||
|
// This method should be called before processing any requests via ServeCodec, ServeHTTP,
|
||||||
|
// ServeListener etc.
|
||||||
|
/*func (s *Server) SetBatchLimits(itemLimit, maxResponseSize int) {
|
||||||
|
s.batchItemLimit = itemLimit
|
||||||
|
s.batchResponseLimit = maxResponseSize
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// SetHTTPBodyLimit sets the size limit for HTTP requests.
|
||||||
|
//
|
||||||
|
// This method should be called before processing any requests via ServeHTTP.
|
||||||
|
/*func (s *Server) SetHTTPBodyLimit(limit int) {
|
||||||
|
s.httpBodyLimit = limit
|
||||||
|
}*/
|
||||||
|
|
||||||
|
// ServeHTTP serves REST API requests over HTTP.
|
||||||
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.ContentLength < 0 {
|
||||||
|
http.Error(w, "request size unknown", http.StatusRequestEntityTooLarge)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if reqLen := int64(len(r.URL.RawQuery)) + r.ContentLength; reqLen > int64(s.requestLimit) {
|
||||||
|
http.Error(w, fmt.Sprintf("request too large (%d>%d)", reqLen, s.requestLimit), http.StatusRequestEntityTooLarge)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.mux.ServeHTTP(w, r)
|
||||||
|
}
|
||||||
27
rest/types.go
Normal file
27
rest/types.go
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
// Copyright 2025 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
package rest
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
// API describes the set of methods offered over the REST API interface
|
||||||
|
type API struct {
|
||||||
|
Namespace string // namespace under which the REST API methods of Service are exposed: /eth/v*/<namespace>
|
||||||
|
Register func(mux *http.ServeMux, maxResponseSize int)
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue