Merge pull request #53 from 8thlight/introduce-repository

Extract Postgres repository
This commit is contained in:
ericmeyer 2017-11-06 09:56:29 -06:00 committed by GitHub
commit 808d4388cd
5 changed files with 290 additions and 190 deletions

View File

@ -2,8 +2,8 @@ package observers
import ( import (
"github.com/8thlight/vulcanizedb/core" "github.com/8thlight/vulcanizedb/core"
"github.com/8thlight/vulcanizedb/repositories"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
) )
type BlockchainDBObserver struct { type BlockchainDBObserver struct {
@ -11,25 +11,5 @@ type BlockchainDBObserver struct {
} }
func (observer BlockchainDBObserver) NotifyBlockAdded(block core.Block) { func (observer BlockchainDBObserver) NotifyBlockAdded(block core.Block) {
insertedBlockId := saveBlock(observer, block) repositories.NewPostgres(observer.Db).CreateBlock(block)
saveTransactions(insertedBlockId, block.Transactions, observer)
}
func saveBlock(observer BlockchainDBObserver, block core.Block) int64 {
insertedBlock := observer.Db.QueryRow(
"Insert INTO blocks "+
"(block_number, block_gaslimit, block_gasused, block_time, block_difficulty, block_hash, block_nonce, block_parenthash, block_size, uncle_hash) "+
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id",
block.Number, block.GasLimit, block.GasUsed, block.Time, block.Difficulty, block.Hash, block.Nonce, block.ParentHash, block.Size, block.UncleHash)
var blockId int64
insertedBlock.Scan(&blockId)
return blockId
}
func saveTransactions(blockId int64, transactions []core.Transaction, observer BlockchainDBObserver) {
for _, transaction := range transactions {
observer.Db.MustExec("Insert INTO transactions "+
"(block_id, tx_hash, tx_nonce, tx_to, tx_gaslimit, tx_gasprice, tx_value) VALUES ($1, $2, $3, $4, $5, $6, $7)",
blockId, transaction.Hash, transaction.Nonce, transaction.To, transaction.GasLimit, transaction.GasPrice, transaction.Value)
}
} }

View File

@ -6,6 +6,7 @@ import (
"github.com/8thlight/vulcanizedb/config" "github.com/8thlight/vulcanizedb/config"
"github.com/8thlight/vulcanizedb/core" "github.com/8thlight/vulcanizedb/core"
"github.com/8thlight/vulcanizedb/observers" "github.com/8thlight/vulcanizedb/observers"
"github.com/8thlight/vulcanizedb/repositories"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
_ "github.com/lib/pq" _ "github.com/lib/pq"
. "github.com/onsi/ginkgo" . "github.com/onsi/ginkgo"
@ -38,180 +39,19 @@ var _ = Describe("Saving blocks to the database", func() {
Expect(observer).NotTo(BeNil()) Expect(observer).NotTo(BeNil())
}) })
It("connects to the database", func() { It("saves a block with one transaction", func() {
Expect(err).Should(BeNil())
Expect(db).ShouldNot(BeNil())
})
It("starts with no blocks", func() {
var count int
queryError := db.Get(&count, "SELECT COUNT(*) FROM blocks")
Expect(queryError).Should(BeNil())
Expect(count).Should(Equal(0))
})
It("inserts a block", func() {
// setup a block in memory
blockNumber := int64(123)
gasLimit := int64(1000000)
gasUsed := int64(10)
blockHash := "x123"
blockParentHash := "x456"
blockNonce := "0x881db2ca900682e9a9"
blockTime := int64(1508981640)
uncleHash := "x789"
blockSize := int64(1000)
difficulty := int64(10)
block := core.Block{ block := core.Block{
Difficulty: difficulty, Number: 123,
GasLimit: gasLimit, Transactions: []core.Transaction{{}},
GasUsed: gasUsed,
Hash: blockHash,
Nonce: blockNonce,
Number: blockNumber,
ParentHash: blockParentHash,
Size: blockSize,
Time: blockTime,
UncleHash: uncleHash,
} }
// save the block to the database
observer := observers.BlockchainDBObserver{Db: db} observer := observers.BlockchainDBObserver{Db: db}
observer.NotifyBlockAdded(block) observer.NotifyBlockAdded(block)
// find the saved block repository := repositories.NewPostgres(db)
rows, err := db.Query( savedBlock := repository.FindBlockByNumber(123)
"SELECT block_number, block_gaslimit, block_gasused, block_time, block_difficulty, block_hash, block_nonce, block_parenthash, block_size, uncle_hash FROM blocks") Expect(savedBlock).NotTo(BeNil())
Expect(err).To(BeNil()) Expect(len(savedBlock.Transactions)).To(Equal(1))
var savedBlocks []core.Block
for rows.Next() {
var blockHash string
var blockNonce string
var blockNumber int64
var blockParentHash string
var blockSize int64
var blockTime float64
var difficulty int64
var gasLimit float64
var gasUsed float64
var uncleHash string
rows.Scan(&blockNumber, &gasLimit, &gasUsed, &blockTime, &difficulty, &blockHash, &blockNonce, &blockParentHash, &blockSize, &uncleHash)
savedBlock := core.Block{
Difficulty: difficulty,
GasLimit: int64(gasLimit),
GasUsed: int64(gasUsed),
Hash: blockHash,
Nonce: blockNonce,
Number: blockNumber,
ParentHash: blockParentHash,
Size: blockSize,
Time: int64(blockTime),
UncleHash: uncleHash,
}
savedBlocks = append(savedBlocks, savedBlock)
}
// assert against the attributes
Expect(len(savedBlocks)).To(Equal(1))
Expect(savedBlocks[0].Difficulty).To(Equal(difficulty))
Expect(savedBlocks[0].GasLimit).To(Equal(gasLimit))
Expect(savedBlocks[0].GasUsed).To(Equal(gasUsed))
Expect(savedBlocks[0].Hash).To(Equal(blockHash))
Expect(savedBlocks[0].Nonce).To(Equal(blockNonce))
Expect(savedBlocks[0].Number).To(Equal(blockNumber))
Expect(savedBlocks[0].ParentHash).To(Equal(blockParentHash))
Expect(savedBlocks[0].Size).To(Equal(blockSize))
Expect(savedBlocks[0].Time).To(Equal(blockTime))
Expect(savedBlocks[0].UncleHash).To(Equal(uncleHash))
})
var _ = Describe("Saving transactions to the database", func() {
It("inserts a transaction", func() {
gasLimit := int64(5000)
gasPrice := int64(3)
nonce := uint64(10000)
to := "1234567890"
value := int64(10)
txRecord := core.Transaction{
Hash: "x1234",
GasPrice: gasPrice,
GasLimit: gasLimit,
Nonce: nonce,
To: to,
Value: value,
}
block := core.Block{Transactions: []core.Transaction{txRecord}}
observer := observers.BlockchainDBObserver{Db: db}
observer.NotifyBlockAdded(block)
rows, err := db.Query("SELECT tx_hash, tx_nonce, tx_to, tx_gaslimit, tx_gasprice, tx_value FROM transactions")
Expect(err).To(BeNil())
var savedTransactions []core.Transaction
for rows.Next() {
var dbHash string
var dbNonce uint64
var dbTo string
var dbGasLimit int64
var dbGasPrice int64
var dbValue int64
rows.Scan(&dbHash, &dbNonce, &dbTo, &dbGasLimit, &dbGasPrice, &dbValue)
savedTransaction := core.Transaction{
GasLimit: dbGasLimit,
GasPrice: dbGasPrice,
Hash: dbHash,
Nonce: dbNonce,
To: dbTo,
Value: dbValue,
}
savedTransactions = append(savedTransactions, savedTransaction)
}
Expect(len(savedTransactions)).To(Equal(1))
savedTransaction := savedTransactions[0]
Expect(savedTransaction.GasLimit).To(Equal(gasLimit))
Expect(savedTransaction.GasPrice).To(Equal(gasPrice))
Expect(savedTransaction.Hash).To(Equal(txRecord.Hash))
Expect(savedTransaction.Nonce).To(Equal(nonce))
Expect(savedTransaction.To).To(Equal(to))
Expect(savedTransaction.Value).To(Equal(value))
})
It("associates the transaction with the block", func() {
txRecord := core.Transaction{}
block := core.Block{
Transactions: []core.Transaction{txRecord},
}
observer := observers.BlockchainDBObserver{Db: db}
observer.NotifyBlockAdded(block)
blockRows, err := db.Query("SELECT id FROM blocks")
Expect(err).To(BeNil())
var actualBlockIds []int64
for blockRows.Next() {
var actualBlockId int64
blockRows.Scan(&actualBlockId)
actualBlockIds = append(actualBlockIds, actualBlockId)
}
transactionRows, err := db.Query("SELECT block_id FROM transactions")
Expect(err).To(BeNil())
var transactionBlockIds []int64
for transactionRows.Next() {
var transactionBlockId int64
transactionRows.Scan(&transactionBlockId)
transactionBlockIds = append(transactionBlockIds, transactionBlockId)
}
Expect(len(actualBlockIds)).To(Equal(1))
Expect(len(transactionBlockIds)).To(Equal(1))
Expect(transactionBlockIds[0]).To(Equal(actualBlockIds[0]))
})
}) })
}) })

