[NOD-495] Remove faucet to separate repository

This commit is contained in:
Mike Zak 2019-12-17 15:09:07 +02:00
parent 4a0b7ad268
commit 6479820fb4
11 changed files with 0 additions and 894 deletions

View File

@ -1,121 +0,0 @@
package config
import (
"github.com/jessevdk/go-flags"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/kasparov/logger"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"path/filepath"
)
const (
defaultLogFilename = "faucet.log"
defaultErrLogFilename = "faucet_err.log"
)
var (
// Default configuration options
defaultLogDir = util.AppDataDir("faucet", false)
defaultDBAddress = "localhost:3306"
defaultHTTPListen = "0.0.0.0:8081"
// activeNetParams are the currently active net params
activeNetParams *dagconfig.Params
)
// Config defines the configuration options for the API server.
type Config struct {
LogDir string `long:"logdir" description:"Directory to log output."`
HTTPListen string `long:"listen" description:"HTTP address to listen on (default: 0.0.0.0:8081)"`
KasparovdURL string `long:"kasparovd-url" description:"The API server url to connect to"`
PrivateKey string `long:"private-key" description:"Faucet Private key"`
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"`
Migrate bool `long:"migrate" description:"Migrate the database to the latest version. The server will not start when using this flag."`
FeeRate float64 `long:"fee-rate" description:"Coins per gram fee rate"`
TestNet bool `long:"testnet" description:"Connect to testnet"`
SimNet bool `long:"simnet" description:"Connect to the simulation test network"`
DevNet bool `long:"devnet" description:"Connect to the development test network"`
}
var cfg *Config
// Parse parses the CLI arguments and returns a config struct.
func Parse() error {
cfg = &Config{
LogDir: defaultLogDir,
DBAddress: defaultDBAddress,
HTTPListen: defaultHTTPListen,
}
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
_, err := parser.Parse()
if err != nil {
return err
}
if !cfg.Migrate {
if cfg.KasparovdURL == "" {
return errors.New("api-server-url argument is required when --migrate flag is not raised")
}
if cfg.PrivateKey == "" {
return errors.New("private-key argument is required when --migrate flag is not raised")
}
}
err = resolveNetwork(cfg)
if err != nil {
return err
}
logFile := filepath.Join(cfg.LogDir, defaultLogFilename)
errLogFile := filepath.Join(cfg.LogDir, defaultErrLogFilename)
logger.InitLog(logFile, errLogFile)
return nil
}
func resolveNetwork(cfg *Config) error {
// Multiple networks can't be selected simultaneously.
numNets := 0
if cfg.TestNet {
numNets++
}
if cfg.SimNet {
numNets++
}
if cfg.DevNet {
numNets++
}
if numNets > 1 {
return errors.New("multiple net params (testnet, simnet, devnet, etc.) can't be used " +
"together -- choose one of them")
}
activeNetParams = &dagconfig.MainNetParams
switch {
case cfg.TestNet:
activeNetParams = &dagconfig.TestNetParams
case cfg.SimNet:
activeNetParams = &dagconfig.SimNetParams
case cfg.DevNet:
activeNetParams = &dagconfig.DevNetParams
}
return nil
}
// MainConfig is a getter to the main config
func MainConfig() (*Config, error) {
if cfg == nil {
return nil, errors.New("No configuration was set for the faucet")
}
return cfg, nil
}
// ActiveNetParams returns the currently active net params
func ActiveNetParams() *dagconfig.Params {
return activeNetParams
}

View File

