From ae25ec2e6b2508399b3c6d0afdccbab7bca05960 Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Tue, 3 Sep 2019 15:54:59 +0300 Subject: [PATCH] [NOD-303] Implement get transaction by id for api server (#391) * [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 --- apiserver/{ => config}/config.go | 15 +-- apiserver/connect.go | 3 +- apiserver/controllers/responsetypes.go | 69 +++++++++++++ apiserver/controllers/transaction.go | 46 +++++++++ apiserver/database/database.go | 74 ++++++++++++++ ...create_transactions_outputs_table.down.sql | 1 - ..._create_transactions_inputs_table.down.sql | 1 - apiserver/main.go | 18 +++- .../000001_create_blocks_table.down.sql | 0 .../000001_create_blocks_table.up.sql | 9 +- ...000002_create_parent_blocks_table.down.sql | 0 .../000002_create_parent_blocks_table.up.sql | 0 .../000003_create_raw_blocks_table.down.sql | 0 .../000003_create_raw_blocks_table.up.sql | 0 .../000004_create_subnetworks_table.down.sql | 0 .../000004_create_subnetworks_table.up.sql | 0 .../000005_create_transactions_table.down.sql | 0 .../000005_create_transactions_table.up.sql | 1 + ...eate_transactions_to_blocks_table.down.sql | 0 ...create_transactions_to_blocks_table.up.sql | 0 ..._create_transaction_outputs_table.down.sql | 1 + ...7_create_transaction_outputs_table.up.sql} | 2 +- ...8_create_transaction_inputs_table.down.sql | 1 + ...08_create_transaction_inputs_table.up.sql} | 2 +- .../000009_create_utxos_table.down.sql | 0 .../000009_create_utxos_table.up.sql | 0 apiserver/models/models.go | 31 +++--- apiserver/server/context.go | 62 ------------ apiserver/server/middlewares.go | 32 +++--- apiserver/server/routes.go | 67 +++++++++++++ apiserver/server/server.go | 13 +-- apiserver/utils/context.go | 79 +++++++++++++++ apiserver/utils/error.go | 20 ++++ apiserver/utils/log.go | 9 ++ go.mod | 9 +- go.sum | 98 +++++++++++++++++++ 36 files changed, 541 insertions(+), 122 deletions(-) rename apiserver/{ => config}/config.go (82%) create mode 100644 apiserver/controllers/responsetypes.go create mode 100644 apiserver/controllers/transaction.go create mode 100644 apiserver/database/database.go delete mode 100644 apiserver/db/migrations/000007_create_transactions_outputs_table.down.sql delete mode 100644 apiserver/db/migrations/000008_create_transactions_inputs_table.down.sql rename apiserver/{db => }/migrations/000001_create_blocks_table.down.sql (100%) rename apiserver/{db => }/migrations/000001_create_blocks_table.up.sql (74%) rename apiserver/{db => }/migrations/000002_create_parent_blocks_table.down.sql (100%) rename apiserver/{db => }/migrations/000002_create_parent_blocks_table.up.sql (100%) rename apiserver/{db => }/migrations/000003_create_raw_blocks_table.down.sql (100%) rename apiserver/{db => }/migrations/000003_create_raw_blocks_table.up.sql (100%) rename apiserver/{db => }/migrations/000004_create_subnetworks_table.down.sql (100%) rename apiserver/{db => }/migrations/000004_create_subnetworks_table.up.sql (100%) rename apiserver/{db => }/migrations/000005_create_transactions_table.down.sql (100%) rename apiserver/{db => }/migrations/000005_create_transactions_table.up.sql (94%) rename apiserver/{db => }/migrations/000006_create_transactions_to_blocks_table.down.sql (100%) rename apiserver/{db => }/migrations/000006_create_transactions_to_blocks_table.up.sql (100%) create mode 100644 apiserver/migrations/000007_create_transaction_outputs_table.down.sql rename apiserver/{db/migrations/000007_create_transactions_outputs_table.up.sql => migrations/000007_create_transaction_outputs_table.up.sql} (93%) create mode 100644 apiserver/migrations/000008_create_transaction_inputs_table.down.sql rename apiserver/{db/migrations/000008_create_transactions_inputs_table.up.sql => migrations/000008_create_transaction_inputs_table.up.sql} (95%) rename apiserver/{db => }/migrations/000009_create_utxos_table.down.sql (100%) rename apiserver/{db => }/migrations/000009_create_utxos_table.up.sql (100%) delete mode 100644 apiserver/server/context.go create mode 100644 apiserver/server/routes.go create mode 100644 apiserver/utils/context.go create mode 100644 apiserver/utils/error.go create mode 100644 apiserver/utils/log.go diff --git a/apiserver/config.go b/apiserver/config/config.go similarity index 82% rename from apiserver/config.go rename to apiserver/config/config.go index 754bc35b..1c17b6f3 100644 --- a/apiserver/config.go +++ b/apiserver/config/config.go @@ -1,4 +1,4 @@ -package main +package config import ( "errors" @@ -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) diff --git a/apiserver/connect.go b/apiserver/connect.go index 8bff8781..dad6791e 100644 --- a/apiserver/connect.go +++ b/apiserver/connect.go @@ -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 diff --git a/apiserver/controllers/responsetypes.go b/apiserver/controllers/responsetypes.go new file mode 100644 index 00000000..f4dcccd4 --- /dev/null +++ b/apiserver/controllers/responsetypes.go @@ -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 +} diff --git a/apiserver/controllers/transaction.go b/apiserver/controllers/transaction.go new file mode 100644 index 00000000..d1d2bd8b --- /dev/null +++ b/apiserver/controllers/transaction.go @@ -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") +} diff --git a/apiserver/database/database.go b/apiserver/database/database.go new file mode 100644 index 00000000..c56c66a9 --- /dev/null +++ b/apiserver/database/database.go @@ -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 +} diff --git a/apiserver/db/migrations/000007_create_transactions_outputs_table.down.sql b/apiserver/db/migrations/000007_create_transactions_outputs_table.down.sql deleted file mode 100644 index afa3f05a..00000000 --- a/apiserver/db/migrations/000007_create_transactions_outputs_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE `transactions_outputs`; diff --git a/apiserver/db/migrations/000008_create_transactions_inputs_table.down.sql b/apiserver/db/migrations/000008_create_transactions_inputs_table.down.sql deleted file mode 100644 index c698781f..00000000 --- a/apiserver/db/migrations/000008_create_transactions_inputs_table.down.sql +++ /dev/null @@ -1 +0,0 @@ -DROP TABLE `transactions_inputs`; diff --git a/apiserver/main.go b/apiserver/main.go index 36a32661..d91ba02c 100644 --- a/apiserver/main.go +++ b/apiserver/main.go @@ -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)) diff --git a/apiserver/db/migrations/000001_create_blocks_table.down.sql b/apiserver/migrations/000001_create_blocks_table.down.sql similarity index 100% rename from apiserver/db/migrations/000001_create_blocks_table.down.sql rename to apiserver/migrations/000001_create_blocks_table.down.sql diff --git a/apiserver/db/migrations/000001_create_blocks_table.up.sql b/apiserver/migrations/000001_create_blocks_table.up.sql similarity index 74% rename from apiserver/db/migrations/000001_create_blocks_table.up.sql rename to apiserver/migrations/000001_create_blocks_table.up.sql index 184b88e0..df111d88 100644 --- a/apiserver/db/migrations/000001_create_blocks_table.up.sql +++ b/apiserver/migrations/000001_create_blocks_table.up.sql @@ -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`), diff --git a/apiserver/db/migrations/000002_create_parent_blocks_table.down.sql b/apiserver/migrations/000002_create_parent_blocks_table.down.sql similarity index 100% rename from apiserver/db/migrations/000002_create_parent_blocks_table.down.sql rename to apiserver/migrations/000002_create_parent_blocks_table.down.sql diff --git a/apiserver/db/migrations/000002_create_parent_blocks_table.up.sql b/apiserver/migrations/000002_create_parent_blocks_table.up.sql similarity index 100% rename from apiserver/db/migrations/000002_create_parent_blocks_table.up.sql rename to apiserver/migrations/000002_create_parent_blocks_table.up.sql diff --git a/apiserver/db/migrations/000003_create_raw_blocks_table.down.sql b/apiserver/migrations/000003_create_raw_blocks_table.down.sql similarity index 100% rename from apiserver/db/migrations/000003_create_raw_blocks_table.down.sql rename to apiserver/migrations/000003_create_raw_blocks_table.down.sql diff --git a/apiserver/db/migrations/000003_create_raw_blocks_table.up.sql b/apiserver/migrations/000003_create_raw_blocks_table.up.sql similarity index 100% rename from apiserver/db/migrations/000003_create_raw_blocks_table.up.sql rename to apiserver/migrations/000003_create_raw_blocks_table.up.sql diff --git a/apiserver/db/migrations/000004_create_subnetworks_table.down.sql b/apiserver/migrations/000004_create_subnetworks_table.down.sql similarity index 100% rename from apiserver/db/migrations/000004_create_subnetworks_table.down.sql rename to apiserver/migrations/000004_create_subnetworks_table.down.sql diff --git a/apiserver/db/migrations/000004_create_subnetworks_table.up.sql b/apiserver/migrations/000004_create_subnetworks_table.up.sql similarity index 100% rename from apiserver/db/migrations/000004_create_subnetworks_table.up.sql rename to apiserver/migrations/000004_create_subnetworks_table.up.sql diff --git a/apiserver/db/migrations/000005_create_transactions_table.down.sql b/apiserver/migrations/000005_create_transactions_table.down.sql similarity index 100% rename from apiserver/db/migrations/000005_create_transactions_table.down.sql rename to apiserver/migrations/000005_create_transactions_table.down.sql diff --git a/apiserver/db/migrations/000005_create_transactions_table.up.sql b/apiserver/migrations/000005_create_transactions_table.up.sql similarity index 94% rename from apiserver/db/migrations/000005_create_transactions_table.up.sql rename to apiserver/migrations/000005_create_transactions_table.up.sql index aece662b..39d66186 100644 --- a/apiserver/db/migrations/000005_create_transactions_table.up.sql +++ b/apiserver/migrations/000005_create_transactions_table.up.sql @@ -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`), diff --git a/apiserver/db/migrations/000006_create_transactions_to_blocks_table.down.sql b/apiserver/migrations/000006_create_transactions_to_blocks_table.down.sql similarity index 100% rename from apiserver/db/migrations/000006_create_transactions_to_blocks_table.down.sql rename to apiserver/migrations/000006_create_transactions_to_blocks_table.down.sql diff --git a/apiserver/db/migrations/000006_create_transactions_to_blocks_table.up.sql b/apiserver/migrations/000006_create_transactions_to_blocks_table.up.sql similarity index 100% rename from apiserver/db/migrations/000006_create_transactions_to_blocks_table.up.sql rename to apiserver/migrations/000006_create_transactions_to_blocks_table.up.sql diff --git a/apiserver/migrations/000007_create_transaction_outputs_table.down.sql b/apiserver/migrations/000007_create_transaction_outputs_table.down.sql new file mode 100644 index 00000000..e117b901 --- /dev/null +++ b/apiserver/migrations/000007_create_transaction_outputs_table.down.sql @@ -0,0 +1 @@ +DROP TABLE `transaction_outputs`; diff --git a/apiserver/db/migrations/000007_create_transactions_outputs_table.up.sql b/apiserver/migrations/000007_create_transaction_outputs_table.up.sql similarity index 93% rename from apiserver/db/migrations/000007_create_transactions_outputs_table.up.sql rename to apiserver/migrations/000007_create_transaction_outputs_table.up.sql index a3976e5d..02016558 100644 --- a/apiserver/db/migrations/000007_create_transactions_outputs_table.up.sql +++ b/apiserver/migrations/000007_create_transaction_outputs_table.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE `transactions_outputs` +CREATE TABLE `transaction_outputs` ( `id` BIGINT UNSIGNED NOT NULL, `transaction_id` BIGINT UNSIGNED NOT NULL, diff --git a/apiserver/migrations/000008_create_transaction_inputs_table.down.sql b/apiserver/migrations/000008_create_transaction_inputs_table.down.sql new file mode 100644 index 00000000..24e8f9cc --- /dev/null +++ b/apiserver/migrations/000008_create_transaction_inputs_table.down.sql @@ -0,0 +1 @@ +DROP TABLE `transaction_inputs`; diff --git a/apiserver/db/migrations/000008_create_transactions_inputs_table.up.sql b/apiserver/migrations/000008_create_transaction_inputs_table.up.sql similarity index 95% rename from apiserver/db/migrations/000008_create_transactions_inputs_table.up.sql rename to apiserver/migrations/000008_create_transaction_inputs_table.up.sql index 27ac54bc..c6ae7fcf 100644 --- a/apiserver/db/migrations/000008_create_transactions_inputs_table.up.sql +++ b/apiserver/migrations/000008_create_transaction_inputs_table.up.sql @@ -1,4 +1,4 @@ -CREATE TABLE `transactions_inputs` +CREATE TABLE `transaction_inputs` ( `id` BIGINT UNSIGNED NOT NULL, `transaction_id` BIGINT UNSIGNED NULL, diff --git a/apiserver/db/migrations/000009_create_utxos_table.down.sql b/apiserver/migrations/000009_create_utxos_table.down.sql similarity index 100% rename from apiserver/db/migrations/000009_create_utxos_table.down.sql rename to apiserver/migrations/000009_create_utxos_table.down.sql diff --git a/apiserver/db/migrations/000009_create_utxos_table.up.sql b/apiserver/migrations/000009_create_utxos_table.up.sql similarity index 100% rename from apiserver/db/migrations/000009_create_utxos_table.up.sql rename to apiserver/migrations/000009_create_utxos_table.up.sql diff --git a/apiserver/models/models.go b/apiserver/models/models.go index c74ca9de..9b500d9c 100644 --- a/apiserver/models/models.go +++ b/apiserver/models/models.go @@ -19,6 +19,7 @@ type Block struct { Nonce uint64 BlueScore uint64 IsChainBlock bool + Mass uint64 ParentBlocks []Block `gorm:"many2many:parent_blocks;"` } @@ -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 @@ -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 @@ -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 diff --git a/apiserver/server/context.go b/apiserver/server/context.go deleted file mode 100644 index eb3c0870..00000000 --- a/apiserver/server/context.go +++ /dev/null @@ -1,62 +0,0 @@ -package server - -import ( - "context" - "fmt" -) - -type contextKey string - -const ( - contextKeyRequestID contextKey = "REQUEST_ID" -) - -type apiServerContext struct { - context.Context -} - -func newAPIServerContext(ctx context.Context) *apiServerContext { - if asCtx, ok := ctx.(*apiServerContext); ok { - return asCtx - } - return &apiServerContext{Context: ctx} -} - -func (ctx *apiServerContext) setRequestID(requestID uint64) context.Context { - context.WithValue(ctx, contextKeyRequestID, nextRequestID) - return ctx -} - -func (ctx *apiServerContext) requestID() uint64 { - id := ctx.Value(contextKeyRequestID) - return id.(uint64) -} - -func (ctx *apiServerContext) getLogString(format string, params ...interface{}) string { - params = append(params, ctx.requestID()) - return fmt.Sprintf("RID %d: "+format, params) -} - -func (ctx *apiServerContext) tracef(format string, params ...interface{}) { - log.Tracef(ctx.getLogString(format, params)) -} - -func (ctx *apiServerContext) debugf(format string, params ...interface{}) { - log.Debugf(ctx.getLogString(format, params)) -} - -func (ctx *apiServerContext) infof(format string, params ...interface{}) { - log.Infof(ctx.getLogString(format, params)) -} - -func (ctx *apiServerContext) warnf(format string, params ...interface{}) { - log.Warnf(ctx.getLogString(format, params)) -} - -func (ctx *apiServerContext) errorf(format string, params ...interface{}) { - log.Errorf(ctx.getLogString(format, params)) -} - -func (ctx *apiServerContext) criticalf(format string, params ...interface{}) { - log.Criticalf(ctx.getLogString(format, params)) -} diff --git a/apiserver/server/middlewares.go b/apiserver/server/middlewares.go index a374aed8..7e921cba 100644 --- a/apiserver/server/middlewares.go +++ b/apiserver/server/middlewares.go @@ -1,15 +1,16 @@ package server import ( - "fmt" + "github.com/daglabs/btcd/apiserver/utils" "net/http" + "runtime/debug" ) var nextRequestID uint64 = 1 func addRequestMetadataMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - rCtx := newAPIServerContext(r.Context()).setRequestID(nextRequestID) + rCtx := utils.ToAPIServerContext(r.Context()).SetRequestID(nextRequestID) r.WithContext(rCtx) nextRequestID++ next.ServeHTTP(w, r) @@ -18,31 +19,30 @@ func addRequestMetadataMiddleware(next http.Handler) http.Handler { func loggingMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := newAPIServerContext(r.Context()) - ctx.infof("Method: %s URI: %s", r.Method, r.RequestURI) + ctx := utils.ToAPIServerContext(r.Context()) + ctx.Infof("Method: %s URI: %s", r.Method, r.RequestURI) next.ServeHTTP(w, r) }) } func recoveryMiddleware(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ctx := newAPIServerContext(r.Context()) - var errStr string + ctx := utils.ToAPIServerContext(r.Context()) defer func() { recoveryErr := recover() if recoveryErr != nil { - switch t := recoveryErr.(type) { - case string: - errStr = t - case error: - errStr = t.Error() - default: - errStr = "unknown error" - } - ctx.errorf("got error: %s", errStr) - http.Error(w, fmt.Sprintf("got error in request %d: %s", ctx.requestID(), errStr), http.StatusInternalServerError) + log.Criticalf("Fatal error: %s", recoveryErr) + log.Criticalf("Stack trace: %s", debug.Stack()) + sendErr(ctx, w, utils.NewHandlerError(http.StatusInternalServerError, "A server error occurred.")) } }() h.ServeHTTP(w, r) }) } + +func setJSONMiddleware(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Type", "application/json; charset=utf-8") + h.ServeHTTP(w, r) + }) +} diff --git a/apiserver/server/routes.go b/apiserver/server/routes.go new file mode 100644 index 00000000..813c9235 --- /dev/null +++ b/apiserver/server/routes.go @@ -0,0 +1,67 @@ +package server + +import ( + "encoding/json" + "fmt" + "github.com/daglabs/btcd/apiserver/controllers" + "github.com/daglabs/btcd/apiserver/utils" + "github.com/gorilla/mux" + "net/http" +) + +const ( + routeParamTxID = "txID" + routeParamTxHash = "txHash" +) + +func makeHandler(handler func(vars map[string]string, ctx *utils.APIServerContext) (interface{}, *utils.HandlerError)) func(http.ResponseWriter, *http.Request) { + return func(w http.ResponseWriter, r *http.Request) { + ctx := utils.ToAPIServerContext(r.Context()) + response, hErr := handler(mux.Vars(r), ctx) + if hErr != nil { + sendErr(ctx, w, hErr) + return + } + sendJSONResponse(w, response) + } +} + +func sendErr(ctx *utils.APIServerContext, w http.ResponseWriter, hErr *utils.HandlerError) { + errMsg := fmt.Sprintf("got error: %s", hErr) + ctx.Warnf(errMsg) + w.WriteHeader(hErr.ErrorCode) + sendJSONResponse(w, hErr) +} + +func sendJSONResponse(w http.ResponseWriter, response interface{}) { + b, err := json.Marshal(response) + if err != nil { + panic(err) + } + _, err = fmt.Fprintf(w, string(b)) + if err != nil { + panic(err) + } +} + +func mainHandler(_ map[string]string, _ *utils.APIServerContext) (interface{}, *utils.HandlerError) { + return "API server is running", nil +} + +func addRoutes(router *mux.Router) { + router.HandleFunc("/", makeHandler(mainHandler)) + + router.HandleFunc( + fmt.Sprintf("/transaction/id/{%s}", routeParamTxID), + makeHandler(func(vars map[string]string, ctx *utils.APIServerContext) (interface{}, *utils.HandlerError) { + return controllers.GetTransactionByIDHandler(vars[routeParamTxID]) + })). + Methods("GET") + + router.HandleFunc( + fmt.Sprintf("/transaction/hash/{%s}", routeParamTxHash), + makeHandler(func(vars map[string]string, ctx *utils.APIServerContext) (interface{}, *utils.HandlerError) { + return controllers.GetTransactionByHashHandler(vars[routeParamTxHash]) + })). + Methods("GET") +} diff --git a/apiserver/server/server.go b/apiserver/server/server.go index f0b2e91b..e5b74181 100644 --- a/apiserver/server/server.go +++ b/apiserver/server/server.go @@ -2,7 +2,6 @@ package server import ( "context" - "fmt" "github.com/gorilla/handlers" "github.com/gorilla/mux" "net/http" @@ -11,13 +10,6 @@ import ( const gracefulShutdownTimeout = 30 * time.Second -func mainHandler(w http.ResponseWriter, r *http.Request) { - _, err := fmt.Fprintf(w, "API Server is running") - if err != nil { - panic(err) - } -} - // Start starts the HTTP REST server and returns a // function to gracefully shutdown it. func Start(listenAddr string) func() { @@ -25,7 +17,8 @@ func Start(listenAddr string) func() { router.Use(addRequestMetadataMiddleware) router.Use(recoveryMiddleware) router.Use(loggingMiddleware) - router.HandleFunc("/", mainHandler) + router.Use(setJSONMiddleware) + addRoutes(router) httpServer := &http.Server{ Addr: listenAddr, Handler: handlers.CORS()(router), @@ -38,7 +31,7 @@ func Start(listenAddr string) func() { defer cancel() err := httpServer.Shutdown(ctx) if err != nil { - log.Errorf("Error shutting down http httpServer: %s", err) + log.Errorf("Error shutting down HTTP server: %s", err) } } } diff --git a/apiserver/utils/context.go b/apiserver/utils/context.go new file mode 100644 index 00000000..923a246c --- /dev/null +++ b/apiserver/utils/context.go @@ -0,0 +1,79 @@ +package utils + +import ( + "context" + "fmt" +) + +type contextKey string + +const ( + contextKeyRequestID contextKey = "REQUEST_ID" +) + +// APIServerContext is a context.Context wrapper that +// enables custom logs with request ID. +type APIServerContext struct { + context.Context +} + +// ToAPIServerContext takes a context.Context instance +// and converts it to *ApiServerContext. +func ToAPIServerContext(ctx context.Context) *APIServerContext { + if asCtx, ok := ctx.(*APIServerContext); ok { + return asCtx + } + return &APIServerContext{Context: ctx} +} + +// SetRequestID associates a request ID for the context. +func (ctx *APIServerContext) SetRequestID(requestID uint64) context.Context { + context.WithValue(ctx, contextKeyRequestID, requestID) + return ctx +} + +func (ctx *APIServerContext) requestID() uint64 { + id := ctx.Value(contextKeyRequestID) + uint64ID, _ := id.(uint64) + return uint64ID +} + +func (ctx *APIServerContext) getLogString(format string, params ...interface{}) string { + return fmt.Sprintf("RID %d: ", ctx.requestID()) + fmt.Sprintf(format, params...) +} + +// Tracef writes a customized formatted context +// related log with log level 'Trace'. +func (ctx *APIServerContext) Tracef(format string, params ...interface{}) { + log.Trace(ctx.getLogString(format, params...)) +} + +// Debugf writes a customized formatted context +// related log with log level 'Debug'. +func (ctx *APIServerContext) Debugf(format string, params ...interface{}) { + log.Debug(ctx.getLogString(format, params...)) +} + +// Infof writes a customized formatted context +// related log with log level 'Info'. +func (ctx *APIServerContext) Infof(format string, params ...interface{}) { + log.Info(ctx.getLogString(format, params...)) +} + +// Warnf writes a customized formatted context +// related log with log level 'Warn'. +func (ctx *APIServerContext) Warnf(format string, params ...interface{}) { + log.Warn(ctx.getLogString(format, params...)) +} + +// Errorf writes a customized formatted context +// related log with log level 'Error'. +func (ctx *APIServerContext) Errorf(format string, params ...interface{}) { + log.Error(ctx.getLogString(format, params...)) +} + +// Criticalf writes a customized formatted context +// related log with log level 'Critical'. +func (ctx *APIServerContext) Criticalf(format string, params ...interface{}) { + log.Criticalf(ctx.getLogString(format, params...)) +} diff --git a/apiserver/utils/error.go b/apiserver/utils/error.go new file mode 100644 index 00000000..c7c9b10e --- /dev/null +++ b/apiserver/utils/error.go @@ -0,0 +1,20 @@ +package utils + +// HandlerError is an error returned from +// a rest route handler or a middleware. +type HandlerError struct { + ErrorCode int + ErrorMessage string +} + +func (hErr *HandlerError) Error() string { + return hErr.ErrorMessage +} + +// NewHandlerError returns a HandlerError with the given code and message. +func NewHandlerError(code int, message string) *HandlerError { + return &HandlerError{ + ErrorCode: code, + ErrorMessage: message, + } +} diff --git a/apiserver/utils/log.go b/apiserver/utils/log.go new file mode 100644 index 00000000..e2d93e8a --- /dev/null +++ b/apiserver/utils/log.go @@ -0,0 +1,9 @@ +package utils + +import "github.com/daglabs/btcd/util/panics" +import "github.com/daglabs/btcd/apiserver/logger" + +var ( + log = logger.BackendLog.Logger("UTIL") + spawn = panics.GoroutineWrapperFunc(log, logger.BackendLog) +) diff --git a/go.mod b/go.mod index b1d9959e..e9c8ac46 100644 --- a/go.mod +++ b/go.mod @@ -11,16 +11,13 @@ require ( github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792 github.com/btcsuite/winsvc v1.0.0 github.com/davecgh/go-spew v1.1.1 - github.com/golang/protobuf v1.3.1 // indirect + github.com/golang-migrate/migrate/v4 v4.6.1 github.com/gorilla/handlers v1.4.2 - github.com/gorilla/mux v1.6.2 + github.com/gorilla/mux v1.7.1 github.com/jessevdk/go-flags v1.4.0 github.com/jinzhu/gorm v1.9.10 github.com/jrick/logrotate v1.0.0 github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec github.com/miekg/dns v1.1.6 - github.com/stretchr/testify v1.3.0 // indirect - golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c - golang.org/x/sync v0.0.0-20190412183630-56d357773e84 // indirect - golang.org/x/sys v0.0.0-20190312061237-fead79001313 // indirect + golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 ) diff --git a/go.sum b/go.sum index 778d8193..acd25e2e 100644 --- a/go.sum +++ b/go.sum @@ -3,7 +3,10 @@ bou.ke/monkey v1.0.1/go.mod h1:FgHuK96Rv2Nlf+0u1OOVDpCMdsWyOFmeeketDHE7LIg= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.37.4/go.mod h1:NHPJ89PdicEuT9hdPXMROBD91xc5uRDxsMtSB16k7hw= +github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= +github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/aead/siphash v1.0.1 h1:FwHfE/T45KPKYuuSAKyyvE+oPWcaQ+CUmFW0bPlM+kg= @@ -11,7 +14,10 @@ github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBA github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/aws/aws-sdk-go v1.17.7/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/bitly/go-hostpool v0.0.0-20171023180738-a3a6125de932/go.mod h1:NOuUCSz6Q9T7+igc/hlvDOUdtWKryOrtFyIVABv/p7k= +github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f h1:bAs4lUbRJpnnkd9VhRV3jjAVU7DJVjMaK+IsvSeZvFo= github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f/go.mod h1:TdznJufoqS23FtqVCzL0ZqgP5MqXbb4fg/WgDys70nA= github.com/btcsuite/go-socks v0.0.0-20170105172521-4720035b7bfd h1:R/opQEbFEy9JGkIguV40SvRY1uliPX8ifOvi6ICsFCw= @@ -25,32 +31,61 @@ github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtE github.com/btcsuite/winsvc v1.0.0 h1:J9B4L7e3oqhXOcm+2IuNApwzQec85lE+QaikUcCs+dk= github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= +github.com/containerd/containerd v1.2.7/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= +github.com/cznic/b v0.0.0-20180115125044-35e9bbe41f07/go.mod h1:URriBxXwVq5ijiJ12C7iIZqlA69nTlI+LgI6/pwftG8= +github.com/cznic/fileutil v0.0.0-20180108211300-6a051e75936f/go.mod h1:8S58EK26zhXSxzv7NQFpnliaOQsmDUxvoQO3rt154Vg= +github.com/cznic/golex v0.0.0-20170803123110-4ab7c5e190e4/go.mod h1:+bmmJDNmKlhWNG+gwWCkaBoTy39Fs+bzRxVBzoTQbIc= +github.com/cznic/internal v0.0.0-20180608152220-f44710a21d00/go.mod h1:olo7eAdKwJdXxb55TKGLiJ6xt1H0/tiiRCWKVLmtjY4= +github.com/cznic/lldb v1.1.0/go.mod h1:FIZVUmYUVhPwRiPzL8nD/mpFcJ/G7SSXjjXYG4uRI3A= +github.com/cznic/mathutil v0.0.0-20180504122225-ca4c9f2c1369/go.mod h1:e6NPNENfs9mPDVNRekM7lKScauxd5kXTr1Mfyig6TDM= +github.com/cznic/ql v1.2.0/go.mod h1:FbpzhyZrqr0PVlK6ury+PoW3T0ODUV22OeWIxcaOrSE= +github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65/go.mod h1:q2w6Bg5jeox1B+QkJ6Wp/+Vn0G/bo3f1uY7Fn3vivIQ= +github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186/go.mod h1:AHHPPPXTw0h6pVabbcbyGRK1DckRn7r/STdZEeIDzZc= +github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc/go.mod h1:Y1SNZ4dRUOKXshKUbwUapqNncRrho4mkjQebgEHZLj8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3/go.mod h1:zAg7JM8CkOJ43xKXIj7eRO9kmWm/TW578qo+oDO6tuM= +github.com/dhui/dktest v0.3.0/go.mod h1:cyzIUfGsBEbZ6BT7tnXqAShHSXCZhSNmFl70sZ7c1yc= +github.com/docker/distribution v2.7.0+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v0.7.3-0.20190103212154-2b7e084dc98b/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v0.7.3-0.20190817195342-4760db040282/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= +github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsouza/fake-gcs-server v1.7.0/go.mod h1:5XIRs4YvwNbNoz+1JF8j6KLAyDh7RHGAyAK3EP2EsNk= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gocql/gocql v0.0.0-20190301043612-f6df8288f9b4/go.mod h1:4Fw1eo5iaEhDUs8XyuhSVCVy52Jq3L+/3GJgYkwc+/0= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang-migrate/migrate/v4 v4.6.1 h1:wTTtB3B+HMT1faZOpDgPd6LcwBZ/VwALjzqQ6PPS5G4= +github.com/golang-migrate/migrate/v4 v4.6.1/go.mod h1:JYi6reN3+Z734VZ0akNuyOJNcrg45ZL7LDBMW3WGJL0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= @@ -59,9 +94,19 @@ github.com/gorilla/handlers v1.4.2 h1:0QniY0USkHQ1RGCLfKxeNHK9bkDHGRYGNDFBCS+YAR github.com/gorilla/handlers v1.4.2/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.1 h1:Dw4jY2nghMMRsh1ol8dv1axHkDwMQK2DHerMNJsIpJU= +github.com/gorilla/mux v1.7.1/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= +github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= +github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGARJA= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jinzhu/gorm v1.9.10 h1:HvrsqdhCW78xpJF67g1hMxS6eCToo9PZH4LDB8WKPac= @@ -69,30 +114,44 @@ github.com/jinzhu/gorm v1.9.10/go.mod h1:Kh6hTsSGffh4ui079FHrR5Gg+5D0hgihqDcsDN2 github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jrick/logrotate v1.0.0 h1:lQ1bL/n9mBNeIXoTUoYRlK4dHuNJVofX9oWqBtPnSzI= github.com/jrick/logrotate v1.0.0/go.mod h1:LNinyqDIJnpAur+b8yyulnQw/wDuN1+BYKlTRt3OuAQ= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec h1:n1NeQ3SgUHyISrjFFoO5dR748Is8dBL9qpaTNfphQrs= github.com/kkdai/bstream v0.0.0-20181106074824-b3251f7901ec/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kshvakov/clickhouse v1.3.5/go.mod h1:DMzX7FxRymoNkVgizH0DWAL8Cur7wHLgx3MUnGwJqpE= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.6 h1:jVwb4GDwD65q/gtItR/lIZHjNH93QfeGxZUkzJcW9mc= github.com/miekg/dns v1.1.6/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/morikuni/aec v0.0.0-20170113033406-39771216ff4c/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nakagami/firebirdsql v0.0.0-20190310045651-3c02a58cfed8/go.mod h1:86wM1zFnC6/uDBfZGNwB65O+pR2OFi5q/YQaEUid1qA= github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.1 h1:PZSj/UFNaVp3KxrzHOcS7oyuWA7LoOY/77yCTEFu21U= github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -103,75 +162,114 @@ github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/xanzy/go-gitlab v0.15.0/go.mod h1:8zdQa/ri1dfn8eS3Ir1SyfvOKlw7WBJ8DVThkpGiXrs= +github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I= +github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y= +gitlab.com/nyarla/go-crypt v0.0.0-20160106005555-d9a5dc2b789b/go.mod h1:T3BPAOm2cqquPa0MKWeNkmOM5RQsRhkrwMWonFMN7fE= +go.mongodb.org/mongo-driver v1.1.0/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c h1:Vj5n4GlwjmQteupaxJ9+0FNOmBrHfq7vN4btdGoDZgI= golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734 h1:p/H982KKEjUnLJkM3tt/LemDnOc1GiZL5FCVlORJ5zo= +golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181108082009-03003ca0c849/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6 h1:FP8hkuE6yUEaJnK7O2eTuejKWwW+Rhfj80dQ2JcKxCU= +golang.org/x/net v0.0.0-20190424112056-4829fb13d2c6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190412183630-56d357773e84 h1:IqXQ59gzdXv58Jmm2xn0tSOR9i6HqroaOFRQ3wR/dJQ= golang.org/x/sync v0.0.0-20190412183630-56d357773e84/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190426135247-a129542de9ae h1:mQLHiymj/JXKnnjc62tb7nD5pZLs940/sXJu+Xp3DBA= +golang.org/x/sys v0.0.0-20190426135247-a129542de9ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425222832-ad9eeb80039a/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.3.2/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=