cmd/faucet: proper error handling all over
This commit is contained in:
parent
0bb194c956
commit
7f7abfe4d1
@ -302,6 +302,8 @@ func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
// apiHandler handles requests for Ether grants and transaction statuses.
|
// apiHandler handles requests for Ether grants and transaction statuses.
|
||||||
func (f *faucet) apiHandler(conn *websocket.Conn) {
|
func (f *faucet) apiHandler(conn *websocket.Conn) {
|
||||||
// Start tracking the connection and drop at the end
|
// Start tracking the connection and drop at the end
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
f.conns = append(f.conns, conn)
|
f.conns = append(f.conns, conn)
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
@ -316,25 +318,50 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
|||||||
}
|
}
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
}()
|
}()
|
||||||
// Send a few initial stats to the client
|
// Gather the initial stats from the network to report
|
||||||
balance, _ := f.client.BalanceAt(context.Background(), f.account.Address, nil)
|
var (
|
||||||
nonce, _ := f.client.NonceAt(context.Background(), f.account.Address, nil)
|
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),
|
"funds": balance.Div(balance, ether),
|
||||||
"funded": nonce,
|
"funded": nonce,
|
||||||
"peers": f.stack.Server().PeerCount(),
|
"peers": f.stack.Server().PeerCount(),
|
||||||
"requests": f.reqs,
|
"requests": f.reqs,
|
||||||
})
|
}, 3*time.Second); err != nil {
|
||||||
// Send the initial block to the client
|
log.Warn("Failed to send initial stats to client", "err", err)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
return
|
||||||
header, err := f.client.HeaderByNumber(ctx, nil)
|
}
|
||||||
cancel()
|
if err = send(conn, head, 3*time.Second); err != nil {
|
||||||
|
log.Warn("Failed to send initial header to client", "err", err)
|
||||||
if err != nil {
|
return
|
||||||
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 {
|
||||||
@ -344,16 +371,22 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
|||||||
Tier uint `json:"tier"`
|
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 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !strings.HasPrefix(msg.URL, "https://gist.github.com/") && !strings.HasPrefix(msg.URL, "https://twitter.com/") &&
|
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/") {
|
!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
|
continue
|
||||||
}
|
}
|
||||||
if msg.Tier >= uint(*tiersFlag) {
|
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
|
continue
|
||||||
}
|
}
|
||||||
log.Info("Faucet funds requested", "url", msg.URL, "tier", msg.Tier)
|
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)
|
res, err := http.PostForm("https://www.google.com/recaptcha/api/siteverify", form)
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
var result struct {
|
var result struct {
|
||||||
@ -376,12 +412,18 @@ func (f *faucet) apiHandler(conn *websocket.Conn) {
|
|||||||
err = json.NewDecoder(res.Body).Decode(&result)
|
err = json.NewDecoder(res.Body).Decode(&result)
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
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-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
|
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")
|
err = errors.New("Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues")
|
||||||
}
|
}
|
||||||
if err != nil {
|
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
|
continue
|
||||||
}
|
}
|
||||||
log.Info("Faucet request valid", "url", msg.URL, "tier", msg.Tier, "user", username, "address", address)
|
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)
|
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()})
|
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
|
if err = sendError(conn, err); err != nil {
|
||||||
|
log.Warn("Failed to send transaction creation error to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
// Submit the transaction and mark as funded if successful
|
// Submit the transaction and mark as funded if successful
|
||||||
if err := f.client.SendTransaction(context.Background(), signed); err != nil {
|
if err := f.client.SendTransaction(context.Background(), signed); err != nil {
|
||||||
websocket.JSON.Send(conn, map[string]string{"error": err.Error()})
|
|
||||||
f.lock.Unlock()
|
f.lock.Unlock()
|
||||||
|
if err = sendError(conn, err); err != nil {
|
||||||
|
log.Warn("Failed to send transaction transmission error to client", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
f.reqs = append(f.reqs, &request{
|
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
|
// Send an error if too frequent funding, othewise a success
|
||||||
if !fund {
|
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
|
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 {
|
select {
|
||||||
case f.update <- struct{}{}:
|
case f.update <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
@ -473,11 +530,31 @@ func (f *faucet) loop() {
|
|||||||
select {
|
select {
|
||||||
case head := <-heads:
|
case head := <-heads:
|
||||||
// New chain head arrived, query the current stats and stream to clients
|
// New chain head arrived, query the current stats and stream to clients
|
||||||
balance, _ := f.client.BalanceAt(context.Background(), f.account.Address, nil)
|
var (
|
||||||
balance = new(big.Int).Div(balance, ether)
|
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())
|
// If querying the data failed, try for the next block
|
||||||
nonce, _ := f.client.NonceAt(context.Background(), f.account.Address, nil)
|
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.lock.Lock()
|
||||||
f.price, f.nonce = price, nonce
|
f.price, f.nonce = price, nonce
|
||||||
@ -488,17 +565,17 @@ func (f *faucet) loop() {
|
|||||||
|
|
||||||
f.lock.RLock()
|
f.lock.RLock()
|
||||||
for _, conn := range f.conns {
|
for _, conn := range f.conns {
|
||||||
if err := websocket.JSON.Send(conn, map[string]interface{}{
|
if err := send(conn, map[string]interface{}{
|
||||||
"funds": balance,
|
"funds": balance,
|
||||||
"funded": f.nonce,
|
"funded": f.nonce,
|
||||||
"peers": f.stack.Server().PeerCount(),
|
"peers": f.stack.Server().PeerCount(),
|
||||||
"requests": f.reqs,
|
"requests": f.reqs,
|
||||||
}); err != nil {
|
}, time.Second); err != nil {
|
||||||
log.Warn("Failed to send stats to client", "err", err)
|
log.Warn("Failed to send stats to client", "err", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
continue
|
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)
|
log.Warn("Failed to send header to client", "err", err)
|
||||||
conn.Close()
|
conn.Close()
|
||||||
}
|
}
|
||||||
@ -509,7 +586,7 @@ func (f *faucet) loop() {
|
|||||||
// Pending requests updated, stream to clients
|
// Pending requests updated, stream to clients
|
||||||
f.lock.RLock()
|
f.lock.RLock()
|
||||||
for _, conn := range f.conns {
|
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)
|
log.Warn("Failed to send requests to client", "err", err)
|
||||||
conn.Close()
|
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
|
// authGitHub tries to authenticate a faucet request using GitHub gists, returning
|
||||||
// the username, avatar URL and Ethereum address to fund on success.
|
// the username, avatar URL and Ethereum address to fund on success.
|
||||||
func authGitHub(url string) (string, string, common.Address, error) {
|
func authGitHub(url string) (string, string, common.Address, error) {
|
||||||
|
@ -140,10 +140,10 @@
|
|||||||
$("#block").text(parseInt(msg.number, 16));
|
$("#block").text(parseInt(msg.number, 16));
|
||||||
}
|
}
|
||||||
if (msg.error !== undefined) {
|
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) {
|
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) {
|
if (msg.requests !== undefined && msg.requests !== null) {
|
||||||
var content = "";
|
var content = "";
|
||||||
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user