@ -1,151 +0,0 @@
package database
import (
nativeerrors "errors"
"fmt"
"github.com/pkg/errors"
"os"
"github.com/golang-migrate/migrate/v4/source"
"github.com/jinzhu/gorm"
"github.com/kaspanet/kaspad/faucet/config"
"github.com/golang-migrate/migrate/v4"
)
// db is the API server database.
var db *gorm.DB
// DB returns a reference to the database connection
func DB() (*gorm.DB, error) {
if db == nil {
return nil, errors.New("Database is not connected")
}
return db, nil
}
type gormLogger struct{}
func (l gormLogger) Print(v ...interface{}) {
str := fmt.Sprint(v...)
log.Errorf(str)
}
// Connect connects to the database mentioned in
// config variable.
func Connect() error {
connectionString, err := buildConnectionString()
if err != nil {
return err
}
migrator, driver, err := openMigrator(connectionString)
if err != nil {
return err
}
isCurrent, version, err := isCurrent(migrator, driver)
if err != nil {
return errors.Errorf("Error checking whether the database is current: %s", err)
}
if !isCurrent {
return errors.Errorf("Database is not current (version %d). Please migrate"+
" the database by running the faucet with --migrate flag and then run it again.", version)
}
db, err = gorm.Open("mysql", connectionString)
if err != nil {
return err
}
db.SetLogger(gormLogger{})
return nil
}
// Close closes the connection to the database
func Close() error {
if db == nil {
return nil
}
err := db.Close()
db = nil
return err
}
func buildConnectionString() (string, error) {
cfg, err := config.MainConfig()
if err != nil {
return "", err
}
return fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True",
cfg.DBUser, cfg.DBPassword, cfg.DBAddress, cfg.DBName), nil
}
// isCurrent resolves whether the database is on the latest
// version of the schema.
func isCurrent(migrator *migrate.Migrate, driver source.Driver) (bool, uint, error) {
// Get the current version
version, isDirty, err := migrator.Version()
if nativeerrors.Is(err, migrate.ErrNilVersion) {
return false, 0, nil
}
if err != nil {
return false, 0, err
}
if isDirty {
return false, 0, errors.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, version, nil
}
}
return false, version, err
}
func openMigrator(connectionString string) (*migrate.Migrate, source.Driver, error) {
driver, err := source.Open("file://migrations")
if err != nil {
return nil, nil, err
}
migrator, err := migrate.NewWithSourceInstance(
"migrations", driver, "mysql://"+connectionString)
if err != nil {
return nil, nil, err
}
return migrator, driver, nil
}
// Migrate database to the latest version.
func Migrate() error {
connectionString, err := buildConnectionString()
if err != nil {
return err
}
migrator, driver, err := openMigrator(connectionString)
if err != nil {
return err
}
isCurrent, version, err := isCurrent(migrator, driver)
if err != nil {
return errors.Errorf("Error checking whether the database is current: %s", err)
}
if isCurrent {
log.Infof("Database is already up-to-date (version %d)", version)
return nil
}
err = migrator.Up()
if err != nil {
return err
}
version, isDirty, err := migrator.Version()
if err != nil {
return err
}
if isDirty {
return errors.Errorf("error migrating database: database is dirty")
}
log.Infof("Migrated database to the latest version (version %d)", version)
return nil
}

View File

@ -1,9 +0,0 @@
package database
import "github.com/kaspanet/kaspad/util/panics"
import "github.com/kaspanet/kaspad/kasparov/logger"
var (
log = logger.BackendLog.Logger("DTBS")
spawn = panics.GoroutineWrapperFunc(log)
)

View File

@ -1,28 +0,0 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.13-alpine AS build
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
WORKDIR /go/src/github.com/kaspanet/kaspad
RUN apk add --no-cache curl git
COPY go.mod .
COPY go.sum .
RUN go mod download
COPY . .
RUN cd faucet && CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o faucet .
# --- multistage docker build: stage #2: runtime image
FROM alpine
WORKDIR /app
RUN apk add --no-cache tini
COPY --from=build /go/src/github.com/kaspanet/kaspad/faucet /app/
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["/app/faucet"]

View File

