Merge pull request #14402 from karalabe/tiered-faucet
cmd/faucet, cmd/puppeth: support multi-tiered faucet
This commit is contained in:
commit
1c2f6f5597
@ -27,11 +27,13 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math"
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@ -67,6 +69,7 @@ var (
|
|||||||
netnameFlag = flag.String("faucet.name", "", "Network name to assign to the faucet")
|
netnameFlag = flag.String("faucet.name", "", "Network name to assign to the faucet")
|
||||||
payoutFlag = flag.Int("faucet.amount", 1, "Number of Ethers to pay out per user request")
|
payoutFlag = flag.Int("faucet.amount", 1, "Number of Ethers to pay out per user request")
|
||||||
minutesFlag = flag.Int("faucet.minutes", 1440, "Number of minutes to wait between funding rounds")
|
minutesFlag = flag.Int("faucet.minutes", 1440, "Number of minutes to wait between funding rounds")
|
||||||
|
tiersFlag = flag.Int("faucet.tiers", 3, "Number of funding tiers to enable (x3 time, x2.5 funds)")
|
||||||
|
|
||||||
accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with")
|
accJSONFlag = flag.String("account.json", "", "Key json file to fund user requests with")
|
||||||
accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds")
|
accPassFlag = flag.String("account.pass", "", "Decryption password to access faucet funds")
|
||||||
@ -89,22 +92,47 @@ func main() {
|
|||||||
flag.Parse()
|
flag.Parse()
|
||||||
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*logFlag), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
log.Root().SetHandler(log.LvlFilterHandler(log.Lvl(*logFlag), log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
|
||||||
|
|
||||||
|
// Construct the payout tiers
|
||||||
|
amounts := make([]string, *tiersFlag)
|
||||||
|
periods := make([]string, *tiersFlag)
|
||||||
|
for i := 0; i < *tiersFlag; i++ {
|
||||||
|
// Calculate the amount for the next tier and format it
|
||||||
|
amount := float64(*payoutFlag) * math.Pow(2.5, float64(i))
|
||||||
|
amounts[i] = fmt.Sprintf("%s Ethers", strconv.FormatFloat(amount, 'f', -1, 64))
|
||||||
|
if amount == 1 {
|
||||||
|
amounts[i] = strings.TrimSuffix(amounts[i], "s")
|
||||||
|
}
|
||||||
|
// Calcualte the period for th enext tier and format it
|
||||||
|
period := *minutesFlag * int(math.Pow(3, float64(i)))
|
||||||
|
periods[i] = fmt.Sprintf("%d mins", period)
|
||||||
|
if period%60 == 0 {
|
||||||
|
period /= 60
|
||||||
|
periods[i] = fmt.Sprintf("%d hours", period)
|
||||||
|
|
||||||
|
if period%24 == 0 {
|
||||||
|
period /= 24
|
||||||
|
periods[i] = fmt.Sprintf("%d days", period)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if period == 1 {
|
||||||
|
periods[i] = strings.TrimSuffix(periods[i], "s")
|
||||||
|
}
|
||||||
|
}
|
||||||
// Load up and render the faucet website
|
// Load up and render the faucet website
|
||||||
tmpl, err := Asset("faucet.html")
|
tmpl, err := Asset("faucet.html")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Crit("Failed to load the faucet template", "err", err)
|
log.Crit("Failed to load the faucet template", "err", err)
|
||||||
}
|
}
|
||||||
period := fmt.Sprintf("%d minute(s)", *minutesFlag)
|
|
||||||
if *minutesFlag%60 == 0 {
|
|
||||||
period = fmt.Sprintf("%d hour(s)", *minutesFlag/60)
|
|
||||||
}
|
|
||||||
website := new(bytes.Buffer)
|
website := new(bytes.Buffer)
|
||||||
template.Must(template.New("").Parse(string(tmpl))).Execute(website, map[string]interface{}{
|
err = template.Must(template.New("").Parse(string(tmpl))).Execute(website, map[string]interface{}{
|
||||||
"Network": *netnameFlag,
|
"Network": *netnameFlag,
|
||||||
"Amount": *payoutFlag,
|
"Amounts": amounts,
|
||||||
"Period": period,
|
"Periods": periods,
|
||||||
"Recaptcha": *captchaToken,
|
"Recaptcha": *captchaToken,
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Crit("Failed to render the faucet template", "err", err)
|
||||||
|
}
|
||||||
// Load and parse the genesis block requested by the user
|
// Load and parse the genesis block requested by the user
|
||||||
blob, err := ioutil.ReadFile(*genesisFlag)
|
blob, err := ioutil.ReadFile(*genesisFlag)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -171,10 +199,10 @@ type faucet struct {
|
|||||||
nonce uint64 // Current pending nonce of the faucet
|
nonce uint64 // Current pending nonce of the faucet
|
||||||
price *big.Int // Current gas price to issue funds with
|
price *big.Int // Current gas price to issue funds with
|
||||||
|
|
||||||
conns []*websocket.Conn // Currently live websocket connections
|
conns []*websocket.Conn // Currently live websocket connections
|
||||||
history map[string]time.Time // History of users and their funding requests
|
timeouts map[string]time.Time // History of users and their funding timeouts
|
||||||
reqs []*request // Currently pending funding requests
|
reqs []*request // Currently pending funding requests
|
||||||
update chan struct{} // Channel to signal request updates
|
update chan struct{} // Channel to signal request updates
|
||||||
|
|
||||||
lock sync.RWMutex // Lock protecting the faucet's internals
|
lock sync.RWMutex // Lock protecting the faucet's internals
|
||||||
}
|
}
|
||||||
@ -241,7 +269,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
|
|||||||
index: index,
|
index: index,
|
||||||
keystore: ks,
|
keystore: ks,
|
||||||
account: ks.Accounts()[0],
|
account: ks.Accounts()[0],
|
||||||
history: make(map[string]time.Time),
|
timeouts: make(map[string]time.Time),
|
||||||
update: make(chan struct{}, 1),
|
update: make(chan struct{}, 1),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -295,14 +323,22 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
|||||||
"peers": f.stack.Server().PeerCount(),
|
"peers": f.stack.Server().PeerCount(),
|
||||||
"requests": f.reqs,
|
"requests": f.reqs,
|
||||||
})
|
})
|
||||||
header, _ := f.client.HeaderByNumber(context.Background(), nil)
|
// Send the initial block to the client
|
||||||
websocket.JSON.Send(conn, header)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
|
header, err := f.client.HeaderByNumber(ctx, nil)
|
||||||
|
cancel()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Error("Failed to retrieve latest header", "err", err)
|
||||||
|
} else {
|
||||||
|
websocket.JSON.Send(conn, header)
|
||||||
|
}
|
||||||
// Keep reading requests from the websocket until the connection breaks
|
// Keep reading requests from the websocket until the connection breaks
|
||||||
for {
|
for {
|
||||||
// Fetch the next funding request and validate against github
|
// Fetch the next funding request and validate against github
|
||||||
var msg struct {
|
var msg struct {
|
||||||
URL string `json:"url"`
|
URL string `json:"url"`
|
||||||
|
Tier uint `json:"tier"`
|
||||||
Captcha string `json:"captcha"`
|
Captcha string `json:"captcha"`
|
||||||
}
|
}
|
||||||
if err := websocket.JSON.Receive(conn, &msg); err != nil {
|
if err := websocket.JSON.Receive(conn, &msg); err != nil {
|
||||||
@ -312,7 +348,11 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
|||||||
websocket.JSON.Send(conn, map[string]string{"error": "URL doesn't link to GitHub Gists"})
|
websocket.JSON.Send(conn, map[string]string{"error": "URL doesn't link to GitHub Gists"})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
log.Info("Faucet funds requested", "gist", msg.URL)
|
if msg.Tier >= uint(*tiersFlag) {
|
||||||
|
websocket.JSON.Send(conn, map[string]string{"error": "Invalid funding tier requested"})
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Info("Faucet funds requested", "gist", msg.URL, "tier", msg.Tier)
|
||||||
|
|
||||||
// If captcha verifications are enabled, make sure we're not dealing with a robot
|
// If captcha verifications are enabled, make sure we're not dealing with a robot
|
||||||
if *captchaToken != "" {
|
if *captchaToken != "" {
|
||||||
@ -337,7 +377,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
|||||||
}
|
}
|
||||||
if !result.Success {
|
if !result.Success {
|
||||||
log.Warn("Captcha verification failed", "err", string(result.Errors))
|
log.Warn("Captcha verification failed", "err", string(result.Errors))
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": "Beep-boop, you're a robot!"})
|
websocket.JSON.Send(conn, map[string]string{"error": "Beep-bop, you're a robot!"})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -396,11 +436,15 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
|||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
var (
|
var (
|
||||||
fund bool
|
fund bool
|
||||||
elapsed time.Duration
|
timeout time.Time
|
||||||
)
|
)
|
||||||
if elapsed = time.Since(f.history[gist.Owner.Login]); elapsed > time.Duration(*minutesFlag)*time.Minute {
|
if timeout = f.timeouts[gist.Owner.Login]; time.Now().After(timeout) {
|
||||||
// User wasn't funded recently, create the funding transaction
|
// User wasn't funded recently, create the funding transaction
|
||||||
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether), big.NewInt(21000), f.price, nil)
|
amount := new(big.Int).Mul(big.NewInt(int64(*payoutFlag)), ether)
|
||||||
|
amount = new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(5), big.NewInt(int64(msg.Tier)), nil))
|
||||||
|
amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil))
|
||||||
|
|
||||||
|
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, big.NewInt(21000), f.price, nil)
|
||||||
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId)
|
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
||||||
@ -419,14 +463,14 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
|||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
Tx: signed,
|
Tx: signed,
|
||||||
})
|
})
|
||||||
f.history[gist.Owner.Login] = time.Now()
|
f.timeouts[gist.Owner.Login] = time.Now().Add(time.Duration(*minutesFlag*int(math.Pow(3, float64(msg.Tier)))) * time.Minute)
|
||||||
fund = true
|
fund = true
|
||||||
}
|
}
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
|
|
||||||
// Send an error if too frequent funding, othewise a success
|
// Send an error if too frequent funding, othewise a success
|
||||||
if !fund {
|
if !fund {
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": fmt.Sprintf("User already funded %s ago", common.PrettyDuration(elapsed))})
|
websocket.JSON.Send(conn, map[string]string{"error": fmt.Sprintf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
websocket.JSON.Send(conn, map[string]string{"success": fmt.Sprintf("Funding request accepted for %s into %s", gist.Owner.Login, address.Hex())})
|
websocket.JSON.Send(conn, map[string]string{"success": fmt.Sprintf("Funding request accepted for %s into %s", gist.Owner.Login, address.Hex())})
|
||||||
|
@ -51,7 +51,10 @@
|
|||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input id="gist" type="text" class="form-control" placeholder="GitHub Gist URL containing your Ethereum address...">
|
<input id="gist" type="text" class="form-control" placeholder="GitHub Gist URL containing your Ethereum address...">
|
||||||
<span class="input-group-btn">
|
<span class="input-group-btn">
|
||||||
<button class="btn btn-default" type="button" onclick="{{if .Recaptcha}}grecaptcha.execute(){{else}}submit(){{end}}">Give me Ether!</button>
|
<button class="btn btn-default dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Give me Ether <i class="fa fa-caret-down" aria-hidden="true"></i></button>
|
||||||
|
<ul class="dropdown-menu dropdown-menu-right">{{range $idx, $amount := .Amounts}}
|
||||||
|
<li><a style="text-align: center;" onclick="tier={{$idx}}; {{if $.Recaptcha}}grecaptcha.execute(){{else}}submit({{$idx}}){{end}}">{{$amount}} / {{index $.Periods $idx}}</a></li>{{end}}
|
||||||
|
</ul>
|
||||||
</span>
|
</span>
|
||||||
</div>{{if .Recaptcha}}
|
</div>{{if .Recaptcha}}
|
||||||
<div class="g-recaptcha" data-sitekey="{{.Recaptcha}}" data-callback="submit" data-size="invisible"></div>{{end}}
|
<div class="g-recaptcha" data-sitekey="{{.Recaptcha}}" data-callback="submit" data-size="invisible"></div>{{end}}
|
||||||
@ -77,8 +80,9 @@
|
|||||||
<div class="row" style="margin-top: 32px;">
|
<div class="row" style="margin-top: 32px;">
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<h3>How does this work?</h3>
|
<h3>How does this work?</h3>
|
||||||
<p>This Ether faucet is running on the {{.Network}} network. To prevent malicious actors from exhausting all available funds or accumulating enough Ether to mount long running spam attacks, requests are tied to GitHub accounts. Anyone having a GitHub account may request funds within the permitted limit of <strong>{{.Amount}} Ether(s) / {{.Period}}</strong>.{{if .Recaptcha}} The faucet is running invisible reCaptcha protection against bots.{{end}}</p>
|
<p>This Ether faucet is running on the {{.Network}} network. To prevent malicious actors from exhausting all available funds or accumulating enough Ether to mount long running spam attacks, requests are tied to GitHub accounts. Anyone having a GitHub account may request funds within the permitted limits.</p>
|
||||||
<p>To request funds, simply create a <a href="https://gist.github.com/" target="_about:blank">GitHub Gist</a> with your Ethereum address pasted into the contents (the file name doesn't matter), copy paste the gists URL into the above input box and fire away! You can track the current pending requests below the input field to see how much you have to wait until your turn comes.</p>
|
<p>To request funds, simply create a <a href="https://gist.github.com/" target="_about:blank">GitHub Gist</a> with your Ethereum address pasted into the contents (the file name doesn't matter), copy paste the gists URL into the above input box and fire away! You can track the current pending requests below the input field to see how much you have to wait until your turn comes.</p>
|
||||||
|
{{if .Recaptcha}}<em>The faucet is running invisible reCaptcha protection against bots.</em>{{end}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -88,10 +92,11 @@
|
|||||||
// Global variables to hold the current status of the faucet
|
// Global variables to hold the current status of the faucet
|
||||||
var attempt = 0;
|
var attempt = 0;
|
||||||
var server;
|
var server;
|
||||||
|
var tier = 0;
|
||||||
|
|
||||||
// Define the function that submits a gist url to the server
|
// Define the function that submits a gist url to the server
|
||||||
var submit = function({{if .Recaptcha}}captcha{{end}}) {
|
var submit = function({{if .Recaptcha}}captcha{{end}}) {
|
||||||
server.send(JSON.stringify({url: $("#gist")[0].value{{if .Recaptcha}}, captcha: captcha{{end}}}));{{if .Recaptcha}}
|
server.send(JSON.stringify({url: $("#gist")[0].value, tier: tier{{if .Recaptcha}}, captcha: captcha{{end}}}));{{if .Recaptcha}}
|
||||||
grecaptcha.reset();{{end}}
|
grecaptcha.reset();{{end}}
|
||||||
};
|
};
|
||||||
// Define a method to reconnect upon server loss
|
// Define a method to reconnect upon server loss
|
||||||
|
File diff suppressed because one or more lines are too long
@ -51,10 +51,10 @@ ADD account.pass /account.pass
|
|||||||
EXPOSE 8080
|
EXPOSE 8080
|
||||||
|
|
||||||
CMD [ \
|
CMD [ \
|
||||||
"/faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", \
|
"/faucet", "--genesis", "/genesis.json", "--network", "{{.NetworkID}}", "--bootnodes", "{{.Bootnodes}}", "--ethstats", "{{.Ethstats}}", "--ethport", "{{.EthPort}}", \
|
||||||
"--ethport", "{{.EthPort}}", "--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", \
|
"--faucet.name", "{{.FaucetName}}", "--faucet.amount", "{{.FaucetAmount}}", "--faucet.minutes", "{{.FaucetMinutes}}", "--faucet.tiers", "{{.FaucetTiers}}", \
|
||||||
"--github.user", "{{.GitHubUser}}", "--github.token", "{{.GitHubToken}}", "--account.json", "/account.json", "--account.pass", "/account.pass" \
|
"--github.user", "{{.GitHubUser}}", "--github.token", "{{.GitHubToken}}", "--account.json", "/account.json", "--account.pass", "/account.pass" \
|
||||||
{{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}} \
|
{{if .CaptchaToken}}, "--captcha.token", "{{.CaptchaToken}}", "--captcha.secret", "{{.CaptchaSecret}}"{{end}} \
|
||||||
]`
|
]`
|
||||||
|
|
||||||
// faucetComposefile is the docker-compose.yml file required to deploy and maintain
|
// faucetComposefile is the docker-compose.yml file required to deploy and maintain
|
||||||
@ -75,6 +75,7 @@ services:
|
|||||||
- ETH_NAME={{.EthName}}
|
- ETH_NAME={{.EthName}}
|
||||||
- FAUCET_AMOUNT={{.FaucetAmount}}
|
- FAUCET_AMOUNT={{.FaucetAmount}}
|
||||||
- FAUCET_MINUTES={{.FaucetMinutes}}
|
- FAUCET_MINUTES={{.FaucetMinutes}}
|
||||||
|
- FAUCET_TIERS={{.FaucetTiers}}
|
||||||
- GITHUB_USER={{.GitHubUser}}
|
- GITHUB_USER={{.GitHubUser}}
|
||||||
- GITHUB_TOKEN={{.GitHubToken}}
|
- GITHUB_TOKEN={{.GitHubToken}}
|
||||||
- CAPTCHA_TOKEN={{.CaptchaToken}}
|
- CAPTCHA_TOKEN={{.CaptchaToken}}
|
||||||
@ -105,6 +106,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
|
|||||||
"FaucetName": strings.Title(network),
|
"FaucetName": strings.Title(network),
|
||||||
"FaucetAmount": config.amount,
|
"FaucetAmount": config.amount,
|
||||||
"FaucetMinutes": config.minutes,
|
"FaucetMinutes": config.minutes,
|
||||||
|
"FaucetTiers": config.tiers,
|
||||||
})
|
})
|
||||||
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
|
files[filepath.Join(workdir, "Dockerfile")] = dockerfile.Bytes()
|
||||||
|
|
||||||
@ -122,6 +124,7 @@ func deployFaucet(client *sshClient, network string, bootnodes []string, config
|
|||||||
"CaptchaSecret": config.captchaSecret,
|
"CaptchaSecret": config.captchaSecret,
|
||||||
"FaucetAmount": config.amount,
|
"FaucetAmount": config.amount,
|
||||||
"FaucetMinutes": config.minutes,
|
"FaucetMinutes": config.minutes,
|
||||||
|
"FaucetTiers": config.tiers,
|
||||||
})
|
})
|
||||||
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
files[filepath.Join(workdir, "docker-compose.yaml")] = composefile.Bytes()
|
||||||
|
|
||||||
@ -147,6 +150,7 @@ type faucetInfos struct {
|
|||||||
port int
|
port int
|
||||||
amount int
|
amount int
|
||||||
minutes int
|
minutes int
|
||||||
|
tiers int
|
||||||
githubUser string
|
githubUser string
|
||||||
githubToken string
|
githubToken string
|
||||||
captchaToken string
|
captchaToken string
|
||||||
@ -155,7 +159,7 @@ type faucetInfos struct {
|
|||||||
|
|
||||||
// String implements the stringer interface.
|
// String implements the stringer interface.
|
||||||
func (info *faucetInfos) String() string {
|
func (info *faucetInfos) String() string {
|
||||||
return fmt.Sprintf("host=%s, api=%d, eth=%d, amount=%d, minutes=%d, github=%s, captcha=%v, ethstats=%s", info.host, info.port, info.node.portFull, info.amount, info.minutes, info.githubUser, info.captchaToken != "", info.node.ethstats)
|
return fmt.Sprintf("host=%s, api=%d, eth=%d, amount=%d, minutes=%d, tiers=%d, github=%s, captcha=%v, ethstats=%s", info.host, info.port, info.node.portFull, info.amount, info.minutes, info.tiers, info.githubUser, info.captchaToken != "", info.node.ethstats)
|
||||||
}
|
}
|
||||||
|
|
||||||
// checkFaucet does a health-check against an faucet server to verify whether
|
// checkFaucet does a health-check against an faucet server to verify whether
|
||||||
@ -186,6 +190,7 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
|
|||||||
}
|
}
|
||||||
amount, _ := strconv.Atoi(infos.envvars["FAUCET_AMOUNT"])
|
amount, _ := strconv.Atoi(infos.envvars["FAUCET_AMOUNT"])
|
||||||
minutes, _ := strconv.Atoi(infos.envvars["FAUCET_MINUTES"])
|
minutes, _ := strconv.Atoi(infos.envvars["FAUCET_MINUTES"])
|
||||||
|
tiers, _ := strconv.Atoi(infos.envvars["FAUCET_TIERS"])
|
||||||
|
|
||||||
// Retrieve the funding account informations
|
// Retrieve the funding account informations
|
||||||
var out []byte
|
var out []byte
|
||||||
@ -213,6 +218,7 @@ func checkFaucet(client *sshClient, network string) (*faucetInfos, error) {
|
|||||||
port: port,
|
port: port,
|
||||||
amount: amount,
|
amount: amount,
|
||||||
minutes: minutes,
|
minutes: minutes,
|
||||||
|
tiers: tiers,
|
||||||
githubUser: infos.envvars["GITHUB_USER"],
|
githubUser: infos.envvars["GITHUB_USER"],
|
||||||
githubToken: infos.envvars["GITHUB_TOKEN"],
|
githubToken: infos.envvars["GITHUB_TOKEN"],
|
||||||
captchaToken: infos.envvars["CAPTCHA_TOKEN"],
|
captchaToken: infos.envvars["CAPTCHA_TOKEN"],
|
||||||
|
@ -44,6 +44,7 @@ func (w *wizard) deployFaucet() {
|
|||||||
host: client.server,
|
host: client.server,
|
||||||
amount: 1,
|
amount: 1,
|
||||||
minutes: 1440,
|
minutes: 1440,
|
||||||
|
tiers: 3,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
infos.node.genesis, _ = json.MarshalIndent(w.conf.genesis, "", " ")
|
infos.node.genesis, _ = json.MarshalIndent(w.conf.genesis, "", " ")
|
||||||
@ -68,6 +69,13 @@ func (w *wizard) deployFaucet() {
|
|||||||
fmt.Printf("How many minutes to enforce between requests? (default = %d)\n", infos.minutes)
|
fmt.Printf("How many minutes to enforce between requests? (default = %d)\n", infos.minutes)
|
||||||
infos.minutes = w.readDefaultInt(infos.minutes)
|
infos.minutes = w.readDefaultInt(infos.minutes)
|
||||||
|
|
||||||
|
fmt.Println()
|
||||||
|
fmt.Printf("How many funding tiers to feature (x2.5 amounts, x3 timeout)? (default = %d)\n", infos.tiers)
|
||||||
|
infos.tiers = w.readDefaultInt(infos.tiers)
|
||||||
|
if infos.tiers == 0 {
|
||||||
|
log.Error("At least one funding tier must be set")
|
||||||
|
return
|
||||||
|
}
|
||||||
// Accessing GitHub gists requires API authorization, retrieve it
|
// Accessing GitHub gists requires API authorization, retrieve it
|
||||||
if infos.githubUser != "" {
|
if infos.githubUser != "" {
|
||||||
fmt.Println()
|
fmt.Println()
|
||||||
|
Loading…
Reference in New Issue
Block a user