Skip to content

Commit

Permalink
[NOD-303] Implement get transaction by id for api server (#391)
Browse files Browse the repository at this point in the history
* [NOD-303] Implement get transaction by id for api server

* [NOD-303] Make routeParamTxID a constant

* [NOD-303] Change database is not current error.

* [NOD-303] Add ID to TransactionInput and TransactionOutput models

* [NOD-303] Change transactions_outputs table name to transaction_outputs and transactions_inputs to transaction_inputs

* [NOD-303] Add json annotations to transaction response types

* [NOD-303] Split server package

* [NOD-303] Add GetTransactionByHashHandler

* [NOD-303] Add comments to exported functions and variables

* [NOD-303] Put response types in a separate file

* [NOD-303] Rename functions
  • Loading branch information
someone235 authored and svarogg committed Sep 3, 2019
1 parent 7521545 commit ae25ec2
Show file tree
Hide file tree
Showing 36 changed files with 541 additions and 122 deletions.
15 changes: 9 additions & 6 deletions apiserver/config.go → apiserver/config/config.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package main
package config

import (
"errors"
Expand All @@ -20,23 +20,26 @@ var (
defaultHTTPListen = "0.0.0.0:8080"
)

type config struct {
// Config defines the configuration options for the API server.
type Config struct {
LogDir string `long:"logdir" description:"Directory to log output."`
RPCUser string `short:"u" long:"rpcuser" description:"RPC username" required:"true"`
RPCPassword string `short:"P" long:"rpcpass" default-mask:"-" description:"RPC password" required:"true"`
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to" required:"true"`
RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"`
DisableTLS bool `long:"notls" description:"Disable TLS"`
DBHost string `long:"dbhost" description:"Database host"`
DBAddress string `long:"dbaddress" description:"Database address"`
DBUser string `long:"dbuser" description:"Database user" required:"true"`
DBPassword string `long:"dbpass" description:"Database password" required:"true"`
DBName string `long:"dbname" description:"Database name" required:"true"`
HTTPListen string `long:"listen" description:"HTTP address to listen on (default: 0.0.0.0:8080)"`
}

func parseConfig() (*config, error) {
cfg := &config{
// Parse parses the CLI arguments and returns a config struct.
func Parse() (*Config, error) {
cfg := &Config{
LogDir: defaultLogDir,
DBHost: defaultDBAddr,
DBAddress: defaultDBAddr,
HTTPListen: defaultHTTPListen,
}
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
Expand Down
3 changes: 2 additions & 1 deletion apiserver/connect.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,12 @@ package main

import (
"fmt"
"github.com/daglabs/btcd/apiserver/config"
"github.com/daglabs/btcd/rpcclient"
"io/ioutil"
)

func connectToServer(cfg *config) (*apiServerClient, error) {
func connectToServer(cfg *config.Config) (*apiServerClient, error) {
var cert []byte
if !cfg.DisableTLS {
var err error
Expand Down
69 changes: 69 additions & 0 deletions apiserver/controllers/responsetypes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package controllers

import (
"encoding/hex"
"github.com/daglabs/btcd/apiserver/models"
)

type transactionResponse struct {
TransactionHash string `json:"transactionHash"`
TransactionID string `json:"transactionId"`
AcceptingBlockHash string `json:"acceptingBlockHash,omitempty"`
AcceptingBlockBlueScore uint64 `json:"acceptingBlockBlueScore,omitempty"`
SubnetworkID string `json:"subnetworkId"`
LockTime uint64 `json:"lockTime"`
Gas uint64 `json:"gas,omitempty"`
PayloadHash string `json:"payloadHash,omitempty"`
Payload string `json:"payload,omitempty"`
Inputs []*transactionInputResponse `json:"inputs"`
Outputs []*transactionOutputResponse `json:"outputs"`
Mass uint64 `json:"mass"`
}

type transactionOutputResponse struct {
TransactionID string `json:"transactionId,omitempty"`
Value uint64 `json:"value"`
PkScript string `json:"pkScript"`
Address string `json:"address"`
}

type transactionInputResponse struct {
TransactionID string `json:"transactionId,omitempty"`
PreviousTransactionID string `json:"previousTransactionId"`
PreviousTransactionOutputIndex uint32 `json:"previousTransactionOutputIndex"`
SignatureScript string `json:"signatureScript"`
Sequence uint64 `json:"sequence"`
}

func convertTxModelToTxResponse(tx *models.Transaction) *transactionResponse {
txRes := &transactionResponse{
TransactionHash: tx.TransactionHash,
TransactionID: tx.TransactionID,
AcceptingBlockHash: tx.AcceptingBlock.BlockHash,
AcceptingBlockBlueScore: tx.AcceptingBlock.BlueScore,
SubnetworkID: tx.Subnetwork.SubnetworkID,
LockTime: tx.LockTime,
Gas: tx.Gas,
PayloadHash: tx.PayloadHash,
Payload: hex.EncodeToString(tx.Payload),
Inputs: make([]*transactionInputResponse, len(tx.TransactionOutputs)),
Outputs: make([]*transactionOutputResponse, len(tx.TransactionInputs)),
Mass: tx.Mass,
}
for i, txOut := range tx.TransactionOutputs {
txRes.Outputs[i] = &transactionOutputResponse{
Value: txOut.Value,
PkScript: hex.EncodeToString(txOut.PkScript),
Address: "", // TODO: Fill it when there's an addrindex in the DB.
}
}
for i, txIn := range tx.TransactionInputs {
txRes.Inputs[i] = &transactionInputResponse{
PreviousTransactionID: txIn.TransactionOutput.Transaction.TransactionID,
PreviousTransactionOutputIndex: txIn.TransactionOutput.Index,
SignatureScript: hex.EncodeToString(txIn.SignatureScript),
Sequence: txIn.Sequence,
}
}
return txRes
}
46 changes: 46 additions & 0 deletions apiserver/controllers/transaction.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package controllers

import (
"fmt"
"github.com/daglabs/btcd/apiserver/database"
"github.com/daglabs/btcd/apiserver/models"
"github.com/daglabs/btcd/apiserver/utils"
"github.com/daglabs/btcd/util/daghash"
"github.com/jinzhu/gorm"
"net/http"
)

// GetTransactionByIDHandler returns a transaction by a given transaction ID.
func GetTransactionByIDHandler(txID string) (interface{}, *utils.HandlerError) {
if len(txID) != daghash.TxIDSize*2 {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("The given txid is not a hex-encoded %d-byte hash.", daghash.TxIDSize))
}
tx := &models.Transaction{}
db := database.DB.Where("transaction_id = ?", txID)
addTxPreloadedFields(db).First(&tx)
if tx.ID == 0 {
return nil, utils.NewHandlerError(http.StatusNotFound, "No transaction with the given txid was found.")
}
return convertTxModelToTxResponse(tx), nil
}

// GetTransactionByHashHandler returns a transaction by a given transaction hash.
func GetTransactionByHashHandler(txHash string) (interface{}, *utils.HandlerError) {
if len(txHash) != daghash.HashSize*2 {
return nil, utils.NewHandlerError(http.StatusUnprocessableEntity, fmt.Sprintf("The given txhash is not a hex-encoded %d-byte hash.", daghash.HashSize))
}
tx := &models.Transaction{}
db := database.DB.Where("transaction_hash = ?", txHash)
addTxPreloadedFields(db).First(&tx)
if tx.ID == 0 {
return nil, utils.NewHandlerError(http.StatusNotFound, "No transaction with the given txhash was found.")
}
return convertTxModelToTxResponse(tx), nil
}

func addTxPreloadedFields(db *gorm.DB) *gorm.DB {
return db.Preload("AcceptingBlock").
Preload("Subnetwork").
Preload("TransactionOutputs").
Preload("TransactionInputs.TransactionOutput.Transaction")
}
74 changes: 74 additions & 0 deletions apiserver/database/database.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package database

import (
"fmt"
"github.com/daglabs/btcd/apiserver/config"
"github.com/golang-migrate/migrate/v4/source"
"github.com/jinzhu/gorm"
"os"

"github.com/golang-migrate/migrate/v4"
)

// DB is the API server database.
var DB *gorm.DB

// Connect connects to the database mentioned in
// config variable.
func Connect(cfg *config.Config) error {
connectionString := buildConnectionString(cfg)
isCurrent, err := isCurrent(connectionString)
if err != nil {
return fmt.Errorf("Error checking whether the database is current: %s", err)
}
if !isCurrent {
return fmt.Errorf("Database is not current. Please migrate" +
" the database and start again.")
}

DB, err = gorm.Open("mysql", connectionString)
if err != nil {
return err
}
return nil
}

func buildConnectionString(cfg *config.Config) string {
return fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True",
cfg.DBUser, cfg.DBPassword, cfg.DBAddress, cfg.DBName)
}

// isCurrent resolves whether the database is on the latest
// version of the schema.
func isCurrent(connectionString string) (bool, error) {
driver, err := source.Open("file://migrations")
if err != nil {
return false, err
}
migrator, err := migrate.NewWithSourceInstance(
"migrations", driver, "mysql://"+connectionString)
if err != nil {
return false, err
}

// Get the current version
version, isDirty, err := migrator.Version()
if err == migrate.ErrNilVersion {
return false, nil
}
if err != nil {
return false, err
}
if isDirty {
return false, fmt.Errorf("Database is dirty")
}

// The database is current if Next returns ErrNotExist
_, err = driver.Next(version)
if pathErr, ok := err.(*os.PathError); ok {
if pathErr.Err == os.ErrNotExist {
return true, nil
}
}
return false, err
}

This file was deleted.

This file was deleted.

18 changes: 17 additions & 1 deletion apiserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,36 @@ package main

import (
"fmt"
"github.com/daglabs/btcd/apiserver/config"
"github.com/daglabs/btcd/apiserver/database"
"github.com/daglabs/btcd/apiserver/server"
"github.com/daglabs/btcd/logger"
"github.com/daglabs/btcd/signal"
"github.com/daglabs/btcd/util/panics"
_ "github.com/golang-migrate/migrate/v4/database/mysql"
_ "github.com/golang-migrate/migrate/v4/source/file"
_ "github.com/jinzhu/gorm/dialects/mysql"
)

func main() {
defer panics.HandlePanic(log, logger.BackendLog)

cfg, err := parseConfig()
cfg, err := config.Parse()
if err != nil {
panic(fmt.Errorf("Error parsing command-line arguments: %s", err))
}

err = database.Connect(cfg)
if err != nil {
panic(fmt.Errorf("Error connecting to database: %s", err))
}
defer func() {
err := database.DB.Close()
if err != nil {
panic(fmt.Errorf("Error closing the database: %s", err))
}
}()

client, err := connectToServer(cfg)
if err != nil {
panic(fmt.Errorf("Error connecting to servers: %s", err))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
CREATE TABLE `blocks`
(
`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
`block_hash` CHAR(64) NOT NULL,
`block_hash` CHAR(64) NOT NULL,
`accepting_block_id` BIGINT UNSIGNED NULL,
`version` INT NOT NULL,
`hash_merkle_root` CHAR(64) NOT NULL,
`accepted_id_merkle_root` CHAR(64) NOT NULL,
`utxo_commitment` CHAR(64) NOT NULL,
`hash_merkle_root` CHAR(64) NOT NULL,
`accepted_id_merkle_root` CHAR(64) NOT NULL,
`utxo_commitment` CHAR(64) NOT NULL,
`timestamp` DATETIME NOT NULL,
`bits` INT UNSIGNED NOT NULL,
`nonce` BIGINT UNSIGNED NOT NULL,
`blue_score` BIGINT UNSIGNED NOT NULL,
`is_chain_block` TINYINT NOT NULL,
`mass` BIGINT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `idx_blocks_block_hash` (`block_hash`),
INDEX `idx_blocks_timestamp` (`timestamp`),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ CREATE TABLE `transactions`
`gas` BIGINT UNSIGNED NOT NULL,
`payload_hash` CHAR(64) NOT NULL,
`payload` BLOB NOT NULL,
`mass` BIGINT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE INDEX `idx_transactions_transaction_hash` (`transaction_hash`),
INDEX `idx_transactions_transaction_id` (`transaction_id`),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE `transaction_outputs`;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CREATE TABLE `transactions_outputs`
CREATE TABLE `transaction_outputs`
(
`id` BIGINT UNSIGNED NOT NULL,
`transaction_id` BIGINT UNSIGNED NOT NULL,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE `transaction_inputs`;
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
CREATE TABLE `transactions_inputs`
CREATE TABLE `transaction_inputs`
(
`id` BIGINT UNSIGNED NOT NULL,
`transaction_id` BIGINT UNSIGNED NULL,
Expand Down
31 changes: 19 additions & 12 deletions apiserver/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type Block struct {
Nonce uint64
BlueScore uint64
IsChainBlock bool
Mass uint64
ParentBlocks []Block `gorm:"many2many:parent_blocks;"`
}

Expand All @@ -40,22 +41,26 @@ type RawBlock struct {
// Subnetwork is the gorm model for the 'subnetworks' table
type Subnetwork struct {
ID uint64 `gorm:"primary_key"`
SubnetworkID []byte
SubnetworkID string
}

// Transaction is the gorm model for the 'transactions' table
type Transaction struct {
ID uint64 `gorm:"primary_key"`
AcceptingBlockID uint64
AcceptingBlock Block
TransactionHash string
TransactionID string
LockTime uint64
SubnetworkID uint64
Gas uint64
PayloadHash string
Payload []byte
Blocks []Block `gorm:"many2many:transactions_to_blocks;"`
ID uint64 `gorm:"primary_key"`
AcceptingBlockID uint64
AcceptingBlock Block
TransactionHash string
TransactionID string
LockTime uint64
SubnetworkID uint64
Subnetwork Subnetwork
Gas uint64
PayloadHash string
Payload []byte
Mass uint64
Blocks []Block `gorm:"many2many:transactions_to_blocks;"`
TransactionOutputs []TransactionOutput
TransactionInputs []TransactionInput
}

// TransactionBlock is the gorm model for the 'transactions_to_blocks' table
Expand All @@ -75,6 +80,7 @@ func (TransactionBlock) TableName() string {

// TransactionOutput is the gorm model for the 'transaction_outputs' table
type TransactionOutput struct {
ID uint64 `gorm:"primary_key"`
TransactionID uint64
Transaction Transaction
Index uint32
Expand All @@ -84,6 +90,7 @@ type TransactionOutput struct {

// TransactionInput is the gorm model for the 'transaction_inputs' table
type TransactionInput struct {
ID uint64 `gorm:"primary_key"`
TransactionID uint64
Transaction Transaction
TransactionOutputID uint64
Expand Down
Loading

0 comments on commit ae25ec2

Please sign in to comment.