@ -1,332 +0,0 @@
package main
import (
"bytes"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"path"
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/faucet/config"
"github.com/kaspanet/kaspad/httpserverutils"
"github.com/kaspanet/kaspad/kasparov/kasparovd/apimodels"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
"github.com/pkg/errors"
)
const (
sendAmount = 10000
// Value 8 bytes + serialized varint size for the length of ScriptPubKey +
// ScriptPubKey bytes.
outputSize uint64 = 8 + 1 + 25
minTxFee uint64 = 3000
requiredConfirmations = 10
)
type utxoSet map[wire.Outpoint]*blockdag.UTXOEntry
// apiURL returns a full concatenated URL from the base
// API server URL and the given path.
func apiURL(requestPath string) (string, error) {
cfg, err := config.MainConfig()
if err != nil {
return "", err
}
u, err := url.Parse(cfg.KasparovdURL)
if err != nil {
return "", errors.WithStack(err)
}
u.Path = path.Join(u.Path, requestPath)
return u.String(), nil
}
// getFromAPIServer makes an HTTP GET request to the API server
// to the given request path, and returns the response body.
func getFromAPIServer(requestPath string) ([]byte, error) {
getAPIURL, err := apiURL(requestPath)
if err != nil {
return nil, err
}
resp, err := http.Get(getAPIURL)
if err != nil {
return nil, errors.WithStack(err)
}
defer func() {
err := resp.Body.Close()
if err != nil {
panic(errors.WithStack(err))
}
}()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return nil, errors.WithStack(err)
}
if resp.StatusCode != http.StatusOK {
clientError := &httpserverutils.ClientError{}
err := json.Unmarshal(body, &clientError)
if err != nil {
return nil, errors.WithStack(err)
}
return nil, errors.WithStack(clientError)
}
return body, nil
}
// getFromAPIServer makes an HTTP POST request to the API server
// to the given request path. It converts the given data to JSON,
// and post it as the POST data.
func postToAPIServer(requestPath string, data interface{}) error {
dataBytes, err := json.Marshal(data)
if err != nil {
return errors.WithStack(err)
}
r := bytes.NewReader(dataBytes)
postAPIURL, err := apiURL(requestPath)
if err != nil {
return err
}
resp, err := http.Post(postAPIURL, "application/json", r)
if err != nil {
return errors.WithStack(err)
}
defer func() {
err := resp.Body.Close()
if err != nil {
panic(errors.WithStack(err))
}
}()
if resp.StatusCode != http.StatusOK {
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.WithStack(err)
}
clientError := &httpserverutils.ClientError{}
err = json.Unmarshal(body, &clientError)
if err != nil {
return errors.WithStack(err)
}
return errors.WithStack(clientError)
}
return nil
}
func isUTXOMatured(entry *blockdag.UTXOEntry, confirmations uint64) bool {
if entry.IsCoinbase() {
return confirmations >= config.ActiveNetParams().BlockCoinbaseMaturity
}
return confirmations >= requiredConfirmations
}
func getWalletUTXOSet() (utxoSet, error) {
body, err := getFromAPIServer(fmt.Sprintf("utxos/address/%s", faucetAddress.EncodeAddress()))
if err != nil {
return nil, err
}
utxoResponses := []*apimodels.TransactionOutputResponse{}
err = json.Unmarshal(body, &utxoResponses)
if err != nil {
return nil, err
}
walletUTXOSet := make(utxoSet)
for _, utxoResponse := range utxoResponses {
scriptPubKey, err := hex.DecodeString(utxoResponse.ScriptPubKey)
if err != nil {
return nil, err
}
txOut := &wire.TxOut{
Value: utxoResponse.Value,
ScriptPubKey: scriptPubKey,
}
txID, err := daghash.NewTxIDFromStr(utxoResponse.TransactionID)
if err != nil {
return nil, err
}
outpoint := wire.NewOutpoint(txID, utxoResponse.Index)
utxoEntry := blockdag.NewUTXOEntry(txOut, *utxoResponse.IsCoinbase, utxoResponse.AcceptingBlockBlueScore)
if !isUTXOMatured(utxoEntry, *utxoResponse.Confirmations) {
continue
}
walletUTXOSet[*outpoint] = utxoEntry
}
return walletUTXOSet, nil
}
func sendToAddress(address util.Address) (*wire.MsgTx, error) {
tx, err := createTx(address)
if err != nil {
return nil, err
}
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
if err := tx.Serialize(buf); err != nil {
return nil, err
}
rawTx := &apimodels.RawTransaction{RawTransaction: hex.EncodeToString(buf.Bytes())}
return tx, postToAPIServer("transaction", rawTx)
}
func createTx(address util.Address) (*wire.MsgTx, error) {
walletUTXOSet, err := getWalletUTXOSet()
if err != nil {
return nil, err
}
tx, err := createUnsignedTx(walletUTXOSet, address)
if err != nil {
return nil, err
}
err = signTx(walletUTXOSet, tx)
if err != nil {
return nil, err
}
return tx, nil
}
func createUnsignedTx(walletUTXOSet utxoSet, address util.Address) (*wire.MsgTx, error) {
tx := wire.NewNativeMsgTx(wire.TxVersion, nil, nil)
netAmount, isChangeOutputRequired, err := fundTx(walletUTXOSet, tx, sendAmount)
if err != nil {
return nil, err
}
if isChangeOutputRequired {
tx.AddTxOut(&wire.TxOut{
Value: sendAmount,
ScriptPubKey: address.ScriptAddress(),
})
tx.AddTxOut(&wire.TxOut{
Value: netAmount - sendAmount,
ScriptPubKey: faucetScriptPubKey,
})
return tx, nil
}
tx.AddTxOut(&wire.TxOut{
Value: netAmount,
ScriptPubKey: address.ScriptAddress(),
})
return tx, nil
}
// signTx signs a transaction
func signTx(walletUTXOSet utxoSet, tx *wire.MsgTx) error {
for i, txIn := range tx.TxIn {
outpoint := txIn.PreviousOutpoint
sigScript, err := txscript.SignatureScript(tx, i, walletUTXOSet[outpoint].ScriptPubKey(),
txscript.SigHashAll, faucetPrivateKey, true)
if err != nil {
return errors.Errorf("Failed to sign transaction: %s", err)
}
txIn.SignatureScript = sigScript
}
return nil
}
func fundTx(walletUTXOSet utxoSet, tx *wire.MsgTx, amount uint64) (netAmount uint64, isChangeOutputRequired bool, err error) {
amountSelected := uint64(0)
isTxFunded := false
for outpoint, entry := range walletUTXOSet {
amountSelected += entry.Amount()
// Add the selected output to the transaction
tx.AddTxIn(wire.NewTxIn(&outpoint, nil))
// Check if transaction has enough funds. If we don't have enough
// coins from the current amount selected to pay the fee continue
// to grab more coins.
isTxFunded, isChangeOutputRequired, netAmount, err = isFundedAndIsChangeOutputRequired(tx, amountSelected, amount, walletUTXOSet)
if err != nil {
return 0, false, err
}
if isTxFunded {
break
}
}
if !isTxFunded {
return 0, false, errors.Errorf("not enough funds for coin selection")
}
return netAmount, isChangeOutputRequired, nil
}
// isFundedAndIsChangeOutputRequired returns three values and an error:
// * isTxFunded is whether the transaction inputs cover the target amount + the required fee.
// * isChangeOutputRequired is whether it is profitable to add an additional change
// output to the transaction.
// * netAmount is the amount of coins that will be eventually sent to the recipient. If no
// change output is needed, the netAmount will be usually a little bit higher than the
// targetAmount. Otherwise, it'll be the same as the targetAmount.
func isFundedAndIsChangeOutputRequired(tx *wire.MsgTx, amountSelected uint64, targetAmount uint64, walletUTXOSet utxoSet) (isTxFunded, isChangeOutputRequired bool, netAmount uint64, err error) {
// First check if it can be funded with one output and the required fee for it.
isFundedWithOneOutput, oneOutputFee, err := isFundedWithNumberOfOutputs(tx, 1, amountSelected, targetAmount, walletUTXOSet)
if err != nil {
return false, false, 0, err
}
if !isFundedWithOneOutput {
return false, false, 0, nil
}
// Now check if it can be funded with two outputs and the required fee for it.
isFundedWithTwoOutputs, twoOutputsFee, err := isFundedWithNumberOfOutputs(tx, 2, amountSelected, targetAmount, walletUTXOSet)
if err != nil {
return false, false, 0, err
}
// If it can be funded with two outputs, check if adding a change output worth it: i.e. check if
// the amount you save by not sending the recipient the whole inputs amount (minus fees) is greater
// than the additional fee that is required by adding a change output. If this is the case, return
// isChangeOutputRequired as true.
if isFundedWithTwoOutputs && twoOutputsFee-oneOutputFee < targetAmount-amountSelected {
return true, true, amountSelected - twoOutputsFee, nil
}
return true, false, amountSelected - oneOutputFee, nil
}
// isFundedWithNumberOfOutputs returns whether the transaction inputs cover
// the target amount + the required fee with the assumed number of outputs.
func isFundedWithNumberOfOutputs(tx *wire.MsgTx, numberOfOutputs uint64, amountSelected uint64, targetAmount uint64, walletUTXOSet utxoSet) (isTxFunded bool, fee uint64, err error) {
reqFee, err := calcFee(tx, numberOfOutputs, walletUTXOSet)
if err != nil {
return false, 0, err
}
return amountSelected > reqFee && amountSelected-reqFee >= targetAmount, reqFee, nil
}
func calcFee(msgTx *wire.MsgTx, numberOfOutputs uint64, walletUTXOSet utxoSet) (uint64, error) {
txMass := calcTxMass(msgTx, walletUTXOSet)
txMassWithOutputs := txMass + outputsTotalSize(numberOfOutputs)*blockdag.MassPerTxByte
cfg, err := config.MainConfig()
if err != nil {
return 0, err
}
reqFee := uint64(float64(txMassWithOutputs) * cfg.FeeRate)
if reqFee < minTxFee {
return minTxFee, nil
}
return reqFee, nil
}
func outputsTotalSize(numberOfOutputs uint64) uint64 {
return numberOfOutputs*outputSize + uint64(wire.VarIntSerializeSize(numberOfOutputs))
}
func calcTxMass(msgTx *wire.MsgTx, walletUTXOSet utxoSet) uint64 {
previousScriptPubKeys := getPreviousScriptPubKeys(msgTx, walletUTXOSet)
return blockdag.CalcTxMass(util.NewTx(msgTx), previousScriptPubKeys)
}
func getPreviousScriptPubKeys(msgTx *wire.MsgTx, walletUTXOSet utxoSet) [][]byte {
previousScriptPubKeys := make([][]byte, len(msgTx.TxIn))
for i, txIn := range msgTx.TxIn {
outpoint := txIn.PreviousOutpoint
previousScriptPubKeys[i] = walletUTXOSet[outpoint].ScriptPubKey()
}
return previousScriptPubKeys
}

