fix encoding when storage is empty #94
@ -1,4 +1,20 @@
|
||||
-- +goose Up
|
||||
-- +goose StatementBegin
|
||||
-- returns if a state leaf node was removed within the provided block number
|
||||
CREATE OR REPLACE FUNCTION was_state_leaf_removed(key character varying, hash character varying)
|
||||
RETURNS boolean AS $$
|
||||
SELECT state_cids.node_type = 3
|
||||
FROM eth.state_cids
|
||||
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
|
||||
WHERE state_leaf_key = key
|
||||
AND block_number <= (SELECT block_number
|
||||
FROM eth.header_cids
|
||||
WHERE block_hash = hash)
|
||||
ORDER BY block_number DESC LIMIT 1;
|
||||
$$
|
||||
language sql;
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose StatementBegin
|
||||
CREATE TYPE child_result AS (
|
||||
has_child BOOLEAN,
|
||||
@ -115,6 +131,7 @@ LANGUAGE 'plpgsql';
|
||||
-- +goose StatementEnd
|
||||
|
||||
-- +goose Down
|
||||
DROP FUNCTION was_state_leaf_removed;
|
||||
DROP FUNCTION canonical_header_id;
|
||||
DROP FUNCTION canonical_header_from_array;
|
||||
DROP FUNCTION has_child;
|
||||
|
@ -20,7 +20,7 @@ services:
|
||||
restart: on-failure
|
||||
depends_on:
|
||||
- db
|
||||
image: vulcanize/statediff-migrations:v0.7.0
|
||||
image: vulcanize/statediff-migrations:v0.8.0
|
||||
environment:
|
||||
DATABASE_USER: vdbm
|
||||
DATABASE_NAME: vulcanize_public
|
||||
@ -39,6 +39,7 @@ services:
|
||||
- vdb_db_eth_server:/var/lib/postgresql/data
|
||||
ports:
|
||||
- "127.0.0.1:8077:5432"
|
||||
command: ["postgres", "-c", "log_statement=all"]
|
||||
|
||||
eth-server:
|
||||
restart: unless-stopped
|
||||
@ -61,6 +62,7 @@ services:
|
||||
DATABASE_USER: "vdbm"
|
||||
DATABASE_PASSWORD: "password"
|
||||
ETH_CHAIN_ID: 4
|
||||
RUN_DB_MIGRATION: "no"
|
||||
volumes:
|
||||
- type: bind
|
||||
source: ./chain.json
|
||||
|
@ -4,6 +4,9 @@
|
||||
# Construct the connection string for postgres
|
||||
VDB_PG_CONNECT=postgresql://$DATABASE_USER:$DATABASE_PASSWORD@$DATABASE_HOSTNAME:$DATABASE_PORT/$DATABASE_NAME?sslmode=disable
|
||||
|
||||
|
||||
if [ "$RUN_DB_MIGRATION" != "no" ]
|
||||
then
|
||||
# Run the DB migrations
|
||||
echo "Connecting with: $VDB_PG_CONNECT"
|
||||
echo "Running database migrations"
|
||||
@ -14,7 +17,7 @@ if [ $rv != 0 ]; then
|
||||
echo "Could not run migrations. Are the database details correct?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
echo "Beginning the ipld-eth-server process"
|
||||
|
||||
|
@ -476,16 +476,16 @@ var _ = Describe("eth state reading tests", func() {
|
||||
It("Returns empty slice if it tries to access a contract which does not exist", func() {
|
||||
storage, err := api.GetStorageAt(ctx, test_helpers.ContractAddr, test_helpers.ContractSlotKeyHash.Hex(), rpc.BlockNumberOrHashWithNumber(0))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(storage).To(Equal(hexutil.Bytes(make([]byte, 32))))
|
||||
Expect(storage).To(Equal(hexutil.Bytes(eth.EmptyNodeValue)))
|
||||
|
||||
storage, err = api.GetStorageAt(ctx, test_helpers.ContractAddr, test_helpers.ContractSlotKeyHash.Hex(), rpc.BlockNumberOrHashWithNumber(1))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(storage).To(Equal(hexutil.Bytes(make([]byte, 32))))
|
||||
Expect(storage).To(Equal(hexutil.Bytes(eth.EmptyNodeValue)))
|
||||
})
|
||||
It("Returns empty slice if it tries to access a contract slot which does not exist", func() {
|
||||
storage, err := api.GetStorageAt(ctx, test_helpers.ContractAddr, randomHash.Hex(), rpc.BlockNumberOrHashWithNumber(2))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(storage).To(Equal(hexutil.Bytes(make([]byte, 32))))
|
||||
Expect(storage).To(Equal(hexutil.Bytes(eth.EmptyNodeValue)))
|
||||
})
|
||||
It("Retrieves the storage value at the provided contract address and storage leaf key at the block with the provided hash or number", func() {
|
||||
// After deployment
|
||||
@ -506,7 +506,7 @@ var _ = Describe("eth state reading tests", func() {
|
||||
|
||||
val, err = api.GetStorageAt(ctx, test_helpers.ContractAddr, test_helpers.IndexOne, rpc.BlockNumberOrHashWithNumber(5))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
Expect(val).To(Equal(hexutil.Bytes{}))
|
||||
Expect(val).To(Equal(hexutil.Bytes(eth.EmptyNodeValue)))
|
||||
})
|
||||
It("Throws an error for a non-existing block hash", func() {
|
||||
_, err := api.GetStorageAt(ctx, test_helpers.ContractAddr, test_helpers.IndexOne, rpc.BlockNumberOrHashWithHash(randomHash, true))
|
||||
|
@ -127,7 +127,7 @@ const (
|
||||
AND block_number <= $2
|
||||
ORDER BY block_number DESC
|
||||
LIMIT 1`
|
||||
RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockNumberPgStr = `SELECT storage_cids.cid, data, storage_cids.node_type
|
||||
RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockNumberPgStr = `SELECT storage_cids.cid, data, storage_cids.node_type, was_state_leaf_removed($1, $3) AS state_leaf_removed
|
||||
FROM eth.storage_cids
|
||||
INNER JOIN eth.state_cids ON (storage_cids.state_id = state_cids.id)
|
||||
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
|
||||
@ -137,7 +137,7 @@ const (
|
||||
AND block_number <= $3
|
||||
ORDER BY block_number DESC
|
||||
LIMIT 1`
|
||||
RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockHashPgStr = `SELECT storage_cids.cid, data, storage_cids.node_type
|
||||
RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockHashPgStr = `SELECT storage_cids.cid, data, storage_cids.node_type, was_state_leaf_removed($1, $3) AS state_leaf_removed
|
||||
FROM eth.storage_cids
|
||||
INNER JOIN eth.state_cids ON (storage_cids.state_id = state_cids.id)
|
||||
INNER JOIN eth.header_cids ON (state_cids.header_id = header_cids.id)
|
||||
@ -152,6 +152,8 @@ const (
|
||||
LIMIT 1`
|
||||
)
|
||||
|
||||
var EmptyNodeValue = make([]byte, common.HashLength)
|
||||
|
||||
type rctIpldResult struct {
|
||||
LeafCID string `db:"leaf_cid"`
|
||||
Data []byte `db:"data"`
|
||||
@ -428,6 +430,7 @@ type nodeInfo struct {
|
||||
CID string `db:"cid"`
|
||||
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
|
||||
@ -438,9 +441,11 @@ func (r *IPLDRetriever) RetrieveAccountByAddressAndBlockHash(address common.Addr
|
||||
if err := r.db.Get(accountResult, RetrieveAccountByLeafKeyAndBlockHashPgStr, leafKey.Hex(), hash.Hex()); err != nil {
|
||||
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if accountResult.NodeType == removedNode {
|
||||
return "", []byte{}, nil
|
||||
return "", EmptyNodeValue, nil
|
||||
}
|
||||
|
||||
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())
|
||||
@ -459,9 +464,11 @@ func (r *IPLDRetriever) RetrieveAccountByAddressAndBlockNumber(address common.Ad
|
||||
if err := r.db.Get(accountResult, RetrieveAccountByLeafKeyAndBlockNumberPgStr, leafKey.Hex(), number); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
|
||||
if accountResult.NodeType == removedNode {
|
||||
return "", []byte{}, nil
|
||||
return "", EmptyNodeValue, nil
|
||||
}
|
||||
|
||||
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())
|
||||
@ -480,8 +487,8 @@ func (r *IPLDRetriever) RetrieveStorageAtByAddressAndStorageSlotAndBlockHash(add
|
||||
if err := r.db.Get(storageResult, RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockHashPgStr, stateLeafKey.Hex(), storageHash.Hex(), hash.Hex()); err != nil {
|
||||
return "", nil, nil, err
|
||||
}
|
||||
if storageResult.NodeType == removedNode {
|
||||
return "", []byte{}, []byte{}, nil
|
||||
if storageResult.StateLeafRemoved || storageResult.NodeType == removedNode {
|
||||
return "", EmptyNodeValue, EmptyNodeValue, nil
|
||||
}
|
||||
var i []interface{}
|
||||
if err := rlp.DecodeBytes(storageResult.Data, &i); err != nil {
|
||||
@ -502,8 +509,9 @@ func (r *IPLDRetriever) RetrieveStorageAtByAddressAndStorageKeyAndBlockNumber(ad
|
||||
if err := r.db.Get(storageResult, RetrieveStorageLeafByAddressHashAndLeafKeyAndBlockNumberPgStr, stateLeafKey.Hex(), storageLeafKey.Hex(), number); err != nil {
|
||||
return "", nil, err
|
||||
}
|
||||
if storageResult.NodeType == removedNode {
|
||||
return "", []byte{}, nil
|
||||
|
||||
if storageResult.StateLeafRemoved || storageResult.NodeType == removedNode {
|
||||
return "", EmptyNodeValue, nil
|
||||
}
|
||||
var i []interface{}
|
||||
if err := rlp.DecodeBytes(storageResult.Data, &i); err != nil {
|
||||
|
@ -18,6 +18,7 @@
|
||||
package graphql
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
@ -31,6 +32,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/eth/filters"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/rpc"
|
||||
|
||||
"github.com/vulcanize/ipld-eth-server/pkg/eth"
|
||||
)
|
||||
|
||||
@ -1011,6 +1013,10 @@ func (r *Resolver) GetStorageAt(ctx context.Context, args struct {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if bytes.Compare(rlpValue, eth.EmptyNodeValue) == 0 {
|
||||
return &StorageResult{value: eth.EmptyNodeValue, cid: cid, ipldBlock: ipldBlock}, nil
|
||||
}
|
||||
|
||||
var value interface{}
|
||||
err = rlp.DecodeBytes(rlpValue, &value)
|
||||
if err != nil {
|
||||
|
@ -5,8 +5,8 @@ set -o xtrace
|
||||
docker-compose down --remove-orphans --volumes
|
||||
|
||||
# Build and start the containers.
|
||||
# Note: Build only if `ipld-eth-server` code is modified. Otherwise comment this line.
|
||||
docker build -t ipld-eth-server_eth-server:latest .
|
||||
# Note: Build only if `ipld-eth-server` or other container code is modified. Otherwise comment this line.
|
||||
docker-compose -f docker-compose.test.yml -f docker-compose.yml build eth-server
|
||||
docker-compose -f docker-compose.test.yml -f docker-compose.yml up -d db dapptools contract eth-server
|
||||
|
||||
export PGPASSWORD=password
|
||||
|
@ -4,4 +4,7 @@ contract GLDToken is ERC20 {
|
||||
constructor() ERC20("Gold", "GLD") {
|
||||
_mint(msg.sender, 1000000000000000000000);
|
||||
}
|
||||
function destroy() public {
|
||||
selfdestruct(payable(msg.sender));
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
const fastify = require('fastify')({ logger: true });
|
||||
const hre = require("hardhat");
|
||||
|
||||
|
||||
// readiness check
|
||||
fastify.get('/v1/healthz', async (req, reply) => {
|
||||
reply
|
||||
@ -22,6 +23,20 @@ fastify.get('/v1/deployContract', async (req, reply) => {
|
||||
}
|
||||
});
|
||||
|
||||
fastify.get('/v1/destroyContract', async (req, reply) => {
|
||||
const addr = req.query.addr;
|
||||
|
||||
const Token = await hre.ethers.getContractFactory("GLDToken");
|
||||
const token = await Token.attach(addr);
|
||||
|
||||
await token.destroy();
|
||||
const blockNum = await hre.ethers.provider.getBlockNumber()
|
||||
|
||||
return {
|
||||
blockNumber: blockNum,
|
||||
}
|
||||
})
|
||||
|
||||
fastify.get('/v1/sendEth', async (req, reply) => {
|
||||
const to = req.query.to;
|
||||
const value = req.query.value;
|
||||
|
@ -14,6 +14,10 @@ type ContractDeployed struct {
|
||||
BlockHash string `json:"blockHash"`
|
||||
}
|
||||
|
||||
type ContractDestroyed struct {
|
||||
BlockNumber int64 `json:"blockNumber"`
|
||||
}
|
||||
|
||||
type Tx struct {
|
||||
From string `json:"from"`
|
||||
To string `json:"to"`
|
||||
@ -43,6 +47,19 @@ func DeployContract() (*ContractDeployed, error) {
|
||||
return &contract, nil
|
||||
}
|
||||
|
||||
func DestroyContract(addr string) (*ContractDestroyed, error) {
|
||||
res, err := http.Get(fmt.Sprintf("%s/v1/destroyContract?addr=%s", srvUrl, addr))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
|
||||
var data ContractDestroyed
|
||||
decoder := json.NewDecoder(res.Body)
|
||||
|
||||
return &data, decoder.Decode(&data)
|
||||
}
|
||||
|
||||
func SendEth(to string, value string) (*Tx, error) {
|
||||
res, err := http.Get(fmt.Sprintf("%s/v1/sendEth?to=%s&value=%s", srvUrl, to, value))
|
||||
if err != nil {
|
||||
|
@ -9,12 +9,13 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
integration "github.com/vulcanize/ipld-eth-server/test"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/vulcanize/ipld-eth-server/pkg/eth"
|
||||
integration "github.com/vulcanize/ipld-eth-server/test"
|
||||
)
|
||||
|
||||
const nonExistingBlockHash = "0x111111111111111111111111111111111111111111111111111111111111111"
|
||||
@ -391,6 +392,37 @@ var _ = Describe("Integration test", func() {
|
||||
Expect(err).To(MatchError("header not found"))
|
||||
Expect(gethStorage).To(Equal(ipldStorage))
|
||||
})
|
||||
|
||||
It("get storage after self destruct", func() {
|
||||
totalSupplyIndex := "0x2"
|
||||
|
||||
tx, err := integration.DestroyContract(contract.Address)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
time.Sleep(sleepInterval)
|
||||
|
||||
gethStorage1, err := gethClient.StorageAt(ctx, common.HexToAddress(contract.Address), common.HexToHash(totalSupplyIndex), big.NewInt(tx.BlockNumber-1))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
gethStorage2, err := gethClient.StorageAt(ctx, common.HexToAddress(contract.Address), common.HexToHash(totalSupplyIndex), big.NewInt(tx.BlockNumber))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(gethStorage1).NotTo(Equal(gethStorage2))
|
||||
Expect(gethStorage2).To(Equal(eth.EmptyNodeValue))
|
||||
|
||||
ipldStorage1, err := ipldClient.StorageAt(ctx, common.HexToAddress(contract.Address), common.HexToHash(totalSupplyIndex), big.NewInt(tx.BlockNumber-1))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
ipldStorage2, err := ipldClient.StorageAt(ctx, common.HexToAddress(contract.Address), common.HexToHash(totalSupplyIndex), big.NewInt(tx.BlockNumber))
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(ipldStorage1).To(Equal(gethStorage1))
|
||||
Expect(ipldStorage2).To(Equal(gethStorage2))
|
||||
|
||||
// Query the current block
|
||||
ipldStorage3, err := ipldClient.StorageAt(ctx, common.HexToAddress(contract.Address), common.HexToHash(totalSupplyIndex), nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
|
||||
Expect(ipldStorage2).To(Equal(ipldStorage3))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("eth call", func() {
|
||||
|
Loading…
Reference in New Issue
Block a user
Just to note, we should only ever see a zero value for a node that is not "Removed" if we are at a block height prior to EIP-158 activation (which is when zeroed value pruning begun). Does this make sense, and align with what we are seeing?
I think we need the analogous check in the account retrieval methods above (
RetrieveAccountByAddressAndBlockNumber
andRetrieveAccountByAddressAndBlockHash
) and the other storage retrieval method below (RetrieveStorageAtByAddressAndStorageKeyAndBlockNumber
).We're running a local/private chain for which the value in genesis is
"eip158Block": 0,
, so this isn't consistent with what we expect?Where is
RetrieveAccountByAddressAndBlockNumber
function called? I couldn't find it. And how to test account deleting?It makes sense but doesn't align with current behavior. Isn't this misalignment was introduced here in this PR https://github.com/vulcanize/go-ethereum/pull/58 ?
Hmm I need to think about this some more, sorry, let me get back.
I think we want to fix the function even if it isn't currently called. We could test account deleting similar to how we do it here: https://github.com/vulcanize/go-ethereum/blob/v1.10.7-statediff/statediff/builder_test.go#L1318
If that is the cause of the misalignment then all we need to do is check that the node isn't a "Removed" type when we query for the latest value at a given path/key, which I think we are already doing?
@arijitAD is taking over here, to echo what I told him in slack:
First thing to do is verify that we are excluding "Removed" type nodes when we perform those queries.
If we arent doing that then we are actually just seeing null values where they are supposed to be, and need to add a check to filter those out
But I think we are already filtering out "Removed" nodes in those queries, and if we are already doing that then there is some deeper reason as to why we are seeing null values for nodes that are not of the "Removed" type
Another possible lead is how we (and geth) are distinguishing (or not) between true nulls and legitimate zero values
On the other side of this coin, if 0 values are not showing up when they are supposed to it is likely because the query being made against the database does not properly consider the "Removed" node type entries as indicating a 0 value and instead the query finds the last entry where the node is not "Removed" and returns that latest non-zero value (which is the behavior we want in some cases, but not here).
Please see https://github.com/vulcanize/ipld-eth-server/issues/95#issuecomment-918202095 there is a much simpler fix now that we have leaf_key for "Removed" nodes