Add checks to validate referential integrity in the data (#7)
* Add checks to validate referential integrity * Add unit tests for referential integrity validation * Use EXISTS in referential integrity validation queries
This commit is contained in:
parent
effbdc3f58
commit
cc935dc97b
2
Makefile
2
Makefile
@ -18,7 +18,7 @@ integrationtest: | $(GINKGO) $(GOOSE)
|
|||||||
test: | $(GINKGO) $(GOOSE)
|
test: | $(GINKGO) $(GOOSE)
|
||||||
go vet ./...
|
go vet ./...
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
$(GINKGO) -r validator_test/ -v
|
$(GINKGO) -r pkg/validator/ validator_test/ -v
|
||||||
|
|
||||||
build:
|
build:
|
||||||
go fmt ./...
|
go fmt ./...
|
||||||
|
224
pkg/validator/ref_integrity.go
Normal file
224
pkg/validator/ref_integrity.go
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValidateReferentialIntegrity validates referential integrity at the given height
|
||||||
|
func ValidateReferentialIntegrity(db *sqlx.DB, blockNumber uint64) error {
|
||||||
|
|
||||||
|
err := ValidateHeaderCIDsRef(db, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateUncleCIDsRef(db, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateTransactionCIDsRef(db, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateReceiptCIDsRef(db, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateStateCIDsRef(db, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateStorageCIDsRef(db, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateStateAccountsRef(db, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateAccessListElementsRef(db, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateLogCIDsRef(db, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateHeaderCIDsRef does a reference integrity check on references in eth.header_cids table
|
||||||
|
func ValidateHeaderCIDsRef(db *sqlx.DB, blockNumber uint64) error {
|
||||||
|
err := ValidateIPFSBlocks(db, blockNumber, "eth.header_cids", "mh_key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateUncleCIDsRef does a reference integrity check on references in eth.uncle_cids table
|
||||||
|
func ValidateUncleCIDsRef(db *sqlx.DB, blockNumber uint64) error {
|
||||||
|
var exists bool
|
||||||
|
err := db.Get(&exists, UncleCIDsRefHeaderCIDs, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf(ReferentialIntegrityErr, blockNumber, "eth.header_cids")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateIPFSBlocks(db, blockNumber, "eth.uncle_cids", "mh_key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateTransactionCIDsRef does a reference integrity check on references in eth.header_cids table
|
||||||
|
func ValidateTransactionCIDsRef(db *sqlx.DB, blockNumber uint64) error {
|
||||||
|
var exists bool
|
||||||
|
err := db.Get(&exists, TransactionCIDsRefHeaderCIDs, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf(ReferentialIntegrityErr, blockNumber, "eth.header_cids")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateIPFSBlocks(db, blockNumber, "eth.transaction_cids", "mh_key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateReceiptCIDsRef does a reference integrity check on references in eth.receipt_cids table
|
||||||
|
func ValidateReceiptCIDsRef(db *sqlx.DB, blockNumber uint64) error {
|
||||||
|
var exists bool
|
||||||
|
err := db.Get(&exists, ReceiptCIDsRefTransactionCIDs, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf(ReferentialIntegrityErr, blockNumber, "eth.transaction_cids")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateIPFSBlocks(db, blockNumber, "eth.receipt_cids", "leaf_mh_key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateStateCIDsRef does a reference integrity check on references in eth.state_cids table
|
||||||
|
func ValidateStateCIDsRef(db *sqlx.DB, blockNumber uint64) error {
|
||||||
|
var exists bool
|
||||||
|
err := db.Get(&exists, StateCIDsRefHeaderCIDs, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf(ReferentialIntegrityErr, blockNumber, "eth.header_cids")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateIPFSBlocks(db, blockNumber, "eth.state_cids", "mh_key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateStorageCIDsRef does a reference integrity check on references in eth.storage_cids table
|
||||||
|
func ValidateStorageCIDsRef(db *sqlx.DB, blockNumber uint64) error {
|
||||||
|
var exists bool
|
||||||
|
err := db.Get(&exists, StorageCIDsRefStateCIDs, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf(ReferentialIntegrityErr, blockNumber, "eth.state_cids")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateIPFSBlocks(db, blockNumber, "eth.storage_cids", "mh_key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateStateAccountsRef does a reference integrity check on references in eth.state_accounts table
|
||||||
|
func ValidateStateAccountsRef(db *sqlx.DB, blockNumber uint64) error {
|
||||||
|
var exists bool
|
||||||
|
err := db.Get(&exists, StateAccountsRefStateCIDs, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf(ReferentialIntegrityErr, blockNumber, "eth.state_cids")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateAccessListElementsRef does a reference integrity check on references in eth.access_list_elements table
|
||||||
|
func ValidateAccessListElementsRef(db *sqlx.DB, blockNumber uint64) error {
|
||||||
|
var exists bool
|
||||||
|
err := db.Get(&exists, AccessListElementsRefTransactionCIDs, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf(ReferentialIntegrityErr, blockNumber, "eth.transaction_cids")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateLogCIDsRef does a reference integrity check on references in eth.log_cids table
|
||||||
|
func ValidateLogCIDsRef(db *sqlx.DB, blockNumber uint64) error {
|
||||||
|
var exists bool
|
||||||
|
err := db.Get(&exists, LogCIDsRefReceiptCIDs, blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf(ReferentialIntegrityErr, blockNumber, "eth.receipt_cids")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ValidateIPFSBlocks(db, blockNumber, "eth.log_cids", "leaf_mh_key")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateIPFSBlocks does a reference integrity check between the given CID table and IPFS blocks table on MHKey and block number
|
||||||
|
func ValidateIPFSBlocks(db *sqlx.DB, blockNumber uint64, CIDTable string, mhKeyField string) error {
|
||||||
|
var exists bool
|
||||||
|
err := db.Get(&exists, fmt.Sprintf(CIDsRefIPLDBlocks, CIDTable, mhKeyField), blockNumber)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf(ReferentialIntegrityErr, blockNumber, "public.blocks")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
119
pkg/validator/ref_integrity_queries.go
Normal file
119
pkg/validator/ref_integrity_queries.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package validator
|
||||||
|
|
||||||
|
// Queries to validate referential integrity in the indexed data:
|
||||||
|
// At the given block number,
|
||||||
|
// In each table, for each (would be) foreign key reference, perform left join with the referenced table on the foreign key fields.
|
||||||
|
// Select rows where there are no matching rows in the referenced table.
|
||||||
|
// If any such rows exist, there are missing entries in the referenced table.
|
||||||
|
|
||||||
|
const (
|
||||||
|
CIDsRefIPLDBlocks = `SELECT EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM %[1]s
|
||||||
|
LEFT JOIN public.blocks ON (
|
||||||
|
%[1]s.%[2]s = blocks.key
|
||||||
|
AND %[1]s.block_number = blocks.block_number
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
%[1]s.block_number = $1
|
||||||
|
AND blocks.key IS NULL
|
||||||
|
)`
|
||||||
|
|
||||||
|
UncleCIDsRefHeaderCIDs = `SELECT EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM eth.uncle_cids
|
||||||
|
LEFT JOIN eth.header_cids ON (
|
||||||
|
uncle_cids.header_id = header_cids.block_hash
|
||||||
|
AND uncle_cids.block_number = header_cids.block_number
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
uncle_cids.block_number = $1
|
||||||
|
AND header_cids.block_hash IS NULL
|
||||||
|
)`
|
||||||
|
|
||||||
|
TransactionCIDsRefHeaderCIDs = `SELECT EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM eth.transaction_cids
|
||||||
|
LEFT JOIN eth.header_cids ON (
|
||||||
|
transaction_cids.header_id = header_cids.block_hash
|
||||||
|
AND transaction_cids.block_number = header_cids.block_number
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
transaction_cids.block_number = $1
|
||||||
|
AND header_cids.block_hash IS NULL
|
||||||
|
)`
|
||||||
|
|
||||||
|
ReceiptCIDsRefTransactionCIDs = `SELECT EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM eth.receipt_cids
|
||||||
|
LEFT JOIN eth.transaction_cids ON (
|
||||||
|
receipt_cids.tx_id = transaction_cids.tx_hash
|
||||||
|
AND receipt_cids.block_number = transaction_cids.block_number
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
receipt_cids.block_number = $1
|
||||||
|
AND transaction_cids.tx_hash IS NULL
|
||||||
|
)`
|
||||||
|
|
||||||
|
StateCIDsRefHeaderCIDs = `SELECT EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM eth.state_cids
|
||||||
|
LEFT JOIN eth.header_cids ON (
|
||||||
|
state_cids.header_id = header_cids.block_hash
|
||||||
|
AND state_cids.block_number = header_cids.block_number
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
state_cids.block_number = $1
|
||||||
|
AND header_cids.block_hash IS NULL
|
||||||
|
)`
|
||||||
|
|
||||||
|
StorageCIDsRefStateCIDs = `SELECT EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM eth.storage_cids
|
||||||
|
LEFT JOIN eth.state_cids ON (
|
||||||
|
storage_cids.state_path = state_cids.state_path
|
||||||
|
AND storage_cids.header_id = state_cids.header_id
|
||||||
|
AND storage_cids.block_number = state_cids.block_number
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
storage_cids.block_number = $1
|
||||||
|
AND state_cids.state_path IS NULL
|
||||||
|
)`
|
||||||
|
|
||||||
|
StateAccountsRefStateCIDs = `SELECT EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM eth.state_accounts
|
||||||
|
LEFT JOIN eth.state_cids ON (
|
||||||
|
state_accounts.state_path = state_cids.state_path
|
||||||
|
AND state_accounts.header_id = state_cids.header_id
|
||||||
|
AND state_accounts.block_number = state_cids.block_number
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
state_accounts.block_number = $1
|
||||||
|
AND state_cids.state_path IS NULL
|
||||||
|
)`
|
||||||
|
|
||||||
|
AccessListElementsRefTransactionCIDs = `SELECT EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM eth.access_list_elements
|
||||||
|
LEFT JOIN eth.transaction_cids ON (
|
||||||
|
access_list_elements.tx_id = transaction_cids.tx_hash
|
||||||
|
AND access_list_elements.block_number = transaction_cids.block_number
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
access_list_elements.block_number = $1
|
||||||
|
AND transaction_cids.tx_hash IS NULL
|
||||||
|
)`
|
||||||
|
|
||||||
|
LogCIDsRefReceiptCIDs = `SELECT EXISTS (
|
||||||
|
SELECT *
|
||||||
|
FROM eth.log_cids
|
||||||
|
LEFT JOIN eth.receipt_cids ON (
|
||||||
|
log_cids.rct_id = receipt_cids.tx_id
|
||||||
|
AND log_cids.block_number = receipt_cids.block_number
|
||||||
|
)
|
||||||
|
WHERE
|
||||||
|
log_cids.block_number = $1
|
||||||
|
AND receipt_cids.tx_id IS NULL
|
||||||
|
)`
|
||||||
|
)
|
334
pkg/validator/ref_integrity_test.go
Normal file
334
pkg/validator/ref_integrity_test.go
Normal file
@ -0,0 +1,334 @@
|
|||||||
|
package validator_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/params"
|
||||||
|
"github.com/ethereum/go-ethereum/statediff/indexer/interfaces"
|
||||||
|
"github.com/ethereum/go-ethereum/statediff/indexer/mocks"
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
|
||||||
|
"github.com/vulcanize/ipld-eth-db-validator/pkg/validator"
|
||||||
|
"github.com/vulcanize/ipld-eth-server/v4/pkg/eth/test_helpers"
|
||||||
|
"github.com/vulcanize/ipld-eth-server/v4/pkg/shared"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = Describe("RefIntegrity", func() {
|
||||||
|
var (
|
||||||
|
ctx = context.Background()
|
||||||
|
|
||||||
|
db *sqlx.DB
|
||||||
|
diffIndexer interfaces.StateDiffIndexer
|
||||||
|
)
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
db = shared.SetupDB()
|
||||||
|
diffIndexer = shared.SetupTestStateDiffIndexer(ctx, params.TestChainConfig, test_helpers.Genesis.Hash())
|
||||||
|
})
|
||||||
|
|
||||||
|
AfterEach(func() {
|
||||||
|
shared.TearDownDB(db)
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("ValidateHeaderCIDsRef", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
tx, err := diffIndexer.PushBlock(test_helpers.MockBlock, test_helpers.MockReceipts, test_helpers.MockBlock.Difficulty())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = tx.Submit(err)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Validates referential integrity of header_cids table", func() {
|
||||||
|
err := validator.ValidateHeaderCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding header IPFS block entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "public.blocks")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateHeaderCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "public.blocks"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("ValidateUncleCIDsRef", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
tx, err := diffIndexer.PushBlock(test_helpers.MockBlock, test_helpers.MockReceipts, test_helpers.MockBlock.Difficulty())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = tx.Submit(err)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Validates referential integrity of uncle_cids table", func() {
|
||||||
|
err := validator.ValidateUncleCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding header_cid entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "eth.header_cids")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateUncleCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "eth.header_cids"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding uncle IPFS block entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "public.blocks")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateUncleCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "public.blocks"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("ValidateTransactionCIDsRef", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
tx, err := diffIndexer.PushBlock(test_helpers.MockBlock, test_helpers.MockReceipts, test_helpers.MockBlock.Difficulty())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = tx.Submit(err)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Validates referential integrity of transaction_cids table", func() {
|
||||||
|
err := validator.ValidateTransactionCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding header_cid entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "eth.header_cids")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateTransactionCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "eth.header_cids"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding transaction IPFS block entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "public.blocks")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateTransactionCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "public.blocks"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("ValidateReceiptCIDsRef", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
tx, err := diffIndexer.PushBlock(test_helpers.MockBlock, test_helpers.MockReceipts, test_helpers.MockBlock.Difficulty())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = tx.Submit(err)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Validates referential integrity of receipt_cids table", func() {
|
||||||
|
err := validator.ValidateReceiptCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding transaction_cids entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "eth.transaction_cids")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateReceiptCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "eth.transaction_cids"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding receipt IPFS block entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "public.blocks")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateReceiptCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "public.blocks"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("ValidateStateCIDsRef", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
tx, err := diffIndexer.PushBlock(test_helpers.MockBlock, test_helpers.MockReceipts, test_helpers.MockBlock.Difficulty())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
for _, node := range test_helpers.MockStateNodes {
|
||||||
|
err = diffIndexer.PushStateNode(tx, node, test_helpers.MockBlock.Hash().String())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Submit(err)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Validates referential integrity of state_cids table", func() {
|
||||||
|
err := validator.ValidateStateCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding header_cids entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "eth.header_cids")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateStateCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "eth.header_cids"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding state IPFS block entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "public.blocks")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateStateCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "public.blocks"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("ValidateStorageCIDsRef", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
tx, err := diffIndexer.PushBlock(test_helpers.MockBlock, test_helpers.MockReceipts, test_helpers.MockBlock.Difficulty())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
for _, node := range test_helpers.MockStateNodes {
|
||||||
|
err = diffIndexer.PushStateNode(tx, node, test_helpers.MockBlock.Hash().String())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Submit(err)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Validates referential integrity of storage_cids table", func() {
|
||||||
|
err := validator.ValidateStorageCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding state_cids entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "eth.state_cids")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateStorageCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "eth.state_cids"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding storage IPFS block entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "public.blocks")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateStorageCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "public.blocks"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("ValidateStateAccountsRef", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
tx, err := diffIndexer.PushBlock(test_helpers.MockBlock, test_helpers.MockReceipts, test_helpers.MockBlock.Difficulty())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
for _, node := range test_helpers.MockStateNodes {
|
||||||
|
err = diffIndexer.PushStateNode(tx, node, test_helpers.MockBlock.Hash().String())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Submit(err)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Validates referential integrity of state_accounts table", func() {
|
||||||
|
err := validator.ValidateStateAccountsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding state_cids entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "eth.state_cids")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateStateAccountsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "eth.state_cids"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("ValidateAccessListElementsRef", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
indexAndPublisher := shared.SetupTestStateDiffIndexer(ctx, mocks.TestConfig, test_helpers.Genesis.Hash())
|
||||||
|
|
||||||
|
tx, err := indexAndPublisher.PushBlock(mocks.MockBlock, mocks.MockReceipts, mocks.MockBlock.Difficulty())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = tx.Submit(err)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Validates referential integrity of access_list_elements table", func() {
|
||||||
|
err := validator.ValidateAccessListElementsRef(db, mocks.MockBlock.NumberU64())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding transaction_cids entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "eth.transaction_cids")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateAccessListElementsRef(db, mocks.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "eth.transaction_cids"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("ValidateLogCIDsRef", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
tx, err := diffIndexer.PushBlock(test_helpers.MockBlock, test_helpers.MockReceipts, test_helpers.MockBlock.Difficulty())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
for _, node := range test_helpers.MockStateNodes {
|
||||||
|
err = diffIndexer.PushStateNode(tx, node, test_helpers.MockBlock.Hash().String())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tx.Submit(err)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Validates referential integrity of log_cids table", func() {
|
||||||
|
err := validator.ValidateLogCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding receipt_cids entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "eth.receipt_cids")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateLogCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "eth.receipt_cids"))
|
||||||
|
})
|
||||||
|
|
||||||
|
It("Throws an error if corresponding log IPFS block entry not found", func() {
|
||||||
|
err := deleteEntriesFrom(db, "public.blocks")
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateLogCIDsRef(db, test_helpers.MockBlock.NumberU64())
|
||||||
|
Expect(err).To(HaveOccurred())
|
||||||
|
Expect(err.Error()).To(ContainSubstring(validator.EntryNotFoundErr, "public.blocks"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
func deleteEntriesFrom(db *sqlx.DB, tableName string) error {
|
||||||
|
pgStr := "DELETE FROM %s"
|
||||||
|
_, err := db.Exec(fmt.Sprintf(pgStr, tableName))
|
||||||
|
return err
|
||||||
|
}
|
@ -20,6 +20,7 @@ import (
|
|||||||
"github.com/ethereum/go-ethereum/rpc"
|
"github.com/ethereum/go-ethereum/rpc"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
|
|
||||||
ipfsethdb "github.com/vulcanize/ipfs-ethdb/v4/postgres"
|
ipfsethdb "github.com/vulcanize/ipfs-ethdb/v4/postgres"
|
||||||
ipldEth "github.com/vulcanize/ipld-eth-server/v4/pkg/eth"
|
ipldEth "github.com/vulcanize/ipld-eth-server/v4/pkg/eth"
|
||||||
ethServerShared "github.com/vulcanize/ipld-eth-server/v4/pkg/shared"
|
ethServerShared "github.com/vulcanize/ipld-eth-server/v4/pkg/shared"
|
||||||
@ -28,6 +29,9 @@ import (
|
|||||||
var (
|
var (
|
||||||
big8 = big.NewInt(8)
|
big8 = big.NewInt(8)
|
||||||
big32 = big.NewInt(32)
|
big32 = big.NewInt(32)
|
||||||
|
|
||||||
|
ReferentialIntegrityErr = "referential integrity check failed at block %d, entry for %s not found"
|
||||||
|
EntryNotFoundErr = "entry for %s not found"
|
||||||
)
|
)
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
@ -129,6 +133,15 @@ func (s *service) Validate(ctx context.Context, api *ipldEth.PublicEthAPI, idxBl
|
|||||||
}
|
}
|
||||||
|
|
||||||
s.logger.Infof("state root verified for block %d", idxBlockNum)
|
s.logger.Infof("state root verified for block %d", idxBlockNum)
|
||||||
|
|
||||||
|
err = ValidateReferentialIntegrity(s.db, idxBlockNum)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Errorf("failed to verify referential integrity at block %d", idxBlockNum)
|
||||||
|
return idxBlockNum, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.logger.Infof("referential integrity verified for block %d", idxBlockNum)
|
||||||
|
|
||||||
idxBlockNum++
|
idxBlockNum++
|
||||||
} else {
|
} else {
|
||||||
// Sleep / wait for head to move ahead
|
// Sleep / wait for head to move ahead
|
||||||
|
13
pkg/validator/validator_suite_test.go
Normal file
13
pkg/validator/validator_suite_test.go
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package validator_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestValidator(t *testing.T) {
|
||||||
|
RegisterFailHandler(Fail)
|
||||||
|
RunSpecs(t, "Validator Suite")
|
||||||
|
}
|
@ -120,6 +120,9 @@ var _ = Describe("eth state reading tests", func() {
|
|||||||
for i := uint64(blockHeight); i <= chainLength-trail; i++ {
|
for i := uint64(blockHeight); i <= chainLength-trail; i++ {
|
||||||
err = validator.ValidateBlock(context.Background(), api, i)
|
err = validator.ValidateBlock(context.Background(), api, i)
|
||||||
Expect(err).ToNot(HaveOccurred())
|
Expect(err).ToNot(HaveOccurred())
|
||||||
|
|
||||||
|
err = validator.ValidateReferentialIntegrity(db, i)
|
||||||
|
Expect(err).ToNot(HaveOccurred())
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user