From 5ff05225c227ec6384a6171b0a6d6730ff4f8fa0 Mon Sep 17 00:00:00 2001 From: Ian Norden Date: Thu, 16 Jan 2020 17:21:49 -0600 Subject: [PATCH] begin eth api recapitulation --- pkg/super_node/eth/api.go | 81 ++++++++++++++++++++++++++++++++ pkg/super_node/eth/backend.go | 88 +++++++++++++++++++++++++++++++++++ 2 files changed, 169 insertions(+) create mode 100644 pkg/super_node/eth/api.go create mode 100644 pkg/super_node/eth/backend.go diff --git a/pkg/super_node/eth/api.go b/pkg/super_node/eth/api.go new file mode 100644 index 00000000..1537c7b2 --- /dev/null +++ b/pkg/super_node/eth/api.go @@ -0,0 +1,81 @@ +package eth + +import ( + "context" + + "github.com/ethereum/go-ethereum/core/types" + + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/rpc" +) + +// APIName is the namespace used for the state diffing service API +const APIName = "eth" + +// APIVersion is the version of the state diffing service API +const APIVersion = "0.0.1" + +type PublicEthAPI struct { + b Backend +} + +// NewPublicEthAPI creates a new PublicEthAPI with the provided underlying Backend +func NewPublicEthAPI(b Backend) *PublicEthAPI { + return &PublicEthAPI{ + b: b, + } +} + +/* +to start, need +eth_blockNumber +eth_getLogs +eth_getHeaderByNumber +*/ + +// BlockNumber returns the block number of the chain head. +func (pea *PublicEthAPI) BlockNumber() hexutil.Uint64 { + number, _ := pea.b.retriever.RetrieveLastBlockNumber() + return hexutil.Uint64(number) +} + +// GetHeaderByNumber returns the requested canonical block header. +// * When blockNr is -1 the chain head is returned. +func (pea *PublicEthAPI) GetHeaderByNumber(ctx context.Context, number rpc.BlockNumber) (map[string]interface{}, error) { + header, err := pea.b.HeaderByNumber(ctx, number) + if header != nil && err == nil { + return pea.rpcMarshalHeader(header), err + } + return nil, err +} + +// rpcMarshalHeader uses the generalized output filler, then adds the total difficulty field, which requires +// a `PublicBlockchainAPI`. +func (pea *PublicEthAPI) rpcMarshalHeader(header *types.Header) map[string]interface{} { + fields := RPCMarshalHeader(header) + fields["totalDifficulty"] = (*hexutil.Big)(pea.b.GetTd(header.Hash())) + return fields +} + +// RPCMarshalHeader converts the given header to the RPC output . +func RPCMarshalHeader(head *types.Header) map[string]interface{} { + return map[string]interface{}{ + "number": (*hexutil.Big)(head.Number), + "hash": head.Hash(), + "parentHash": head.ParentHash, + "nonce": head.Nonce, + "mixHash": head.MixDigest, + "sha3Uncles": head.UncleHash, + "logsBloom": head.Bloom, + "stateRoot": head.Root, + "miner": head.Coinbase, + "difficulty": (*hexutil.Big)(head.Difficulty), + "extraData": hexutil.Bytes(head.Extra), + "size": hexutil.Uint64(head.Size()), + "gasLimit": hexutil.Uint64(head.GasLimit), + "gasUsed": hexutil.Uint64(head.GasUsed), + "timestamp": hexutil.Uint64(head.Time), + "transactionsRoot": head.TxHash, + "receiptsRoot": head.ReceiptHash, + } +} diff --git a/pkg/super_node/eth/backend.go b/pkg/super_node/eth/backend.go new file mode 100644 index 00000000..8cc46a75 --- /dev/null +++ b/pkg/super_node/eth/backend.go @@ -0,0 +1,88 @@ +package eth + +import ( + "context" + "errors" + "fmt" + "math/big" + + "github.com/vulcanize/vulcanizedb/pkg/datastore/postgres" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" + "github.com/ethereum/go-ethereum/rpc" + "github.com/hashicorp/golang-lru" + + "github.com/vulcanize/vulcanizedb/pkg/ipfs" + "github.com/vulcanize/vulcanizedb/pkg/super_node" +) + +var ( + errPendingBlockNumber = errors.New("pending block number not supported") +) + +type Backend struct { + retriever super_node.CIDRetriever + fetcher ipfs.IPLDFetcher + db *postgres.DB + + headerCache *lru.Cache // Cache for the most recent block headers + tdCache *lru.Cache // Cache for the most recent block total difficulties + numberCache *lru.Cache // Cache for the most recent block numbers +} + +func NewEthBackend(r super_node.CIDRetriever, f ipfs.IPLDFetcher) *Backend { + return &Backend{ + retriever: r, + fetcher: f, + db: r.Database(), + } +} + +func (b *Backend) HeaderByNumber(ctx context.Context, blockNumber rpc.BlockNumber) (*types.Header, error) { + number := blockNumber.Int64() + var err error + if blockNumber == rpc.LatestBlockNumber { + number, err = b.retriever.RetrieveLastBlockNumber() + if err != nil { + return nil, err + } + } + if blockNumber == rpc.PendingBlockNumber { + return nil, errPendingBlockNumber + } + // Retrieve the CIDs for headers at this height + tx, err := b.db.Beginx() + if err != nil { + return nil, err + } + headerCids, err := b.retriever.RetrieveHeaderCIDs(tx, number) + if err != nil { + return nil, err + } + if err := tx.Commit(); err != nil { + return nil, err + } + // If there are none, throw an error + if len(headerCids) < 1 { + return nil, fmt.Errorf("header at block %d is not available", number) + } + // Fetch the header IPLDs for those CIDs + headerIPLDs, err := b.fetcher.FetchHeaders(headerCids) + if err != nil { + return nil, err + } + // Decode the first header at this block height and return it + // We throw an error in FetchHeaders() if the number of headers does not match the number of CIDs and we already + // confirmed the number of CIDs is greater than 0 so there is no need to bound check the slice before accessing + header := new(types.Header) + if err := rlp.DecodeBytes(headerIPLDs[0].RawData(), header); err != nil { + return nil, err + } + return header, nil +} + +func (b *Backend) GetTd(blockHash common.Hash) *big.Int { + panic("implement me") +}