@@ -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,47 @@ 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+ }
117+ if period == 1 {
118+ periods [i ] = strings .TrimSuffix (periods [i ], "s" )
119+ }
120+ }
92121 // Load up and render the faucet website
93122 tmpl , err := Asset ("faucet.html" )
94123 if err != nil {
95124 log .Crit ("Failed to load the faucet template" , "err" , err )
96125 }
97- period := fmt .Sprintf ("%d minute(s)" , * minutesFlag )
98- if * minutesFlag % 60 == 0 {
99- period = fmt .Sprintf ("%d hour(s)" , * minutesFlag / 60 )
100- }
101126 website := new (bytes.Buffer )
102- template .Must (template .New ("" ).Parse (string (tmpl ))).Execute (website , map [string ]interface {}{
127+ err = template .Must (template .New ("" ).Parse (string (tmpl ))).Execute (website , map [string ]interface {}{
103128 "Network" : * netnameFlag ,
104- "Amount " : * payoutFlag ,
105- "Period " : period ,
129+ "Amounts " : amounts ,
130+ "Periods " : periods ,
106131 "Recaptcha" : * captchaToken ,
107132 })
133+ if err != nil {
134+ log .Crit ("Failed to render the faucet template" , "err" , err )
135+ }
108136 // Load and parse the genesis block requested by the user
109137 blob , err := ioutil .ReadFile (* genesisFlag )
110138 if err != nil {
@@ -171,10 +199,10 @@ type faucet struct {
171199 nonce uint64 // Current pending nonce of the faucet
172200 price * big.Int // Current gas price to issue funds with
173201
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
202+ conns []* websocket.Conn // Currently live websocket connections
203+ timeouts map [string ]time.Time // History of users and their funding timeouts
204+ reqs []* request // Currently pending funding requests
205+ update chan struct {} // Channel to signal request updates
178206
179207 lock sync.RWMutex // Lock protecting the faucet's internals
180208}
@@ -241,7 +269,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u
241269 index : index ,
242270 keystore : ks ,
243271 account : ks .Accounts ()[0 ],
244- history : make (map [string ]time.Time ),
272+ timeouts : make (map [string ]time.Time ),
245273 update : make (chan struct {}, 1 ),
246274 }, nil
247275}
@@ -295,14 +323,22 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
295323 "peers" : f .stack .Server ().PeerCount (),
296324 "requests" : f .reqs ,
297325 })
298- header , _ := f .client .HeaderByNumber (context .Background (), nil )
299- websocket .JSON .Send (conn , header )
326+ // Send the initial block to the client
327+ ctx , cancel := context .WithTimeout (context .Background (), time .Second )
328+ header , err := f .client .HeaderByNumber (ctx , nil )
329+ cancel ()
300330
331+ if err != nil {
332+ log .Error ("Failed to retrieve latest header" , "err" , err )
333+ } else {
334+ websocket .JSON .Send (conn , header )
335+ }
301336 // Keep reading requests from the websocket until the connection breaks
302337 for {
303338 // Fetch the next funding request and validate against github
304339 var msg struct {
305340 URL string `json:"url"`
341+ Tier uint `json:"tier"`
306342 Captcha string `json:"captcha"`
307343 }
308344 if err := websocket .JSON .Receive (conn , & msg ); err != nil {
@@ -312,7 +348,11 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
312348 websocket .JSON .Send (conn , map [string ]string {"error" : "URL doesn't link to GitHub Gists" })
313349 continue
314350 }
315- log .Info ("Faucet funds requested" , "gist" , msg .URL )
351+ if msg .Tier >= uint (* tiersFlag ) {
352+ websocket .JSON .Send (conn , map [string ]string {"error" : "Invalid funding tier requested" })
353+ continue
354+ }
355+ log .Info ("Faucet funds requested" , "gist" , msg .URL , "tier" , msg .Tier )
316356
317357 // If captcha verifications are enabled, make sure we're not dealing with a robot
318358 if * captchaToken != "" {
@@ -337,7 +377,7 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
337377 }
338378 if ! result .Success {
339379 log .Warn ("Captcha verification failed" , "err" , string (result .Errors ))
340- websocket .JSON .Send (conn , map [string ]string {"error" : "Beep-boop , you're a robot!" })
380+ websocket .JSON .Send (conn , map [string ]string {"error" : "Beep-bop , you're a robot!" })
341381 continue
342382 }
343383 }
@@ -396,11 +436,15 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
396436 f .lock .Lock ()
397437 var (
398438 fund bool
399- elapsed time.Duration
439+ timeout time.Time
400440 )
401- if elapsed = time . Since ( f . history [gist .Owner .Login ]); elapsed > time .Duration ( * minutesFlag ) * time . Minute {
441+ if timeout = f . timeouts [gist .Owner .Login ]; time .Now (). After ( timeout ) {
402442 // 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 )
443+ amount := new (big.Int ).Mul (big .NewInt (int64 (* payoutFlag )), ether )
444+ amount = new (big.Int ).Mul (amount , new (big.Int ).Exp (big .NewInt (5 ), big .NewInt (int64 (msg .Tier )), nil ))
445+ amount = new (big.Int ).Div (amount , new (big.Int ).Exp (big .NewInt (2 ), big .NewInt (int64 (msg .Tier )), nil ))
446+
447+ tx := types .NewTransaction (f .nonce + uint64 (len (f .reqs )), address , amount , big .NewInt (21000 ), f .price , nil )
404448 signed , err := f .keystore .SignTx (f .account , tx , f .config .ChainId )
405449 if err != nil {
406450 websocket .JSON .Send (conn , map [string ]string {"error" : err .Error ()})
@@ -419,14 +463,14 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
419463 Time : time .Now (),
420464 Tx : signed ,
421465 })
422- f .history [gist .Owner .Login ] = time .Now ()
466+ f .timeouts [gist .Owner .Login ] = time .Now (). Add ( time . Duration ( * minutesFlag * int ( math . Pow ( 3 , float64 ( msg . Tier )))) * time . Minute )
423467 fund = true
424468 }
425469 f .lock .Unlock ()
426470
427471 // Send an error if too frequent funding, othewise a success
428472 if ! fund {
429- websocket .JSON .Send (conn , map [string ]string {"error" : fmt .Sprintf ("User already funded %s ago " , common .PrettyDuration (elapsed ))})
473+ websocket .JSON .Send (conn , map [string ]string {"error" : fmt .Sprintf ("%s left until next allowance " , common .PrettyDuration (timeout . Sub ( time . Now ()) ))})
430474 continue
431475 }
432476 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