@@ -27,11 +27,13 @@ import (
2727 "fmt"
2828 "html/template"
2929 "io/ioutil"
30+ "math"
3031 "math/big"
3132 "net/http"
3233 "net/url"
3334 "os"
3435 "path/filepath"
36+ "strconv"
3537 "strings"
3638 "sync"
3739 "time"
6769 netnameFlag = flag .String ("faucet.name" , "" , "Network name to assign to the faucet" )
6870 payoutFlag = flag .Int ("faucet.amount" , 1 , "Number of Ethers to pay out per user request" )
6971 minutesFlag = flag .Int ("faucet.minutes" , 1440 , "Number of minutes to wait between funding rounds" )
72+ tiersFlag = flag .Int ("faucet.tiers" , 3 , "Number of funding tiers to enable (x3 time, x2.5 funds)" )
7073
7174 accJSONFlag = flag .String ("account.json" , "" , "Key json file to fund user requests with" )
7275 accPassFlag = flag .String ("account.pass" , "" , "Decryption password to access faucet funds" )
@@ -89,22 +92,46 @@ func main() {
8992 flag .Parse ()
9093 log .Root ().SetHandler (log .LvlFilterHandler (log .Lvl (* logFlag ), log .StreamHandler (os .Stderr , log .TerminalFormat (true ))))
9194
95+ // Construct the payout tiers
96+ amounts := make ([]string , * tiersFlag )
97+ periods := make ([]string , * tiersFlag )
98+ for i := 0 ; i < * tiersFlag ; i ++ {
99+ // Calculate the amount for the next tier and format it
100+ amount := float64 (* payoutFlag ) * math .Pow (2.5 , float64 (i ))
101+ amounts [i ] = fmt .Sprintf ("%s Ethers" , strconv .FormatFloat (amount , 'f' , - 1 , 64 ))
102+ if amount == 1 {
103+ amounts [i ] = strings .TrimSuffix (amounts [i ], "s" )
104+ }
105+ // Calcualte the period for th enext tier and format it
106+ period := * minutesFlag * int (math .Pow (3 , float64 (i )))
107+ periods [i ] = fmt .Sprintf ("%d mins" , period )
108+ if period % 60 == 0 {
109+ period /= 60
110+ periods [i ] = fmt .Sprintf ("%d hours" , period )
111+ }
112+ if period % 24 == 0 {
113+ period /= 24
114+ periods [i ] = fmt .Sprintf ("%d days" , period )
115+ }
116+ if period == 1 {
117+ periods [i ] = strings .TrimSuffix (periods [i ], "s" )
118+ }
119+ }
92120 // Load up and render the faucet website
93121 tmpl , err := Asset ("faucet.html" )
94122 if err != nil {
95123 log .Crit ("Failed to load the faucet template" , "err" , err )
96124 }
97- period := fmt .Sprintf ("%d minute(s)" , * minutesFlag )
98- if * minutesFlag % 60 == 0 {
99- period = fmt .Sprintf ("%d hour(s)" , * minutesFlag / 60 )
100- }
101125 website := new (bytes.Buffer )
102- template .Must (template .New ("" ).Parse (string (tmpl ))).Execute (website , map [string ]interface {}{
126+ err = template .Must (template .New ("" ).Parse (string (tmpl ))).Execute (website , map [string ]interface {}{
103127 "Network" : * netnameFlag ,
104- "Amount " : * payoutFlag ,
105- "Period " : period ,
128+ "Amounts " : amounts ,
129+ "Periods " : periods ,
106130 "Recaptcha" : * captchaToken ,
107131 })
132+ if err != nil {
133+ log .Crit ("Failed to render the faucet template" , "err" , err )
134+ }
108135 // Load and parse the genesis block requested by the user
109136 blob , err := ioutil .ReadFile (* genesisFlag )
110137 if err != nil {
@@ -171,10 +198,10 @@ type faucet struct {
171198 nonce uint64 // Current pending nonce of the faucet
172199 price * big.Int // Current gas price to issue funds with
173200
174- conns []* websocket.Conn // Currently live websocket connections
175- history map [string ]time.Time // History of users and their funding requests
176- reqs []* request // Currently pending funding requests
177- update chan struct {} // Channel to signal request updates
201+ conns []* websocket.Conn // Currently live websocket connections
202+ timeouts map [string ]time.Time // History of users and their funding timeouts
203+ reqs []* request // Currently pending funding requests
204+ update chan struct {} // Channel to signal request updates
178205
179206 lock sync.RWMutex // Lock protecting the faucet's internals
180207}
@@ -241,7 +268,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
241268 index : index ,
242269 keystore : ks ,
243270 account : ks .Accounts ()[0 ],
244- history : make (map [string ]time.Time ),
271+ timeouts : make (map [string ]time.Time ),
245272 update : make (chan struct {}, 1 ),
246273 }, nil
247274}
@@ -295,14 +322,22 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
295322 "peers" : f .stack .Server ().PeerCount (),
296323 "requests" : f .reqs ,
297324 })
298- header , _ := f .client .HeaderByNumber (context .Background (), nil )
299- websocket .JSON .Send (conn , header )
325+ // Send the initial block to the client
326+ ctx , cancel := context .WithTimeout (context .Background (), time .Second )
327+ header , err := f .client .HeaderByNumber (ctx , nil )
328+ cancel ()
300329
330+ if err != nil {
331+ log .Error ("Failed to retrieve latest header" , "err" , err )
332+ } else {
333+ websocket .JSON .Send (conn , header )
334+ }
301335 // Keep reading requests from the websocket until the connection breaks
302336 for {
303337 // Fetch the next funding request and validate against github
304338 var msg struct {
305339 URL string `json:"url"`
340+ Tier uint `json:"tier"`
306341 Captcha string `json:"captcha"`
307342 }
308343 if err := websocket .JSON .Receive (conn , & msg ); err != nil {
@@ -312,7 +347,11 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
312347 websocket .JSON .Send (conn , map [string ]string {"error" : "URL doesn't link to GitHub Gists" })
313348 continue
314349 }
315- log .Info ("Faucet funds requested" , "gist" , msg .URL )
350+ if msg .Tier >= uint (* tiersFlag ) {
351+ websocket .JSON .Send (conn , map [string ]string {"error" : "Invalid funding tier requested" })
352+ continue
353+ }
354+ log .Info ("Faucet funds requested" , "gist" , msg .URL , "tier" , msg .Tier )
316355
317356 // If captcha verifications are enabled, make sure we're not dealing with a robot
318357 if * captchaToken != "" {
@@ -337,7 +376,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
337376 }
338377 if ! result .Success {
339378 log .Warn ("Captcha verification failed" , "err" , string (result .Errors ))
340- websocket .JSON .Send (conn , map [string ]string {"error" : "Beep-boop , you're a robot!" })
379+ websocket .JSON .Send (conn , map [string ]string {"error" : "Beep-bop , you're a robot!" })
341380 continue
342381 }
343382 }
@@ -396,11 +435,15 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
396435 f .lock .Lock ()
397436 var (
398437 fund bool
399- elapsed time.Duration
438+ timeout time.Time
400439 )
401- if elapsed = time . Since ( f . history [gist .Owner .Login ]); elapsed > time .Duration ( * minutesFlag ) * time . Minute {
440+ if timeout = f . timeouts [gist .Owner .Login ]; time .Now (). After ( timeout ) {
402441 // User wasn't funded recently, create the funding transaction
403- 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 )
442+ amount := new (big.Int ).Mul (big .NewInt (int64 (* payoutFlag )), ether )
443+ amount = new (big.Int ).Mul (amount , new (big.Int ).Exp (big .NewInt (5 ), big .NewInt (int64 (msg .Tier )), nil ))
444+ amount = new (big.Int ).Div (amount , new (big.Int ).Exp (big .NewInt (2 ), big .NewInt (int64 (msg .Tier )), nil ))
445+
446+ tx := types .NewTransaction (f .nonce + uint64 (len (f .reqs )), address , amount , big .NewInt (21000 ), f .price , nil )
404447 signed , err := f .keystore .SignTx (f .account , tx , f .config .ChainId )
405448 if err != nil {
406449 websocket .JSON .Send (conn , map [string ]string {"error" : err .Error ()})
@@ -419,14 +462,14 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
419462 Time : time .Now (),
420463 Tx : signed ,
421464 })
422- f .history [gist .Owner .Login ] = time .Now ()
465+ f .timeouts [gist .Owner .Login ] = time .Now (). Add ( time . Duration ( * minutesFlag * int ( math . Pow ( 3 , float64 ( msg . Tier )))) * time . Minute )
423466 fund = true
424467 }
425468 f .lock .Unlock ()
426469
427470 // Send an error if too frequent funding, othewise a success
428471 if ! fund {
429- websocket .JSON .Send (conn , map [string ]string {"error" : fmt .Sprintf ("User already funded %s ago " , common .PrettyDuration (elapsed ))})
472+ websocket .JSON .Send (conn , map [string ]string {"error" : fmt .Sprintf ("%s left until next allowance " , common .PrettyDuration (timeout . Sub ( time . Now ()) ))})
430473 continue
431474 }
432475 websocket .JSON .Send (conn , map [string ]string {"success" : fmt .Sprintf ("Funding request accepted for %s into %s" , gist .Owner .Login , address .Hex ())})
0 commit comments