View File

@ -1,66 +0,0 @@
package main
import (
"github.com/kaspanet/kaspad/faucet/database"
"github.com/kaspanet/kaspad/httpserverutils"
"github.com/pkg/errors"
"net"
"net/http"
"time"
)
const minRequestInterval = time.Hour * 24
type ipUse struct {
IP string
LastUse time.Time
}
func ipFromRequest(r *http.Request) (string, error) {
ip, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
return "", err
}
return ip, nil
}
func validateIPUsage(r *http.Request) error {
db, err := database.DB()
if err != nil {
return err
}
now := time.Now()
timeBeforeMinRequestInterval := now.Add(-minRequestInterval)
var count int
ip, err := ipFromRequest(r)
if err != nil {
return err
}
dbResult := db.Model(&ipUse{}).Where(&ipUse{IP: ip}).Where("last_use BETWEEN ? AND ?", timeBeforeMinRequestInterval, now).Count(&count)
dbErrors := dbResult.GetErrors()
if httpserverutils.HasDBError(dbErrors) {
return httpserverutils.NewErrorFromDBErrors("Some errors were encountered when checking the last use of an IP:", dbResult.GetErrors())
}
if count != 0 {
return httpserverutils.NewHandlerError(http.StatusForbidden, errors.New("A user is allowed to to have one request from the faucet every 24 hours"))
}
return nil
}
func updateIPUsage(r *http.Request) error {
db, err := database.DB()
if err != nil {
return err
}
ip, err := ipFromRequest(r)
if err != nil {
return err
}
dbResult := db.Where(&ipUse{IP: ip}).Assign(&ipUse{LastUse: time.Now()}).FirstOrCreate(&ipUse{})
dbErrors := dbResult.GetErrors()
if httpserverutils.HasDBError(dbErrors) {
return httpserverutils.NewErrorFromDBErrors("Some errors were encountered when upserting the IP to the new date:", dbResult.GetErrors())
}
return nil
}

