cmd/faucet: proper error handling all over

This commit is contained in:
Péter Szilágyi 2017-10-17 12:08:57 +03:00
parent 0bb194c956
commit 7f7abfe4d1
No known key found for this signature in database
GPG Key ID: E9AE538CEDF8293D
3 changed files with 135 additions and 36 deletions

@ -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) {

@ -140,10 +140,10 @@
$("#block").text(parseInt(msg.number, 16));
}
if (msg.error !== undefined) {
noty({layout: 'topCenter', text: msg.error, type: 'error'});
noty({layout: 'topCenter', text: msg.error, type: 'error', timeout: 5000, progressBar: true});
}
if (msg.success !== undefined) {
noty({layout: 'topCenter', text: msg.success, type: 'success'});
noty({layout: 'topCenter', text: msg.success, type: 'success', timeout: 15000, progressBar: true});
}
if (msg.requests !== undefined && msg.requests !== null) {
var content = "";

File diff suppressed because one or more lines are too long