Skip to content

Commit

Permalink
Implement faucet (iov-one#246)
Browse files Browse the repository at this point in the history
  • Loading branch information
orkunkl authored Jun 26, 2020
1 parent d450539 commit c43117c
Show file tree
Hide file tree
Showing 14 changed files with 551 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ env:
- VERSION=$(git describe --tags | sed 's/^v//')
- COMMIT=$(git log -1 --format='%H')
- IMAGE_NAME="iov1/iovns:${BUILD_VERSION}"
- FAUCET_IMAGE_NAME="iov1/iovns-faucet:${BUILD_VERSION}"

script:
- set -eo pipefail
Expand All @@ -53,19 +54,25 @@ script:
./contrib/gitian-build.sh linux ;
mkdir -p ${TRAVIS_BUILD_DIR}/build && tar -zxf $TRAVIS_BUILD_DIR/iovns-build-linux/build/out/iovns-${VERSION}-linux-amd64.tar.gz -C ${TRAVIS_BUILD_DIR}/build ;
docker build --pull --tag ${IMAGE_NAME} . ;
cd cmd/faucet && make build && docker build --pull --tag ${FAUCET_IMAGE_NAME} . ;
fi;

if [[ $release_latest == "yes" ]]; then
docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD";
docker tag "iov1/iovns:${BUILD_VERSION}" "iov1/iovns:latest" ;
docker push "iov1/iovns:latest";

docker tag "iov1/iovns-faucet:${BUILD_VERSION}" "iov1/iovns-faucet:latest" ;
docker push "iov1/iovns-faucet:latest";
docker logout;
fi;

if [[ $release_tag == "yes" ]]; then
docker login -u "$DOCKER_USERNAME" -p "$DOCKER_PASSWORD";
docker tag "iov1/iovns:${BUILD_VERSION}" "iov1/iovns:$TRAVIS_TAG" ;
docker push "iov1/iovns:$TRAVIS_TAG";
docker tag "iov1/iovns-faucet:${BUILD_VERSION}" "iov1/iovns-faucet:$TRAVIS_TAG" ;
docker push "iov1/iovns-faucet:$TRAVIS_TAG";
docker logout;
fi;
fi;
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- CLI: Add broker field to registerDomain and registerAccount
- FIX: fix fee deduct and improve tests
- FIX: TransferDomain flushes empty account content
- Implement faucet

## v0.4.3

Expand Down
2 changes: 2 additions & 0 deletions cmd/faucet/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.envrc
faucet
9 changes: 9 additions & 0 deletions cmd/faucet/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# Final image
FROM alpine:edge

WORKDIR /root

# Copy over binaries from the build-env
COPY /faucet /usr/bin/faucet

CMD ["faucet"]
16 changes: 16 additions & 0 deletions cmd/faucet/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.PHONY: all install test

# make sure we turn on go modules
export GO111MODULE := on

all: test install

install:
go install .

build:
GOARCH=amd64 CGO_ENABLED=0 GOOS=linux go build .

test:
go vet -mod=readonly ./...
go test -mod=readonly -race ./...
31 changes: 31 additions & 0 deletions cmd/faucet/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
## How to export private key

```bash
iovnscli keys export faucet
```
- Input a passphrase. Remember you will use this pass phrase as env variable.
- Copy armor and use it in env variable:
```
-----BEGIN TENDERMINT PRIVATE KEY-----
kdf: bcrypt
salt: 93EFF493A3EA0A6AB71C00D69176AF19
type: secp256k1
l2zXyG3OOCXzzUxzmYv7Td1OFsc+vnCf7BckhUic8Y11KGCEm76fvRtdzlSwW0A5
fcz4CbdxSMEYktjtW5zyE+nLveB/UoJ3YK8Sbr4=
=Vhcg
-----END TENDERMINT PRIVATE KEY-----
```
## Enviroment variables
- GAS_PRICES
- GAS_ADJUST
- SEND_AMOUNT
- TENDERMINT_RPC
- PORT
- CHAIN_ID
- COIN_DENOM
- ARMOR
- PASSPHRASE

## How to use
`http://localhost:8080/credit?address=<bech32addr>`
61 changes: 61 additions & 0 deletions cmd/faucet/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package main

import (
"context"
"log"
"net/http"
"os"
"os/signal"
"time"

keys2 "github.com/cosmos/cosmos-sdk/crypto/keys"

"github.com/gorilla/mux"
"github.com/iov-one/iovns/cmd/faucet/pkg"
rpchttp "github.com/tendermint/tendermint/rpc/client"
)

func main() {
// setup configuration
conf, err := pkg.NewConfiguration()
if err != nil {
log.Fatalf("configuration: %s", err)
}
// setup node
node, err := rpchttp.NewHTTP(conf.TendermintRPC, "/websocket")
kb := keys2.NewInMemory()
if err := kb.ImportPrivKey("faucet", conf.Armor, conf.Passphrase); err != nil {
log.Fatalf("keybase: %v", err)
}
// setup tx manager
txManager := pkg.NewTxManager(*conf, node).WithKeybase(kb)
if err := txManager.Init(); err != nil {
log.Fatalf("tx manager: %v", err)
}

// Wait for ListenAndServe goroutine to close.
r := mux.NewRouter()
faucet := pkg.NewFaucetHandler(txManager)
r.Handle("/credit", faucet)
r.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
return
})
server := &http.Server{Addr: conf.Port, Handler: r}

