aboutsummaryrefslogtreecommitdiffstats
path: root/cmd/faucet/faucet.go
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/faucet/faucet.go')
-rw-r--r--cmd/faucet/faucet.go165
1 files changed, 132 insertions, 33 deletions
diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go
index 6b2987315..715f3dec9 100644
--- a/cmd/faucet/faucet.go
+++ b/cmd/faucet/faucet.go
@@ -302,6 +302,8 @@ func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) {
// apiHandler handles requests for Ether grants and transaction statuses.
func (f *faucet) apiHandler(conn *websocket.Conn) {
// Start tracking the connection and drop at the end
+ defer conn.Close()
+
f.lock.Lock()
f.conns = append(f.conns, conn)
f.lock.Unlock()
@@ -316,25 +318,50 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
}
f.lock.Unlock()
}()
- // Send a few initial stats to the client
- balance, _ := f.client.BalanceAt(context.Background(), f.account.Address, nil)
- nonce, _ := f.client.NonceAt(context.Background(), f.account.Address, nil)
+ // Gather the initial stats from the network to report
+ var (
+ head *types.Header
+ balance *big.Int
+ nonce uint64
+ err error
+ )
+ for {
+ // Attempt to retrieve the stats, may error on no faucet connectivity
+ ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
+ head, err = f.client.HeaderByNumber(ctx, nil)
+ if err == nil {
+ balance, err = f.client.BalanceAt(ctx, f.account.Address, head.Number)
+ if err == nil {
+ nonce, err = f.client.NonceAt(ctx, f.account.Address, nil)
+ }
+ }
+ cancel()
- websocket.JSON.Send(conn, map[string]interface{}{
+ // If stats retrieval failed, wait a bit and retry
+ if err != nil {
+ if err = sendError(conn, errors.New("Faucet offline: "+err.Error())); err != nil {
+ log.Warn("Failed to send faucet error to client", "err", err)
+ return
+ }
+ time.Sleep(3 * time.Second)
+ continue
+ }
+ // Initial stats reported successfully, proceed with user interaction
+ break
+ }
+ // Send over the initial stats and the latest header
+ if err = send(conn, map[string]interface{}{
"funds": balance.Div(balance, ether),
"funded": nonce,
"peers": f.stack.Server().PeerCount(),
"requests": f.reqs,
- })
- // Send the initial block to the client
- 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)
+ }, 3*time.Second); err != nil {
+ log.Warn("Failed to send initial stats to client", "err", err)
+ return
+ }
+ if err = send(conn, head, 3*time.Second); err != nil {
+ log.Warn("Failed to send initial header to client", "err", err)
+ return
}
// Keep reading requests from the websocket until the connection breaks
for {
@@ -344,16 +371,22 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
Tier uint `json:"tier"`
Captcha string `json:"captcha"`
}
- if err := websocket.JSON.Receive(conn, &msg); err != nil {
+ if err = websocket.JSON.Receive(conn, &msg); err != nil {
return
}
if !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") &&
!strings.HasPrefix(msg.URL, "https://plus.google.com/") && !strings.HasPrefix(msg.URL, "https://www.facebook.com/") {
- websocket.JSON.Send(conn, map[string]string{"error": "URL doesn't link to supported services"})
+ if err = sendError(conn, errors.New("URL doesn't link to supported services")); err != nil {
+ log.Warn("Failed to send URL error to client", "err", err)
+ return
+ }
continue
}
if msg.Tier >= uint(*tiersFlag) {
- websocket.JSON.Send(conn, map[string]string{"error": "Invalid funding tier requested"})
+ if err = sendError(conn, errors.New("Invalid funding tier requested")); err != nil {
+ log.Warn("Failed to send tier error to client", "err", err)
+ return
+ }
continue
}
log.Info("Faucet funds requested", "url", msg.URL, "tier", msg.Tier)
@@ -366,7 +399,10 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form)
if err != nil {
- websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
+ if err = sendError(conn, err); err != nil {
+ log.Warn("Failed to send captcha post error to client", "err", err)
+ return
+ }
continue
}
var result struct {
@@ -376,12 +412,18 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
err = json.NewDecoder(res.Body).Decode(&result)
res.Body.Close()
if err != nil {
- websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
+ if err = sendError(conn, err); err != nil {
+ log.Warn("Failed to send captcha decode error to client", "err", err)
+ return
+ }
continue
}
if !result.Success {
log.Warn("Captcha verification failed", "err", string(result.Errors))
- websocket.JSON.Send(conn, map[string]string{"error": "Beep-bop, you're a robot!"})
+ if err = sendError(conn, errors.New("Beep-bop, you're a robot!")); err != nil {
+ log.Warn("Failed to send captcha failure to client", "err", err)
+ return
+ }
continue
}
}
@@ -404,7 +446,10 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues")
}
if err != nil {
- websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
+ if err = sendError(conn, err); err != nil {
+ log.Warn("Failed to send prefix error to client", "err", err)
+ return
+ }
continue
}
log.Info("Faucet request valid", "url", msg.URL, "tier", msg.Tier, "user", username, "address", address)
@@ -424,14 +469,20 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
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)
if err != nil {
- websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
f.lock.Unlock()
+ if err = sendError(conn, err); err != nil {
+ log.Warn("Failed to send transaction creation error to client", "err", err)
+ return
+ }
continue
}
// Submit the transaction and mark as funded if successful
if err := f.client.SendTransaction(context.Background(), signed); err != nil {
- websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
f.lock.Unlock()
+ if err = sendError(conn, err); err != nil {
+ log.Warn("Failed to send transaction transmission error to client", "err", err)
+ return
+ }
continue
}
f.reqs = append(f.reqs, &request{
@@ -447,10 +498,16 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
// Send an error if too frequent funding, othewise a success
if !fund {
- websocket.JSON.Send(conn, map[string]string{"error": fmt.Sprintf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))})
+ if err = sendError(conn, fmt.Errorf("%s left until next allowance", common.PrettyDuration(timeout.Sub(time.Now())))); err != nil {
+ log.Warn("Failed to send funding error to client", "err", err)
+ return
+ }
continue
}
- websocket.JSON.Send(conn, map[string]string{"success": fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())})
+ if err = sendSuccess(conn, fmt.Sprintf("Funding request accepted for %s into %s", username, address.Hex())); err != nil {
+ log.Warn("Failed to send funding success to client", "err", err)
+ return
+ }
select {
case f.update <- struct{}{}:
default:
@@ -473,11 +530,31 @@ func (f *faucet) loop() {
select {
case head := <-heads:
// New chain head arrived, query the current stats and stream to clients
- balance, _ := f.client.BalanceAt(context.Background(), f.account.Address, nil)
- balance = new(big.Int).Div(balance, ether)
+ var (
+ balance *big.Int
+ nonce uint64
+ price *big.Int
+ err error
+ )
+ ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+ balance, err = f.client.BalanceAt(ctx, f.account.Address, head.Number)
+ if err == nil {
+ nonce, err = f.client.NonceAt(ctx, f.account.Address, nil)
+ if err == nil {
+ price, err = f.client.SuggestGasPrice(ctx)
+ }
+ }
+ cancel()
- price, _ := f.client.SuggestGasPrice(context.Background())
- nonce, _ := f.client.NonceAt(context.Background(), f.account.Address, nil)
+ // If querying the data failed, try for the next block
+ if err != nil {
+ log.Warn("Failed to update faucet state", "block", head.Number, "hash", head.Hash(), "err", err)
+ continue
+ } else {
+ log.Info("Updated faucet state", "block", head.Number, "hash", head.Hash(), "balance", balance, "nonce", nonce, "price", price)
+ }
+ // Faucet state retrieved, update locally and send to clients
+ balance = new(big.Int).Div(balance, ether)
f.lock.Lock()
f.price, f.nonce = price, nonce
@@ -488,17 +565,17 @@ func (f *faucet) loop() {
f.lock.RLock()
for _, conn := range f.conns {
- if err := websocket.JSON.Send(conn, map[string]interface{}{
+ if err := send(conn, map[string]interface{}{
"funds": balance,
"funded": f.nonce,
"peers": f.stack.Server().PeerCount(),
"requests": f.reqs,
- }); err != nil {
+ }, time.Second); err != nil {
log.Warn("Failed to send stats to client", "err", err)
conn.Close()
continue
}
- if err := websocket.JSON.Send(conn, head); err != nil {
+ if err := send(conn, head, time.Second); err != nil {
log.Warn("Failed to send header to client", "err", err)
conn.Close()
}
@@ -509,7 +586,7 @@ func (f *faucet) loop() {
// Pending requests updated, stream to clients
f.lock.RLock()
for _, conn := range f.conns {
- if err := websocket.JSON.Send(conn, map[string]interface{}{"requests": f.reqs}); err != nil {
+ if err := send(conn, map[string]interface{}{"requests": f.reqs}, time.Second); err != nil {
log.Warn("Failed to send requests to client", "err", err)
conn.Close()
}
@@ -519,6 +596,28 @@ func (f *faucet) loop() {
}
}
+// sends transmits a data packet to the remote end of the websocket, but also
+// setting a write deadline to prevent waiting forever on the node.
+func send(conn *websocket.Conn, value interface{}, timeout time.Duration) error {
+ if timeout == 0 {
+ timeout = 60 * time.Second
+ }
+ conn.SetWriteDeadline(time.Now().Add(timeout))
+ return websocket.JSON.Send(conn, value)
+}
+
+// sendError transmits an error to the remote end of the websocket, also setting
+// the write deadline to 1 second to prevent waiting forever.
+func sendError(conn *websocket.Conn, err error) error {
+ return send(conn, map[string]string{"error": err.Error()}, time.Second)
+}
+
+// sendSuccess transmits a success message to the remote end of the websocket, also
+// setting the write deadline to 1 second to prevent waiting forever.
+func sendSuccess(conn *websocket.Conn, msg string) error {
+ return send(conn, map[string]string{"success": msg}, time.Second)
+}
+
// authGitHub tries to authenticate a faucet request using GitHub gists, returning
// the username, avatar URL and Ethereum address to fund on success.
func authGitHub(url string) (string, string, common.Address, error) {