|
|
|
@ -199,6 +199,8 @@ type faucet struct { |
|
|
|
|
|
|
|
|
|
keystore *keystore.KeyStore // Keystore containing the single signer
|
|
|
|
|
account accounts.Account // Account funding user faucet requests
|
|
|
|
|
head *types.Header // Current head header of the faucet
|
|
|
|
|
balance *big.Int // Current balance of the faucet
|
|
|
|
|
nonce uint64 // Current pending nonce of the faucet
|
|
|
|
|
price *big.Int // Current gas price to issue funds with
|
|
|
|
|
|
|
|
|
@ -324,33 +326,30 @@ func (f *faucet) apiHandler(conn *websocket.Conn) { |
|
|
|
|
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) |
|
|
|
|
} |
|
|
|
|
for head == nil || balance == nil { |
|
|
|
|
// Retrieve the current stats cached by the faucet
|
|
|
|
|
f.lock.RLock() |
|
|
|
|
if f.head != nil { |
|
|
|
|
head = types.CopyHeader(f.head) |
|
|
|
|
} |
|
|
|
|
cancel() |
|
|
|
|
if f.balance != nil { |
|
|
|
|
balance = new(big.Int).Set(f.balance) |
|
|
|
|
} |
|
|
|
|
nonce = f.nonce |
|
|
|
|
f.lock.RUnlock() |
|
|
|
|
|
|
|
|
|
// If stats retrieval failed, wait a bit and retry
|
|
|
|
|
if err != nil { |
|
|
|
|
if err = sendError(conn, errors.New("Faucet offline: "+err.Error())); err != nil { |
|
|
|
|
if head == nil || balance == nil { |
|
|
|
|
// Report the faucet offline until initial stats are ready
|
|
|
|
|
if err = sendError(conn, errors.New("Faucet offline")); 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": new(big.Int).Div(balance, ether), |
|
|
|
|
"funded": nonce, |
|
|
|
|
"peers": f.stack.Server().PeerCount(), |
|
|
|
|
"requests": f.reqs, |
|
|
|
@ -520,6 +519,47 @@ func (f *faucet) apiHandler(conn *websocket.Conn) { |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// refresh attempts to retrieve the latest header from the chain and extract the
|
|
|
|
|
// associated faucet balance and nonce for connectivity caching.
|
|
|
|
|
func (f *faucet) refresh(head *types.Header) error { |
|
|
|
|
// Ensure a state update does not run for too long
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) |
|
|
|
|
defer cancel() |
|
|
|
|
|
|
|
|
|
// If no header was specified, use the current chain head
|
|
|
|
|
var err error |
|
|
|
|
if head == nil { |
|
|
|
|
if head, err = f.client.HeaderByNumber(ctx, nil); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
// Retrieve the balance, nonce and gas price from the current head
|
|
|
|
|
var ( |
|
|
|
|
balance *big.Int |
|
|
|
|
nonce uint64 |
|
|
|
|
price *big.Int |
|
|
|
|
) |
|
|
|
|
if balance, err = f.client.BalanceAt(ctx, f.account.Address, head.Number); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if nonce, err = f.client.NonceAt(ctx, f.account.Address, head.Number); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
if price, err = f.client.SuggestGasPrice(ctx); err != nil { |
|
|
|
|
return err |
|
|
|
|
} |
|
|
|
|
// Everything succeeded, update the cached stats and eject old requests
|
|
|
|
|
f.lock.Lock() |
|
|
|
|
f.head, f.balance = head, balance |
|
|
|
|
f.price, f.nonce = price, nonce |
|
|
|
|
for len(f.reqs) > 0 && f.reqs[0].Tx.Nonce() < f.nonce { |
|
|
|
|
f.reqs = f.reqs[1:] |
|
|
|
|
} |
|
|
|
|
f.lock.Unlock() |
|
|
|
|
|
|
|
|
|
return nil |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// loop keeps waiting for interesting events and pushes them out to connected
|
|
|
|
|
// websockets.
|
|
|
|
|
func (f *faucet) loop() { |
|
|
|
@ -537,45 +577,27 @@ func (f *faucet) loop() { |
|
|
|
|
go func() { |
|
|
|
|
for head := range update { |
|
|
|
|
// New chain head arrived, query the current stats and stream to clients
|
|
|
|
|
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) |
|
|
|
|
} |
|
|
|
|
timestamp := time.Unix(head.Time.Int64(), 0) |
|
|
|
|
if time.Since(timestamp) > time.Hour { |
|
|
|
|
log.Warn("Skipping faucet refresh, head too old", "number", head.Number, "hash", head.Hash(), "age", common.PrettyAge(timestamp)) |
|
|
|
|
continue |
|
|
|
|
} |
|
|
|
|
cancel() |
|
|
|
|
|
|
|
|
|
// If querying the data failed, try for the next block
|
|
|
|
|
if err != nil { |
|
|
|
|
if err := f.refresh(head); 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.RLock() |
|
|
|
|
log.Info("Updated faucet state", "number", head.Number, "hash", head.Hash(), "age", common.PrettyAge(timestamp), "balance", f.balance, "nonce", f.nonce, "price", f.price) |
|
|
|
|
|
|
|
|
|
f.lock.Lock() |
|
|
|
|
f.price, f.nonce = price, nonce |
|
|
|
|
for len(f.reqs) > 0 && f.reqs[0].Tx.Nonce() < f.nonce { |
|
|
|
|
f.reqs = f.reqs[1:] |
|
|
|
|
} |
|
|
|
|
f.lock.Unlock() |
|
|
|
|
balance := new(big.Int).Div(f.balance, ether) |
|
|
|
|
peers := f.stack.Server().PeerCount() |
|
|
|
|
|
|
|
|
|
f.lock.RLock() |
|
|
|
|
for _, conn := range f.conns { |
|
|
|
|
if err := send(conn, map[string]interface{}{ |
|
|
|
|
"funds": balance, |
|
|
|
|
"funded": f.nonce, |
|
|
|
|
"peers": f.stack.Server().PeerCount(), |
|
|
|
|
"peers": peers, |
|
|
|
|
"requests": f.reqs, |
|
|
|
|
}, time.Second); err != nil { |
|
|
|
|
log.Warn("Failed to send stats to client", "err", err) |
|
|
|
|