go func() {
log.Print("server started")
if err := server.ListenAndServe(); err != nil {
log.Fatalf("http server: %s", err)
}
}()

stop := make(chan os.Signal, 1)
signal.Notify(stop, os.Interrupt)

<-stop

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
_ = server.Shutdown(ctx)
}
30 changes: 30 additions & 0 deletions cmd/faucet/pkg/codec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package pkg

import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/iov-one/iovns/app"
)

// ModuleCdc instantiates a new codec for the domain module
var ModuleCdc = codec.New()

func init() {
RegisterCodec(ModuleCdc)
config := sdk.GetConfig()
config.SetCoinType(app.CoinType)
config.SetFullFundraiserPath(app.FullFundraiserPath)
config.SetBech32PrefixForAccount(app.Bech32PrefixAccAddr, app.Bech32PrefixAccPub)
config.SetBech32PrefixForValidator(app.Bech32PrefixValAddr, app.Bech32PrefixValPub)
config.SetBech32PrefixForConsensusNode(app.Bech32PrefixConsAddr, app.Bech32PrefixConsPub)
config.Seal()
}

func RegisterCodec(cdc *codec.Codec) {
sdk.RegisterCodec(cdc)
bank.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
}
70 changes: 70 additions & 0 deletions cmd/faucet/pkg/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package pkg

import (
"os"
"strconv"

"github.com/pkg/errors"
)

type Configuration struct {
TendermintRPC string
Port string
ChainID string
CoinDenom string
Armor string
Passphrase string
Memo string
SendAmount int64
GasPrices string
GasAdjust float64
KeyringPass string
}

func env(name, fallback string) string {
if v, ok := os.LookupEnv(name); ok {
return v
}
return fallback
}

const (
gas = "0"
ga = "0.2"
fallBackArmor = `
-----BEGIN TENDERMINT PRIVATE KEY-----
salt: BF94D84D7E0BFEF9AB735D9315AD271E
type: secp256k1
kdf: bcrypt
PECa11ktJ6mV4iTnhHGIL9nhjdXjplQDt5n+o5nddvnmS613AWbCL5FrC3WErdCR
vdsyKdlue2uLJizP46Ao3w6PKMBVYgIkKe97GjA=
=MZbT
-----END TENDERMINT PRIVATE KEY-----
`
faucetAddr = "star1pdp388k2jj5zsxx67v02pxtttguf6r4jj79v00"
)

func NewConfiguration() (*Configuration, error) {
gasPrices := env("GAS_PRICES", "10.0uvoi")
ga := env("GAS_ADJUST", ga)
gasAdjust, err := strconv.ParseFloat(ga, 64)
if err != nil {
return nil, errors.Wrap(err, "GAS_ADJUST")
}

sendStr := env("SEND_AMOUNT", "100")
send, err := strconv.Atoi(sendStr)
return &Configuration{
TendermintRPC: env("TENDERMINT_RPC", "http://localhost:26657"),
Port: env("PORT", ":8080"),
ChainID: env("CHAIN_ID", "local"),
CoinDenom: env("COIN_DENOM", "tiov"),
Armor: env("ARMOR", fallBackArmor),
Passphrase: env("PASSPHRASE", "12345678"),
Memo: "sent by IOV with love",
SendAmount: int64(send),
GasPrices: gasPrices,
GasAdjust: gasAdjust,
}, nil
}
82 changes: 82 additions & 0 deletions cmd/faucet/pkg/handlers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package pkg

import (
"encoding/json"
"net/http"

"github.com/cosmos/cosmos-sdk/types/errors"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/prometheus/common/log"
)

// keeps tx manager and mutex locks sequence bump
type FaucetHandler struct {
tm *TxManager
}

func NewFaucetHandler(tm *TxManager) *FaucetHandler {
return &FaucetHandler{
tm: tm,
}
}

func jsonErr(w http.ResponseWriter, status int, msg string) {
errJson := struct {
Error string `json:"error"`
}{
Error: msg,
}

w.Header().Set("Content-Type", "application/json")
w.WriteHeader(status)
json.NewEncoder(w).Encode(errJson)

return
}

func (f *FaucetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
addrStr := r.URL.Query().Get("address")
if addrStr == "" {
jsonErr(w, http.StatusBadRequest, "provide a bech32 address")
return
}
addr, err := sdk.AccAddressFromBech32(addrStr)
if err != nil {
log.Error(errors.Wrap(err, "incorrect bech32 address"))
jsonErr(w, http.StatusBadRequest, "provide a bech32 address")
return
}

tx, err := f.tm.BuildAndSignTx(addr)
if err != nil {
log.Error(errors.Wrap(err, "tx signing failed"))
jsonErr(w, http.StatusInternalServerError, "internal error")
return
}

res, err := f.tm.BroadcastTx(tx)
if err != nil {
log.Error(errors.Wrap(err, "broadcast tx failed"))
jsonErr(w, http.StatusInternalServerError, "internal error")
return
}

if res.Code != errors.SuccessABCICode {
log.Error(errors.Wrap(err, "broadcast tx failed"))
jsonErr(w, http.StatusInternalServerError, "internal error")
return
}

resp := struct {
Msg string `json:"msg"`
Hash string `json:"hash"`
}{
Msg: "check your balance :-)",
Hash: res.Hash.String(),
}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(resp)
return
}
Loading

0 comments on commit c43117c

Please sign in to comment.