View File

@ -1,11 +0,0 @@
package main
import (
"github.com/kaspanet/kaspad/logger"
"github.com/kaspanet/kaspad/util/panics"
)
var (
log = logger.BackendLog.Logger("FAUC")
spawn = panics.GoroutineWrapperFunc(log)
)

View File

@ -1,88 +0,0 @@
package main
import (
"fmt"
"github.com/kaspanet/kaspad/dagconfig"
"github.com/kaspanet/kaspad/ecc"
"github.com/kaspanet/kaspad/faucet/config"
"github.com/kaspanet/kaspad/faucet/database"
"github.com/kaspanet/kaspad/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/base58"
"github.com/pkg/errors"
"os"
_ "github.com/golang-migrate/migrate/v4/database/mysql"
_ "github.com/golang-migrate/migrate/v4/source/file"
_ "github.com/jinzhu/gorm/dialects/mysql"
"github.com/kaspanet/kaspad/signal"
"github.com/kaspanet/kaspad/util/panics"
)
var (
faucetAddress util.Address
faucetPrivateKey *ecc.PrivateKey
faucetScriptPubKey []byte
)
func main() {
defer panics.HandlePanic(log, nil, nil)
err := config.Parse()
if err != nil {
err := errors.Wrap(err, "Error parsing command-line arguments")
_, err = fmt.Fprintf(os.Stderr, err.Error())
if err != nil {
panic(err)
}
return
}
cfg, err := config.MainConfig()
if err != nil {
panic(err)
}
if cfg.Migrate {
err := database.Migrate()
if err != nil {
panic(errors.Errorf("Error migrating database: %s", err))
}
return
}
err = database.Connect()
if err != nil {
panic(errors.Errorf("Error connecting to database: %s", err))
}
defer func() {
err := database.Close()
if err != nil {
panic(errors.Errorf("Error closing the database: %s", err))
}
}()
privateKeyBytes := base58.Decode(cfg.PrivateKey)
faucetPrivateKey, _ = ecc.PrivKeyFromBytes(ecc.S256(), privateKeyBytes)
faucetAddress, err = privateKeyToP2PKHAddress(faucetPrivateKey, config.ActiveNetParams())
if err != nil {
panic(errors.Errorf("Failed to get P2PKH address from private key: %s", err))
}
faucetScriptPubKey, err = txscript.PayToAddrScript(faucetAddress)
if err != nil {
panic(errors.Errorf("failed to generate faucetScriptPubKey to address: %s", err))
}
shutdownServer := startHTTPServer(cfg.HTTPListen)
defer shutdownServer()
interrupt := signal.InterruptListener()
<-interrupt
}
// privateKeyToP2PKHAddress generates p2pkh address from private key.
func privateKeyToP2PKHAddress(key *ecc.PrivateKey, net *dagconfig.Params) (util.Address, error) {
return util.NewAddressPubKeyHashFromPublicKey(key.PubKey().SerializeCompressed(), net.Prefix)
}

