diff --git a/core/block.go b/core/block.go index c5247602..ce4d413a 100644 --- a/core/block.go +++ b/core/block.go @@ -6,40 +6,24 @@ import ( "github.com/ethereum/go-ethereum/core/types" ) -//Our block representation type Block struct { - Number *big.Int - GasLimit *big.Int - GasUsed *big.Int - Time *big.Int - NumberOfTransactions int + Number *big.Int + GasLimit *big.Int + GasUsed *big.Int + Time *big.Int + Transactions []Transaction } -//Our Block to DB -func BlockToBlockRecord(block Block) *BlockRecord { - return &BlockRecord{ - BlockNumber: block.Number.Int64(), - GasLimit: block.GasLimit.Int64(), - GasUsed: block.GasUsed.Int64(), - Time: block.Time.Int64(), - } -} - -//DB block representation -type BlockRecord struct { - BlockNumber int64 `db:"block_number"` - GasLimit int64 `db:"block_gaslimit"` - GasUsed int64 `db:"block_gasused"` - Time int64 `db:"block_time"` -} - -//Geth Block to Ours func GethBlockToCoreBlock(gethBlock *types.Block) Block { + transactions := []Transaction{} + for _, gethTransaction := range gethBlock.Transactions() { + transactions = append(transactions, gethTransToCoreTrans(gethTransaction)) + } return Block{ - Number: gethBlock.Number(), - GasLimit: gethBlock.GasLimit(), - GasUsed: gethBlock.GasUsed(), - Time: gethBlock.Time(), - NumberOfTransactions: len(gethBlock.Transactions()), + Number: gethBlock.Number(), + GasLimit: gethBlock.GasLimit(), + GasUsed: gethBlock.GasUsed(), + Time: gethBlock.Time(), + Transactions: transactions, } } diff --git a/core/block_test.go b/core/block_test.go deleted file mode 100644 index ac622baf..00000000 --- a/core/block_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package core - -import ( - "math/big" - - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" -) - -var _ = Describe("Converting core.Block to DB record", func() { - - It("Converts core.Block to BlockRecord", func() { - blockNumber := big.NewInt(1) - gasLimit := big.NewInt(100000) - gasUsed := big.NewInt(10) - blockTime := big.NewInt(1508981640) - block := Block{Number: blockNumber, GasLimit: gasLimit, GasUsed: gasUsed, Time: blockTime} - blockRecord := BlockToBlockRecord(block) - Expect(blockRecord.BlockNumber).To(Equal(int64(1))) - Expect(blockRecord.GasLimit).To(Equal(int64(100000))) - Expect(blockRecord.GasUsed).To(Equal(int64(10))) - - }) -}) diff --git a/core/blockchain_db_observer.go b/core/blockchain_db_observer.go index 3fb72cb2..23bb2fb7 100644 --- a/core/blockchain_db_observer.go +++ b/core/blockchain_db_observer.go @@ -10,10 +10,18 @@ type BlockchainDBObserver struct { } func (observer BlockchainDBObserver) NotifyBlockAdded(block Block) { - blockRecord := BlockToBlockRecord(block) - observer.Db.NamedExec( - "INSERT INTO blocks "+ - "(block_number, block_gaslimit, block_gasused, block_time) "+ - "VALUES (:block_number, :block_gaslimit, :block_gasused, :block_time)", blockRecord) - //observer.Db.MustExec("Insert INTO blocks (block_number) VALUES ($1)", block.Number.Int64()) + observer.Db.MustExec("Insert INTO blocks "+ + "(block_number, block_gaslimit, block_gasused, block_time) "+ + "VALUES ($1, $2, $3, $4)", + block.Number.Int64(), block.GasLimit.Int64(), block.GasUsed.Int64(), block.Time.Int64()) + + for _, transaction := range block.Transactions { + observer.saveTransaction(transaction) + } +} + +func (observer BlockchainDBObserver) saveTransaction(transaction Transaction) { + observer.Db.MustExec("Insert INTO transactions "+ + "(tx_hash, tx_nonce, tx_to, tx_gaslimit, tx_gasprice, tx_value) VALUES ($1, $2, $3, $4, $5, $6)", + transaction.Hash, transaction.Nonce, transaction.To, transaction.GasLimit, transaction.GasPrice, transaction.Value) } diff --git a/core/blockchain_db_observer_test.go b/core/blockchain_db_observer_test.go index ad6ff0c1..dae1a78b 100644 --- a/core/blockchain_db_observer_test.go +++ b/core/blockchain_db_observer_test.go @@ -24,13 +24,14 @@ var _ = Describe("Saving blocks to the database", func() { var db *sqlx.DB var err error + pgConfig := fmt.Sprintf( + "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", + host, port, user, password, dbname) BeforeEach(func() { - pgConfig := fmt.Sprintf( - "host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", - host, port, user, password, dbname) db, err = sqlx.Connect("postgres", pgConfig) db.MustExec("DELETE FROM blocks") + db.MustExec("DELETE FROM transactions") }) It("implements the observer interface", func() { @@ -51,25 +52,100 @@ var _ = Describe("Saving blocks to the database", func() { }) It("inserts a block", func() { + // setup a block in memory blockNumber := big.NewInt(1) gasLimit := big.NewInt(1000000) gasUsed := big.NewInt(10) blockTime := big.NewInt(1508981640) block := core.Block{Number: blockNumber, GasLimit: gasLimit, GasUsed: gasUsed, Time: blockTime} + // save the block to the database observer := core.BlockchainDBObserver{Db: db} observer.NotifyBlockAdded(block) - rows, err := db.Queryx("SELECT * FROM blocks") + // find the saved block + rows, err := db.Query("SELECT block_number, block_gaslimit, block_gasused, block_time FROM blocks") Expect(err).To(BeNil()) - var savedBlocks []core.BlockRecord + var savedBlocks []core.Block for rows.Next() { - var savedBlock core.BlockRecord - rows.StructScan(&savedBlock) + var blockNumber int64 + var blockTime float64 + var gasLimit float64 + var gasUsed float64 + rows.Scan(&blockNumber, &gasLimit, &gasUsed, &blockTime) + savedBlock := core.Block{ + GasUsed: big.NewInt(int64(gasUsed)), + GasLimit: big.NewInt(int64(gasLimit)), + Number: big.NewInt(blockNumber), + Time: big.NewInt(int64(blockTime)), + } savedBlocks = append(savedBlocks, savedBlock) } + // assert against the attributes Expect(len(savedBlocks)).To(Equal(1)) - Expect(savedBlocks[0].BlockNumber) + Expect(savedBlocks[0].Number.Int64()).To(Equal(blockNumber.Int64())) + Expect(savedBlocks[0].GasLimit.Int64()).To(Equal(gasLimit.Int64())) + Expect(savedBlocks[0].GasUsed.Int64()).To(Equal(gasUsed.Int64())) + Expect(savedBlocks[0].Time).To(Equal(blockTime)) + }) + + 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, + } + blockNumber := big.NewInt(1) + gasUsed := big.NewInt(10) + blockTime := big.NewInt(1508981640) + block := core.Block{Number: blockNumber, GasLimit: big.NewInt(gasLimit), GasUsed: gasUsed, Time: blockTime, Transactions: []core.Transaction{txRecord}} + + observer := core.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{ + Hash: dbHash, + Nonce: dbNonce, + To: dbTo, + GasLimit: dbGasLimit, + GasPrice: dbGasPrice, + Value: dbValue, + } + savedTransactions = append(savedTransactions, savedTransaction) + } + + Expect(len(savedTransactions)).To(Equal(1)) + savedTransaction := savedTransactions[0] + Expect(savedTransaction.Hash).To(Equal(txRecord.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)) + }) }) }) diff --git a/core/blockchain_logging_observer.go b/core/blockchain_logging_observer.go index 1abb2547..4fc4468d 100644 --- a/core/blockchain_logging_observer.go +++ b/core/blockchain_logging_observer.go @@ -12,5 +12,5 @@ func (blockchainObserver BlockchainLoggingObserver) NotifyBlockAdded(block Block "\tTime: %v\n"+ "\tGas Limit: %d\n"+ "\tGas Used: %d\n"+ - "\tNumber of Transactions %d\n", block.Number, time.Unix(block.Time.Int64(), 0), block.GasLimit, block.GasUsed, block.NumberOfTransactions) + "\tNumber of Transactions %d\n", block.Number, time.Unix(block.Time.Int64(), 0), block.GasLimit, block.GasUsed, len(block.Transactions)) } diff --git a/core/converting_a_geth_block_test.go b/core/converting_a_geth_block_test.go new file mode 100644 index 00000000..5874dbd5 --- /dev/null +++ b/core/converting_a_geth_block_test.go @@ -0,0 +1,75 @@ +package core_test + +import ( + "math/big" + + "github.com/8thlight/vulcanizedb/core" + "github.com/ethereum/go-ethereum/common" + "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() { + blockNumber := big.NewInt(1) + gasUsed := big.NewInt(100000) + gasLimit := big.NewInt(100000) + time := big.NewInt(140000000) + + header := types.Header{Number: blockNumber, GasUsed: gasUsed, Time: time, GasLimit: gasLimit} + block := types.NewBlock(&header, []*types.Transaction{}, []*types.Header{}, []*types.Receipt{}) + gethBlock := core.GethBlockToCoreBlock(block) + + Expect(gethBlock.Number).To(Equal(blockNumber)) + Expect(gethBlock.GasUsed).To(Equal(gasUsed)) + Expect(gethBlock.GasLimit).To(Equal(gasLimit)) + Expect(gethBlock.Time).To(Equal(time)) + }) + + Describe("the converted transations", func() { + It("is empty", func() { + header := types.Header{} + block := types.NewBlock(&header, []*types.Transaction{}, []*types.Header{}, []*types.Receipt{}) + + coreBlock := core.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 := core.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 := core.GethBlockToCoreBlock(gethBlock) + + coreTransaction := coreBlock.Transactions[0] + Expect(coreTransaction.To).To(Equal("")) + }) + }) + +}) diff --git a/core/geth_blockchain.go b/core/geth_blockchain.go index be61c4f3..827f776f 100644 --- a/core/geth_blockchain.go +++ b/core/geth_blockchain.go @@ -20,7 +20,6 @@ func NewGethBlockchain(ipcPath string) *GethBlockchain { fmt.Printf("Creating Geth Blockchain to: %s\n", ipcPath) blockchain := GethBlockchain{} client, _ := ethclient.Dial(ipcPath) - // TODO: handle error gracefully blockchain.client = client return &blockchain } diff --git a/core/transaction.go b/core/transaction.go new file mode 100644 index 00000000..84390347 --- /dev/null +++ b/core/transaction.go @@ -0,0 +1,38 @@ +package core + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" +) + +type Transaction struct { + Hash string + Data []byte + Nonce uint64 + To string + GasLimit int64 + GasPrice int64 + Value int64 +} + +func gethTransToCoreTrans(transaction *types.Transaction) Transaction { + to := transaction.To() + toHex := convertTo(to) + return 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 convertTo(to *common.Address) string { + if to == nil { + return "" + } else { + return to.Hex() + } +} diff --git a/migrations/1509119369_initial_transaction_table.down.sql b/migrations/1509119369_initial_transaction_table.down.sql new file mode 100644 index 00000000..bd4e7b76 --- /dev/null +++ b/migrations/1509119369_initial_transaction_table.down.sql @@ -0,0 +1 @@ +DROP TABLE transactions \ No newline at end of file diff --git a/migrations/1509119369_initial_transaction_table.up.sql b/migrations/1509119369_initial_transaction_table.up.sql new file mode 100644 index 00000000..d73b09fe --- /dev/null +++ b/migrations/1509119369_initial_transaction_table.up.sql @@ -0,0 +1,10 @@ +CREATE TABLE transactions +( + id SERIAL PRIMARY KEY, + tx_hash VARCHAR(66), + tx_nonce NUMERIC, + tx_to varchar(66), + tx_gaslimit NUMERIC, + tx_gasprice NUMERIC, + tx_value NUMERIC +) \ No newline at end of file diff --git a/migrations/schema.sql b/migrations/schema.sql index cdef6443..44a5f4f4 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -76,6 +76,40 @@ CREATE TABLE schema_migrations ( ); +-- +-- Name: transactions; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE transactions ( + id integer NOT NULL, + tx_hash character varying(66), + tx_nonce numeric, + tx_to character varying(66), + tx_gaslimit numeric, + tx_gasprice numeric, + tx_value numeric +); + + +-- +-- Name: transactions_id_seq; Type: SEQUENCE; Schema: public; Owner: - +-- + +CREATE SEQUENCE transactions_id_seq + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +-- +-- Name: transactions_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: - +-- + +ALTER SEQUENCE transactions_id_seq OWNED BY transactions.id; + + -- -- Name: blocks id; Type: DEFAULT; Schema: public; Owner: - -- @@ -83,6 +117,13 @@ CREATE TABLE schema_migrations ( ALTER TABLE ONLY blocks ALTER COLUMN id SET DEFAULT nextval('blocks_id_seq'::regclass); +-- +-- Name: transactions id; Type: DEFAULT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY transactions ALTER COLUMN id SET DEFAULT nextval('transactions_id_seq'::regclass); + + -- -- Name: blocks blocks_pkey; Type: CONSTRAINT; Schema: public; Owner: - -- @@ -99,6 +140,14 @@ ALTER TABLE ONLY schema_migrations ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); +-- +-- Name: transactions transactions_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY transactions + ADD CONSTRAINT transactions_pkey PRIMARY KEY (id); + + -- -- PostgreSQL database dump complete --