Nest packages under pkg

This commit is contained in:
Eric Meyer
2017-11-06 13:06:03 -06:00
parent cf5f2f28db
commit f4a603efcb
36 changed files with 45 additions and 54 deletions
@@ -0,0 +1,37 @@
package blockchain_listener
import "github.com/8thlight/vulcanizedb/pkg/core"
type BlockchainListener struct {
inputBlocks chan core.Block
blockchain core.Blockchain
observers []core.BlockchainObserver
}
func NewBlockchainListener(blockchain core.Blockchain, observers []core.BlockchainObserver) BlockchainListener {
inputBlocks := make(chan core.Block, 10)
blockchain.SubscribeToBlocks(inputBlocks)
listener := BlockchainListener{
inputBlocks: inputBlocks,
blockchain: blockchain,
observers: observers,
}
return listener
}
func (listener BlockchainListener) Start() {
go listener.blockchain.StartListening()
for block := range listener.inputBlocks {
listener.notifyObservers(block)
}
}
func (listener BlockchainListener) notifyObservers(block core.Block) {
for _, observer := range listener.observers {
observer.NotifyBlockAdded(block)
}
}
func (listener BlockchainListener) Stop() {
listener.blockchain.StopListening()
}
@@ -0,0 +1,13 @@
package blockchain_listener_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestListener(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Listener Suite")
}
@@ -0,0 +1,69 @@
package blockchain_listener_test
import (
"github.com/8thlight/vulcanizedb/pkg/blockchain_listener"
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/fakes"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Blockchain listeners", func() {
It("starts with no blocks", func(done Done) {
observer := fakes.NewFakeBlockchainObserver()
blockchain := &fakes.Blockchain{}
blockchain_listener.NewBlockchainListener(blockchain, []core.BlockchainObserver{observer})
Expect(len(observer.CurrentBlocks)).To(Equal(0))
close(done)
}, 1)
It("sees when one block was added", func(done Done) {
observer := fakes.NewFakeBlockchainObserver()
blockchain := &fakes.Blockchain{}
listener := blockchain_listener.NewBlockchainListener(blockchain, []core.BlockchainObserver{observer})
go listener.Start()
go blockchain.AddBlock(core.Block{Number: 123})
wasObserverNotified := <-observer.WasNotified
Expect(wasObserverNotified).To(BeTrue())
Expect(len(observer.CurrentBlocks)).To(Equal(1))
addedBlock := observer.CurrentBlocks[0]
Expect(addedBlock.Number).To(Equal(int64(123)))
close(done)
}, 1)
It("sees a second block", func(done Done) {
observer := fakes.NewFakeBlockchainObserver()
blockchain := &fakes.Blockchain{}
listener := blockchain_listener.NewBlockchainListener(blockchain, []core.BlockchainObserver{observer})
go listener.Start()
go blockchain.AddBlock(core.Block{Number: 123})
<-observer.WasNotified
go blockchain.AddBlock(core.Block{Number: 456})
wasObserverNotified := <-observer.WasNotified
Expect(wasObserverNotified).To(BeTrue())
Expect(len(observer.CurrentBlocks)).To(Equal(2))
addedBlock := observer.CurrentBlocks[1]
Expect(addedBlock.Number).To(Equal(int64(456)))
close(done)
}, 1)
It("stops listening", func(done Done) {
observer := fakes.NewFakeBlockchainObserver()
blockchain := &fakes.Blockchain{}
listener := blockchain_listener.NewBlockchainListener(blockchain, []core.BlockchainObserver{observer})
go listener.Start()
listener.Stop()
Expect(blockchain.WasToldToStop).To(BeTrue())
close(done)
}, 1)
})
+5
View File
@@ -0,0 +1,5 @@
package config
type Client struct {
IPCPath string
}
+46
View File
@@ -0,0 +1,46 @@
package config
import (
"log"
"os"
"fmt"
"path/filepath"
"path"
"runtime"
"github.com/BurntSushi/toml"
)
type Config struct {
Database Database
Client Client
}
func NewConfig(environment string) Config {
filenameWithExtension := fmt.Sprintf("%s.toml", environment)
absolutePath := filepath.Join(ProjectRoot(), "pkg", "config", "environments", filenameWithExtension)
config := parseConfigFile(absolutePath)
config.Client.IPCPath = filepath.Join(ProjectRoot(), config.Client.IPCPath)
return config
}
func ProjectRoot() string {
var _, filename, _, _ = runtime.Caller(0)
return path.Join(path.Dir(filename), "..", "..")
}
func parseConfigFile(configfile string) Config {
var cfg Config
_, err := os.Stat(configfile)
if err != nil {
log.Fatal("Config file is missing: ", configfile)
}
if _, err := toml.DecodeFile(configfile, &cfg); err != nil {
log.Fatal(err)
}
return cfg
}
+13
View File
@@ -0,0 +1,13 @@
package config_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestConfig(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Config Suite")
}
+23
View File
@@ -0,0 +1,23 @@
package config_test
import (
"path/filepath"
"github.com/8thlight/vulcanizedb/pkg/config"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Loading the config", func() {
It("reads the private config using the environment", func() {
privateConfig := config.NewConfig("private")
Expect(privateConfig.Database.Hostname).To(Equal("localhost"))
Expect(privateConfig.Database.Name).To(Equal("vulcanize_private"))
Expect(privateConfig.Database.Port).To(Equal(5432))
expandedPath := filepath.Join(config.ProjectRoot(), "test_data_dir/geth.ipc")
Expect(privateConfig.Client.IPCPath).To(Equal(expandedPath))
})
})
+13
View File
@@ -0,0 +1,13 @@
package config
import "fmt"
type Database struct {
Hostname string
Name string
Port int
}
func DbConnectionString(dbConfig Database) string {
return fmt.Sprintf("postgresql://%s:%d/%s?sslmode=disable", dbConfig.Hostname, dbConfig.Port, dbConfig.Name)
}
+7
View File
@@ -0,0 +1,7 @@
[database]
name = "vulcanize_private"
hostname = "localhost"
port = 5432
[client]
ipcPath = "test_data_dir/geth.ipc"
+15
View File
@@ -0,0 +1,15 @@
package core
type Block struct {
Difficulty int64
GasLimit int64
GasUsed int64
Hash string
Nonce string
Number int64
ParentHash string
Size int64
Time int64
Transactions []Transaction
UncleHash string
}
+7
View File
@@ -0,0 +1,7 @@
package core
type Blockchain interface {
SubscribeToBlocks(blocks chan Block)
StartListening()
StopListening()
}
+5
View File
@@ -0,0 +1,5 @@
package core
type BlockchainObserver interface {
NotifyBlockAdded(Block)
}
+13
View File
@@ -0,0 +1,13 @@
package core_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestVulcanizedb(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Vulcanizedb Suite")
}
+11
View File
@@ -0,0 +1,11 @@
package core
type Transaction struct {
Hash string
Data []byte
Nonce uint64
To string
GasLimit int64
GasPrice int64
Value int64
}
+22
View File
@@ -0,0 +1,22 @@
package fakes
import "github.com/8thlight/vulcanizedb/pkg/core"
type Blockchain struct {
outputBlocks chan core.Block
WasToldToStop bool
}
func (blockchain *Blockchain) SubscribeToBlocks(outputBlocks chan core.Block) {
blockchain.outputBlocks = outputBlocks
}
func (blockchain Blockchain) AddBlock(block core.Block) {
blockchain.outputBlocks <- block
}
func (*Blockchain) StartListening() {}
func (blockchain *Blockchain) StopListening() {
blockchain.WasToldToStop = true
}
+23
View File
@@ -0,0 +1,23 @@
package fakes
import "github.com/8thlight/vulcanizedb/pkg/core"
type BlockchainObserver struct {
CurrentBlocks []core.Block
WasNotified chan bool
}
func (observer *BlockchainObserver) LastBlock() core.Block {
return observer.CurrentBlocks[len(observer.CurrentBlocks)-1]
}
func NewFakeBlockchainObserver() *BlockchainObserver {
return &BlockchainObserver{
WasNotified: make(chan bool),
}
}
func (observer *BlockchainObserver) NotifyBlockAdded(block core.Block) {
observer.CurrentBlocks = append(observer.CurrentBlocks, block)
observer.WasNotified <- true
}
+50
View File
@@ -0,0 +1,50 @@
package geth
import (
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
)
func gethTransToCoreTrans(transaction *types.Transaction) core.Transaction {
to := transaction.To()
toHex := convertTo(to)
return core.Transaction{
Hash: transaction.Hash().Hex(),
Data: transaction.Data(),
Nonce: transaction.Nonce(),
To: toHex,
GasLimit: transaction.Gas().Int64(),
GasPrice: transaction.GasPrice().Int64(),
Value: transaction.Value().Int64(),
}
}
func GethBlockToCoreBlock(gethBlock *types.Block) core.Block {
transactions := []core.Transaction{}
for _, gethTransaction := range gethBlock.Transactions() {
transactions = append(transactions, gethTransToCoreTrans(gethTransaction))
}
return core.Block{
Difficulty: gethBlock.Difficulty().Int64(),
GasLimit: gethBlock.GasLimit().Int64(),
GasUsed: gethBlock.GasUsed().Int64(),
Hash: gethBlock.Hash().Hex(),
Nonce: hexutil.Encode(gethBlock.Header().Nonce[:]),
Number: gethBlock.Number().Int64(),
ParentHash: gethBlock.ParentHash().Hex(),
Size: gethBlock.Size().Int64(),
Time: gethBlock.Time().Int64(),
Transactions: transactions,
UncleHash: gethBlock.UncleHash().Hex(),
}
}
func convertTo(to *common.Address) string {
if to == nil {
return ""
} else {
return to.Hex()
}
}
+93
View File
@@ -0,0 +1,93 @@
package geth_test
import (
"math/big"
"github.com/8thlight/vulcanizedb/pkg/geth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Conversion of GethBlock to core.Block", func() {
It("converts basic Block metada", func() {
difficulty := big.NewInt(1)
gasLimit := int64(100000)
gasUsed := int64(100000)
nonce := types.BlockNonce{10}
number := int64(1)
time := int64(140000000)
header := types.Header{
Difficulty: difficulty,
GasLimit: big.NewInt(gasLimit),
GasUsed: big.NewInt(gasUsed),
Nonce: nonce,
Number: big.NewInt(number),
ParentHash: common.Hash{64},
Time: big.NewInt(time),
UncleHash: common.Hash{128},
}
block := types.NewBlock(&header, []*types.Transaction{}, []*types.Header{}, []*types.Receipt{})
gethBlock := geth.GethBlockToCoreBlock(block)
Expect(gethBlock.Difficulty).To(Equal(difficulty.Int64()))
Expect(gethBlock.GasLimit).To(Equal(gasLimit))
Expect(gethBlock.GasUsed).To(Equal(gasUsed))
Expect(gethBlock.Hash).To(Equal(block.Hash().Hex()))
Expect(gethBlock.Nonce).To(Equal(hexutil.Encode(header.Nonce[:])))
Expect(gethBlock.Number).To(Equal(number))
Expect(gethBlock.ParentHash).To(Equal(block.ParentHash().Hex()))
Expect(gethBlock.Size).To(Equal(block.Size().Int64()))
Expect(gethBlock.Time).To(Equal(time))
Expect(gethBlock.UncleHash).To(Equal(block.UncleHash().Hex()))
})
Describe("the converted transations", func() {
It("is empty", func() {
header := types.Header{}
block := types.NewBlock(&header, []*types.Transaction{}, []*types.Header{}, []*types.Receipt{})
coreBlock := geth.GethBlockToCoreBlock(block)
Expect(len(coreBlock.Transactions)).To(Equal(0))
})
It("converts a single transations", func() {
nonce := uint64(10000)
header := types.Header{}
to := common.Address{1}
amount := big.NewInt(10)
gasLimit := big.NewInt(5000)
gasPrice := big.NewInt(3)
payload := []byte("1234")
gethTransaction := types.NewTransaction(nonce, to, amount, gasLimit, gasPrice, payload)
gethBlock := types.NewBlock(&header, []*types.Transaction{gethTransaction}, []*types.Header{}, []*types.Receipt{})
coreBlock := geth.GethBlockToCoreBlock(gethBlock)
Expect(len(coreBlock.Transactions)).To(Equal(1))
coreTransaction := coreBlock.Transactions[0]
Expect(coreTransaction.Data).To(Equal(gethTransaction.Data()))
Expect(coreTransaction.To).To(Equal(gethTransaction.To().Hex()))
Expect(coreTransaction.GasLimit).To(Equal(gethTransaction.Gas().Int64()))
Expect(coreTransaction.GasPrice).To(Equal(gethTransaction.GasPrice().Int64()))
Expect(coreTransaction.Value).To(Equal(gethTransaction.Value().Int64()))
Expect(coreTransaction.Nonce).To(Equal(gethTransaction.Nonce()))
})
It("has an empty to field when transaction creates a new contract", func() {
gethTransaction := types.NewContractCreation(uint64(10000), big.NewInt(10), big.NewInt(5000), big.NewInt(3), []byte("1234"))
gethBlock := types.NewBlock(&types.Header{}, []*types.Transaction{gethTransaction}, []*types.Header{}, []*types.Receipt{})
coreBlock := geth.GethBlockToCoreBlock(gethBlock)
coreTransaction := coreBlock.Transactions[0]
Expect(coreTransaction.To).To(Equal(""))
})
})
})
+49
View File
@@ -0,0 +1,49 @@
package geth
import (
"fmt"
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"golang.org/x/net/context"
)
type GethBlockchain struct {
client *ethclient.Client
readGethHeaders chan *types.Header
outputBlocks chan core.Block
newHeadSubscription ethereum.Subscription
}
func NewGethBlockchain(ipcPath string) *GethBlockchain {
fmt.Printf("Creating Geth Blockchain to: %s\n", ipcPath)
blockchain := GethBlockchain{}
client, _ := ethclient.Dial(ipcPath)
blockchain.client = client
return &blockchain
}
func (blockchain *GethBlockchain) SubscribeToBlocks(blocks chan core.Block) {
blockchain.outputBlocks = blocks
fmt.Println("SubscribeToBlocks")
inputHeaders := make(chan *types.Header, 10)
myContext := context.Background()
blockchain.readGethHeaders = inputHeaders
subscription, _ := blockchain.client.SubscribeNewHead(myContext, inputHeaders)
blockchain.newHeadSubscription = subscription
}
func (blockchain *GethBlockchain) StartListening() {
myContext := context.Background()
for header := range blockchain.readGethHeaders {
gethBlock, _ := blockchain.client.BlockByNumber(myContext, header.Number)
block := GethBlockToCoreBlock(gethBlock)
blockchain.outputBlocks <- block
}
}
func (blockchain *GethBlockchain) StopListening() {
blockchain.newHeadSubscription.Unsubscribe()
}
+13
View File
@@ -0,0 +1,13 @@
package geth_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestGeth(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Geth Suite")
}
+18
View File
@@ -0,0 +1,18 @@
package observers
import (
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/repositories"
)
type BlockchainDbObserver struct {
repository repositories.Repository
}
func NewBlockchainDbObserver(repository repositories.Repository) BlockchainDbObserver {
return BlockchainDbObserver{repository: repository}
}
func (observer BlockchainDbObserver) NotifyBlockAdded(block core.Block) {
observer.repository.CreateBlock(block)
}
@@ -0,0 +1,38 @@
package observers_test
import (
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/observers"
"github.com/8thlight/vulcanizedb/pkg/repositories"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Saving blocks to the database", func() {
var repository *repositories.InMemory
BeforeEach(func() {
repository = repositories.NewInMemory()
})
It("implements the observer interface", func() {
var observer core.BlockchainObserver = observers.NewBlockchainDbObserver(repository)
Expect(observer).NotTo(BeNil())
})
It("saves a block with one transaction", func() {
block := core.Block{
Number: 123,
Transactions: []core.Transaction{{}},
}
observer := observers.NewBlockchainDbObserver(repository)
observer.NotifyBlockAdded(block)
savedBlock := repository.FindBlockByNumber(123)
Expect(savedBlock).NotTo(BeNil())
Expect(len(savedBlock.Transactions)).To(Equal(1))
})
})
@@ -0,0 +1,18 @@
package observers
import (
"fmt"
"time"
"github.com/8thlight/vulcanizedb/pkg/core"
)
type BlockchainLoggingObserver struct{}
func (blockchainObserver BlockchainLoggingObserver) NotifyBlockAdded(block core.Block) {
fmt.Printf("New block was added: %d\n"+
"\tTime: %v\n"+
"\tGas Limit: %d\n"+
"\tGas Used: %d\n"+
"\tNumber of Transactions %d\n", block.Number, time.Unix(block.Time, 0), block.GasLimit, block.GasUsed, len(block.Transactions))
}
+13
View File
@@ -0,0 +1,13 @@
package observers_test
import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"testing"
)
func TestObservers(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Observers Suite")
}
+27
View File
@@ -0,0 +1,27 @@
package repositories
import (
"github.com/8thlight/vulcanizedb/pkg/core"
)
type InMemory struct {
blocks map[int64]*core.Block
}
func NewInMemory() *InMemory {
return &InMemory{
blocks: make(map[int64]*core.Block),
}
}
func (repository *InMemory) CreateBlock(block core.Block) {
repository.blocks[block.Number] = &block
}
func (repository *InMemory) BlockCount() int {
return len(repository.blocks)
}
func (repository *InMemory) FindBlockByNumber(blockNumber int64) *core.Block {
return repository.blocks[blockNumber]
}
+141
View File
@@ -0,0 +1,141 @@
package repositories_test
import (
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/repositories"
_ "github.com/lib/pq"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("The In Memory Repository", func() {
Describe("Saving blocks", func() {
It("starts with no blocks", func() {
count := buildRepository().BlockCount()
Expect(count).Should(Equal(0))
})
It("increments the block count", func() {
block := core.Block{Number: 123}
repository := buildRepository()
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 := buildRepository()
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 := buildRepository()
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 := buildRepository()
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 := buildRepository()
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 := buildRepository()
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))
})
})
})
func buildRepository() *repositories.InMemory {
return repositories.NewInMemory()
}
+108
View File
@@ -0,0 +1,108 @@
package repositories
import (
"database/sql"
"github.com/8thlight/vulcanizedb/pkg/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
}
+159
View File
@@ -0,0 +1,159 @@
package repositories_test
import (
"github.com/8thlight/vulcanizedb/pkg/config"
"github.com/8thlight/vulcanizedb/pkg/core"
"github.com/8thlight/vulcanizedb/pkg/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))
})
})
})
@@ -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")
}
+7
View File
@@ -0,0 +1,7 @@
package repositories
import "github.com/8thlight/vulcanizedb/pkg/core"
type Repository interface {
CreateBlock(block core.Block)
}