108
repositories/postgres.go Normal file
View File

@ -0,0 +1,108 @@
package repositories
import (
"database/sql"
"github.com/8thlight/vulcanizedb/core"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
)
type Postgres struct {
Db *sqlx.DB
}
func NewPostgres(db *sqlx.DB) Postgres {
return Postgres{Db: db}
}
func (repository Postgres) FindBlockByNumber(blockNumber int64) *core.Block {
blockRows, _ := repository.Db.Query("SELECT id, block_number, block_gaslimit, block_gasused, block_time, block_difficulty, block_hash, block_nonce, block_parenthash, block_size, uncle_hash FROM blocks")
var savedBlocks []core.Block
for blockRows.Next() {
savedBlock := repository.loadBlock(blockRows)
savedBlocks = append(savedBlocks, savedBlock)
}
if len(savedBlocks) > 0 {
return &savedBlocks[0]
} else {
return nil
}
}
func (repository Postgres) BlockCount() int {
var count int
repository.Db.Get(&count, "SELECT COUNT(*) FROM blocks")
return count
}
func (repository Postgres) CreateBlock(block core.Block) {
insertedBlock := repository.Db.QueryRow(
"Insert INTO blocks "+
"(block_number, block_gaslimit, block_gasused, block_time, block_difficulty, block_hash, block_nonce, block_parenthash, block_size, uncle_hash) "+
"VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10) RETURNING id",
block.Number, block.GasLimit, block.GasUsed, block.Time, block.Difficulty, block.Hash, block.Nonce, block.ParentHash, block.Size, block.UncleHash)
var blockId int64
insertedBlock.Scan(&blockId)
repository.createTransactions(blockId, block.Transactions)
}
func (repository Postgres) createTransactions(blockId int64, transactions []core.Transaction) {
for _, transaction := range transactions {
repository.Db.MustExec("Insert INTO transactions "+
"(block_id, tx_hash, tx_nonce, tx_to, tx_gaslimit, tx_gasprice, tx_value) VALUES ($1, $2, $3, $4, $5, $6, $7)",
blockId, transaction.Hash, transaction.Nonce, transaction.To, transaction.GasLimit, transaction.GasPrice, transaction.Value)
}
}
func (repository Postgres) loadBlock(blockRows *sql.Rows) core.Block {
var blockId int64
var blockHash string
var blockNonce string
var blockNumber int64
var blockParentHash string
var blockSize int64
var blockTime float64
var difficulty int64
var gasLimit float64
var gasUsed float64
var uncleHash string
blockRows.Scan(&blockId, &blockNumber, &gasLimit, &gasUsed, &blockTime, &difficulty, &blockHash, &blockNonce, &blockParentHash, &blockSize, &uncleHash)
transactions := repository.loadTransactions(blockId)
return core.Block{
Difficulty: difficulty,
GasLimit: int64(gasLimit),
GasUsed: int64(gasUsed),
Hash: blockHash,
Nonce: blockNonce,
Number: blockNumber,
ParentHash: blockParentHash,
Size: blockSize,
Time: int64(blockTime),
Transactions: transactions,
UncleHash: uncleHash,
}
}
func (repository Postgres) loadTransactions(blockId int64) []core.Transaction {
transactionRows, _ := repository.Db.Query("SELECT tx_hash, tx_nonce, tx_to, tx_gaslimit, tx_gasprice, tx_value FROM transactions")
var transactions []core.Transaction
for transactionRows.Next() {
var hash string
var nonce uint64
var to string
var gasLimit int64
var gasPrice int64
var value int64
transactionRows.Scan(&hash, &nonce, &to, &gasLimit, &gasPrice, &value)
transaction := core.Transaction{
Hash: hash,
Nonce: nonce,
To: to,
GasLimit: gasLimit,
GasPrice: gasPrice,
Value: value,
}
transactions = append(transactions, transaction)
}
return transactions
}

