diff --git a/cmd/rpcdaemon/commands/daemon.go b/cmd/rpcdaemon/commands/daemon.go index 746c2a97d..fecdc8680 100644 --- a/cmd/rpcdaemon/commands/daemon.go +++ b/cmd/rpcdaemon/commands/daemon.go @@ -1,6 +1,7 @@ package commands import ( + "bytes" "context" "encoding/binary" "fmt" @@ -11,8 +12,11 @@ import ( "github.com/ledgerwatch/turbo-geth/common" "github.com/ledgerwatch/turbo-geth/common/dbutils" + "github.com/ledgerwatch/turbo-geth/core/types" "github.com/ledgerwatch/turbo-geth/ethdb/remote" + "github.com/ledgerwatch/turbo-geth/internal/ethapi" "github.com/ledgerwatch/turbo-geth/log" + "github.com/ledgerwatch/turbo-geth/rlp" "github.com/ledgerwatch/turbo-geth/rpc" ) @@ -29,6 +33,7 @@ func splitAndTrim(input string) []string { // EthAPI is a collection of functions that are exposed in the type EthAPI interface { BlockNumber(ctx context.Context) (uint64, error) + GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) } // APIImpl is implementation of the EthAPI interface based on remote Db access @@ -89,6 +94,138 @@ func (api *APIImpl) BlockNumber(ctx context.Context) (uint64, error) { return blockNumber, nil } +// GetBlockByNumber see https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getblockbynumber +// see internal/ethapi.PublicBlockChainAPI.GetBlockByNumber +func (api *APIImpl) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { + if err := api.ensureConnected(); err != nil { + return nil, err + } + + var block *types.Block + + if err := api.db.View(func(tx *remote.Tx) error { + block = GetBlockByNumber(tx, uint64(number.Int64())) + return nil + }); err != nil { + api.db.Close() + api.db = nil + return nil, err + } + fmt.Printf("Debug: %#v %#v %#v\n", block.Number(), block.Hash(), block.Size()) + + if block != nil { + response, err := api.rpcMarshalBlock(block, true, fullTx) + if err == nil && number == rpc.PendingBlockNumber { + // Pending blocks need to nil out a few fields + for _, field := range []string{"hash", "nonce", "miner"} { + response[field] = nil + } + } + return response, err + } + return nil, nil +} + +// rpcMarshalBlock reimplementation of ethapi.rpcMarshalBlock +func (api *APIImpl) rpcMarshalBlock(b *types.Block, inclTx bool, fullTx bool) (map[string]interface{}, error) { + fields, err := ethapi.RPCMarshalBlock(b, inclTx, fullTx) + if err != nil { + return nil, err + } + // TODO: implement .GetTd method and uncomment next line + //fields["totalDifficulty"] = (*hexutil.Big)(s.b.GetTd(b.Hash())) + return fields, err +} + +// ReadCanonicalHash reimplementation of rawdb.ReadCanonicalHash +func ReadCanonicalHash(tx *remote.Tx, number uint64) common.Hash { + bucket := tx.Bucket(dbutils.HeaderPrefix) + if bucket == nil { + return common.Hash{} + //return fmt.Errorf("bucket %s not found", dbutils.HeaderPrefix) + } + + data := bucket.Get(dbutils.HeaderHashKey(number)) + if len(data) == 0 { + return common.Hash{} + } + return common.BytesToHash(data) +} + +// GetBlockByNumber reimplementation of chain.GetBlockByNumber +func GetBlockByNumber(tx *remote.Tx, number uint64) *types.Block { + hash := ReadCanonicalHash(tx, number) + if hash == (common.Hash{}) { + return nil + } + return ReadBlock(tx, hash, number) +} + +// ReadBlock reimplementation of rawdb.ReadBlock +func ReadBlock(tx *remote.Tx, hash common.Hash, number uint64) *types.Block { + header := ReadHeader(tx, hash, number) + if header == nil { + return nil + } + body := ReadBody(tx, hash, number) + if body == nil { + return nil + } + return types.NewBlockWithHeader(header).WithBody(body.Transactions, body.Uncles) +} + +// ReadBlock reimplementation of rawdb.ReadBlock +func ReadHeaderRLP(tx *remote.Tx, hash common.Hash, number uint64) rlp.RawValue { + bucket := tx.Bucket(dbutils.HeaderPrefix) + if bucket == nil { + //return fmt.Errorf("bucket %s not found", dbutils.HeaderPrefix) + log.Error("Bucket not founc", "error", dbutils.HeaderPrefix) + return rlp.RawValue{} + } + return bucket.Get(dbutils.HeaderKey(number, hash)) +} + +// ReadHeader reimplementation of rawdb.ReadHeader +func ReadHeader(tx *remote.Tx, hash common.Hash, number uint64) *types.Header { + data := ReadHeaderRLP(tx, hash, number) + if len(data) == 0 { + return nil + } + header := new(types.Header) + if err := rlp.Decode(bytes.NewReader(data), header); err != nil { + log.Error("Invalid block header RLP", "hash", hash, "err", err) + return nil + } + return header +} + +// ReadBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. +func ReadBodyRLP(tx *remote.Tx, hash common.Hash, number uint64) rlp.RawValue { + bucket := tx.Bucket(dbutils.BlockBodyPrefix) + if bucket == nil { + //return fmt.Errorf("bucket %s not found", dbutils.HeaderPrefix) + log.Error("Bucket not founc", "error", dbutils.BlockBodyPrefix) + return rlp.RawValue{} + } + return bucket.Get(dbutils.BlockBodyKey(number, hash)) +} + +// ReadBody reimplementation of rawdb.ReadBody +func ReadBody(tx *remote.Tx, hash common.Hash, number uint64) *types.Body { + data := ReadBodyRLP(tx, hash, number) + if len(data) == 0 { + return nil + } + body := new(types.Body) + if err := rlp.Decode(bytes.NewReader(data), body); err != nil { + log.Error("Invalid block body RLP", "hash", hash, "err", err) + return nil + } + // Post-processing + body.SendersToTxs() + return body +} + func daemon(cfg Config) { vhosts := splitAndTrim(cfg.rpcVirtualHost) cors := splitAndTrim(cfg.rpcCORSDomain) diff --git a/cmd/rpcdaemon/test.http b/cmd/rpcdaemon/test.http new file mode 100644 index 000000000..16a5ed663 --- /dev/null +++ b/cmd/rpcdaemon/test.http @@ -0,0 +1,18 @@ +### + +# curl -X POST -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"eth_getBlockByNumber", "params": ["0x1b4", true], "id":1}' localhost:8545 +POST localhost:8545 +Content-Type: application/json + +{ + "jsonrpc": "2.0", + "method": "eth_getBlockByNumber", + "params": [ + "0x1b4", + true + ], + "id": 1 +} + +### +