prysm-pulse/tools/faucet/server.go
Preston Van Loon 5eb5f6afa9
Faucet enhancements + cluster private key management system (#1679)
* second pass at faucet, no rate limiting yet

* Add authentication support, step 1. This stuff needs to be refactored and tested

* move deposit input to keystore pkg, add proof of possession and withdrawal addr

* checkpoint on progress with cluster private key manager

* checkpoint w/ bootnode config

* checkpoint

* resolve todo

* encrypt the secrets

* add note about querying testnet

* workspace

* checkpoitn

* remove limits

* update

* checkpoint

* checkpoint

* remove jwt stuff

* fix build

* lint

* lint

* remove init

* remove jwt

* update

* checkpoint
2019-03-07 11:14:57 -05:00

135 lines
3.2 KiB
Go

package main
import (
"context"
"crypto/ecdsa"
"errors"
"fmt"
"log"
"math/big"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/params"
recaptcha "github.com/prestonvanloon/go-recaptcha"
faucetpb "github.com/prysmaticlabs/prysm/proto/faucet"
"google.golang.org/grpc/peer"
)
const minScore = 0.8
var fundingAmount = big.NewInt(0.5 * params.Ether)
type faucetServer struct {
r recaptcha.Recaptcha
client *ethclient.Client
funder common.Address
pk *ecdsa.PrivateKey
}
func newFaucetServer(
r recaptcha.Recaptcha,
rpcPath string,
funderPrivateKey string,
) *faucetServer {
client, err := ethclient.DialContext(context.Background(), rpcPath)
if err != nil {
panic(err)
}
pk, err := crypto.HexToECDSA(funderPrivateKey)
if err != nil {
panic(err)
}
funder := crypto.PubkeyToAddress(pk.PublicKey)
bal, err := client.BalanceAt(context.Background(), funder, nil)
if err != nil {
panic(err)
}
fmt.Printf("Funder is %s\n", funder.Hex())
fmt.Printf("Funder has %d\n", bal)
return &faucetServer{
r: r,
client: client,
funder: funder,
pk: pk,
}
}
func (s *faucetServer) verifyRecaptcha(ctx context.Context, req *faucetpb.FundingRequest) error {
p, ok := peer.FromContext(ctx)
if !ok {
return errors.New("peer from ctx not ok")
}
fmt.Printf("Sending captcha request for peer %s\n", p.Addr.String())
rr, err := s.r.Check(p.Addr.String(), req.RecaptchaResponse)
if err != nil {
return err
}
if !rr.Success {
fmt.Printf("Unsuccessful recaptcha request. Error codes: %+v\n", rr.ErrorCodes)
return errors.New("failed")
}
if rr.Score < minScore {
return errors.New("recaptcha score too low")
}
return nil
}
// RequestFunds from the ethereum 1.x faucet. Requires a valid captcha
// response.
func (s *faucetServer) RequestFunds(ctx context.Context, req *faucetpb.FundingRequest) (*faucetpb.FundingResponse, error) {
if err := s.verifyRecaptcha(ctx, req); err != nil {
return &faucetpb.FundingResponse{Error: fmt.Sprintf("Recaptcha failure: %v", err)}, nil
}
txHash, err := s.fundAndWait(common.HexToAddress(req.WalletAddress))
if err != nil {
return &faucetpb.FundingResponse{Error: fmt.Sprintf("Failed to send transaction %v", err)}, nil
}
return &faucetpb.FundingResponse{
Amount: fundingAmount.String(),
TransactionHash: txHash,
}, nil
}
func (s *faucetServer) fundAndWait(to common.Address) (string, error) {
nonce := uint64(0)
nonce, err := s.client.PendingNonceAt(context.Background(), s.funder)
if err != nil {
return "", err
}
tx := types.NewTransaction(nonce, to, fundingAmount, 40000, big.NewInt(1*params.GWei), nil /*data*/)
tx, err = types.SignTx(tx, types.NewEIP155Signer(big.NewInt(5)), s.pk)
if err != nil {
return "", err
}
if err := s.client.SendTransaction(context.Background(), tx); err != nil {
return "", err
}
// Wait for contract to mine
for pending := true; pending; _, pending, err = s.client.TransactionByHash(context.Background(), tx.Hash()) {
if err != nil {
log.Fatal(err)
}
time.Sleep(1 * time.Second)
}
return tx.Hash().Hex(), nil
}