View File

@ -1 +0,0 @@
DROP TABLE `ip_uses`;

View File

@ -1,6 +0,0 @@
CREATE TABLE `ip_uses`
(
`ip` VARCHAR(39) NOT NULL,
`last_use` DATETIME NOT NULL,
PRIMARY KEY (`ip`)
);

View File

@ -1,81 +0,0 @@
package main
import (
"context"
"encoding/json"
"github.com/kaspanet/kaspad/faucet/config"
"github.com/kaspanet/kaspad/httpserverutils"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"net/http"
"time"
"github.com/gorilla/handlers"
"github.com/gorilla/mux"
)
const gracefulShutdownTimeout = 30 * time.Second
// startHTTPServer starts the HTTP REST server and returns a
// function to gracefully shutdown it.
func startHTTPServer(listenAddr string) func() {
router := mux.NewRouter()
router.Use(httpserverutils.AddRequestMetadataMiddleware)
router.Use(httpserverutils.RecoveryMiddleware)
router.Use(httpserverutils.LoggingMiddleware)
router.Use(httpserverutils.SetJSONMiddleware)
router.HandleFunc(
"/request_money",
httpserverutils.MakeHandler(requestMoneyHandler)).
Methods("POST")
httpServer := &http.Server{
Addr: listenAddr,
Handler: handlers.CORS()(router),
}
spawn(func() {
log.Errorf("%s", httpServer.ListenAndServe())
})
return func() {
ctx, cancel := context.WithTimeout(context.Background(), gracefulShutdownTimeout)
defer cancel()
err := httpServer.Shutdown(ctx)
if err != nil {
log.Errorf("Error shutting down HTTP server: %s", err)
}
}
}
type requestMoneyData struct {
Address string `json:"address"`
}
func requestMoneyHandler(_ *httpserverutils.ServerContext, r *http.Request, _ map[string]string, _ map[string]string,
requestBody []byte) (interface{}, error) {
hErr := validateIPUsage(r)
if hErr != nil {
return nil, hErr
}
requestData := &requestMoneyData{}
err := json.Unmarshal(requestBody, requestData)
if err != nil {
return nil, httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
errors.Wrap(err, "Error unmarshalling request body"),
"The request body is not json-formatted")
}
address, err := util.DecodeAddress(requestData.Address, config.ActiveNetParams().Prefix)
if err != nil {
return nil, httpserverutils.NewHandlerErrorWithCustomClientMessage(http.StatusUnprocessableEntity,
errors.Wrap(err, "Error decoding address"),
"Error decoding address")
}
tx, err := sendToAddress(address)
if err != nil {
return nil, err
}
hErr = updateIPUsage(r)
if hErr != nil {
return nil, hErr
}
return tx.TxID().String(), nil
}