/
faucet.go
131 lines (109 loc) · 3.65 KB
/
faucet.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
package relayer
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
bank "github.com/cosmos/cosmos-sdk/x/bank/types"
)
// SendMsgWithKey allows the user to specify which relayer key will sign the message
func (c *Chain) SendMsgWithKey(msg sdk.Msg, keyName string) (res *sdk.TxResponse, err error) {
c.Key = keyName
return c.SendMsg(msg)
}
// FaucetHandler listens for addresses
func (c *Chain) FaucetHandler(fromKey sdk.AccAddress, amounts sdk.Coins) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
c.Log("handling faucet request...")
byt, err := ioutil.ReadAll(r.Body)
if err != nil {
str := "Failed to read request body"
c.Error(fmt.Errorf("%s: %w", str, err))
respondWithError(w, http.StatusBadGateway, str)
return
}
var fr FaucetRequest
err = json.Unmarshal(byt, &fr)
switch {
case err != nil:
str := fmt.Sprintf("Failed to unmarshal request payload: %s", string(byt))
c.Log(str)
respondWithError(w, http.StatusBadRequest, str)
return
case fr.ChainID != c.ChainID:
str := fmt.Sprintf("Invalid chain id: exp(%s) got(%s)", c.ChainID, fr.ChainID)
c.Log(str)
respondWithError(w, http.StatusBadRequest, str)
return
}
if wait, err := c.checkAddress(fr.Address); err != nil {
c.Log(fmt.Sprintf("%s hit rate limit, needs to wait %s", fr.Address, wait.String()))
respondWithError(w, http.StatusTooManyRequests, err.Error())
return
}
c.UseSDKContext()
// defer done()
if err := c.faucetSend(fromKey, fr.addr(), amounts); err != nil {
c.Error(err)
respondWithError(w, http.StatusInternalServerError, err.Error())
return
}
c.Log(fmt.Sprintf("%s was sent %s successfully", fr.Address, amounts.String()))
respondWithJSON(w, http.StatusCreated, success{Address: fr.Address, Amount: amounts.String()})
}
}
func (c *Chain) faucetSend(fromAddr, toAddr sdk.AccAddress, amounts sdk.Coins) error {
// Set sdk config to use custom Bech32 account prefix
info, err := c.Keybase.KeyByAddress(fromAddr)
if err != nil {
return err
}
res, err := c.SendMsgWithKey(bank.NewMsgSend(fromAddr, toAddr, sdk.NewCoins(amounts...)), info.GetName())
if err != nil {
return fmt.Errorf("failed to send transaction: %w\n%s", err, res)
} else if res.Code != 0 {
return fmt.Errorf("transaction failed to execute\n%s", res)
}
return nil
}
func (c *Chain) checkAddress(addr string) (time.Duration, error) {
faucetTimeout := 5 * time.Minute
if val, ok := c.faucetAddrs[addr]; ok {
sinceLastRequest := time.Since(val)
if faucetTimeout > sinceLastRequest {
wait := faucetTimeout - sinceLastRequest
return wait, fmt.Errorf("%s has requested funds within the last %s, wait %s before trying again",
addr, faucetTimeout.String(), wait.String())
}
}
c.faucetAddrs[addr] = time.Now()
return 1 * time.Second, nil
}
func respondWithError(w http.ResponseWriter, code int, message string) {
respondWithJSON(w, code, map[string]string{"error": message})
}
func respondWithJSON(w http.ResponseWriter, code int, payload interface{}) {
response, _ := json.Marshal(payload)
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(code)
_, err := w.Write(response)
if err != nil {
fmt.Printf("error writing to the underlying response")
}
}
// FaucetRequest represents a request to the facuet
type FaucetRequest struct {
ChainID string `json:"chain-id"`
Address string `json:"address"`
}
func (fr FaucetRequest) addr() sdk.AccAddress {
addr, _ := sdk.AccAddressFromBech32(fr.Address)
return addr
}
type success struct {
Address string `json:"address"`
Amount string `json:"amount"`
}