Skip to content

Commit 8800def

Browse files
authored
Add faucet for Web3Q (ethereum#55)
1 parent 0381b48 commit 8800def

File tree

4 files changed

+37
-169
lines changed

4 files changed

+37
-169
lines changed

cmd/faucet/README.md

+8-12
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,27 @@
11
# Faucet
22

3-
The `faucet` is a simplistic web application with the goal of distributing small amounts of Ether in private and test networks.
3+
The `faucet` is a simplistic web application with the goal of distributing small amounts of W3Q in private and test networks.
44

5-
Users need to post their Ethereum addresses to fund in a Twitter status update or public Facebook post and share the link to the faucet. The faucet will in turn deduplicate user requests and send the Ether. After a funding round, the faucet prevents the same user requesting again for a pre-configured amount of time, proportional to the amount of Ether requested.
5+
Users need to post their Web3Q addresses to fund in a Twitter status update or public Facebook post and share the link to the faucet. The faucet will in turn deduplicate user requests and send the W3Q. After a funding round, the faucet prevents the same user requesting again for a pre-configured amount of time, proportional to the amount of W3Q requested.
66

77
## Operation
88

99
The `faucet` is a single binary app (everything included) with all configurations set via command line flags and a few files.
1010

11-
First thing's first, the `faucet` needs to connect to an Ethereum network, for which it needs the necessary genesis and network infos. Each of the following flags must be set:
11+
First thing's first, the `faucet` needs to connect to an Web3Q network, for which it needs the wsrpc to connect network. Each of the following flags must be set:
1212

13-
- `--genesis` is a path to a file containin the network `genesis.json`
14-
- `--network` is the devp2p network id used during connection
15-
- `--bootnodes` is a list of `enode://` ids to join the network through
16-
17-
The `faucet` will use the `les` protocol to join the configured Ethereum network and will store its data in `$HOME/.faucet` (currently not configurable).
13+
- `--wsrpc` is a websocket rpc URL for ethclient to get data and submit tx from network
1814

1915
## Funding
2016

21-
To be able to distribute funds, the `faucet` needs access to an already funded Ethereum account. This can be configured via:
17+
To be able to distribute funds, the `faucet` needs access to an already funded Web3Q account. This can be configured via:
2218

23-
- `--account.json` is a path to the Ethereum account's JSON key file
19+
- `--account.json` is a path to the Web3Q account's JSON key file
2420
- `--account.pass` is a path to a text file with the decryption passphrase
2521

26-
The faucet is able to distribute various amounts of Ether in exchange for various timeouts. These can be configured via:
22+
The faucet is able to distribute various amounts of W3Q in exchange for various timeouts. These can be configured via:
2723

28-
- `--faucet.amount` is the number of Ethers to send by default
24+
- `--faucet.amount` is the number of W3Qs to send by default
2925
- `--faucet.minutes` is the time to wait before allowing a rerequest
3026
- `--faucet.tiers` is the funding tiers to support (x3 time, x2.5 funds)
3127

cmd/faucet/faucet.go

+23-127
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,16 @@ import (
4343

4444
"github.com/ethereum/go-ethereum/accounts"
4545
"github.com/ethereum/go-ethereum/accounts/keystore"
46-
"github.com/ethereum/go-ethereum/cmd/utils"
4746
"github.com/ethereum/go-ethereum/common"
48-
"github.com/ethereum/go-ethereum/core"
4947
"github.com/ethereum/go-ethereum/core/types"
50-
"github.com/ethereum/go-ethereum/eth/downloader"
51-
"github.com/ethereum/go-ethereum/eth/ethconfig"
5248
"github.com/ethereum/go-ethereum/ethclient"
53-
"github.com/ethereum/go-ethereum/ethstats"
54-
"github.com/ethereum/go-ethereum/les"
5549
"github.com/ethereum/go-ethereum/log"
56-
"github.com/ethereum/go-ethereum/node"
57-
"github.com/ethereum/go-ethereum/p2p"
58-
"github.com/ethereum/go-ethereum/p2p/enode"
59-
"github.com/ethereum/go-ethereum/p2p/nat"
60-
"github.com/ethereum/go-ethereum/params"
6150
"github.com/gorilla/websocket"
6251
)
6352

6453
var (
65-
genesisFlag = flag.String("genesis", "", "Genesis json file to seed the chain with")
66-
apiPortFlag = flag.Int("apiport", 8080, "Listener port for the HTTP API connection")
67-
ethPortFlag = flag.Int("ethport", 30303, "Listener port for the devp2p connection")
68-
bootFlag = flag.String("bootnodes", "", "Comma separated bootnode enode URLs to seed with")
69-
netFlag = flag.Uint64("network", 0, "Network ID to use for the Ethereum protocol")
70-
statsFlag = flag.String("ethstats", "", "Ethstats network monitoring auth string")
54+
wsRpcFlag = flag.String("wsrpc", "", "websocket rpc URL for ethclient to get data and submit tx")
55+
apiPortFlag = flag.Int("apiport", 81, "Listener port for the HTTP API connection")
7156

7257
netnameFlag = flag.String("faucet.name", "", "Network name to assign to the faucet")
7358
payoutFlag = flag.Int("faucet.amount", 1, "Number of Ethers to pay out per user request")
@@ -85,20 +70,12 @@ var (
8570

8671
twitterTokenFlag = flag.String("twitter.token", "", "Bearer token to authenticate with the v2 Twitter API")
8772
twitterTokenV1Flag = flag.String("twitter.token.v1", "", "Bearer token to authenticate with the v1.1 Twitter API")
88-
89-
goerliFlag = flag.Bool("goerli", false, "Initializes the faucet with Görli network config")
90-
rinkebyFlag = flag.Bool("rinkeby", false, "Initializes the faucet with Rinkeby network config")
9173
)
9274

9375
var (
9476
ether = new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)
9577
)
9678

97-
var (
98-
gitCommit = "" // Git SHA1 commit hash of the release (set via linker flags)
99-
gitDate = "" // Git commit date YYYYMMDD of the release (set via linker flags)
100-
)
101-
10279
func main() {
10380
// Parse the flags and set up the logger to print everything requested
10481
flag.Parse()
@@ -110,7 +87,7 @@ func main() {
11087
for i := 0; i < *tiersFlag; i++ {
11188
// Calculate the amount for the next tier and format it
11289
amount := float64(*payoutFlag) * math.Pow(2.5, float64(i))
113-
amounts[i] = fmt.Sprintf("%s Ethers", strconv.FormatFloat(amount, 'f', -1, 64))
90+
amounts[i] = fmt.Sprintf("%s W3Qs", strconv.FormatFloat(amount, 'f', -1, 64))
11491
if amount == 1 {
11592
amounts[i] = strings.TrimSuffix(amounts[i], "s")
11693
}
@@ -146,20 +123,7 @@ func main() {
146123
if err != nil {
147124
log.Crit("Failed to render the faucet template", "err", err)
148125
}
149-
// Load and parse the genesis block requested by the user
150-
genesis, err := getGenesis(genesisFlag, *goerliFlag, *rinkebyFlag)
151-
if err != nil {
152-
log.Crit("Failed to parse genesis config", "err", err)
153-
}
154-
// Convert the bootnodes to internal enode representations
155-
var enodes []*enode.Node
156-
for _, boot := range strings.Split(*bootFlag, ",") {
157-
if url, err := enode.Parse(enode.ValidSchemes, boot); err == nil {
158-
enodes = append(enodes, url)
159-
} else {
160-
log.Error("Failed to parse bootnode URL", "url", boot, "err", err)
161-
}
162-
}
126+
163127
// Load up the account key and decrypt its password
164128
blob, err := ioutil.ReadFile(*accPassFlag)
165129
if err != nil {
@@ -179,11 +143,10 @@ func main() {
179143
log.Crit("Failed to unlock faucet signer account", "err", err)
180144
}
181145
// Assemble and start the faucet light service
182-
faucet, err := newFaucet(genesis, *ethPortFlag, enodes, *netFlag, *statsFlag, ks, website.Bytes())
146+
faucet, err := newFaucet(*wsRpcFlag, ks, website.Bytes())
183147
if err != nil {
184148
log.Crit("Failed to start faucet", "err", err)
185149
}
186-
defer faucet.close()
187150

188151
if err := faucet.listenAndServe(*apiPortFlag); err != nil {
189152
log.Crit("Failed to launch faucet API", "err", err)
@@ -200,16 +163,15 @@ type request struct {
200163

201164
// faucet represents a crypto faucet backed by an Ethereum light client.
202165
type faucet struct {
203-
config *params.ChainConfig // Chain configurations for signing
204-
stack *node.Node // Ethereum protocol stack
205-
client *ethclient.Client // Client connection to the Ethereum chain
206-
index []byte // Index page to serve up on the web
166+
client *ethclient.Client // Client connection to the Ethereum chain
167+
index []byte // Index page to serve up on the web
207168

208169
keystore *keystore.KeyStore // Keystore containing the single signer
209170
account accounts.Account // Account funding user faucet requests
210171
head *types.Header // Current head header of the faucet
211172
balance *big.Int // Current balance of the faucet
212173
nonce uint64 // Current pending nonce of the faucet
174+
chainId *big.Int // Current chainId use to generate faucet tx
213175
price *big.Int // Current gas price to issue funds with
214176

215177
conns []*wsConn // Currently live websocket connections
@@ -227,78 +189,27 @@ type wsConn struct {
227189
wlock sync.Mutex
228190
}
229191

230-
func newFaucet(genesis *core.Genesis, port int, enodes []*enode.Node, network uint64, stats string, ks *keystore.KeyStore, index []byte) (*faucet, error) {
231-
// Assemble the raw devp2p protocol stack
232-
stack, err := node.New(&node.Config{
233-
Name: "geth",
234-
Version: params.VersionWithCommit(gitCommit, gitDate),
235-
DataDir: filepath.Join(os.Getenv("HOME"), ".faucet"),
236-
P2P: p2p.Config{
237-
NAT: nat.Any(),
238-
NoDiscovery: true,
239-
DiscoveryV5: true,
240-
ListenAddr: fmt.Sprintf(":%d", port),
241-
MaxPeers: 25,
242-
BootstrapNodesV5: enodes,
243-
},
244-
})
245-
if err != nil {
246-
return nil, err
247-
}
248-
249-
// Assemble the Ethereum light client protocol
250-
cfg := ethconfig.Defaults
251-
cfg.SyncMode = downloader.LightSync
252-
cfg.NetworkId = network
253-
cfg.Genesis = genesis
254-
utils.SetDNSDiscoveryDefaults(&cfg, genesis.ToBlock(nil).Hash())
255-
256-
lesBackend, err := les.New(stack, &cfg)
192+
func newFaucet(rpcFlag string, ks *keystore.KeyStore, index []byte) (*faucet, error) {
193+
client, err := ethclient.Dial(rpcFlag)
257194
if err != nil {
258-
return nil, fmt.Errorf("Failed to register the Ethereum service: %w", err)
259-
}
260-
261-
// Assemble the ethstats monitoring and reporting service'
262-
if stats != "" {
263-
if err := ethstats.New(stack, lesBackend.ApiBackend, lesBackend.Engine(), stats); err != nil {
264-
return nil, err
265-
}
266-
}
267-
// Boot up the client and ensure it connects to bootnodes
268-
if err := stack.Start(); err != nil {
269195
return nil, err
270196
}
271-
for _, boot := range enodes {
272-
old, err := enode.Parse(enode.ValidSchemes, boot.String())
273-
if err == nil {
274-
stack.Server().AddPeer(old)
275-
}
276-
}
277-
// Attach to the client and retrieve and interesting metadatas
278-
api, err := stack.Attach()
197+
chainId, err := client.ChainID(context.Background())
279198
if err != nil {
280-
stack.Close()
281199
return nil, err
282200
}
283-
client := ethclient.NewClient(api)
284201

285202
return &faucet{
286-
config: genesis.Config,
287-
stack: stack,
288203
client: client,
289204
index: index,
290205
keystore: ks,
206+
chainId: chainId,
291207
account: ks.Accounts()[0],
292208
timeouts: make(map[string]time.Time),
293209
update: make(chan struct{}, 1),
294210
}, nil
295211
}
296212

297-
// close terminates the Ethereum connection and tears down the faucet.
298-
func (f *faucet) close() error {
299-
return f.stack.Close()
300-
}
301-
302213
// listenAndServe registers the HTTP handlers for the faucet and boots it up
303214
// for service user funding requests.
304215
func (f *faucet) listenAndServe(port int) error {
@@ -317,7 +228,11 @@ func (f *faucet) webHandler(w http.ResponseWriter, r *http.Request) {
317228

318229
// apiHandler handles requests for Ether grants and transaction statuses.
319230
func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) {
320-
upgrader := websocket.Upgrader{}
231+
upgrader := websocket.Upgrader{
232+
CheckOrigin: func(r *http.Request) bool {
233+
return true
234+
},
235+
}
321236
conn, err := upgrader.Upgrade(w, r, nil)
322237
if err != nil {
323238
return
@@ -376,7 +291,6 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) {
376291
if err = send(wsconn, map[string]interface{}{
377292
"funds": new(big.Int).Div(balance, ether),
378293
"funded": nonce,
379-
"peers": f.stack.Server().PeerCount(),
380294
"requests": reqs,
381295
}, 3*time.Second); err != nil {
382296
log.Warn("Failed to send initial stats to client", "err", err)
@@ -493,7 +407,7 @@ func (f *faucet) apiHandler(w http.ResponseWriter, r *http.Request) {
493407
amount = new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(2), big.NewInt(int64(msg.Tier)), nil))
494408

495409
tx := types.NewTransaction(f.nonce+uint64(len(f.reqs)), address, amount, 21000, f.price, nil)
496-
signed, err := f.keystore.SignTx(f.account, tx, f.config.ChainID)
410+
signed, err := f.keystore.SignTx(f.account, tx, f.chainId)
497411
if err != nil {
498412
f.lock.Unlock()
499413
if err = sendError(wsconn, err); err != nil {
@@ -616,13 +530,11 @@ func (f *faucet) loop() {
616530
log.Info("Updated faucet state", "number", head.Number, "hash", head.Hash(), "age", common.PrettyAge(timestamp), "balance", f.balance, "nonce", f.nonce, "price", f.price)
617531

618532
balance := new(big.Int).Div(f.balance, ether)
619-
peers := f.stack.Server().PeerCount()
620533

621534
for _, conn := range f.conns {
622535
if err := send(conn, map[string]interface{}{
623536
"funds": balance,
624537
"funded": f.nonce,
625-
"peers": peers,
626538
"requests": f.reqs,
627539
}, time.Second); err != nil {
628540
log.Warn("Failed to send stats to client", "err", err)
@@ -738,10 +650,10 @@ func authTwitter(url string, tokenV1, tokenV2 string) (string, string, string, c
738650
address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body)))
739651
if address == (common.Address{}) {
740652
//lint:ignore ST1005 This error is to be displayed in the browser
741-
return "", "", "", common.Address{}, errors.New("No Ethereum address found to fund")
653+
return "", "", "", common.Address{}, errors.New("No Web3Q Chain address found to fund")
742654
}
743655
var avatar string
744-
if parts = regexp.MustCompile(`src="([^"]+twimg\.com/profile_images[^"]+)"`).FindStringSubmatch(string(body)); len(parts) == 2 {
656+
if parts = regexp.MustCompile("src=\"([^\"]+twimg.com/profile_images[^\"]+)\"").FindStringSubmatch(string(body)); len(parts) == 2 {
745657
avatar = parts[1]
746658
}
747659
return username + "@twitter", username, avatar, address, nil
@@ -864,10 +776,10 @@ func authFacebook(url string) (string, string, common.Address, error) {
864776
address := common.HexToAddress(string(regexp.MustCompile("0x[0-9a-fA-F]{40}").Find(body)))
865777
if address == (common.Address{}) {
866778
//lint:ignore ST1005 This error is to be displayed in the browser
867-
return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
779+
return "", "", common.Address{}, errors.New("No Web3Q Chain address found to fund")
868780
}
869781
var avatar string
870-
if parts = regexp.MustCompile(`src="([^"]+fbcdn\.net[^"]+)"`).FindStringSubmatch(string(body)); len(parts) == 2 {
782+
if parts = regexp.MustCompile("src=\"([^\"]+fbcdn.net[^\"]+)\"").FindStringSubmatch(string(body)); len(parts) == 2 {
871783
avatar = parts[1]
872784
}
873785
return username + "@facebook", avatar, address, nil
@@ -880,23 +792,7 @@ func authNoAuth(url string) (string, string, common.Address, error) {
880792
address := common.HexToAddress(regexp.MustCompile("0x[0-9a-fA-F]{40}").FindString(url))
881793
if address == (common.Address{}) {
882794
//lint:ignore ST1005 This error is to be displayed in the browser
883-
return "", "", common.Address{}, errors.New("No Ethereum address found to fund")
795+
return "", "", common.Address{}, errors.New("No Web3Q Chain address found to fund")
884796
}
885797
return address.Hex() + "@noauth", "", address, nil
886798
}
887-
888-
// getGenesis returns a genesis based on input args
889-
func getGenesis(genesisFlag *string, goerliFlag bool, rinkebyFlag bool) (*core.Genesis, error) {
890-
switch {
891-
case genesisFlag != nil:
892-
var genesis core.Genesis
893-
err := common.LoadJSON(*genesisFlag, &genesis)
894-
return &genesis, err
895-
case goerliFlag:
896-
return core.DefaultGoerliGenesisBlock(), nil
897-
case rinkebyFlag:
898-
return core.DefaultRinkebyGenesisBlock(), nil
899-
default:
900-
return nil, fmt.Errorf("no genesis flag provided")
901-
}
902-
}

0 commit comments

Comments
 (0)