2020-08-19 11:46:20 +00:00
package cli
import (
"context"
2021-04-24 15:46:29 +00:00
"encoding/binary"
2020-08-19 11:46:20 +00:00
"fmt"
2020-09-18 10:23:35 +00:00
"net/http"
2021-04-19 05:44:14 +00:00
"path"
2020-10-10 06:06:54 +00:00
"time"
2020-09-18 10:23:35 +00:00
2021-07-01 21:31:14 +00:00
"github.com/ledgerwatch/erigon-lib/gointerfaces"
2021-09-03 04:19:35 +00:00
"github.com/ledgerwatch/erigon-lib/gointerfaces/grpcutil"
2021-08-07 05:03:12 +00:00
"github.com/ledgerwatch/erigon-lib/gointerfaces/remote"
2021-07-29 11:53:13 +00:00
"github.com/ledgerwatch/erigon-lib/kv"
kv2 "github.com/ledgerwatch/erigon-lib/kv/mdbx"
2021-08-17 08:52:55 +00:00
"github.com/ledgerwatch/erigon-lib/kv/remotedb"
2021-05-22 10:00:13 +00:00
"github.com/ledgerwatch/erigon/cmd/rpcdaemon/services"
2021-05-20 18:25:53 +00:00
"github.com/ledgerwatch/erigon/cmd/utils"
"github.com/ledgerwatch/erigon/common/paths"
2021-07-28 02:47:38 +00:00
"github.com/ledgerwatch/erigon/ethdb/remotedbserver"
2021-05-20 18:25:53 +00:00
"github.com/ledgerwatch/erigon/internal/debug"
"github.com/ledgerwatch/erigon/node"
"github.com/ledgerwatch/erigon/rpc"
2021-07-29 10:23:23 +00:00
"github.com/ledgerwatch/log/v3"
2020-08-19 11:46:20 +00:00
"github.com/spf13/cobra"
)
type Flags struct {
2020-11-10 09:08:42 +00:00
PrivateApiAddr string
2021-05-26 10:35:39 +00:00
SingleNodeMode bool // Erigon's database can be read by separated processes on same machine - in read-only mode - with full support of transactions. It will share same "OS PageCache" with Erigon process.
2021-04-19 05:44:14 +00:00
Datadir string
2020-11-10 09:08:42 +00:00
Chaindata string
SnapshotDir string
SnapshotMode string
HttpListenAddress string
TLSCertfile string
TLSCACert string
TLSKeyFile string
HttpPort int
HttpCORSDomain [ ] string
HttpVirtualHost [ ] string
2021-05-31 16:18:32 +00:00
HttpCompression bool
2020-11-10 09:08:42 +00:00
API [ ] string
Gascap uint64
MaxTraces uint64
WebsocketEnabled bool
2021-06-11 09:21:39 +00:00
WebsocketCompression bool
2020-11-10 09:08:42 +00:00
RpcAllowListFilePath string
2021-07-07 03:48:21 +00:00
RpcBatchConcurrency uint
2021-06-16 17:24:56 +00:00
TraceCompatibility bool // Bug for bug compatibility for trace_ routines with OpenEthereum
2021-09-03 04:19:35 +00:00
TxPoolV2 bool
TxPoolApiAddr string
2020-08-19 11:46:20 +00:00
}
var rootCmd = & cobra . Command {
Use : "rpcdaemon" ,
2021-05-26 10:35:39 +00:00
Short : "rpcdaemon is JSON RPC server that connects to Erigon node for remote DB access" ,
2020-08-19 11:46:20 +00:00
}
func RootCommand ( ) ( * cobra . Command , * Flags ) {
utils . CobraFlags ( rootCmd , append ( debug . Flags , utils . MetricFlags ... ) )
cfg := & Flags { }
2021-09-03 04:19:35 +00:00
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . PrivateApiAddr , "private.api.addr" , "127.0.0.1:9090" , "private api network address, for example: 127.0.0.1:9090" )
2021-05-26 10:35:39 +00:00
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . Datadir , "datadir" , "" , "path to Erigon working directory" )
2020-08-19 11:46:20 +00:00
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . Chaindata , "chaindata" , "" , "path to the database" )
2021-04-19 05:44:14 +00:00
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . SnapshotDir , "snapshot.dir" , "" , "path to snapshot dir(only for chaindata mode)" )
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . SnapshotMode , "snapshot.mode" , "" , ` Configures the storage mode of the app ( only for chaindata mode ) :
2020-10-27 22:31:47 +00:00
* h - use headers snapshot
* b - use bodies snapshot
* s - use state snapshot
* r - use receipts snapshot
` )
2020-08-19 11:46:20 +00:00
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . HttpListenAddress , "http.addr" , node . DefaultHTTPHost , "HTTP-RPC server listening interface" )
2020-09-11 20:17:37 +00:00
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . TLSCertfile , "tls.cert" , "" , "certificate for client side TLS handshake" )
2020-09-19 14:16:04 +00:00
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . TLSKeyFile , "tls.key" , "" , "key file for client side TLS handshake" )
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . TLSCACert , "tls.cacert" , "" , "CA certificate for client side TLS handshake" )
2020-08-19 11:46:20 +00:00
rootCmd . PersistentFlags ( ) . IntVar ( & cfg . HttpPort , "http.port" , node . DefaultHTTPPort , "HTTP-RPC server listening port" )
rootCmd . PersistentFlags ( ) . StringSliceVar ( & cfg . HttpCORSDomain , "http.corsdomain" , [ ] string { } , "Comma separated list of domains from which to accept cross origin requests (browser enforced)" )
rootCmd . PersistentFlags ( ) . StringSliceVar ( & cfg . HttpVirtualHost , "http.vhosts" , node . DefaultConfig . HTTPVirtualHosts , "Comma separated list of virtual hostnames from which to accept requests (server enforced). Accepts '*' wildcard." )
2021-05-31 16:18:32 +00:00
rootCmd . PersistentFlags ( ) . BoolVar ( & cfg . HttpCompression , "http.compression" , true , "Disable http compression" )
2021-07-11 05:25:21 +00:00
rootCmd . PersistentFlags ( ) . StringSliceVar ( & cfg . API , "http.api" , [ ] string { "eth" , "erigon" } , "API's offered over the HTTP-RPC interface: eth,erigon,web3,net,debug,trace,txpool,shh,db. Supported methods: https://github.com/ledgerwatch/erigon/tree/devel/cmd/rpcdaemon" )
2021-08-23 14:30:21 +00:00
rootCmd . PersistentFlags ( ) . Uint64Var ( & cfg . Gascap , "rpc.gascap" , 50000000 , "Sets a cap on gas that can be used in eth_call/estimateGas" )
2020-08-29 15:50:24 +00:00
rootCmd . PersistentFlags ( ) . Uint64Var ( & cfg . MaxTraces , "trace.maxtraces" , 200 , "Sets a limit on traces that can be returned in trace_filter" )
2020-09-01 16:00:47 +00:00
rootCmd . PersistentFlags ( ) . BoolVar ( & cfg . WebsocketEnabled , "ws" , false , "Enable Websockets" )
2021-06-11 09:21:39 +00:00
rootCmd . PersistentFlags ( ) . BoolVar ( & cfg . WebsocketCompression , "ws.compression" , false , "Enable Websocket compression (RFC 7692)" )
2020-11-10 09:08:42 +00:00
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . RpcAllowListFilePath , "rpc.accessList" , "" , "Specify granular (method-by-method) API allowlist" )
2021-07-07 03:48:21 +00:00
rootCmd . PersistentFlags ( ) . UintVar ( & cfg . RpcBatchConcurrency , "rpc.batch.concurrency" , 50 , "Does limit amount of goroutines to process 1 batch request. Means 1 bach request can't overload server. 1 batch still can have unlimited amount of request" )
2021-06-16 17:24:56 +00:00
rootCmd . PersistentFlags ( ) . BoolVar ( & cfg . TraceCompatibility , "trace.compat" , false , "Bug for bug compatibility with OE for trace_ routines" )
2021-09-03 04:19:35 +00:00
rootCmd . PersistentFlags ( ) . BoolVar ( & cfg . TxPoolV2 , "txpool.v2" , false , "experimental external txpool" )
2021-09-08 05:31:51 +00:00
rootCmd . PersistentFlags ( ) . StringVar ( & cfg . TxPoolApiAddr , "txpool.api.addr" , "127.0.0.1:9090" , "txpool api network address, for example: 127.0.0.1:9090" )
2020-11-10 09:08:42 +00:00
if err := rootCmd . MarkPersistentFlagFilename ( "rpc.accessList" , "json" ) ; err != nil {
panic ( err )
}
2021-04-19 05:44:14 +00:00
if err := rootCmd . MarkPersistentFlagDirname ( "datadir" ) ; err != nil {
panic ( err )
}
if err := rootCmd . MarkPersistentFlagDirname ( "chaindata" ) ; err != nil {
panic ( err )
}
if err := rootCmd . MarkPersistentFlagDirname ( "snapshot.dir" ) ; err != nil {
panic ( err )
}
rootCmd . PersistentPreRunE = func ( cmd * cobra . Command , args [ ] string ) error {
2021-04-19 14:14:12 +00:00
if err := utils . SetupCobra ( cmd ) ; err != nil {
return err
}
2021-04-19 05:44:14 +00:00
cfg . SingleNodeMode = cfg . Datadir != "" || cfg . Chaindata != ""
if cfg . SingleNodeMode {
if cfg . Datadir == "" {
2021-04-19 21:58:05 +00:00
cfg . Datadir = paths . DefaultDataDir ( )
2021-04-19 05:44:14 +00:00
}
if cfg . Chaindata == "" {
2021-08-18 09:24:03 +00:00
cfg . Chaindata = path . Join ( cfg . Datadir , "chaindata" )
2021-04-19 05:44:14 +00:00
}
}
return nil
}
rootCmd . PersistentPostRunE = func ( cmd * cobra . Command , args [ ] string ) error {
utils . StopDebug ( )
return nil
}
2020-08-19 11:46:20 +00:00
return rootCmd , cfg
}
2021-07-28 02:47:38 +00:00
func checkDbCompatibility ( db kv . RoDB ) error {
2021-04-24 15:46:29 +00:00
// DB schema version compatibility check
var version [ ] byte
var compatErr error
2021-07-28 02:47:38 +00:00
var compatTx kv . Tx
2021-04-24 15:46:29 +00:00
if compatTx , compatErr = db . BeginRo ( context . Background ( ) ) ; compatErr != nil {
return fmt . Errorf ( "open Ro Tx for DB schema compability check: %w" , compatErr )
}
defer compatTx . Rollback ( )
2021-07-28 02:47:38 +00:00
if version , compatErr = compatTx . GetOne ( kv . DatabaseInfo , kv . DBSchemaVersionKey ) ; compatErr != nil {
2021-04-24 15:46:29 +00:00
return fmt . Errorf ( "read version for DB schema compability check: %w" , compatErr )
}
if len ( version ) != 12 {
2021-05-26 10:35:39 +00:00
return fmt . Errorf ( "database does not have major schema version. upgrade and restart Erigon core" )
2021-04-24 15:46:29 +00:00
}
2021-07-17 02:09:56 +00:00
major := binary . BigEndian . Uint32 ( version )
2021-04-24 15:46:29 +00:00
minor := binary . BigEndian . Uint32 ( version [ 4 : ] )
patch := binary . BigEndian . Uint32 ( version [ 8 : ] )
var compatible bool
2021-07-28 02:47:38 +00:00
dbSchemaVersion := & kv . DBSchemaVersion
2021-05-22 09:20:43 +00:00
if major != dbSchemaVersion . Major {
2021-04-24 15:46:29 +00:00
compatible = false
2021-05-22 09:20:43 +00:00
} else if minor != dbSchemaVersion . Minor {
2021-04-24 15:46:29 +00:00
compatible = false
} else {
compatible = true
}
if ! compatible {
return fmt . Errorf ( "incompatible DB Schema versions: reader %d.%d.%d, database %d.%d.%d" ,
2021-05-22 09:20:43 +00:00
dbSchemaVersion . Major , dbSchemaVersion . Minor , dbSchemaVersion . Patch ,
2021-04-24 15:46:29 +00:00
major , minor , patch )
}
2021-05-22 09:20:43 +00:00
log . Info ( "DB schemas compatible" , "reader" , fmt . Sprintf ( "%d.%d.%d" , dbSchemaVersion . Major , dbSchemaVersion . Minor , dbSchemaVersion . Patch ) ,
2021-04-24 15:46:29 +00:00
"database" , fmt . Sprintf ( "%d.%d.%d" , major , minor , patch ) )
return nil
}
2021-09-03 04:19:35 +00:00
func RemoteServices ( ctx context . Context , cfg Flags , logger log . Logger , rootCancel context . CancelFunc ) ( db kv . RoDB , eth services . ApiBackend , txPool * services . TxPoolService , mining * services . MiningService , err error ) {
2021-04-26 05:37:48 +00:00
if ! cfg . SingleNodeMode && cfg . PrivateApiAddr == "" {
2021-06-16 10:57:58 +00:00
return nil , nil , nil , nil , fmt . Errorf ( "either remote db or local db must be specified" )
2021-04-26 05:37:48 +00:00
}
2020-09-18 10:23:35 +00:00
// Do not change the order of these checks. Chaindata needs to be checked first, because PrivateApiAddr has default value which is not ""
// If PrivateApiAddr is checked first, the Chaindata option will never work
2021-04-19 05:44:14 +00:00
if cfg . SingleNodeMode {
2021-07-28 02:47:38 +00:00
var rwKv kv . RwDB
rwKv , err = kv2 . NewMDBX ( logger ) . Path ( cfg . Chaindata ) . Readonly ( ) . Open ( )
2021-06-16 10:57:58 +00:00
if err != nil {
return nil , nil , nil , nil , err
2020-08-19 11:46:20 +00:00
}
2021-06-16 10:57:58 +00:00
if compatErr := checkDbCompatibility ( rwKv ) ; compatErr != nil {
2021-05-17 12:15:19 +00:00
return nil , nil , nil , nil , compatErr
2021-04-24 15:46:29 +00:00
}
2021-07-28 02:47:38 +00:00
db = rwKv
2021-07-01 21:30:55 +00:00
} else {
log . Info ( "if you run RPCDaemon on same machine with Erigon add --datadir option" )
2021-02-12 16:47:32 +00:00
}
if cfg . PrivateApiAddr != "" {
2021-09-03 04:19:35 +00:00
creds , err := grpcutil . TLS ( cfg . TLSCACert , cfg . TLSCertfile , cfg . TLSKeyFile )
2020-09-18 10:23:35 +00:00
if err != nil {
2021-09-03 04:19:35 +00:00
return nil , nil , nil , nil , fmt . Errorf ( "open tls cert: %w" , err )
}
conn , err := grpcutil . Connect ( creds , cfg . PrivateApiAddr )
if err != nil {
return nil , nil , nil , nil , fmt . Errorf ( "could not connect to execution service privateApi: %w" , err )
2020-09-18 10:23:35 +00:00
}
2021-08-07 05:03:12 +00:00
kvClient := remote . NewKVClient ( conn )
remoteKv , err := remotedb . NewRemote ( gointerfaces . VersionFromProto ( remotedbserver . KvServiceAPIVersion ) , logger , kvClient ) . Open ( )
if err != nil {
return nil , nil , nil , nil , fmt . Errorf ( "could not connect to remoteKv: %w" , err )
}
remoteEth := services . NewRemoteBackend ( conn )
2021-09-03 04:19:35 +00:00
txpoolConn := conn
if cfg . TxPoolV2 {
txpoolConn , err = grpcutil . Connect ( creds , cfg . TxPoolApiAddr )
if err != nil {
return nil , nil , nil , nil , fmt . Errorf ( "could not connect to txpool api: %w" , err )
}
}
2021-09-08 04:13:34 +00:00
mining = services . NewMiningService ( txpoolConn )
2021-09-03 04:19:35 +00:00
txPool = services . NewTxPoolService ( txpoolConn )
2021-07-28 02:47:38 +00:00
if db == nil {
db = remoteKv
2021-02-12 16:47:32 +00:00
}
2021-05-22 10:00:13 +00:00
eth = remoteEth
go func ( ) {
if ! remoteKv . EnsureVersionCompatibility ( ) {
rootCancel ( )
}
if ! remoteEth . EnsureVersionCompatibility ( ) {
rootCancel ( )
}
2021-09-03 04:19:35 +00:00
if mining != nil && ! mining . EnsureVersionCompatibility ( ) {
2021-05-22 10:00:13 +00:00
rootCancel ( )
}
if ! txPool . EnsureVersionCompatibility ( ) {
rootCancel ( )
}
} ( )
2020-08-19 11:46:20 +00:00
}
2021-07-28 02:47:38 +00:00
return db , eth , txPool , mining , err
2020-08-19 11:46:20 +00:00
}
2020-08-20 03:52:27 +00:00
func StartRpcServer ( ctx context . Context , cfg Flags , rpcAPI [ ] rpc . API ) error {
2020-08-19 11:46:20 +00:00
// register apis and create handler stack
httpEndpoint := fmt . Sprintf ( "%s:%d" , cfg . HttpListenAddress , cfg . HttpPort )
2020-09-02 05:56:48 +00:00
2021-07-07 03:48:21 +00:00
srv := rpc . NewServer ( cfg . RpcBatchConcurrency )
2020-11-10 09:08:42 +00:00
allowListForRPC , err := parseAllowListForRPC ( cfg . RpcAllowListFilePath )
if err != nil {
return err
}
srv . SetAllowList ( allowListForRPC )
2020-08-19 11:46:20 +00:00
if err := node . RegisterApisFromWhitelist ( rpcAPI , cfg . API , srv , false ) ; err != nil {
2020-08-20 03:52:27 +00:00
return fmt . Errorf ( "could not start register RPC apis: %w" , err )
2020-08-19 11:46:20 +00:00
}
2020-09-02 05:56:48 +00:00
2021-05-31 16:18:32 +00:00
httpHandler := node . NewHTTPHandlerStack ( srv , cfg . HttpCORSDomain , cfg . HttpVirtualHost , cfg . HttpCompression )
2020-09-02 05:56:48 +00:00
var wsHandler http . Handler
2020-09-01 16:00:47 +00:00
if cfg . WebsocketEnabled {
2021-06-11 09:21:39 +00:00
wsHandler = srv . WebsocketHandler ( [ ] string { "*" } , cfg . WebsocketCompression )
2020-09-02 05:56:48 +00:00
}
2020-09-09 20:21:19 +00:00
var handler http . Handler = http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
2020-09-02 05:56:48 +00:00
if cfg . WebsocketEnabled && r . Method == "GET" {
wsHandler . ServeHTTP ( w , r )
2021-07-02 10:10:54 +00:00
return
2020-09-01 16:00:47 +00:00
}
2020-09-02 05:56:48 +00:00
httpHandler . ServeHTTP ( w , r )
} )
listener , _ , err := node . StartHTTPEndpoint ( httpEndpoint , rpc . DefaultHTTPTimeouts , handler )
if err != nil {
return fmt . Errorf ( "could not start RPC api: %w" , err )
2020-08-19 11:46:20 +00:00
}
2020-09-02 05:56:48 +00:00
2021-06-11 09:21:39 +00:00
log . Info ( "HTTP endpoint opened" , "url" , httpEndpoint , "ws" , cfg . WebsocketEnabled , "ws.compression" , cfg . WebsocketCompression )
2020-08-19 11:46:20 +00:00
defer func ( ) {
2020-10-10 06:06:54 +00:00
srv . Stop ( )
shutdownCtx , cancel := context . WithTimeout ( context . Background ( ) , 5 * time . Second )
defer cancel ( )
_ = listener . Shutdown ( shutdownCtx )
2020-08-19 11:46:20 +00:00
log . Info ( "HTTP endpoint closed" , "url" , httpEndpoint )
} ( )
2020-10-10 06:06:54 +00:00
<- ctx . Done ( )
log . Info ( "Exiting..." )
2020-08-20 03:52:27 +00:00
return nil
2020-08-19 11:46:20 +00:00
}