diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go index f951633d05..43dcc7f86e 100644 --- a/cmd/utils/flags.go +++ b/cmd/utils/flags.go @@ -21,6 +21,7 @@ import ( "crypto/ecdsa" "fmt" "github.com/PlatONnetwork/PlatON-Go/eth/tracers" + "github.com/PlatONnetwork/PlatON-Go/ethdb/remotedb" "io" "math" "math/big" @@ -117,6 +118,10 @@ var ( Usage: "Data directory for the databases and keystore", Value: DirectoryString(node.DefaultDataDir()), } + RemoteDBFlag = cli.StringFlag{ + Name: "remotedb", + Usage: "URL for remote database", + } AncientFlag = DirectoryFlag{ Name: "datadir.ancient", Usage: "Data directory for ancient chain segments (default = inside chaindata)", @@ -725,6 +730,24 @@ var ( } ) +var ( + // DatabasePathFlags is the flag group of all database path flags. + DatabasePathFlags = []cli.Flag{ + DataDirFlag, + AncientFlag, + RemoteDBFlag, + } +) + +// GroupFlags combines the given flag slices together and returns the merged one. +func GroupFlags(groups ...[]cli.Flag) []cli.Flag { + var ret []cli.Flag + for _, group := range groups { + ret = append(ret, group...) + } + return ret +} + // MakeDataDir retrieves the currently requested data directory, terminating // if none (or the empty string) is specified. If the node is starting a testnet, // then a subdirectory of the specified datadir will be used. @@ -1508,12 +1531,19 @@ func MakeChainDatabase(ctx *cli.Context, stack *node.Node, readonly bool) ethdb. var ( cache = ctx.GlobalInt(CacheFlag.Name) * ctx.GlobalInt(CacheDatabaseFlag.Name) / 100 handles = MakeDatabaseHandles(ctx.GlobalInt(FDLimitFlag.Name)) + + err error + chainDb ethdb.Database ) - name := "chaindata" - if ctx.GlobalString(SyncModeFlag.Name) == "light" { - name = "lightchaindata" + switch { + case ctx.GlobalIsSet(RemoteDBFlag.Name): + log.Info("Using remote db", "url", ctx.GlobalString(RemoteDBFlag.Name)) + chainDb, err = remotedb.New(ctx.GlobalString(RemoteDBFlag.Name)) + case ctx.GlobalString(SyncModeFlag.Name) == "light": + chainDb, err = stack.OpenDatabase("lightchaindata", cache, handles, "", readonly) + default: + chainDb, err = stack.OpenDatabaseWithFreezer("chaindata", cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly) } - chainDb, err := stack.OpenDatabaseWithFreezer(name, cache, handles, ctx.GlobalString(AncientFlag.Name), "", readonly) if err != nil { Fatalf("Could not open database: %v", err) } diff --git a/common/bytes.go b/common/bytes.go index 3b59f133c9..e25500bcf5 100644 --- a/common/bytes.go +++ b/common/bytes.go @@ -21,6 +21,8 @@ import ( "bytes" "encoding/binary" "encoding/hex" + "errors" + "github.com/PlatONnetwork/PlatON-Go/common/hexutil" "math" ) @@ -81,6 +83,15 @@ func Hex2Bytes(str string) []byte { return h } +// ParseHexOrString tries to hexdecode b, but if the prefix is missing, it instead just returns the raw bytes +func ParseHexOrString(str string) ([]byte, error) { + b, err := hexutil.Decode(str) + if errors.Is(err, hexutil.ErrMissingPrefix) { + return []byte(str), nil + } + return b, err +} + // RightPadBytes zero-pads slice to the right up to length l. func RightPadBytes(slice []byte, l int) []byte { if l <= len(slice) { diff --git a/ethdb/remotedb/remotedb.go b/ethdb/remotedb/remotedb.go new file mode 100644 index 0000000000..a929cfe236 --- /dev/null +++ b/ethdb/remotedb/remotedb.go @@ -0,0 +1,173 @@ +// Copyright 2022 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 . + +// Package remotedb implements the key-value database layer based on a remote geth +// node. Under the hood, it utilises the `debug_dbGet` method to implement a +// read-only database. +// There really are no guarantees in this database, since the local geth does not +// exclusive access, but it can be used for basic diagnostics of a remote node. +package remotedb + +import ( + "errors" + "strings" + + "github.com/PlatONnetwork/PlatON-Go/common/hexutil" + "github.com/PlatONnetwork/PlatON-Go/ethdb" + "github.com/PlatONnetwork/PlatON-Go/rpc" +) + +// Database is a key-value lookup for a remote database via debug_dbGet. +type Database struct { + remote *rpc.Client +} + +func (db *Database) Has(key []byte) (bool, error) { + if _, err := db.Get(key); err != nil { + return true, nil + } + return false, nil +} + +func (db *Database) Get(key []byte) ([]byte, error) { + var resp hexutil.Bytes + err := db.remote.Call(&resp, "debug_dbGet", hexutil.Bytes(key)) + if err != nil { + return nil, err + } + return resp, nil +} + +func (db *Database) HasAncient(kind string, number uint64) (bool, error) { + if _, err := db.Ancient(kind, number); err != nil { + return true, nil + } + return false, nil +} + +func (db *Database) Ancient(kind string, number uint64) ([]byte, error) { + var resp hexutil.Bytes + err := db.remote.Call(&resp, "debug_dbAncient", kind, number) + if err != nil { + return nil, err + } + return resp, nil +} + +func (db *Database) AncientRange(kind string, start, count, maxBytes uint64) ([][]byte, error) { + panic("not supported") +} + +func (db *Database) Ancients() (uint64, error) { + var resp uint64 + err := db.remote.Call(&resp, "debug_dbAncients") + return resp, err +} + +func (db *Database) Tail() (uint64, error) { + panic("not supported") +} + +func (db *Database) AncientSize(kind string) (uint64, error) { + panic("not supported") +} + +func (db *Database) ReadAncients(fn func(op ethdb.AncientReaderOp) error) (err error) { + return fn(db) +} + +func (db *Database) Put(key []byte, value []byte) error { + panic("not supported") +} + +func (db *Database) Delete(key []byte) error { + panic("not supported") +} + +func (db *Database) ModifyAncients(f func(ethdb.AncientWriteOp) error) (int64, error) { + panic("not supported") +} + +func (db *Database) TruncateHead(n uint64) error { + panic("not supported") +} + +func (db *Database) TruncateTail(n uint64) error { + panic("not supported") +} + +func (db *Database) Sync() error { + return nil +} + +func (db *Database) MigrateTable(s string, f func([]byte) ([]byte, error)) error { + panic("not supported") +} + +func (db *Database) NewBatch() ethdb.Batch { + panic("not supported") +} + +func (db *Database) NewBatchWithSize(size int) ethdb.Batch { + panic("not supported") +} + +func (db *Database) NewIterator(prefix []byte, start []byte) ethdb.Iterator { + panic("not supported") +} + +func (db *Database) Stat(property string) (string, error) { + panic("not supported") +} + +func (db *Database) AncientDatadir() (string, error) { + panic("not supported") +} + +func (db *Database) Compact(start []byte, limit []byte) error { + return nil +} + +func (db *Database) NewSnapshot() (ethdb.Snapshot, error) { + panic("not supported") +} + +func (db *Database) Close() error { + db.remote.Close() + return nil +} + +func dialRPC(endpoint string) (*rpc.Client, error) { + if endpoint == "" { + return nil, errors.New("endpoint must be specified") + } + if strings.HasPrefix(endpoint, "rpc:") || strings.HasPrefix(endpoint, "ipc:") { + // Backwards compatibility with geth < 1.5 which required + // these prefixes. + endpoint = endpoint[4:] + } + return rpc.Dial(endpoint) +} + +func New(endpoint string) (ethdb.Database, error) { + client, err := dialRPC(endpoint) + if err != nil { + return nil, err + } + return &Database{ + remote: client, + }, nil +} diff --git a/internal/ethapi/dbapi.go b/internal/ethapi/dbapi.go new file mode 100644 index 0000000000..9438ae2843 --- /dev/null +++ b/internal/ethapi/dbapi.go @@ -0,0 +1,43 @@ +// Copyright 2022 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 . + +package ethapi + +import ( + "github.com/PlatONnetwork/PlatON-Go/common" + "github.com/PlatONnetwork/PlatON-Go/common/hexutil" +) + +// DbGet returns the raw value of a key stored in the database. +func (api *PrivateDebugAPI) DbGet(key string) (hexutil.Bytes, error) { + blob, err := common.ParseHexOrString(key) + if err != nil { + return nil, err + } + return api.b.ChainDb().Get(blob) +} + +// DbAncient retrieves an ancient binary blob from the append-only immutable files. +// It is a mapping to the `AncientReaderOp.Ancient` method +func (api *PrivateDebugAPI) DbAncient(kind string, number uint64) (hexutil.Bytes, error) { + return api.b.ChainDb().Ancient(kind, number) +} + +// DbAncients returns the ancient item numbers in the ancient store. +// It is a mapping to the `AncientReaderOp.Ancients` method +func (api *PrivateDebugAPI) DbAncients() (uint64, error) { + return api.b.ChainDb().Ancients() +} diff --git a/internal/web3ext/web3ext.go b/internal/web3ext/web3ext.go index a976909400..8a47a0d7d2 100644 --- a/internal/web3ext/web3ext.go +++ b/internal/web3ext/web3ext.go @@ -384,6 +384,21 @@ web3._extend({ params: 2, inputFormatter:[web3._extend.formatters.inputBlockNumberFormatter, web3._extend.formatters.inputBlockNumberFormatter], }), + new web3._extend.Method({ + name: 'dbGet', + call: 'debug_dbGet', + params: 1 + }), + new web3._extend.Method({ + name: 'dbAncient', + call: 'debug_dbAncient', + params: 2 + }), + new web3._extend.Method({ + name: 'dbAncients', + call: 'debug_dbAncients', + params: 0 + }), new web3._extend.Method({ name: 'consensusStatus', call: 'debug_consensusStatus',