// VulcanizeDB // Copyright © 2019 Vulcanize // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // This program 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 Affero General Public License for more details. // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . package eth import ( "fmt" "strconv" "github.com/cerc-io/ipld-eth-server/v4/pkg/shared" "github.com/ethereum/go-ethereum/statediff/trie_helpers" sdtypes "github.com/ethereum/go-ethereum/statediff/types" "github.com/jmoiron/sqlx" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) const ( RetrieveHeaderByHashPgStr = `SELECT cid, data FROM eth.header_cids INNER JOIN public.blocks ON ( header_cids.mh_key = blocks.key AND header_cids.block_number = blocks.block_number ) WHERE block_hash = $1` RetrieveUnclesPgStr = `SELECT uncle_cids.cid, data FROM eth.uncle_cids INNER JOIN eth.header_cids ON ( uncle_cids.header_id = header_cids.block_hash AND uncle_cids.block_number = header_cids.block_number ) INNER JOIN public.blocks ON ( uncle_cids.mh_key = blocks.key AND uncle_cids.block_number = blocks.block_number ) WHERE header_cids.block_hash = $1 AND header_cids.block_number = $2 ORDER BY uncle_cids.parent_hash` RetrieveUnclesByBlockHashPgStr = `SELECT uncle_cids.cid, data FROM eth.uncle_cids INNER JOIN eth.header_cids ON ( uncle_cids.header_id = header_cids.block_hash AND uncle_cids.block_number = header_cids.block_number ) INNER JOIN public.blocks ON ( uncle_cids.mh_key = blocks.key AND uncle_cids.block_number = blocks.block_number ) WHERE header_cids.block_hash = $1 ORDER BY uncle_cids.parent_hash` RetrieveTransactionsPgStr = `SELECT transaction_cids.cid, data FROM eth.transaction_cids INNER JOIN eth.header_cids ON ( transaction_cids.header_id = header_cids.block_hash AND transaction_cids.block_number = header_cids.block_number ) INNER JOIN public.blocks ON ( transaction_cids.mh_key = blocks.key AND transaction_cids.block_number = blocks.block_number ) WHERE block_hash = $1 AND header_cids.block_number = $2 ORDER BY eth.transaction_cids.index ASC` RetrieveTransactionsByBlockHashPgStr = `SELECT transaction_cids.cid, data FROM eth.transaction_cids INNER JOIN eth.header_cids ON ( transaction_cids.header_id = header_cids.block_hash AND transaction_cids.block_number = header_cids.block_number ) INNER JOIN public.blocks ON ( transaction_cids.mh_key = blocks.key AND transaction_cids.block_number = blocks.block_number ) WHERE block_hash = $1 ORDER BY eth.transaction_cids.index ASC` RetrieveReceiptsPgStr = `SELECT receipt_cids.leaf_cid, data, eth.transaction_cids.tx_hash FROM eth.receipt_cids INNER JOIN eth.transaction_cids ON ( receipt_cids.tx_id = transaction_cids.tx_hash AND receipt_cids.header_id = transaction_cids.header_id AND receipt_cids.block_number = transaction_cids.block_number ) INNER JOIN eth.header_cids ON ( transaction_cids.header_id = header_cids.block_hash AND transaction_cids.block_number = header_cids.block_number ) INNER JOIN public.blocks ON ( receipt_cids.leaf_mh_key = blocks.key AND receipt_cids.block_number = blocks.block_number ) WHERE block_hash = $1 AND header_cids.block_number = $2 ORDER BY eth.transaction_cids.index ASC` RetrieveReceiptsByBlockHashPgStr = `SELECT receipt_cids.leaf_cid, data, eth.transaction_cids.tx_hash FROM eth.receipt_cids INNER JOIN eth.transaction_cids ON ( receipt_cids.tx_id = transaction_cids.tx_hash AND receipt_cids.header_id = transaction_cids.header_id AND receipt_cids.block_number = transaction_cids.block_number ) INNER JOIN eth.header_cids ON ( transaction_cids.header_id = header_cids.block_hash AND transaction_cids.block_number = header_cids.block_number ) INNER JOIN public.blocks ON ( receipt_cids.leaf_mh_key = blocks.key AND receipt_cids.block_number = blocks.block_number ) WHERE block_hash = $1 ORDER BY eth.transaction_cids.index ASC` RetrieveAccountByLeafKeyAndBlockHashPgStr = `SELECT state_cids.cid, state_cids.mh_key, state_cids.block_number, state_cids.node_type FROM eth.state_cids INNER JOIN eth.header_cids ON ( state_cids.header_id = header_cids.block_hash AND state_cids.block_number = header_cids.block_number ) WHERE state_leaf_key = $1 AND header_cids.block_number <= (SELECT block_number FROM eth.header_cids WHERE block_hash = $2) AND header_cids.block_hash = (SELECT canonical_header_hash(header_cids.block_number)) ORDER BY header_cids.block_number DESC LIMIT 1` RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockHashPgStr = `SELECT cid, mh_key, block_number, node_type, state_leaf_removed FROM get_storage_at_by_hash($1, $2, $3)` ) var EmptyNodeValue = make([]byte, common.HashLength) type rctIpldResult struct { LeafCID string `db:"leaf_cid"` Data []byte `db:"data"` TxHash string `db:"tx_hash"` } type ipldResult struct { CID string `db:"cid"` Data []byte `db:"data"` TxHash string `db:"tx_hash"` } type IPLDRetriever struct { db *sqlx.DB } func NewIPLDRetriever(db *sqlx.DB) *IPLDRetriever { return &IPLDRetriever{ db: db, } } // RetrieveHeaderByHash returns the cid and rlp bytes for the header corresponding to the provided block hash func (r *IPLDRetriever) RetrieveHeaderByHash(tx *sqlx.Tx, hash common.Hash) (string, []byte, error) { headerResult := new(ipldResult) return headerResult.CID, headerResult.Data, tx.Get(headerResult, RetrieveHeaderByHashPgStr, hash.Hex()) } // RetrieveUncles returns the cids and rlp bytes for the uncles corresponding to the provided block hash, number (of non-omner root block) func (r *IPLDRetriever) RetrieveUncles(tx *sqlx.Tx, hash common.Hash, number uint64) ([]string, [][]byte, error) { uncleResults := make([]ipldResult, 0) if err := tx.Select(&uncleResults, RetrieveUnclesPgStr, hash.Hex(), number); err != nil { return nil, nil, err } cids := make([]string, len(uncleResults)) uncles := make([][]byte, len(uncleResults)) for i, res := range uncleResults { cids[i] = res.CID uncles[i] = res.Data } return cids, uncles, nil } // RetrieveUnclesByBlockHash returns the cids and rlp bytes for the uncles corresponding to the provided block hash (of non-omner root block) func (r *IPLDRetriever) RetrieveUnclesByBlockHash(tx *sqlx.Tx, hash common.Hash) ([]string, [][]byte, error) { uncleResults := make([]ipldResult, 0) if err := tx.Select(&uncleResults, RetrieveUnclesByBlockHashPgStr, hash.Hex()); err != nil { return nil, nil, err } cids := make([]string, len(uncleResults)) uncles := make([][]byte, len(uncleResults)) for i, res := range uncleResults { cids[i] = res.CID uncles[i] = res.Data } return cids, uncles, nil } // RetrieveTransactions returns the cids and rlp bytes for the transactions corresponding to the provided block hash, number func (r *IPLDRetriever) RetrieveTransactions(tx *sqlx.Tx, hash common.Hash, number uint64) ([]string, [][]byte, error) { txResults := make([]ipldResult, 0) if err := tx.Select(&txResults, RetrieveTransactionsPgStr, hash.Hex(), number); err != nil { return nil, nil, err } cids := make([]string, len(txResults)) txs := make([][]byte, len(txResults)) for i, res := range txResults { cids[i] = res.CID txs[i] = res.Data } return cids, txs, nil } // RetrieveTransactionsByBlockHash returns the cids and rlp bytes for the transactions corresponding to the provided block hash func (r *IPLDRetriever) RetrieveTransactionsByBlockHash(tx *sqlx.Tx, hash common.Hash) ([]string, [][]byte, error) { txResults := make([]ipldResult, 0) if err := tx.Select(&txResults, RetrieveTransactionsByBlockHashPgStr, hash.Hex()); err != nil { return nil, nil, err } cids := make([]string, len(txResults)) txs := make([][]byte, len(txResults)) for i, res := range txResults { cids[i] = res.CID txs[i] = res.Data } return cids, txs, nil } // DecodeLeafNode decodes the leaf node data func DecodeLeafNode(node []byte) ([]byte, error) { var nodeElements []interface{} if err := rlp.DecodeBytes(node, &nodeElements); err != nil { return nil, err } ty, err := trie_helpers.CheckKeyType(nodeElements) if err != nil { return nil, err } if ty != sdtypes.Leaf { return nil, fmt.Errorf("expected leaf node but found %s", ty) } return nodeElements[1].([]byte), nil } // RetrieveReceipts returns the cids and rlp bytes for the receipts corresponding to the provided block hash, number. // cid returned corresponds to the leaf node data which contains the receipt. func (r *IPLDRetriever) RetrieveReceipts(tx *sqlx.Tx, hash common.Hash, number uint64) ([]string, [][]byte, []common.Hash, error) { rctResults := make([]rctIpldResult, 0) if err := tx.Select(&rctResults, RetrieveReceiptsPgStr, hash.Hex(), number); err != nil { return nil, nil, nil, err } cids := make([]string, len(rctResults)) rcts := make([][]byte, len(rctResults)) txs := make([]common.Hash, len(rctResults)) for i, res := range rctResults { cids[i] = res.LeafCID nodeVal, err := DecodeLeafNode(res.Data) if err != nil { return nil, nil, nil, err } rcts[i] = nodeVal txs[i] = common.HexToHash(res.TxHash) } return cids, rcts, txs, nil } // RetrieveReceiptsByBlockHash returns the cids and rlp bytes for the receipts corresponding to the provided block hash. // cid returned corresponds to the leaf node data which contains the receipt. func (r *IPLDRetriever) RetrieveReceiptsByBlockHash(tx *sqlx.Tx, hash common.Hash) ([]string, [][]byte, []common.Hash, error) { rctResults := make([]rctIpldResult, 0) if err := tx.Select(&rctResults, RetrieveReceiptsByBlockHashPgStr, hash.Hex()); err != nil { return nil, nil, nil, err } cids := make([]string, len(rctResults)) rcts := make([][]byte, len(rctResults)) txs := make([]common.Hash, len(rctResults)) for i, res := range rctResults { cids[i] = res.LeafCID nodeVal, err := DecodeLeafNode(res.Data) if err != nil { return nil, nil, nil, err } rcts[i] = nodeVal txs[i] = common.HexToHash(res.TxHash) } return cids, rcts, txs, nil } type nodeInfo struct { CID string `db:"cid"` MhKey string `db:"mh_key"` BlockNumber string `db:"block_number"` Data []byte `db:"data"` NodeType int `db:"node_type"` StateLeafRemoved bool `db:"state_leaf_removed"` } // RetrieveAccountByAddressAndBlockHash returns the cid and rlp bytes for the account corresponding to the provided address and block hash // TODO: ensure this handles deleted accounts appropriately func (r *IPLDRetriever) RetrieveAccountByAddressAndBlockHash(address common.Address, hash common.Hash) (string, []byte, error) { accountResult := new(nodeInfo) leafKey := crypto.Keccak256Hash(address.Bytes()) if err := r.db.Get(accountResult, RetrieveAccountByLeafKeyAndBlockHashPgStr, leafKey.Hex(), hash.Hex()); err != nil { return "", nil, err } if accountResult.NodeType == sdtypes.Removed.Int() { return "", EmptyNodeValue, nil } blockNumber, err := strconv.ParseUint(accountResult.BlockNumber, 10, 64) if err != nil { return "", nil, err } accountResult.Data, err = shared.FetchIPLD(r.db, accountResult.MhKey, blockNumber) if err != nil { return "", nil, err } var i []interface{} if err := rlp.DecodeBytes(accountResult.Data, &i); err != nil { return "", nil, fmt.Errorf("error decoding state leaf node rlp: %s", err.Error()) } if len(i) != 2 { return "", nil, fmt.Errorf("eth IPLDRetriever expected state leaf node rlp to decode into two elements") } return accountResult.CID, i[1].([]byte), nil } // RetrieveStorageAtByAddressAndStorageSlotAndBlockHash returns the cid and rlp bytes for the storage value corresponding to the provided address, storage slot, and block hash func (r *IPLDRetriever) RetrieveStorageAtByAddressAndStorageSlotAndBlockHash(address common.Address, key, hash common.Hash) (string, []byte, []byte, error) { storageResult := new(nodeInfo) stateLeafKey := crypto.Keccak256Hash(address.Bytes()) storageHash := crypto.Keccak256Hash(key.Bytes()) if err := r.db.Get(storageResult, RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockHashPgStr, stateLeafKey.Hex(), storageHash.Hex(), hash.Hex()); err != nil { return "", nil, nil, err } if storageResult.StateLeafRemoved || storageResult.NodeType == sdtypes.Removed.Int() { return "", EmptyNodeValue, EmptyNodeValue, nil } blockNumber, err := strconv.ParseUint(storageResult.BlockNumber, 10, 64) if err != nil { return "", nil, nil, err } storageResult.Data, err = shared.FetchIPLD(r.db, storageResult.MhKey, blockNumber) if err != nil { return "", nil, nil, err } var i []interface{} if err := rlp.DecodeBytes(storageResult.Data, &i); err != nil { err = fmt.Errorf("error decoding storage leaf node rlp: %s", err.Error()) return "", nil, nil, err } if len(i) != 2 { return "", nil, nil, fmt.Errorf("eth IPLDRetriever expected storage leaf node rlp to decode into two elements") } return storageResult.CID, storageResult.Data, i[1].([]byte), nil }