2017-04-11 02:25:53 +03:00
// Copyright 2017 The go-ethereum Authors
// This file is part of go-ethereum.
//
// go-ethereum is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// go-ethereum is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with go-ethereum. If not, see <http://www.gnu.org/licenses/>.
2021-01-07 14:04:20 +03:00
// faucet is an Ether faucet backed by a light client.
2017-04-11 02:25:53 +03:00
package main
import (
"bytes"
"context"
2022-04-25 12:15:14 +03:00
_ "embed"
2017-04-11 02:25:53 +03:00
"encoding/json"
2017-10-16 16:30:13 +03:00
"errors"
2017-04-11 02:25:53 +03:00
"flag"
"fmt"
"html/template"
2022-05-16 12:59:35 +03:00
"io"
2017-05-02 13:52:51 +03:00
"math"
2017-04-11 02:25:53 +03:00
"math/big"
"net/http"
2017-04-16 19:49:40 +03:00
"net/url"
2017-04-11 02:25:53 +03:00
"os"
"path/filepath"
2017-10-16 16:30:13 +03:00
"regexp"
2017-05-02 13:52:51 +03:00
"strconv"
2017-04-11 02:25:53 +03:00
"strings"
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts"
2020-07-03 10:04:17 +03:00
"github.com/ethereum/go-ethereum/accounts/abi"
2017-04-11 02:25:53 +03:00
"github.com/ethereum/go-ethereum/accounts/keystore"
2020-10-05 13:50:26 +03:00
"github.com/ethereum/go-ethereum/cmd/utils"
2017-04-11 02:25:53 +03:00
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
2017-04-12 17:27:23 +03:00
"github.com/ethereum/go-ethereum/eth/downloader"
2021-02-05 15:51:15 +03:00
"github.com/ethereum/go-ethereum/eth/ethconfig"
2017-04-11 02:25:53 +03:00
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/ethstats"
2022-09-23 15:08:25 +03:00
"github.com/ethereum/go-ethereum/internal/version"
2017-04-11 02:25:53 +03:00
"github.com/ethereum/go-ethereum/les"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/node"
2017-04-12 17:27:23 +03:00
"github.com/ethereum/go-ethereum/p2p"
all: new p2p node representation (#17643)
Package p2p/enode provides a generalized representation of p2p nodes
which can contain arbitrary information in key/value pairs. It is also
the new home for the node database. The "v4" identity scheme is also
moved here from p2p/enr to remove the dependency on Ethereum crypto from
that package.
Record signature handling is changed significantly. The identity scheme
registry is removed and acceptable schemes must be passed to any method
that needs identity. This means records must now be validated explicitly
after decoding.
The enode API is designed to make signature handling easy and safe: most
APIs around the codebase work with enode.Node, which is a wrapper around
a valid record. Going from enr.Record to enode.Node requires a valid
signature.
* p2p/discover: port to p2p/enode
This ports the discovery code to the new node representation in
p2p/enode. The wire protocol is unchanged, this can be considered a
refactoring change. The Kademlia table can now deal with nodes using an
arbitrary identity scheme. This requires a few incompatible API changes:
- Table.Lookup is not available anymore. It used to take a public key
as argument because v4 protocol requires one. Its replacement is
LookupRandom.
- Table.Resolve takes *enode.Node instead of NodeID. This is also for
v4 protocol compatibility because nodes cannot be looked up by ID
alone.
- Types Node and NodeID are gone. Further commits in the series will be
fixes all over the the codebase to deal with those removals.
* p2p: port to p2p/enode and discovery changes
This adapts package p2p to the changes in p2p/discover. All uses of
discover.Node and discover.NodeID are replaced by their equivalents from
p2p/enode.
New API is added to retrieve the enode.Node instance of a peer. The
behavior of Server.Self with discovery disabled is improved. It now
tries much harder to report a working IP address, falling back to
127.0.0.1 if no suitable address can be determined through other means.
These changes were needed for tests of other packages later in the
series.
* p2p/simulations, p2p/testing: port to p2p/enode
No surprises here, mostly replacements of discover.Node, discover.NodeID
with their new equivalents. The 'interesting' API changes are:
- testing.ProtocolSession tracks complete nodes, not just their IDs.
- adapters.NodeConfig has a new method to create a complete node.
These changes were needed to make swarm tests work.
Note that the NodeID change makes the code incompatible with old
simulation snapshots.
* whisper/whisperv5, whisper/whisperv6: port to p2p/enode
This port was easy because whisper uses []byte for node IDs and
URL strings in the API.
* eth: port to p2p/enode
Again, easy to port because eth uses strings for node IDs and doesn't
care about node information in any way.
* les: port to p2p/enode
Apart from replacing discover.NodeID with enode.ID, most changes are in
the server pool code. It now deals with complete nodes instead
of (Pubkey, IP, Port) triples. The database format is unchanged for now,
but we should probably change it to use the node database later.
* node: port to p2p/enode
This change simply replaces discover.Node and discover.NodeID with their
new equivalents.
* swarm/network: port to p2p/enode
Swarm has its own node address representation, BzzAddr, containing both
an overlay address (the hash of a secp256k1 public key) and an underlay
address (enode:// URL).
There are no changes to the BzzAddr format in this commit, but certain
operations such as creating a BzzAddr from a node ID are now impossible
because node IDs aren't public keys anymore.
Most swarm-related changes in the series remove uses of
NewAddrFromNodeID, replacing it with NewAddr which takes a complete node
as argument. ToOverlayAddr is removed because we can just use the node
ID directly.
2018-09-25 01:59:00 +03:00
"github.com/ethereum/go-ethereum/p2p/enode"
2017-04-11 02:25:53 +03:00
"github.com/ethereum/go-ethereum/p2p/nat"
"github.com/ethereum/go-ethereum/params"
2019-11-14 11:05:17 +03:00
"github.com/gorilla/websocket"
2017-04-11 02:25:53 +03:00
)
var (
genesisFlag = flag . String ( "genesis" , "" , "Genesis json file to seed the chain with" )
apiPortFlag = flag . Int ( "apiport" , 8080 , "Listener port for the HTTP API connection" )
ethPortFlag = flag . Int ( "ethport" , 30303 , "Listener port for the devp2p connection" )
bootFlag = flag . String ( "bootnodes" , "" , "Comma separated bootnode enode URLs to seed with" )
2017-04-25 14:31:15 +03:00
netFlag = flag . Uint64 ( "network" , 0 , "Network ID to use for the Ethereum protocol" )
2017-04-11 02:25:53 +03:00
statsFlag = flag . String ( "ethstats" , "" , "Ethstats network monitoring auth string" )
netnameFlag = flag . String ( "faucet.name" , "" , "Network name to assign to the faucet" )
payoutFlag = flag . Int ( "faucet.amount" , 1 , "Number of Ethers to pay out per user request" )
minutesFlag = flag . Int ( "faucet.minutes" , 1440 , "Number of minutes to wait between funding rounds" )
2017-05-02 13:52:51 +03:00
tiersFlag = flag . Int ( "faucet.tiers" , 3 , "Number of funding tiers to enable (x3 time, x2.5 funds)" )
2017-04-11 02:25:53 +03:00
accJSONFlag = flag . String ( "account.json" , "" , "Key json file to fund user requests with" )
accPassFlag = flag . String ( "account.pass" , "" , "Decryption password to access faucet funds" )
2017-04-16 19:49:40 +03:00
captchaToken = flag . String ( "captcha.token" , "" , "Recaptcha site key to authenticate client side" )
captchaSecret = flag . String ( "captcha.secret" , "" , "Recaptcha secret key to authenticate server side" )
2017-10-23 10:22:23 +03:00
noauthFlag = flag . Bool ( "noauth" , false , "Enables funding requests without authentication" )
logFlag = flag . Int ( "loglevel" , 3 , "Log level to use for Ethereum and the faucet" )
2020-05-20 06:44:06 +03:00
2021-04-16 07:45:26 +03:00
bep2eContracts = flag . String ( "bep2eContracts" , "" , "the list of bep2p contracts" )
bep2eSymbols = flag . String ( "bep2eSymbols" , "" , "the symbol of bep2p tokens" )
bep2eAmounts = flag . String ( "bep2eAmounts" , "" , "the amount of bep2p tokens" )
fixGasPrice = flag . Int64 ( "faucet.fixedprice" , 0 , "Will use fixed gas price if specified" )
2021-01-04 14:58:46 +03:00
twitterTokenFlag = flag . String ( "twitter.token" , "" , "Bearer token to authenticate with the v2 Twitter API" )
twitterTokenV1Flag = flag . String ( "twitter.token.v1" , "" , "Bearer token to authenticate with the v1.1 Twitter API" )
2017-04-11 02:25:53 +03:00
)
var (
2020-06-30 11:16:26 +03:00
ether = new ( big . Int ) . Exp ( big . NewInt ( 10 ) , big . NewInt ( 18 ) , nil )
bep2eAbiJson = ` [ { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "owner", "type": "address" }, { "indexed": true, "internalType": "address", "name": "spender", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Approval", "type": "event" }, { "anonymous": false, "inputs": [ { "indexed": true, "internalType": "address", "name": "from", "type": "address" }, { "indexed": true, "internalType": "address", "name": "to", "type": "address" }, { "indexed": false, "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" }, { "inputs": [], "name": "totalSupply", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "decimals", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "symbol", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getOwner", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "name": "balanceOf", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transfer", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "_owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" } ], "name": "allowance", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "approve", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "recipient", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" } ], "name": "transferFrom", "outputs": [ { "internalType": "bool", "name": "", "type": "bool" } ], "stateMutability": "nonpayable", "type": "function" } ] `
2017-04-11 02:25:53 +03:00
)
2022-04-25 12:15:14 +03:00
//go:embed faucet.html
var websiteTmpl string
2019-05-08 17:11:33 +03:00
2017-04-11 02:25:53 +03:00
func main ( ) {
// Parse the flags and set up the logger to print everything requested
flag . Parse ( )
log . Root ( ) . SetHandler ( log . LvlFilterHandler ( log . Lvl ( * logFlag ) , log . StreamHandler ( os . Stderr , log . TerminalFormat ( true ) ) ) )
2017-05-02 13:52:51 +03:00
// Construct the payout tiers
amounts := make ( [ ] string , * tiersFlag )
for i := 0 ; i < * tiersFlag ; i ++ {
// Calculate the amount for the next tier and format it
amount := float64 ( * payoutFlag ) * math . Pow ( 2.5 , float64 ( i ) )
2023-04-06 06:17:53 +03:00
amounts [ i ] = fmt . Sprintf ( "0.%s BNBs" , strconv . FormatFloat ( amount , 'f' , - 1 , 64 ) )
2017-05-02 13:52:51 +03:00
if amount == 1 {
amounts [ i ] = strings . TrimSuffix ( amounts [ i ] , "s" )
}
}
2020-06-30 11:16:26 +03:00
bep2eNumAmounts := make ( [ ] string , 0 )
if bep2eAmounts != nil && len ( * bep2eAmounts ) > 0 {
bep2eNumAmounts = strings . Split ( * bep2eAmounts , "," )
}
symbols := make ( [ ] string , 0 )
if bep2eSymbols != nil && len ( * bep2eSymbols ) > 0 {
symbols = strings . Split ( * bep2eSymbols , "," )
}
contracts := make ( [ ] string , 0 )
if bep2eContracts != nil && len ( * bep2eContracts ) > 0 {
contracts = strings . Split ( * bep2eContracts , "," )
}
2020-05-20 06:44:06 +03:00
2020-06-30 11:16:26 +03:00
if len ( bep2eNumAmounts ) != len ( symbols ) || len ( symbols ) != len ( contracts ) {
log . Crit ( "Length of bep2eContracts, bep2eSymbols, bep2eAmounts mismatch" )
}
2022-07-05 06:14:21 +03:00
bep2eInfos := make ( map [ string ] bep2eInfo , len ( symbols ) )
2020-06-30 11:16:26 +03:00
for idx , s := range symbols {
n , ok := big . NewInt ( 0 ) . SetString ( bep2eNumAmounts [ idx ] , 10 )
if ! ok {
log . Crit ( "failed to parse bep2eAmounts" )
}
amountStr := big . NewFloat ( 0 ) . Quo ( big . NewFloat ( 0 ) . SetInt ( n ) , big . NewFloat ( 0 ) . SetInt64 ( params . Ether ) ) . String ( )
bep2eInfos [ s ] = bep2eInfo {
Contract : common . HexToAddress ( contracts [ idx ] ) ,
Amount : * n ,
AmountStr : amountStr ,
}
}
2017-04-11 02:25:53 +03:00
website := new ( bytes . Buffer )
2023-08-23 12:46:08 +03:00
err := template . Must ( template . New ( "" ) . Parse ( string ( websiteTmpl ) ) ) . Execute ( website , map [ string ] interface { } {
2020-06-30 11:16:26 +03:00
"Network" : * netnameFlag ,
"Amounts" : amounts ,
"Recaptcha" : * captchaToken ,
"NoAuth" : * noauthFlag ,
"Bep2eInfos" : bep2eInfos ,
2017-04-11 02:25:53 +03:00
} )
2017-05-02 13:52:51 +03:00
if err != nil {
log . Crit ( "Failed to render the faucet template" , "err" , err )
}
2017-04-11 02:25:53 +03:00
// Load and parse the genesis block requested by the user
2023-08-23 12:46:08 +03:00
genesis , err := getGenesis ( * genesisFlag , false , false )
2017-04-11 02:25:53 +03:00
if err != nil {
2023-04-06 06:17:53 +03:00
log . Crit ( "Failed to read genesis block contents" , "genesis" , * genesisFlag , "err" , err )
}
2017-04-11 02:25:53 +03:00
// Convert the bootnodes to internal enode representations
2021-01-26 23:41:35 +03:00
var enodes [ ] * enode . Node
2017-04-11 02:25:53 +03:00
for _ , boot := range strings . Split ( * bootFlag , "," ) {
2021-01-26 23:41:35 +03:00
if url , err := enode . Parse ( enode . ValidSchemes , boot ) ; err == nil {
2017-04-11 02:25:53 +03:00
enodes = append ( enodes , url )
} else {
log . Error ( "Failed to parse bootnode URL" , "url" , boot , "err" , err )
}
}
// Load up the account key and decrypt its password
2022-05-16 12:59:35 +03:00
blob , err := os . ReadFile ( * accPassFlag )
2021-04-14 00:51:46 +03:00
if err != nil {
2017-04-11 02:25:53 +03:00
log . Crit ( "Failed to read account password contents" , "file" , * accPassFlag , "err" , err )
}
2018-09-04 14:16:49 +03:00
pass := strings . TrimSuffix ( string ( blob ) , "\n" )
2017-04-11 02:25:53 +03:00
2023-04-06 06:17:53 +03:00
ks := keystore . NewKeyStore ( filepath . Join ( os . Getenv ( "HOME" ) , ".faucet" , "keys_2" ) , keystore . StandardScryptN , keystore . StandardScryptP )
2022-05-16 12:59:35 +03:00
if blob , err = os . ReadFile ( * accJSONFlag ) ; err != nil {
2017-04-11 02:25:53 +03:00
log . Crit ( "Failed to read account key contents" , "file" , * accJSONFlag , "err" , err )
}
acc , err := ks . Import ( blob , pass , pass )
2020-06-04 08:59:26 +03:00
if err != nil && err != keystore . ErrAccountAlreadyExists {
2017-04-11 02:25:53 +03:00
log . Crit ( "Failed to import faucet signer account" , "err" , err )
}
2020-06-04 10:22:11 +03:00
if err := ks . Unlock ( acc , pass ) ; err != nil {
log . Crit ( "Failed to unlock faucet signer account" , "err" , err )
}
2017-04-11 02:25:53 +03:00
// Assemble and start the faucet light service
2020-06-30 11:16:26 +03:00
faucet , err := newFaucet ( genesis , * ethPortFlag , enodes , * netFlag , * statsFlag , ks , website . Bytes ( ) , bep2eInfos )
2017-04-11 02:25:53 +03:00
if err != nil {
log . Crit ( "Failed to start faucet" , "err" , err )
}
defer faucet . close ( )
if err := faucet . listenAndServe ( * apiPortFlag ) ; err != nil {
log . Crit ( "Failed to launch faucet API" , "err" , err )
}
}
// request represents an accepted funding request.
type request struct {
2017-10-16 16:30:13 +03:00
Avatar string ` json:"avatar" ` // Avatar URL to make the UI nicer
Account common . Address ` json:"account" ` // Ethereum address being funded
2017-10-17 14:55:21 +03:00
Time time . Time ` json:"time" ` // Timestamp when the request was accepted
2017-10-16 16:30:13 +03:00
Tx * types . Transaction ` json:"tx" ` // Transaction funding the account
2017-04-11 02:25:53 +03:00
}
2020-06-30 11:16:26 +03:00
type bep2eInfo struct {
Contract common . Address
Amount big . Int
AmountStr string
}
2017-04-11 02:25:53 +03:00
// faucet represents a crypto faucet backed by an Ethereum light client.
type faucet struct {
config * params . ChainConfig // Chain configurations for signing
stack * node . Node // Ethereum protocol stack
client * ethclient . Client // Client connection to the Ethereum chain
index [ ] byte // Index page to serve up on the web
keystore * keystore . KeyStore // Keystore containing the single signer
account accounts . Account // Account funding user faucet requests
2018-09-21 13:15:09 +03:00
head * types . Header // Current head header of the faucet
balance * big . Int // Current balance of the faucet
2017-04-11 02:25:53 +03:00
nonce uint64 // Current pending nonce of the faucet
price * big . Int // Current gas price to issue funds with
2021-01-07 11:23:50 +03:00
conns [ ] * wsConn // Currently live websocket connections
2017-05-02 13:52:51 +03:00
timeouts map [ string ] time . Time // History of users and their funding timeouts
reqs [ ] * request // Currently pending funding requests
update chan struct { } // Channel to signal request updates
2017-04-11 02:25:53 +03:00
lock sync . RWMutex // Lock protecting the faucet's internals
2020-06-30 11:16:26 +03:00
bep2eInfos map [ string ] bep2eInfo
bep2eAbi abi . ABI
2017-04-11 02:25:53 +03:00
}
2021-01-07 11:23:50 +03:00
// wsConn wraps a websocket connection with a write mutex as the underlying
// websocket library does not synchronize access to the stream.
type wsConn struct {
conn * websocket . Conn
wlock sync . Mutex
}
2021-04-16 07:45:26 +03:00
func newFaucet ( genesis * core . Genesis , port int , enodes [ ] * enode . Node , network uint64 , stats string , ks * keystore . KeyStore , index [ ] byte , bep2eInfos map [ string ] bep2eInfo ) ( * faucet , error ) {
2017-04-11 02:25:53 +03:00
// Assemble the raw devp2p protocol stack
2022-09-23 15:08:25 +03:00
git , _ := version . VCS ( )
2017-04-11 02:25:53 +03:00
stack , err := node . New ( & node . Config {
2017-04-12 17:27:23 +03:00
Name : "geth" ,
2022-09-23 15:08:25 +03:00
Version : params . VersionWithCommit ( git . Commit , git . Date ) ,
2017-04-12 17:27:23 +03:00
DataDir : filepath . Join ( os . Getenv ( "HOME" ) , ".faucet" ) ,
2020-05-20 06:44:06 +03:00
NoUSB : true ,
2017-04-12 17:27:23 +03:00
P2P : p2p . Config {
NAT : nat . Any ( ) ,
NoDiscovery : true ,
DiscoveryV5 : true ,
ListenAddr : fmt . Sprintf ( ":%d" , port ) ,
MaxPeers : 25 ,
BootstrapNodesV5 : enodes ,
} ,
2017-04-11 02:25:53 +03:00
} )
if err != nil {
return nil , err
}
2020-06-30 11:16:26 +03:00
bep2eAbi , err := abi . JSON ( strings . NewReader ( bep2eAbiJson ) )
if err != nil {
return nil , err
}
2017-04-11 02:25:53 +03:00
// Assemble the Ethereum light client protocol
2021-02-05 15:51:15 +03:00
cfg := ethconfig . Defaults
2020-08-03 20:40:46 +03:00
cfg . SyncMode = downloader . LightSync
cfg . NetworkId = network
cfg . Genesis = genesis
2022-08-09 12:44:39 +03:00
utils . SetDNSDiscoveryDefaults ( & cfg , genesis . ToBlock ( ) . Hash ( ) )
2021-01-04 14:58:46 +03:00
2020-08-03 20:40:46 +03:00
lesBackend , err := les . New ( stack , & cfg )
if err != nil {
return nil , fmt . Errorf ( "Failed to register the Ethereum service: %w" , err )
2017-04-11 02:25:53 +03:00
}
2020-08-03 20:40:46 +03:00
2017-04-11 02:25:53 +03:00
// Assemble the ethstats monitoring and reporting service'
if stats != "" {
2020-08-03 20:40:46 +03:00
if err := ethstats . New ( stack , lesBackend . ApiBackend , lesBackend . Engine ( ) , stats ) ; err != nil {
2017-04-11 02:25:53 +03:00
return nil , err
}
}
// Boot up the client and ensure it connects to bootnodes
if err := stack . Start ( ) ; err != nil {
return nil , err
}
for _ , boot := range enodes {
2019-06-07 16:31:00 +03:00
old , err := enode . Parse ( enode . ValidSchemes , boot . String ( ) )
2018-12-11 14:41:18 +03:00
if err == nil {
all: new p2p node representation (#17643)
Package p2p/enode provides a generalized representation of p2p nodes
which can contain arbitrary information in key/value pairs. It is also
the new home for the node database. The "v4" identity scheme is also
moved here from p2p/enr to remove the dependency on Ethereum crypto from
that package.
Record signature handling is changed significantly. The identity scheme
registry is removed and acceptable schemes must be passed to any method
that needs identity. This means records must now be validated explicitly
after decoding.
The enode API is designed to make signature handling easy and safe: most
APIs around the codebase work with enode.Node, which is a wrapper around
a valid record. Going from enr.Record to enode.Node requires a valid
signature.
* p2p/discover: port to p2p/enode
This ports the discovery code to the new node representation in
p2p/enode. The wire protocol is unchanged, this can be considered a
refactoring change. The Kademlia table can now deal with nodes using an
arbitrary identity scheme. This requires a few incompatible API changes:
- Table.Lookup is not available anymore. It used to take a public key
as argument because v4 protocol requires one. Its replacement is
LookupRandom.
- Table.Resolve takes *enode.Node instead of NodeID. This is also for
v4 protocol compatibility because nodes cannot be looked up by ID
alone.
- Types Node and NodeID are gone. Further commits in the series will be
fixes all over the the codebase to deal with those removals.
* p2p: port to p2p/enode and discovery changes
This adapts package p2p to the changes in p2p/discover. All uses of
discover.Node and discover.NodeID are replaced by their equivalents from
p2p/enode.
New API is added to retrieve the enode.Node instance of a peer. The
behavior of Server.Self with discovery disabled is improved. It now
tries much harder to report a working IP address, falling back to
127.0.0.1 if no suitable address can be determined through other means.
These changes were needed for tests of other packages later in the
series.
* p2p/simulations, p2p/testing: port to p2p/enode
No surprises here, mostly replacements of discover.Node, discover.NodeID
with their new equivalents. The 'interesting' API changes are:
- testing.ProtocolSession tracks complete nodes, not just their IDs.
- adapters.NodeConfig has a new method to create a complete node.
These changes were needed to make swarm tests work.
Note that the NodeID change makes the code incompatible with old
simulation snapshots.
* whisper/whisperv5, whisper/whisperv6: port to p2p/enode
This port was easy because whisper uses []byte for node IDs and
URL strings in the API.
* eth: port to p2p/enode
Again, easy to port because eth uses strings for node IDs and doesn't
care about node information in any way.
* les: port to p2p/enode
Apart from replacing discover.NodeID with enode.ID, most changes are in
the server pool code. It now deals with complete nodes instead
of (Pubkey, IP, Port) triples. The database format is unchanged for now,
but we should probably change it to use the node database later.
* node: port to p2p/enode
This change simply replaces discover.Node and discover.NodeID with their
new equivalents.
* swarm/network: port to p2p/enode
Swarm has its own node address representation, BzzAddr, containing both
an overlay address (the hash of a secp256k1 public key) and an underlay
address (enode:// URL).
There are no changes to the BzzAddr format in this commit, but certain
operations such as creating a BzzAddr from a node ID are now impossible
because node IDs aren't public keys anymore.
Most swarm-related changes in the series remove uses of
NewAddrFromNodeID, replacing it with NewAddr which takes a complete node
as argument. ToOverlayAddr is removed because we can just use the node
ID directly.
2018-09-25 01:59:00 +03:00
stack . Server ( ) . AddPeer ( old )
}
2017-04-11 02:25:53 +03:00
}
// Attach to the client and retrieve and interesting metadatas
2023-06-14 15:24:47 +03:00
api := stack . Attach ( )
2017-04-11 02:25:53 +03:00
client := ethclient . NewClient ( api )
return & faucet {
2020-06-30 11:16:26 +03:00
config : genesis . Config ,
stack : stack ,
client : client ,
index : index ,
keystore : ks ,
account : ks . Accounts ( ) [ 0 ] ,
timeouts : make ( map [ string ] time . Time ) ,
update : make ( chan struct { } , 1 ) ,
bep2eInfos : bep2eInfos ,
bep2eAbi : bep2eAbi ,
2017-04-11 02:25:53 +03:00
} , nil
}
// close terminates the Ethereum connection and tears down the faucet.
func ( f * faucet ) close ( ) error {
2019-02-07 13:40:36 +03:00
return f . stack . Close ( )
2017-04-11 02:25:53 +03:00
}
// listenAndServe registers the HTTP handlers for the faucet and boots it up
// for service user funding requests.
func ( f * faucet ) listenAndServe ( port int ) error {
go f . loop ( )
http . HandleFunc ( "/" , f . webHandler )
2019-11-14 11:05:17 +03:00
http . HandleFunc ( "/api" , f . apiHandler )
2020-05-20 06:44:06 +03:00
http . HandleFunc ( "/faucet-smart/api" , f . apiHandler )
2017-04-11 02:25:53 +03:00
return http . ListenAndServe ( fmt . Sprintf ( ":%d" , port ) , nil )
}
// webHandler handles all non-api requests, simply flattening and returning the
// faucet website.
func ( f * faucet ) webHandler ( w http . ResponseWriter , r * http . Request ) {
w . Write ( f . index )
}
// apiHandler handles requests for Ether grants and transaction statuses.
2019-11-14 11:05:17 +03:00
func ( f * faucet ) apiHandler ( w http . ResponseWriter , r * http . Request ) {
upgrader := websocket . Upgrader { }
conn , err := upgrader . Upgrade ( w , r , nil )
if err != nil {
return
}
2017-04-11 02:25:53 +03:00
// Start tracking the connection and drop at the end
2017-10-17 12:08:57 +03:00
defer conn . Close ( )
2023-04-06 06:17:53 +03:00
ipsStr := r . Header . Get ( "X-Forwarded-For" )
ips := strings . Split ( ipsStr , "," )
if len ( ips ) < 2 {
return
}
2017-10-17 12:08:57 +03:00
2017-04-11 02:25:53 +03:00
f . lock . Lock ( )
2021-01-07 11:23:50 +03:00
wsconn := & wsConn { conn : conn }
f . conns = append ( f . conns , wsconn )
2017-04-11 02:25:53 +03:00
f . lock . Unlock ( )
defer func ( ) {
f . lock . Lock ( )
for i , c := range f . conns {
2021-01-07 11:23:50 +03:00
if c . conn == conn {
2017-04-11 02:25:53 +03:00
f . conns = append ( f . conns [ : i ] , f . conns [ i + 1 : ] ... )
break
}
}
f . lock . Unlock ( )
} ( )
2017-10-17 12:08:57 +03:00
// Gather the initial stats from the network to report
var (
head * types . Header
balance * big . Int
nonce uint64
)
2018-09-21 13:15:09 +03:00
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 )
2017-10-17 12:08:57 +03:00
}
2018-09-21 13:15:09 +03:00
if f . balance != nil {
balance = new ( big . Int ) . Set ( f . balance )
}
nonce = f . nonce
f . lock . RUnlock ( )
2017-04-11 02:25:53 +03:00
2018-09-21 13:15:09 +03:00
if head == nil || balance == nil {
// Report the faucet offline until initial stats are ready
2019-11-29 13:38:34 +03:00
//lint:ignore ST1005 This error is to be displayed in the browser
2021-01-07 11:23:50 +03:00
if err = sendError ( wsconn , errors . New ( "Faucet offline" ) ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send faucet error to client" , "err" , err )
return
}
time . Sleep ( 3 * time . Second )
}
}
// Send over the initial stats and the latest header
2020-02-15 21:14:29 +03:00
f . lock . RLock ( )
reqs := f . reqs
f . lock . RUnlock ( )
2021-01-07 11:23:50 +03:00
if err = send ( wsconn , map [ string ] interface { } {
2018-09-21 13:15:09 +03:00
"funds" : new ( big . Int ) . Div ( balance , ether ) ,
2017-04-11 02:25:53 +03:00
"funded" : nonce ,
"peers" : f . stack . Server ( ) . PeerCount ( ) ,
2020-02-15 21:14:29 +03:00
"requests" : reqs ,
2017-10-17 12:08:57 +03:00
} , 3 * time . Second ) ; err != nil {
log . Warn ( "Failed to send initial stats to client" , "err" , err )
return
}
2021-01-07 11:23:50 +03:00
if err = send ( wsconn , head , 3 * time . Second ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send initial header to client" , "err" , err )
return
2017-05-02 13:52:51 +03:00
}
2017-04-11 02:25:53 +03:00
// Keep reading requests from the websocket until the connection breaks
for {
// Fetch the next funding request and validate against github
var msg struct {
2017-04-16 19:49:40 +03:00
URL string ` json:"url" `
2017-05-02 13:52:51 +03:00
Tier uint ` json:"tier" `
2017-04-16 19:49:40 +03:00
Captcha string ` json:"captcha" `
2020-06-30 11:16:26 +03:00
Symbol string ` json:"symbol" `
2017-04-11 02:25:53 +03:00
}
2019-11-14 11:05:17 +03:00
if err = conn . ReadJSON ( & msg ) ; err != nil {
2017-04-11 02:25:53 +03:00
return
}
2021-01-04 14:58:46 +03:00
if ! * noauthFlag && ! strings . HasPrefix ( msg . URL , "https://twitter.com/" ) && ! strings . HasPrefix ( msg . URL , "https://www.facebook.com/" ) {
2021-01-07 11:23:50 +03:00
if err = sendError ( wsconn , errors . New ( "URL doesn't link to supported services" ) ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send URL error to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
continue
}
2017-05-02 13:52:51 +03:00
if msg . Tier >= uint ( * tiersFlag ) {
2019-11-29 13:38:34 +03:00
//lint:ignore ST1005 This error is to be displayed in the browser
2021-01-07 11:23:50 +03:00
if err = sendError ( wsconn , errors . New ( "Invalid funding tier requested" ) ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send tier error to client" , "err" , err )
return
}
2017-05-02 13:52:51 +03:00
continue
}
2017-10-16 16:30:13 +03:00
log . Info ( "Faucet funds requested" , "url" , msg . URL , "tier" , msg . Tier )
2017-04-16 19:49:40 +03:00
// If captcha verifications are enabled, make sure we're not dealing with a robot
if * captchaToken != "" {
form := url . Values { }
form . Add ( "secret" , * captchaSecret )
form . Add ( "response" , msg . Captcha )
2023-04-06 06:17:53 +03:00
res , err := http . PostForm ( "https://hcaptcha.com/siteverify" , form )
2017-04-16 19:49:40 +03:00
if err != nil {
2021-01-07 11:23:50 +03:00
if err = sendError ( wsconn , err ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send captcha post error to client" , "err" , err )
return
}
2017-04-16 19:49:40 +03:00
continue
}
var result struct {
Success bool ` json:"success" `
Errors json . RawMessage ` json:"error-codes" `
}
err = json . NewDecoder ( res . Body ) . Decode ( & result )
res . Body . Close ( )
if err != nil {
2021-01-07 11:23:50 +03:00
if err = sendError ( wsconn , err ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send captcha decode error to client" , "err" , err )
return
}
2017-04-16 19:49:40 +03:00
continue
}
if ! result . Success {
log . Warn ( "Captcha verification failed" , "err" , string ( result . Errors ) )
2019-11-29 13:38:34 +03:00
//lint:ignore ST1005 it's funny and the robot won't mind
2021-01-07 11:23:50 +03:00
if err = sendError ( wsconn , errors . New ( "Beep-bop, you're a robot!" ) ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send captcha failure to client" , "err" , err )
return
}
2017-04-16 19:49:40 +03:00
continue
}
}
2017-10-16 16:30:13 +03:00
// Retrieve the Ethereum address to fund, the requesting user and a profile picture
var (
2020-12-11 12:35:39 +03:00
id string
2017-10-16 16:30:13 +03:00
username string
avatar string
address common . Address
)
switch {
2023-04-06 06:17:53 +03:00
case strings . HasPrefix ( msg . URL , "https://gist.github.com/" ) :
if err = sendError ( wsconn , errors . New ( "GitHub authentication discontinued at the official request of GitHub" ) ) ; err != nil {
log . Warn ( "Failed to send GitHub deprecation to client" , "err" , err )
return
}
continue
case strings . HasPrefix ( msg . URL , "https://plus.google.com/" ) :
//lint:ignore ST1005 Google is a company name and should be capitalized.
if err = sendError ( wsconn , errors . New ( "Google+ authentication discontinued as the service was sunset" ) ) ; err != nil {
log . Warn ( "Failed to send Google+ deprecation to client" , "err" , err )
return
}
continue
2017-10-16 16:30:13 +03:00
case strings . HasPrefix ( msg . URL , "https://twitter.com/" ) :
2021-01-04 14:58:46 +03:00
id , username , avatar , address , err = authTwitter ( msg . URL , * twitterTokenV1Flag , * twitterTokenFlag )
2017-10-16 16:30:13 +03:00
case strings . HasPrefix ( msg . URL , "https://www.facebook.com/" ) :
username , avatar , address , err = authFacebook ( msg . URL )
2020-12-11 12:35:39 +03:00
id = username
2017-10-23 10:22:23 +03:00
case * noauthFlag :
username , avatar , address , err = authNoAuth ( msg . URL )
2020-12-11 12:35:39 +03:00
id = username
2017-10-16 16:30:13 +03:00
default :
2019-11-29 13:38:34 +03:00
//lint:ignore ST1005 This error is to be displayed in the browser
2017-10-16 16:30:13 +03:00
err = errors . New ( "Something funky happened, please open an issue at https://github.com/ethereum/go-ethereum/issues" )
2017-04-11 02:25:53 +03:00
}
if err != nil {
2021-01-07 11:23:50 +03:00
if err = sendError ( wsconn , err ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send prefix error to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
continue
}
2017-10-16 16:30:13 +03:00
log . Info ( "Faucet request valid" , "url" , msg . URL , "tier" , msg . Tier , "user" , username , "address" , address )
2017-04-16 18:49:06 +03:00
2017-04-11 02:25:53 +03:00
// Ensure the user didn't request funds too recently
f . lock . Lock ( )
var (
fund bool
2017-05-02 13:52:51 +03:00
timeout time . Time
2017-04-11 02:25:53 +03:00
)
2023-04-06 06:17:53 +03:00
if ipTimeout := f . timeouts [ ips [ len ( ips ) - 2 ] ] ; time . Now ( ) . Before ( ipTimeout ) {
if err = sendError ( wsconn , fmt . Errorf ( "%s left until next allowance" , common . PrettyDuration ( time . Until ( ipTimeout ) ) ) ) ; err != nil { // nolint: gosimple
log . Warn ( "Failed to send funding error to client" , "err" , err )
}
f . lock . Unlock ( )
continue
}
2020-12-11 12:35:39 +03:00
if timeout = f . timeouts [ id ] ; time . Now ( ) . After ( timeout ) {
2020-06-30 11:16:26 +03:00
var tx * types . Transaction
if msg . Symbol == "BNB" {
// User wasn't funded recently, create the funding transaction
2023-04-06 06:17:53 +03:00
amount := new ( big . Int ) . Div ( new ( big . Int ) . Mul ( big . NewInt ( int64 ( * payoutFlag ) ) , ether ) , big . NewInt ( 10 ) )
2020-06-30 11:16:26 +03:00
amount = new ( big . Int ) . Mul ( amount , new ( big . Int ) . Exp ( big . NewInt ( 5 ) , big . NewInt ( int64 ( msg . Tier ) ) , nil ) )
amount = new ( big . Int ) . Div ( amount , new ( big . Int ) . Exp ( big . NewInt ( 2 ) , big . NewInt ( int64 ( msg . Tier ) ) , nil ) )
tx = types . NewTransaction ( f . nonce + uint64 ( len ( f . reqs ) ) , address , amount , 21000 , f . price , nil )
} else {
tokenInfo , ok := f . bep2eInfos [ msg . Symbol ]
if ! ok {
f . lock . Unlock ( )
log . Warn ( "Failed to find symbol" , "symbol" , msg . Symbol )
continue
}
input , err := f . bep2eAbi . Pack ( "transfer" , address , & tokenInfo . Amount )
if err != nil {
f . lock . Unlock ( )
log . Warn ( "Failed to pack transfer transaction" , "err" , err )
continue
}
tx = types . NewTransaction ( f . nonce + uint64 ( len ( f . reqs ) ) , tokenInfo . Contract , nil , 420000 , f . price , input )
}
2018-06-05 13:31:34 +03:00
signed , err := f . keystore . SignTx ( f . account , tx , f . config . ChainID )
2017-04-11 02:25:53 +03:00
if err != nil {
f . lock . Unlock ( )
2021-01-07 11:23:50 +03:00
if err = sendError ( wsconn , err ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send transaction creation error to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
continue
}
// Submit the transaction and mark as funded if successful
if err := f . client . SendTransaction ( context . Background ( ) , signed ) ; err != nil {
f . lock . Unlock ( )
2021-01-07 11:23:50 +03:00
if err = sendError ( wsconn , err ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send transaction transmission error to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
continue
}
f . reqs = append ( f . reqs , & request {
2017-10-16 16:30:13 +03:00
Avatar : avatar ,
Account : address ,
Time : time . Now ( ) ,
Tx : signed ,
2017-04-11 02:25:53 +03:00
} )
2019-07-23 12:52:41 +03:00
timeout := time . Duration ( * minutesFlag * int ( math . Pow ( 3 , float64 ( msg . Tier ) ) ) ) * time . Minute
grace := timeout / 288 // 24h timeout => 5m grace
2020-12-11 12:35:39 +03:00
f . timeouts [ id ] = time . Now ( ) . Add ( timeout - grace )
2023-04-06 06:17:53 +03:00
f . timeouts [ ips [ len ( ips ) - 2 ] ] = time . Now ( ) . Add ( timeout - grace )
2017-04-11 02:25:53 +03:00
fund = true
}
f . lock . Unlock ( )
// Send an error if too frequent funding, othewise a success
if ! fund {
2021-01-07 11:23:50 +03:00
if err = sendError ( wsconn , fmt . Errorf ( "%s left until next allowance" , common . PrettyDuration ( time . Until ( timeout ) ) ) ) ; err != nil { // nolint: gosimple
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send funding error to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
continue
}
2021-01-07 11:23:50 +03:00
if err = sendSuccess ( wsconn , fmt . Sprintf ( "Funding request accepted for %s into %s" , username , address . Hex ( ) ) ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to send funding success to client" , "err" , err )
return
}
2017-04-11 02:25:53 +03:00
select {
case f . update <- struct { } { } :
default :
}
}
}
2018-09-21 13:15:09 +03:00
// 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
}
2020-05-20 06:44:06 +03:00
if fixGasPrice != nil && * fixGasPrice > 0 {
price = big . NewInt ( * fixGasPrice )
} else {
if price , err = f . client . SuggestGasPrice ( ctx ) ; err != nil {
return err
}
2018-09-21 13:15:09 +03:00
}
// 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
2023-04-14 04:52:15 +03:00
if len ( f . reqs ) > 0 && f . reqs [ 0 ] . Tx . Nonce ( ) > f . nonce {
f . reqs = f . reqs [ : 0 ]
}
2018-09-21 13:15:09 +03:00
for len ( f . reqs ) > 0 && f . reqs [ 0 ] . Tx . Nonce ( ) < f . nonce {
f . reqs = f . reqs [ 1 : ]
}
f . lock . Unlock ( )
return nil
}
2017-04-11 02:25:53 +03:00
// loop keeps waiting for interesting events and pushes them out to connected
// websockets.
func ( f * faucet ) loop ( ) {
// Wait for chain events and push them to clients
heads := make ( chan * types . Header , 16 )
sub , err := f . client . SubscribeNewHead ( context . Background ( ) , heads )
if err != nil {
log . Crit ( "Failed to subscribe to head events" , "err" , err )
}
defer sub . Unsubscribe ( )
2018-03-02 12:57:11 +03:00
// Start a goroutine to update the state from head notifications in the background
update := make ( chan * types . Header )
go func ( ) {
for head := range update {
2017-04-11 02:25:53 +03:00
// New chain head arrived, query the current stats and stream to clients
2019-04-02 23:28:48 +03:00
timestamp := time . Unix ( int64 ( head . Time ) , 0 )
2018-09-21 13:15:09 +03:00
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
2017-10-17 12:08:57 +03:00
}
2018-09-21 13:15:09 +03:00
if err := f . refresh ( head ) ; err != nil {
2017-10-17 12:08:57 +03:00
log . Warn ( "Failed to update faucet state" , "block" , head . Number , "hash" , head . Hash ( ) , "err" , err )
continue
}
// Faucet state retrieved, update locally and send to clients
2018-09-21 13:15:09 +03:00
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 )
2017-04-11 02:25:53 +03:00
2018-09-21 13:15:09 +03:00
balance := new ( big . Int ) . Div ( f . balance , ether )
peers := f . stack . Server ( ) . PeerCount ( )
2017-04-11 02:25:53 +03:00
for _ , conn := range f . conns {
2017-10-17 12:08:57 +03:00
if err := send ( conn , map [ string ] interface { } {
2017-04-11 02:25:53 +03:00
"funds" : balance ,
"funded" : f . nonce ,
2018-09-21 13:15:09 +03:00
"peers" : peers ,
2017-04-11 02:25:53 +03:00
"requests" : f . reqs ,
2017-10-17 12:08:57 +03:00
} , time . Second ) ; err != nil {
2017-04-11 02:25:53 +03:00
log . Warn ( "Failed to send stats to client" , "err" , err )
2021-01-07 11:23:50 +03:00
conn . conn . Close ( )
2017-04-11 02:25:53 +03:00
continue
}
2017-10-17 12:08:57 +03:00
if err := send ( conn , head , time . Second ) ; err != nil {
2017-04-11 02:25:53 +03:00
log . Warn ( "Failed to send header to client" , "err" , err )
2021-01-07 11:23:50 +03:00
conn . conn . Close ( )
2017-04-11 02:25:53 +03:00
}
}
f . lock . RUnlock ( )
2018-03-02 12:57:11 +03:00
}
} ( )
// Wait for various events and assing to the appropriate background threads
for {
select {
case head := <- heads :
// New head arrived, send if for state update if there's none running
select {
case update <- head :
default :
}
2017-04-11 02:25:53 +03:00
case <- f . update :
// Pending requests updated, stream to clients
f . lock . RLock ( )
for _ , conn := range f . conns {
2017-10-17 12:08:57 +03:00
if err := send ( conn , map [ string ] interface { } { "requests" : f . reqs } , time . Second ) ; err != nil {
2017-04-11 02:25:53 +03:00
log . Warn ( "Failed to send requests to client" , "err" , err )
2021-01-07 11:23:50 +03:00
conn . conn . Close ( )
2017-04-11 02:25:53 +03:00
}
}
f . lock . RUnlock ( )
}
}
}
2017-10-16 16:30:13 +03:00
2017-10-17 12:08:57 +03:00
// sends transmits a data packet to the remote end of the websocket, but also
// setting a write deadline to prevent waiting forever on the node.
2021-01-07 11:23:50 +03:00
func send ( conn * wsConn , value interface { } , timeout time . Duration ) error {
2017-10-17 12:08:57 +03:00
if timeout == 0 {
timeout = 60 * time . Second
}
2021-01-07 11:23:50 +03:00
conn . wlock . Lock ( )
defer conn . wlock . Unlock ( )
conn . conn . SetWriteDeadline ( time . Now ( ) . Add ( timeout ) )
return conn . conn . WriteJSON ( value )
2017-10-17 12:08:57 +03:00
}
// sendError transmits an error to the remote end of the websocket, also setting
// the write deadline to 1 second to prevent waiting forever.
2021-01-07 11:23:50 +03:00
func sendError ( conn * wsConn , err error ) error {
2017-10-17 12:08:57 +03:00
return send ( conn , map [ string ] string { "error" : err . Error ( ) } , time . Second )
}
// sendSuccess transmits a success message to the remote end of the websocket, also
// setting the write deadline to 1 second to prevent waiting forever.
2021-01-07 11:23:50 +03:00
func sendSuccess ( conn * wsConn , msg string ) error {
2017-10-17 12:08:57 +03:00
return send ( conn , map [ string ] string { "success" : msg } , time . Second )
}
2017-10-16 16:30:13 +03:00
// authTwitter tries to authenticate a faucet request using Twitter posts, returning
2020-12-11 12:35:39 +03:00
// the uniqueness identifier (user id/username), username, avatar URL and Ethereum address to fund on success.
2021-01-04 14:58:46 +03:00
func authTwitter ( url string , tokenV1 , tokenV2 string ) ( string , string , string , common . Address , error ) {
2017-10-16 16:30:13 +03:00
// Ensure the user specified a meaningful URL, no fancy nonsense
parts := strings . Split ( url , "/" )
if len ( parts ) < 4 || parts [ len ( parts ) - 2 ] != "status" {
2019-11-29 13:38:34 +03:00
//lint:ignore ST1005 This error is to be displayed in the browser
2020-12-11 12:35:39 +03:00
return "" , "" , "" , common . Address { } , errors . New ( "Invalid Twitter status URL" )
}
2021-01-04 14:58:46 +03:00
// Strip any query parameters from the tweet id and ensure it's numeric
tweetID := strings . Split ( parts [ len ( parts ) - 1 ] , "?" ) [ 0 ]
if ! regexp . MustCompile ( "^[0-9]+$" ) . MatchString ( tweetID ) {
return "" , "" , "" , common . Address { } , errors . New ( "Invalid Tweet URL" )
}
2020-12-11 12:35:39 +03:00
// Twitter's API isn't really friendly with direct links.
// It is restricted to 300 queries / 15 minute with an app api key.
// Anything more will require read only authorization from the users and that we want to avoid.
2021-01-04 14:58:46 +03:00
// If Twitter bearer token is provided, use the API, selecting the version
// the user would prefer (currently there's a limit of 1 v2 app / developer
// but unlimited v1.1 apps).
switch {
case tokenV1 != "" :
return authTwitterWithTokenV1 ( tweetID , tokenV1 )
case tokenV2 != "" :
return authTwitterWithTokenV2 ( tweetID , tokenV2 )
2017-10-16 16:30:13 +03:00
}
2022-08-19 09:00:21 +03:00
// Twitter API token isn't provided so we just load the public posts
2020-06-04 08:59:26 +03:00
// and scrape it for the Ethereum address and profile URL. We need to load
// the mobile page though since the main page loads tweet contents via JS.
url = strings . Replace ( url , "https://twitter.com/" , "https://mobile.twitter.com/" , 1 )
2017-10-16 16:30:13 +03:00
res , err := http . Get ( url )
if err != nil {
2020-12-11 12:35:39 +03:00
return "" , "" , "" , common . Address { } , err
2017-10-16 16:30:13 +03:00
}
defer res . Body . Close ( )
2018-02-22 14:20:36 +03:00
// Resolve the username from the final redirect, no intermediate junk
parts = strings . Split ( res . Request . URL . String ( ) , "/" )
if len ( parts ) < 4 || parts [ len ( parts ) - 2 ] != "status" {
2019-11-29 13:38:34 +03:00
//lint:ignore ST1005 This error is to be displayed in the browser
2020-12-11 12:35:39 +03:00
return "" , "" , "" , common . Address { } , errors . New ( "Invalid Twitter status URL" )
2018-02-22 14:20:36 +03:00
}
username := parts [ len ( parts ) - 3 ]
2022-05-16 12:59:35 +03:00
body , err := io . ReadAll ( res . Body )
2017-10-16 16:30:13 +03:00
if err != nil {
2020-12-11 12:35:39 +03:00
return "" , "" , "" , common . Address { } , err
2017-10-16 16:30:13 +03:00
}
address := common . HexToAddress ( string ( regexp . MustCompile ( "0x[0-9a-fA-F]{40}" ) . Find ( body ) ) )
if address == ( common . Address { } ) {
2019-11-29 13:38:34 +03:00
//lint:ignore ST1005 This error is to be displayed in the browser
2023-04-06 06:17:53 +03:00
return "" , "" , "" , common . Address { } , errors . New ( "No BNB Smart Chain address found to fund" )
2017-10-16 16:30:13 +03:00
}
var avatar string
2021-10-13 18:31:02 +03:00
if parts = regexp . MustCompile ( ` src="([^"]+twimg\.com/profile_images[^"]+)" ` ) . FindStringSubmatch ( string ( body ) ) ; len ( parts ) == 2 {
2017-10-16 16:30:13 +03:00
avatar = parts [ 1 ]
}
2020-12-11 12:35:39 +03:00
return username + "@twitter" , username , avatar , address , nil
}
2021-01-04 14:58:46 +03:00
// authTwitterWithTokenV1 tries to authenticate a faucet request using Twitter's v1
// API, returning the user id, username, avatar URL and Ethereum address to fund on
// success.
func authTwitterWithTokenV1 ( tweetID string , token string ) ( string , string , string , common . Address , error ) {
// Query the tweet details from Twitter
url := fmt . Sprintf ( "https://api.twitter.com/1.1/statuses/show.json?id=%s" , tweetID )
2023-01-24 12:12:25 +03:00
req , err := http . NewRequest ( http . MethodGet , url , nil )
2021-01-04 14:58:46 +03:00
if err != nil {
return "" , "" , "" , common . Address { } , err
}
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , token ) )
res , err := http . DefaultClient . Do ( req )
if err != nil {
return "" , "" , "" , common . Address { } , err
}
defer res . Body . Close ( )
2020-12-11 12:35:39 +03:00
2021-01-04 14:58:46 +03:00
var result struct {
Text string ` json:"text" `
User struct {
ID string ` json:"id_str" `
Username string ` json:"screen_name" `
Avatar string ` json:"profile_image_url" `
} ` json:"user" `
2020-12-11 12:35:39 +03:00
}
2021-01-04 14:58:46 +03:00
err = json . NewDecoder ( res . Body ) . Decode ( & result )
if err != nil {
return "" , "" , "" , common . Address { } , err
}
address := common . HexToAddress ( regexp . MustCompile ( "0x[0-9a-fA-F]{40}" ) . FindString ( result . Text ) )
if address == ( common . Address { } ) {
//lint:ignore ST1005 This error is to be displayed in the browser
return "" , "" , "" , common . Address { } , errors . New ( "No Ethereum address found to fund" )
}
return result . User . ID + "@twitter" , result . User . Username , result . User . Avatar , address , nil
}
2020-12-11 12:35:39 +03:00
2021-01-04 14:58:46 +03:00
// authTwitterWithTokenV2 tries to authenticate a faucet request using Twitter's v2
// API, returning the user id, username, avatar URL and Ethereum address to fund on
// success.
func authTwitterWithTokenV2 ( tweetID string , token string ) ( string , string , string , common . Address , error ) {
2020-12-11 12:35:39 +03:00
// Query the tweet details from Twitter
2021-01-04 14:58:46 +03:00
url := fmt . Sprintf ( "https://api.twitter.com/2/tweets/%s?expansions=author_id&user.fields=profile_image_url" , tweetID )
2023-01-24 12:12:25 +03:00
req , err := http . NewRequest ( http . MethodGet , url , nil )
2020-12-11 12:35:39 +03:00
if err != nil {
return "" , "" , "" , common . Address { } , err
}
req . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , token ) )
res , err := http . DefaultClient . Do ( req )
if err != nil {
return "" , "" , "" , common . Address { } , err
}
defer res . Body . Close ( )
var result struct {
Data struct {
AuthorID string ` json:"author_id" `
Text string ` json:"text" `
} ` json:"data" `
Includes struct {
Users [ ] struct {
2021-01-04 14:58:46 +03:00
ID string ` json:"id" `
Username string ` json:"username" `
Avatar string ` json:"profile_image_url" `
2020-12-11 12:35:39 +03:00
} ` json:"users" `
} ` json:"includes" `
}
err = json . NewDecoder ( res . Body ) . Decode ( & result )
if err != nil {
return "" , "" , "" , common . Address { } , err
}
address := common . HexToAddress ( regexp . MustCompile ( "0x[0-9a-fA-F]{40}" ) . FindString ( result . Data . Text ) )
if address == ( common . Address { } ) {
//lint:ignore ST1005 This error is to be displayed in the browser
return "" , "" , "" , common . Address { } , errors . New ( "No Ethereum address found to fund" )
}
2021-01-04 14:58:46 +03:00
return result . Data . AuthorID + "@twitter" , result . Includes . Users [ 0 ] . Username , result . Includes . Users [ 0 ] . Avatar , address , nil
2017-10-16 16:30:13 +03:00
}
// authFacebook tries to authenticate a faucet request using Facebook posts,
// returning the username, avatar URL and Ethereum address to fund on success.
func authFacebook ( url string ) ( string , string , common . Address , error ) {
// Ensure the user specified a meaningful URL, no fancy nonsense
2020-11-24 12:33:58 +03:00
parts := strings . Split ( strings . Split ( url , "?" ) [ 0 ] , "/" )
if parts [ len ( parts ) - 1 ] == "" {
parts = parts [ 0 : len ( parts ) - 1 ]
}
2017-10-16 16:30:13 +03:00
if len ( parts ) < 4 || parts [ len ( parts ) - 2 ] != "posts" {
2019-11-29 13:38:34 +03:00
//lint:ignore ST1005 This error is to be displayed in the browser
2017-10-16 16:30:13 +03:00
return "" , "" , common . Address { } , errors . New ( "Invalid Facebook post URL" )
}
username := parts [ len ( parts ) - 3 ]
// Facebook's Graph API isn't really friendly with direct links. Still, we don't
// want to do ask read permissions from users, so just load the public posts and
// scrape it for the Ethereum address and profile URL.
2021-01-07 14:04:20 +03:00
//
// Facebook recently changed their desktop webpage to use AJAX for loading post
// content, so switch over to the mobile site for now. Will probably end up having
// to use the API eventually.
crawl := strings . Replace ( url , "www.facebook.com" , "m.facebook.com" , 1 )
res , err := http . Get ( crawl )
2017-10-16 16:30:13 +03:00
if err != nil {
return "" , "" , common . Address { } , err
}
defer res . Body . Close ( )
2022-05-16 12:59:35 +03:00
body , err := io . ReadAll ( res . Body )
2017-10-16 16:30:13 +03:00
if err != nil {
return "" , "" , common . Address { } , err
}
address := common . HexToAddress ( string ( regexp . MustCompile ( "0x[0-9a-fA-F]{40}" ) . Find ( body ) ) )
if address == ( common . Address { } ) {
2019-11-29 13:38:34 +03:00
//lint:ignore ST1005 This error is to be displayed in the browser
2023-08-23 12:46:08 +03:00
return "" , "" , common . Address { } , errors . New ( "No BNB Smart Chain address found to fund. Please check the post URL and verify that it can be viewed publicly." )
2017-10-16 16:30:13 +03:00
}
var avatar string
2021-10-13 18:31:02 +03:00
if parts = regexp . MustCompile ( ` src="([^"]+fbcdn\.net[^"]+)" ` ) . FindStringSubmatch ( string ( body ) ) ; len ( parts ) == 2 {
2017-10-16 16:30:13 +03:00
avatar = parts [ 1 ]
}
return username + "@facebook" , avatar , address , nil
}
2017-10-23 10:22:23 +03:00
// authNoAuth tries to interpret a faucet request as a plain Ethereum address,
// without actually performing any remote authentication. This mode is prone to
// Byzantine attack, so only ever use for truly private networks.
func authNoAuth ( url string ) ( string , string , common . Address , error ) {
address := common . HexToAddress ( regexp . MustCompile ( "0x[0-9a-fA-F]{40}" ) . FindString ( url ) )
if address == ( common . Address { } ) {
2019-11-29 13:38:34 +03:00
//lint:ignore ST1005 This error is to be displayed in the browser
2023-04-06 06:17:53 +03:00
return "" , "" , common . Address { } , errors . New ( "No BNB Smart Chain address found to fund" )
2017-10-23 10:22:23 +03:00
}
return address . Hex ( ) + "@noauth" , "" , address , nil
}
2021-04-14 00:51:46 +03:00
// getGenesis returns a genesis based on input args
2023-06-02 14:03:21 +03:00
func getGenesis ( genesisFlag string , goerliFlag bool , sepoliaFlag bool ) ( * core . Genesis , error ) {
2021-04-14 00:51:46 +03:00
switch {
2022-04-23 17:53:21 +03:00
case genesisFlag != "" :
2021-04-14 00:51:46 +03:00
var genesis core . Genesis
2022-04-23 17:53:21 +03:00
err := common . LoadJSON ( genesisFlag , & genesis )
2021-04-14 00:51:46 +03:00
return & genesis , err
default :
2023-05-24 13:21:29 +03:00
return nil , errors . New ( "no genesis flag provided" )
2021-04-14 00:51:46 +03:00
}
}