lotus/cmd/lotus-fountain/main.go

437 lines
11 KiB
Go
Raw Normal View History

2019-09-20 21:27:40 +00:00
package main
import (
"bytes"
2019-09-20 21:27:40 +00:00
"context"
"fmt"
"html/template"
"io"
"io/ioutil"
"net"
2019-09-20 21:27:40 +00:00
"net/http"
"os"
"sort"
2019-10-29 18:28:54 +00:00
"strconv"
"time"
2019-09-20 21:27:40 +00:00
2019-10-13 07:33:25 +00:00
rice "github.com/GeertJohan/go.rice"
"github.com/ipfs/go-cid"
logging "github.com/ipfs/go-log/v2"
"github.com/libp2p/go-libp2p-core/peer"
"github.com/urfave/cli/v2"
2020-06-05 22:59:01 +00:00
"golang.org/x/xerrors"
2019-09-20 21:27:40 +00:00
"github.com/filecoin-project/lotus/extern/sector-storage/ffiwrapper"
2020-04-29 18:06:05 +00:00
"github.com/filecoin-project/specs-actors/actors/abi"
2020-08-13 12:04:45 +00:00
"github.com/filecoin-project/specs-actors/actors/abi/big"
2020-04-29 18:06:05 +00:00
"github.com/filecoin-project/specs-actors/actors/builtin"
"github.com/filecoin-project/specs-actors/actors/builtin/power"
"github.com/filecoin-project/go-address"
"github.com/filecoin-project/lotus/api"
"github.com/filecoin-project/lotus/build"
2019-10-17 08:12:53 +00:00
"github.com/filecoin-project/lotus/chain/actors"
"github.com/filecoin-project/lotus/chain/types"
lcli "github.com/filecoin-project/lotus/cli"
"github.com/filecoin-project/specs-actors/actors/builtin/miner"
2019-09-20 21:27:40 +00:00
)
var log = logging.Logger("main")
var supportedSectors struct {
SectorSizes []struct {
Name string
Value uint64
Default bool
}
}
func init() {
for supportedSector := range miner.SupportedProofTypes {
sectorSize, err := supportedSector.SectorSize()
if err != nil {
panic(err)
}
supportedSectors.SectorSizes = append(supportedSectors.SectorSizes, struct {
Name string
Value uint64
Default bool
}{
Name: sectorSize.ShortString(),
Value: uint64(sectorSize),
Default: false,
})
}
sort.Slice(supportedSectors.SectorSizes[:], func(i, j int) bool {
return supportedSectors.SectorSizes[i].Value < supportedSectors.SectorSizes[j].Value
})
supportedSectors.SectorSizes[0].Default = true
}
2019-09-20 21:27:40 +00:00
func main() {
logging.SetLogLevel("*", "INFO")
log.Info("Starting fountain")
local := []*cli.Command{
runCmd,
}
app := &cli.App{
Name: "lotus-fountain",
Usage: "Devnet token distribution utility",
2020-06-01 18:43:51 +00:00
Version: build.UserVersion(),
2019-09-20 21:27:40 +00:00
Flags: []cli.Flag{
&cli.StringFlag{
Name: "repo",
EnvVars: []string{"LOTUS_PATH"},
Value: "~/.lotus", // TODO: Consider XDG_DATA_HOME
},
},
Commands: local,
}
if err := app.Run(os.Args); err != nil {
log.Warn(err)
return
}
}
var runCmd = &cli.Command{
Name: "run",
Usage: "Start lotus fountain",
Flags: []cli.Flag{
&cli.StringFlag{
Name: "front",
Value: "127.0.0.1:7777",
},
&cli.StringFlag{
Name: "from",
},
&cli.StringFlag{
Name: "amount",
EnvVars: []string{"LOTUS_FOUNTAIN_AMOUNT"},
Value: "50",
},
2019-09-20 21:27:40 +00:00
},
Action: func(cctx *cli.Context) error {
sendPerRequest, err := types.ParseFIL(cctx.String("amount"))
if err != nil {
return err
}
2019-10-04 22:43:04 +00:00
nodeApi, closer, err := lcli.GetFullNodeAPI(cctx)
2019-09-20 21:27:40 +00:00
if err != nil {
return err
}
2019-10-04 16:02:25 +00:00
defer closer()
2019-09-20 21:27:40 +00:00
ctx := lcli.ReqContext(cctx)
v, err := nodeApi.Version(ctx)
if err != nil {
return err
}
log.Info("Remote version: %s", v.Version)
from, err := address.NewFromString(cctx.String("from"))
if err != nil {
return xerrors.Errorf("parsing source address (provide correct --from flag!): %w", err)
}
defaultMinerPeer, err := peer.Decode("12D3KooWJpBNhwgvoZ15EB1JwRTRpxgM9D2fwq6eEktrJJG74aP6")
if err != nil {
return err
}
2019-09-20 21:27:40 +00:00
h := &handler{
ctx: ctx,
api: nodeApi,
from: from,
sendPerRequest: sendPerRequest,
limiter: NewLimiter(LimiterConfig{
2020-07-29 01:22:29 +00:00
TotalRate: 500 * time.Millisecond,
TotalBurst: build.BlockMessageLimit,
IPRate: 10 * time.Minute,
IPBurst: 5,
2019-10-17 14:28:03 +00:00
WalletRate: 15 * time.Minute,
WalletBurst: 2,
}),
minerLimiter: NewLimiter(LimiterConfig{
2020-07-29 01:22:29 +00:00
TotalRate: 500 * time.Millisecond,
TotalBurst: build.BlockMessageLimit,
2019-10-17 14:28:03 +00:00
IPRate: 10 * time.Minute,
IPBurst: 2,
2019-10-17 14:28:03 +00:00
WalletRate: 1 * time.Hour,
WalletBurst: 2,
}),
defaultMinerPeer: defaultMinerPeer,
2019-09-20 21:27:40 +00:00
}
2019-10-13 07:33:25 +00:00
http.Handle("/", http.FileServer(rice.MustFindBox("site").HTTPBox()))
http.HandleFunc("/miner.html", h.minerhtml)
2019-09-20 21:27:40 +00:00
http.HandleFunc("/send", h.send)
2019-10-17 00:43:38 +00:00
http.HandleFunc("/mkminer", h.mkminer)
http.HandleFunc("/msgwait", h.msgwait)
http.HandleFunc("/msgwaitaddr", h.msgwaitaddr)
2019-09-20 21:27:40 +00:00
fmt.Printf("Open http://%s\n", cctx.String("front"))
go func() {
<-ctx.Done()
os.Exit(0)
}()
return http.ListenAndServe(cctx.String("front"), nil)
},
}
type handler struct {
ctx context.Context
api api.FullNode
from address.Address
sendPerRequest types.FIL
limiter *Limiter
minerLimiter *Limiter
defaultMinerPeer peer.ID
2019-09-20 21:27:40 +00:00
}
func (h *handler) minerhtml(w http.ResponseWriter, r *http.Request) {
f, err := rice.MustFindBox("site").Open("_miner.html")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl, err := ioutil.ReadAll(f)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
var executedTmpl bytes.Buffer
t, err := template.New("miner.html").Parse(string(tmpl))
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
if err := t.Execute(&executedTmpl, supportedSectors); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
if _, err := io.Copy(w, &executedTmpl); err != nil {
log.Errorf("failed to write template to string %s", err)
}
return
}
2019-09-20 21:27:40 +00:00
func (h *handler) send(w http.ResponseWriter, r *http.Request) {
to, err := address.NewFromString(r.FormValue("address"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
// Limit based on wallet address
limiter := h.limiter.GetWalletLimiter(to.String())
if !limiter.Allow() {
http.Error(w, http.StatusText(http.StatusTooManyRequests)+": wallet limit", http.StatusTooManyRequests)
return
}
// Limit based on IP
reqIP := r.Header.Get("X-Real-IP")
if reqIP == "" {
h, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
log.Errorf("could not get ip from: %s, err: %s", r.RemoteAddr, err)
}
reqIP = h
}
if i := net.ParseIP(reqIP); i != nil && i.IsLoopback() {
log.Errorf("rate limiting localhost: %s", reqIP)
}
limiter = h.limiter.GetIPLimiter(reqIP)
if !limiter.Allow() {
http.Error(w, http.StatusText(http.StatusTooManyRequests)+": IP limit", http.StatusTooManyRequests)
2019-09-20 21:27:40 +00:00
return
}
// General limiter to allow throttling all messages that can make it into the mpool
if !h.limiter.Allow() {
http.Error(w, http.StatusText(http.StatusTooManyRequests)+": global limit", http.StatusTooManyRequests)
return
}
2019-09-20 21:27:40 +00:00
smsg, err := h.api.MpoolPushMessage(h.ctx, &types.Message{
Value: types.BigInt(h.sendPerRequest),
2019-09-20 21:27:40 +00:00
From: h.from,
To: to,
}, nil)
2019-09-20 21:27:40 +00:00
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
2019-09-20 21:27:40 +00:00
return
}
_, _ = w.Write([]byte(smsg.Cid().String()))
2019-09-20 21:27:40 +00:00
}
2019-10-13 07:33:25 +00:00
2019-10-17 00:43:38 +00:00
func (h *handler) mkminer(w http.ResponseWriter, r *http.Request) {
2019-10-17 08:12:53 +00:00
owner, err := address.NewFromString(r.FormValue("address"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
2019-10-17 08:12:53 +00:00
return
}
if owner.Protocol() != address.BLS {
http.Error(w,
"Miner address must use BLS. A BLS address starts with the prefix 't3'."+
"Please create a BLS address by running \"lotus wallet new bls\" while connected to a Lotus node.",
http.StatusBadRequest)
2019-10-17 08:12:53 +00:00
return
}
ssize, err := strconv.ParseInt(r.FormValue("sectorSize"), 10, 64)
if err != nil {
return
}
log.Infof("%s: create actor start", owner)
2019-10-17 08:12:53 +00:00
// Limit based on wallet address
limiter := h.minerLimiter.GetWalletLimiter(owner.String())
if !limiter.Allow() {
http.Error(w, http.StatusText(http.StatusTooManyRequests)+": wallet limit", http.StatusTooManyRequests)
return
}
// Limit based on IP
2020-04-09 17:02:59 +00:00
reqIP := r.Header.Get("X-Real-IP")
if reqIP == "" {
h, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
log.Errorf("could not get ip from: %s, err: %s", r.RemoteAddr, err)
}
reqIP = h
}
limiter = h.minerLimiter.GetIPLimiter(reqIP)
2019-10-17 08:12:53 +00:00
if !limiter.Allow() {
http.Error(w, http.StatusText(http.StatusTooManyRequests)+": IP limit", http.StatusTooManyRequests)
2019-10-17 08:12:53 +00:00
return
}
// General limiter owner allow throttling all messages that can make it into the mpool
if !h.minerLimiter.Allow() {
http.Error(w, http.StatusText(http.StatusTooManyRequests)+": global limit", http.StatusTooManyRequests)
return
}
2019-10-17 08:12:53 +00:00
smsg, err := h.api.MpoolPushMessage(h.ctx, &types.Message{
Value: types.BigInt(h.sendPerRequest),
2019-10-17 08:12:53 +00:00
From: h.from,
To: owner,
}, nil)
2019-10-17 08:12:53 +00:00
if err != nil {
http.Error(w, "pushfunds: "+err.Error(), http.StatusBadRequest)
2019-10-17 08:12:53 +00:00
return
}
log.Infof("%s: push funds %s", owner, smsg.Cid())
2019-10-17 08:12:53 +00:00
2020-04-29 18:06:05 +00:00
spt, err := ffiwrapper.SealProofTypeFromSectorSize(abi.SectorSize(ssize))
if err != nil {
http.Error(w, "sealprooftype: "+err.Error(), http.StatusBadRequest)
2020-04-29 18:06:05 +00:00
return
}
2020-02-25 21:09:22 +00:00
params, err := actors.SerializeParams(&power.CreateMinerParams{
2020-04-29 18:06:05 +00:00
Owner: owner,
Worker: owner,
SealProofType: spt,
2020-06-05 20:06:11 +00:00
Peer: abi.PeerID(h.defaultMinerPeer),
2019-10-17 08:12:53 +00:00
})
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
2019-10-17 08:12:53 +00:00
return
}
createStorageMinerMsg := &types.Message{
2020-02-25 21:09:22 +00:00
To: builtin.StoragePowerActorAddr,
2019-10-17 08:12:53 +00:00
From: h.from,
2020-08-13 12:04:45 +00:00
Value: big.Zero(),
2019-10-17 08:12:53 +00:00
2020-02-25 21:09:22 +00:00
Method: builtin.MethodsPower.CreateMiner,
2019-10-17 08:12:53 +00:00
Params: params,
}
signed, err := h.api.MpoolPushMessage(r.Context(), createStorageMinerMsg, nil)
2019-10-17 08:12:53 +00:00
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
2019-10-17 08:12:53 +00:00
return
}
log.Infof("%s: create miner msg: %s", owner, signed.Cid())
http.Redirect(w, r, fmt.Sprintf("/wait.html?f=%s&m=%s&o=%s", signed.Cid(), smsg.Cid(), owner), http.StatusSeeOther)
}
2019-10-17 08:12:53 +00:00
func (h *handler) msgwait(w http.ResponseWriter, r *http.Request) {
c, err := cid.Parse(r.FormValue("cid"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
2020-06-03 21:42:06 +00:00
mw, err := h.api.StateWaitMsg(r.Context(), c, build.MessageConfidence)
2019-10-17 08:12:53 +00:00
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
2019-10-17 08:12:53 +00:00
return
}
if mw.Receipt.ExitCode != 0 {
http.Error(w, err.Error(), http.StatusBadRequest)
2019-10-17 08:12:53 +00:00
return
}
w.WriteHeader(http.StatusOK)
}
2019-10-17 08:12:53 +00:00
func (h *handler) msgwaitaddr(w http.ResponseWriter, r *http.Request) {
c, err := cid.Parse(r.FormValue("cid"))
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
2020-06-03 21:42:06 +00:00
mw, err := h.api.StateWaitMsg(r.Context(), c, build.MessageConfidence)
2019-10-17 08:12:53 +00:00
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
2019-10-17 08:12:53 +00:00
return
}
if mw.Receipt.ExitCode != 0 {
http.Error(w, xerrors.Errorf("create miner failed: exit code %d", mw.Receipt.ExitCode).Error(), http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
var ma power.CreateMinerReturn
if err := ma.UnmarshalCBOR(bytes.NewReader(mw.Receipt.Return)); err != nil {
log.Errorf("%w", err)
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
fmt.Fprintf(w, "{\"addr\": \"%s\"}", ma.IDAddress)
2019-10-13 07:33:25 +00:00
}