Canonical blocks (#108)

* Update Block w/ newest Block

* Add cascading delete to blocks and transactions tables

* Add handling for new conflicting blocks

* Command line version of sliding window n behind HEAD
This commit is contained in:
Matt K
2017-12-19 14:14:41 -06:00
committed by GitHub
parent 84e77f259d
commit 266c9587c8
19 changed files with 352 additions and 70 deletions
+10 -7
View File
@@ -7,9 +7,10 @@ import (
)
type InMemory struct {
blocks map[int64]core.Block
contracts map[string]core.Contract
logs map[string][]core.Log
blocks map[int64]core.Block
contracts map[string]core.Contract
logs map[string][]core.Log
HandleBlockCallCount int
}
func (repository *InMemory) CreateLogs(logs []core.Log) error {
@@ -70,13 +71,15 @@ func (repository *InMemory) MissingBlockNumbers(startingBlockNumber int64, endin
func NewInMemory() *InMemory {
return &InMemory{
blocks: make(map[int64]core.Block),
contracts: make(map[string]core.Contract),
logs: make(map[string][]core.Log),
HandleBlockCallCount: 0,
blocks: make(map[int64]core.Block),
contracts: make(map[string]core.Contract),
logs: make(map[string][]core.Log),
}
}
func (repository *InMemory) CreateBlock(block core.Block) error {
func (repository *InMemory) CreateOrUpdateBlock(block core.Block) error {
repository.HandleBlockCallCount++
repository.blocks[block.Number] = block
return nil
}
+53 -4
View File
@@ -15,6 +15,8 @@ import (
_ "github.com/lib/pq"
)
type BlockStatus int
type Postgres struct {
Db *sqlx.DB
node core.Node
@@ -23,6 +25,7 @@ type Postgres struct {
var (
ErrDBInsertFailed = errors.New("postgres: insert failed")
ErrDBDeleteFailed = errors.New("postgres: delete failed")
ErrDBConnectionFailed = errors.New("postgres: db connection failed")
ErrUnableToSetNode = errors.New("postgres: unable to set node")
)
@@ -134,8 +137,10 @@ func (repository Postgres) CreateContract(contract core.Contract) error {
func (repository Postgres) ContractExists(contractHash string) bool {
var exists bool
repository.Db.QueryRow(
`SELECT exists(SELECT 1 FROM watched_contracts WHERE contract_hash=$1)
FROM watched_contracts`, contractHash).Scan(&exists)
`SELECT exists(
SELECT 1
FROM watched_contracts
WHERE contract_hash = $1)`, contractHash).Scan(&exists)
return exists
}
@@ -205,9 +210,41 @@ func (repository Postgres) BlockCount() int {
return count
}
func (repository Postgres) CreateBlock(block core.Block) error {
tx, _ := repository.Db.BeginTx(context.Background(), nil)
func (repository Postgres) getBlockHash(block core.Block) (string, bool) {
var retrievedBlockHash string
repository.Db.Get(&retrievedBlockHash,
`SELECT block_hash
FROM blocks
WHERE block_number = $1 AND node_id = $2`,
block.Number, repository.nodeId)
return retrievedBlockHash, blockExists(retrievedBlockHash)
}
func blockExists(retrievedBlockHash string) bool {
return retrievedBlockHash != ""
}
func (repository Postgres) CreateOrUpdateBlock(block core.Block) error {
var err error
retrievedBlockHash, ok := repository.getBlockHash(block)
if !ok {
err = repository.insertBlock(block)
return err
}
if ok && retrievedBlockHash != block.Hash {
err = repository.removeBlock(block.Number)
if err != nil {
return err
}
err = repository.insertBlock(block)
return err
}
return nil
}
func (repository Postgres) insertBlock(block core.Block) error {
var blockId int64
tx, _ := repository.Db.BeginTx(context.Background(), nil)
err := tx.QueryRow(
`INSERT INTO blocks
(node_id, block_number, block_gaslimit, block_gasused, block_time, block_difficulty, block_hash, block_nonce, block_parenthash, block_size, uncle_hash)
@@ -228,6 +265,18 @@ func (repository Postgres) CreateBlock(block core.Block) error {
return nil
}
func (repository Postgres) removeBlock(blockNumber int64) error {
_, err := repository.Db.Exec(
`DELETE FROM
blocks
WHERE block_number=$1 AND node_id=$2`,
blockNumber, repository.nodeId)
if err != nil {
return ErrDBDeleteFailed
}
return nil
}
func (repository Postgres) createTransactions(tx *sql.Tx, blockId int64, transactions []core.Transaction) error {
for _, transaction := range transactions {
_, err := tx.Exec(
+9 -2
View File
@@ -4,6 +4,9 @@ import (
"fmt"
"strings"
"io/ioutil"
"log"
"github.com/8thlight/vulcanizedb/pkg/config"
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/repositories"
@@ -14,6 +17,10 @@ import (
. "github.com/onsi/gomega"
)
func init() {
log.SetOutput(ioutil.Discard)
}
var _ = Describe("Postgres repository", func() {
It("connects to the database", func() {
@@ -43,7 +50,7 @@ var _ = Describe("Postgres repository", func() {
node := core.Node{GenesisBlock: "GENESIS", NetworkId: 1}
repository, _ := repositories.NewPostgres(cfg.Database, node)
err1 := repository.CreateBlock(badBlock)
err1 := repository.CreateOrUpdateBlock(badBlock)
savedBlock, err2 := repository.FindBlockByNumber(123)
Expect(err1).To(HaveOccurred())
@@ -97,7 +104,7 @@ var _ = Describe("Postgres repository", func() {
node := core.Node{GenesisBlock: "GENESIS", NetworkId: 1}
repository, _ := repositories.NewPostgres(cfg.Database, node)
err1 := repository.CreateBlock(block)
err1 := repository.CreateOrUpdateBlock(block)
savedBlock, err2 := repository.FindBlockByNumber(123)
Expect(err1).To(HaveOccurred())
+1 -1
View File
@@ -3,7 +3,7 @@ package repositories
import "github.com/8thlight/vulcanizedb/pkg/core"
type Repository interface {
CreateBlock(block core.Block) error
CreateOrUpdateBlock(block core.Block) error
BlockCount() int
FindBlockByNumber(blockNumber int64) (core.Block, error)
MaxBlockNumber() int64
+70 -22
View File
@@ -33,7 +33,7 @@ func AssertRepositoryBehavior(buildRepository func(node core.Node) repositories.
It("increments the block count", func() {
block := core.Block{Number: 123}
repository.CreateBlock(block)
repository.CreateOrUpdateBlock(block)
Expect(repository.BlockCount()).To(Equal(1))
})
@@ -42,7 +42,7 @@ func AssertRepositoryBehavior(buildRepository func(node core.Node) repositories.
block := core.Block{
Number: 123,
}
repository.CreateBlock(block)
repository.CreateOrUpdateBlock(block)
nodeTwo := core.Node{
GenesisBlock: "0x456",
NetworkId: 1,
@@ -77,7 +77,7 @@ func AssertRepositoryBehavior(buildRepository func(node core.Node) repositories.
UncleHash: uncleHash,
}
repository.CreateBlock(block)
repository.CreateOrUpdateBlock(block)
savedBlock, err := repository.FindBlockByNumber(blockNumber)
Expect(err).NotTo(HaveOccurred())
@@ -105,7 +105,7 @@ func AssertRepositoryBehavior(buildRepository func(node core.Node) repositories.
Transactions: []core.Transaction{{}},
}
repository.CreateBlock(block)
repository.CreateOrUpdateBlock(block)
savedBlock, _ := repository.FindBlockByNumber(123)
Expect(len(savedBlock.Transactions)).To(Equal(1))
@@ -117,12 +117,60 @@ func AssertRepositoryBehavior(buildRepository func(node core.Node) repositories.
Transactions: []core.Transaction{{}, {}},
}
repository.CreateBlock(block)
repository.CreateOrUpdateBlock(block)
savedBlock, _ := repository.FindBlockByNumber(123)
Expect(len(savedBlock.Transactions)).To(Equal(2))
})
It(`replaces blocks and transactions associated to the block
when a more new block is in conflict (same block number + nodeid)`, func() {
blockOne := core.Block{
Number: 123,
Hash: "xabc",
Transactions: []core.Transaction{{Hash: "x123"}, {Hash: "x345"}},
}
blockTwo := core.Block{
Number: 123,
Hash: "xdef",
Transactions: []core.Transaction{{Hash: "x678"}, {Hash: "x9ab"}},
}
repository.CreateOrUpdateBlock(blockOne)
repository.CreateOrUpdateBlock(blockTwo)
savedBlock, _ := repository.FindBlockByNumber(123)
Expect(len(savedBlock.Transactions)).To(Equal(2))
Expect(savedBlock.Transactions[0].Hash).To(Equal("x678"))
Expect(savedBlock.Transactions[1].Hash).To(Equal("x9ab"))
})
It(`does not replace blocks when block number is not unique
but block number + node id is`, func() {
blockOne := core.Block{
Number: 123,
Transactions: []core.Transaction{{Hash: "x123"}, {Hash: "x345"}},
}
blockTwo := core.Block{
Number: 123,
Transactions: []core.Transaction{{Hash: "x678"}, {Hash: "x9ab"}},
}
repository.CreateOrUpdateBlock(blockOne)
nodeTwo := core.Node{
GenesisBlock: "0x456",
NetworkId: 1,
}
repositoryTwo := buildRepository(nodeTwo)
repository.CreateOrUpdateBlock(blockOne)
repositoryTwo.CreateOrUpdateBlock(blockTwo)
retrievedBlockOne, _ := repository.FindBlockByNumber(123)
retrievedBlockTwo, _ := repositoryTwo.FindBlockByNumber(123)
Expect(retrievedBlockOne.Transactions[0].Hash).To(Equal("x123"))
Expect(retrievedBlockTwo.Transactions[0].Hash).To(Equal("x678"))
})
It("saves the attributes associated to a transaction", func() {
gasLimit := int64(5000)
gasPrice := int64(3)
@@ -144,7 +192,7 @@ func AssertRepositoryBehavior(buildRepository func(node core.Node) repositories.
Transactions: []core.Transaction{transaction},
}
repository.CreateBlock(block)
repository.CreateOrUpdateBlock(block)
savedBlock, _ := repository.FindBlockByNumber(123)
Expect(len(savedBlock.Transactions)).To(Equal(1))
@@ -162,41 +210,41 @@ func AssertRepositoryBehavior(buildRepository func(node core.Node) repositories.
Describe("The missing block numbers", func() {
It("is empty the starting block number is the highest known block number", func() {
repository.CreateBlock(core.Block{Number: 1})
repository.CreateOrUpdateBlock(core.Block{Number: 1})
Expect(len(repository.MissingBlockNumbers(1, 1))).To(Equal(0))
})
It("is the only missing block number", func() {
repository.CreateBlock(core.Block{Number: 2})
repository.CreateOrUpdateBlock(core.Block{Number: 2})
Expect(repository.MissingBlockNumbers(1, 2)).To(Equal([]int64{1}))
})
It("is both missing block numbers", func() {
repository.CreateBlock(core.Block{Number: 3})
repository.CreateOrUpdateBlock(core.Block{Number: 3})
Expect(repository.MissingBlockNumbers(1, 3)).To(Equal([]int64{1, 2}))
})
It("goes back to the starting block number", func() {
repository.CreateBlock(core.Block{Number: 6})
repository.CreateOrUpdateBlock(core.Block{Number: 6})
Expect(repository.MissingBlockNumbers(4, 6)).To(Equal([]int64{4, 5}))
})
It("only includes missing block numbers", func() {
repository.CreateBlock(core.Block{Number: 4})
repository.CreateBlock(core.Block{Number: 6})
repository.CreateOrUpdateBlock(core.Block{Number: 4})
repository.CreateOrUpdateBlock(core.Block{Number: 6})
Expect(repository.MissingBlockNumbers(4, 6)).To(Equal([]int64{5}))
})
It("is a list with multiple gaps", func() {
repository.CreateBlock(core.Block{Number: 4})
repository.CreateBlock(core.Block{Number: 5})
repository.CreateBlock(core.Block{Number: 8})
repository.CreateBlock(core.Block{Number: 10})
repository.CreateOrUpdateBlock(core.Block{Number: 4})
repository.CreateOrUpdateBlock(core.Block{Number: 5})
repository.CreateOrUpdateBlock(core.Block{Number: 8})
repository.CreateOrUpdateBlock(core.Block{Number: 10})
Expect(repository.MissingBlockNumbers(3, 10)).To(Equal([]int64{3, 6, 7, 9}))
})
@@ -206,8 +254,8 @@ func AssertRepositoryBehavior(buildRepository func(node core.Node) repositories.
})
It("only returns requested range even when other gaps exist", func() {
repository.CreateBlock(core.Block{Number: 3})
repository.CreateBlock(core.Block{Number: 8})
repository.CreateOrUpdateBlock(core.Block{Number: 3})
repository.CreateOrUpdateBlock(core.Block{Number: 8})
Expect(repository.MissingBlockNumbers(1, 5)).To(Equal([]int64{1, 2, 4, 5}))
})
@@ -215,14 +263,14 @@ func AssertRepositoryBehavior(buildRepository func(node core.Node) repositories.
Describe("The max block numbers", func() {
It("returns the block number when a single block", func() {
repository.CreateBlock(core.Block{Number: 1})
repository.CreateOrUpdateBlock(core.Block{Number: 1})
Expect(repository.MaxBlockNumber()).To(Equal(int64(1)))
})
It("returns highest known block number when multiple blocks", func() {
repository.CreateBlock(core.Block{Number: 1})
repository.CreateBlock(core.Block{Number: 10})
repository.CreateOrUpdateBlock(core.Block{Number: 1})
repository.CreateOrUpdateBlock(core.Block{Number: 10})
Expect(repository.MaxBlockNumber()).To(Equal(int64(10)))
})
@@ -261,7 +309,7 @@ func AssertRepositoryBehavior(buildRepository func(node core.Node) repositories.
{Hash: "TRANSACTION3", To: "x123"},
},
}
repository.CreateBlock(block)
repository.CreateOrUpdateBlock(block)
repository.CreateContract(core.Contract{Hash: "x123"})
contract, err := repository.FindContract("x123")