View File

@ -0,0 +1,159 @@
package repositories_test
import (
"github.com/8thlight/vulcanizedb/config"
"github.com/8thlight/vulcanizedb/core"
"github.com/8thlight/vulcanizedb/repositories"
"github.com/jmoiron/sqlx"
_ "github.com/lib/pq"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("The Postgres Repository", func() {
var db *sqlx.DB
var err error
BeforeEach(func() {
pgConfig := config.DbConnectionString(config.NewConfig("private").Database)
db, err = sqlx.Connect("postgres", pgConfig)
db.MustExec("DELETE FROM transactions")
db.MustExec("DELETE FROM blocks")
})
AfterEach(func() {
db.Close()
})
It("connects to the database", func() {
Expect(err).Should(BeNil())
Expect(db).ShouldNot(BeNil())
})
Describe("Saving blocks", func() {
It("starts with no blocks", func() {
count := repositories.NewPostgres(db).BlockCount()
Expect(count).Should(Equal(0))
})
It("increments the block count", func() {
block := core.Block{Number: 123}
repository := repositories.NewPostgres(db)
repository.CreateBlock(block)
Expect(repository.BlockCount()).To(Equal(1))
})
It("saves the attributes of the block", func() {
blockNumber := int64(123)
gasLimit := int64(1000000)
gasUsed := int64(10)
blockHash := "x123"
blockParentHash := "x456"
blockNonce := "0x881db2ca900682e9a9"
blockTime := int64(1508981640)
uncleHash := "x789"
blockSize := int64(1000)
difficulty := int64(10)
block := core.Block{
Difficulty: difficulty,
GasLimit: gasLimit,
GasUsed: gasUsed,
Hash: blockHash,
Nonce: blockNonce,
Number: blockNumber,
ParentHash: blockParentHash,
Size: blockSize,
Time: blockTime,
UncleHash: uncleHash,
}
repository := repositories.NewPostgres(db)
repository.CreateBlock(block)
savedBlock := repository.FindBlockByNumber(blockNumber)
Expect(savedBlock.Difficulty).To(Equal(difficulty))
Expect(savedBlock.GasLimit).To(Equal(gasLimit))
Expect(savedBlock.GasUsed).To(Equal(gasUsed))
Expect(savedBlock.Hash).To(Equal(blockHash))
Expect(savedBlock.Nonce).To(Equal(blockNonce))
Expect(savedBlock.Number).To(Equal(blockNumber))
Expect(savedBlock.ParentHash).To(Equal(blockParentHash))
Expect(savedBlock.Size).To(Equal(blockSize))
Expect(savedBlock.Time).To(Equal(blockTime))
Expect(savedBlock.UncleHash).To(Equal(uncleHash))
})
It("does not find a block when searching for a number that does not exist", func() {
repository := repositories.NewPostgres(db)
savedBlock := repository.FindBlockByNumber(111)
Expect(savedBlock).To(BeNil())
})
It("saves one transaction associated to the block", func() {
block := core.Block{
Number: 123,
Transactions: []core.Transaction{{}},
}
repository := repositories.NewPostgres(db)
repository.CreateBlock(block)
savedBlock := repository.FindBlockByNumber(123)
Expect(len(savedBlock.Transactions)).To(Equal(1))
})
It("saves two transactions associated to the block", func() {
block := core.Block{
Number: 123,
Transactions: []core.Transaction{{}, {}},
}
repository := repositories.NewPostgres(db)
repository.CreateBlock(block)
savedBlock := repository.FindBlockByNumber(123)
Expect(len(savedBlock.Transactions)).To(Equal(2))
})
It("saves the attributes associated to a transaction", func() {
gasLimit := int64(5000)
gasPrice := int64(3)
nonce := uint64(10000)
to := "1234567890"
value := int64(10)
transaction := core.Transaction{
Hash: "x1234",
GasPrice: gasPrice,
GasLimit: gasLimit,
Nonce: nonce,
To: to,
Value: value,
}
block := core.Block{
Number: 123,
Transactions: []core.Transaction{transaction},
}
repository := repositories.NewPostgres(db)
repository.CreateBlock(block)
savedBlock := repository.FindBlockByNumber(123)
Expect(len(savedBlock.Transactions)).To(Equal(1))
savedTransaction := savedBlock.Transactions[0]
Expect(savedTransaction.Hash).To(Equal(transaction.Hash))
Expect(savedTransaction.To).To(Equal(to))
Expect(savedTransaction.Nonce).To(Equal(nonce))
Expect(savedTransaction.GasLimit).To(Equal(gasLimit))
Expect(savedTransaction.GasPrice).To(Equal(gasPrice))
Expect(savedTransaction.Value).To(Equal(value))
})
})
})

View File

@ -0,0 +1,13 @@
package repositories_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestRepositories(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Repositories Suite")
}